Query Builder¶
SpecStar includes a high-level query builder for searching indexed resource fields and resource metadata.
Use it when you want expressive filtering logic in Python or when you want your HTTP qb expressions to mirror the same mental model.
If you need a complete method-by-method lookup, see the Query Builder reference.
When to use it¶
The query builder is useful for:
- filtering lists by field values
- combining multiple conditions with AND, OR, and NOT
- building reusable search logic in services or tests
- keeping search code readable instead of hand-writing JSON filter payloads
Basic Python usage¶
from specstar import QB
query = (
QB["status"].eq("active")
.filter(QB["priority"] >= 3)
.exclude(QB["archived"].eq(True))
.sort("-created_time")
.page(1, 20)
)
results = manager.search_resources(query)
QB[...] returns a field-aware builder object, so comparison operators and helper methods can be chained naturally.
HTTP usage¶
The same ideas can be passed to the API through the qb query parameter:
GET /tasks?qb=(QB["status"] == "active") & (QB["priority"] >= 3)
GET /tasks?qb=QB["owner"].one_of(["alice", "bob"])
GET /tasks?qb=QB.created_time().last_n_days(7)
List endpoints are paginated by default. The startup default comes from the SPECSTAR_DEFAULT_QUERY_LIMIT environment variable, and you can still pass a different limit per request.
The server parses the expression with a safe AST parser.
Common patterns¶
Simple comparisons¶
String matching¶
QB["name"].contains("ali")
QB["email"].ends_with("@example.com")
QB["title"].icontains("urgent")
QB["code"].regex(r"^[A-Z]{3}")
List membership¶
QB["status"].in_(["draft", "review"])
QB["role"].not_in(["guest"])
QB["owner"].one_of(["alice", "bob"])
Null and value checks¶
QB["deleted_at"].is_null()
QB["email"].is_not_null()
QB["nickname"].is_blank()
QB["profile"].has_value()
Date helpers¶
Sorting and pagination¶
QB["status"].eq("active").sort("-created_time", "+name")
QB["status"].eq("active").limit(10).offset(20)
QB["status"].eq("active").page(2, 10)
QB["status"].eq("active").first()
Metadata accessors¶
Use metadata helper methods when the filter targets resource metadata instead of indexed data.
QB.resource_id().starts_with("task-")
QB.current_revision_id().eq("rev-123")
QB.created_by().eq("admin")
QB.is_deleted().is_false()
QB.total_revision_count() > 3
All built-in metadata accessors are filterable and sortable, including resource_id, current_revision_id, created_time, updated_time, created_by, updated_by, is_deleted, schema_version, and total_revision_count.
Revision mirror fields¶
SpecStar stores a denormalized snapshot of the current revision's key attributes directly in ResourceMeta. These can be filtered and sorted without any extra revision reads:
QB.rev_status().eq("draft") # only resources with a draft current revision
QB.rev_status().eq("stable") # only stable
QB.rev_created_by().one_of(["alice", "bob"]) # current revision created by alice or bob
QB.rev_updated_by().ne("guest") # current revision not last touched by guest
QB.rev_created_time().last_n_days(7) # current revision created in the past week
QB.rev_updated_time().this_month() # current revision updated this month
These fields are kept in sync by SpecStar on every create(), update(), modify(), and switch() call.
Low-level alternative¶
If you need fully explicit structured queries, you can still build ResourceMetaSearchQuery objects manually:
from specstar.types import (
DataSearchCondition,
DataSearchOperator,
ResourceMetaSearchQuery,
)
query = ResourceMetaSearchQuery(
conditions=[
DataSearchCondition(
field_path="status",
operator=DataSearchOperator.equals,
value="open",
),
DataSearchCondition(
field_path="priority",
operator=DataSearchOperator.greater_than_or_equal,
value=3,
),
],
limit=20,
)
results = manager.search_resources(query)
This is useful for generated clients or integrations that prefer explicit JSON-like structures.
Important limitations¶
- queries only work reliably on metadata fields and indexed fields
- if
qbis used in HTTP requests, do not combine it withdata_conditions,conditions,sorts, time-range / user filter params (created_time_start,created_time_end,updated_time_start,updated_time_end,created_bys,updated_bys), or revision filter params (rev_statuses,rev_created_bys,rev_updated_bys,rev_created_time_start,rev_created_time_end,rev_updated_time_start,rev_updated_time_end); conflicting requests return HTTP 422 is_deletedis the one exception: it may be combined withqb. The server ANDs it into the QB conditions automatically. Swagger always sendsis_deleted=falseby default, so QB expressions work in Swagger out of the box.- invalid or unsupported QB expressions return HTTP 400
- URL
limitandoffsetoverride pagination values defined inside the QB expression - for metadata filtering in QB mode (time ranges, creator filters, revision filters, etc.), include them directly in the expression — for example
QB.created_time().last_n_days(7),QB.created_by().eq("alice"), orQB.rev_status().eq("draft")
QB error responses at a glance¶
| Situation | HTTP | What to do |
|---|---|---|
| malformed or unsupported QB expression | 400 | fix the expression itself |
qb combined with JSON conditions, sorts, or time-range/user filter params |
422 | choose either QB mode or individual query parameters |
For the shared route-level error mapping, see the HTTP error reference page.
Good practices¶
- index fields that you plan to search frequently
- start with small filters and expand only when needed
- use
QB.all()andQB.any()for nested grouped logic - prefer QB for readability and JSON conditions for machine-generated requests