Query contracts¶
A query contract is a class that declares the fields, types, operators, aliases, sorting behavior, and required filters for an endpoint.
Contracts are used by strict mode:
No contract means loose mode:
Basic contract¶
from datetime import datetime
from typing import Annotated
from paramora import QueryContract, query_field
class ItemQuery(QueryContract):
status: Annotated[str, query_field("eq", "in")]
active: bool
created_at: Annotated[datetime, query_field("gte", "lte", sortable=True)]
price: Annotated[float, query_field("eq", "gt", "gte", "lt", "lte")]
Bare annotations¶
A bare annotation creates an equality-only field.
This accepts:
It rejects:
because ne was not explicitly allowed.
query_field(...)¶
Use query_field(...) inside typing.Annotated when the field needs metadata.
The first positional arguments are allowed operators. The supported operators are:
The function also accepts keyword metadata:
Why Annotated?¶
This style keeps type checkers happy.
Good:
The field is still understood as datetime by type checkers.
Avoid assignment-style metadata:
That makes editors and type checkers see a QueryFieldInfo assigned to a
datetime field.
Supported field types¶
Paramora supports:
strintfloatbooldatetime.datetime- simple
enum.Enumsubclasses
For in and nin, comma-separated values are parsed as lists and each element
is coerced using the field type.
Strings¶
emits values like:
Numbers¶
class ItemQuery(QueryContract):
price: Annotated[float, query_field("eq", "gt", "gte", "lt", "lte")]
quantity: Annotated[int, query_field("eq", "gte", "lte")]
Invalid numbers produce structured errors such as query.type_error.float.
Booleans¶
Accepted true values:
Accepted false values:
Parsing is case-insensitive.
Datetimes¶
class ItemQuery(QueryContract):
created_at: Annotated[datetime, query_field("gte", "lte", sortable=True)]
Paramora uses standard-library ISO-8601 parsing. A trailing Z is treated as
UTC.
Enums¶
from enum import Enum
from typing import Annotated
class Status(Enum):
FREE = "free"
BUSY = "busy"
class ItemQuery(QueryContract):
status: Annotated[Status, query_field("eq", "in")]
Invalid enum values raise query.type_error.enum.
Sorting¶
Fields are not sortable by default. Enable sorting explicitly:
class ItemQuery(QueryContract):
created_at: Annotated[datetime, query_field("gte", "lte", sortable=True)]
Now these are allowed:
Sorting should be opt-in because it can affect indexes and query performance.
Required fields¶
Required fields are useful for safety filters such as tenant IDs.
class ItemQuery(QueryContract):
tenant_id: Annotated[str, query_field(required=True)]
status: Annotated[str, query_field("eq", "in")]
If the request omits tenant_id, Paramora raises:
{
"detail": [
{
"loc": ["query", "tenant_id"],
"msg": "Required filter field is missing.",
"type": "query.required"
}
]
}
Backend aliases¶
Aliases map public query names to backend names.
MongoDB:
class ItemQuery(QueryContract):
created_at: Annotated[
datetime,
query_field("gte", "lte", sortable=True, alias="createdAt"),
]
Request clients use created_at:
MongoDB receives createdAt.
SQL:
class ItemQuery(QueryContract):
created_at: Annotated[
datetime,
query_field("gte", "lte", sortable=True, alias="items.created_at"),
]
SQL output uses:
Contract design guidance¶
For public APIs:
- prefer strict mode with a contract
- allow only the operators the endpoint actually needs
- mark sorting fields explicitly
- consider required tenant/user filters for multi-tenant systems
- use aliases to decouple public API names from backend names
- keep raw backend syntax out of query parameters