Skip to content

Query API

Complete reference for the Query Builder API.

Query

Query.where accepts either a QueryNode (the operator and col() paths) or a lambda predicate of shape Callable[[QueryProxy[TModel]], QueryNode]. See Typed Query Predicates for a full treatment of the three predicate styles.

Query

Bases: Generic[T]

Build and execute fluent ORM queries.

Attributes:

Name Type Description
model_cls

Model class used to hydrate results.

where_clause list[QueryNode]

Accumulated filter nodes for the query.

order_by_clause list[dict[str, str]]

Sort definitions sent to the Rust core.

Functions

where(node)
where(node: QueryNode) -> Query[T]
where(node: Predicate[T]) -> Query[T]

Add a filter condition to the query.

Accepts either a :class:QueryNode (built directly with operator syntax or with :func:ferro.query.col) or a lambda predicate of shape Callable[[QueryProxy[T]], QueryNode]. The lambda receives a fresh :class:QueryProxy whose attributes return :class:FieldProxy instances, so lambda t: t.archived == False builds a comparison without static-typing friction.

Parameters:

Name Type Description Default
node QueryNode | Predicate[T]

A QueryNode or a predicate callable.

required

Returns:

Type Description
Query[T]

The current Query instance for chaining.

Raises:

Type Description
TypeError

If node is neither a QueryNode nor a callable, or if the callable does not return a QueryNode.

Examples:

>>> q1 = User.where(User.id == 1)
>>> q2 = User.where(lambda t: t.archived == False)  # noqa: E712
>>> isinstance(q1, Query) and isinstance(q2, Query)
True
order_by(field, direction='asc')

Add an ordering clause to the query

Parameters:

Name Type Description Default
field Any

The field to order by (e.g., User.username).

required
direction str

The direction of the sort ("asc" or "desc").

'asc'

Returns:

Type Description
Query[T]

The current Query instance for chaining.

Raises:

Type Description
ValueError

If direction is not "asc" or "desc".

Examples:

>>> query = User.select().order_by(User.username, "desc")
>>> query.order_by_clause[-1]["direction"]
'desc'
limit(value)

Limit the number of records returned

Parameters:

Name Type Description Default
value int

The maximum number of records to return.

required

Returns:

Type Description
Query[T]

The current Query instance for chaining.

Examples:

>>> query = User.select().limit(10)
>>> query._limit
10
offset(value)

Skip a specific number of records

Parameters:

Name Type Description Default
value int

The number of records to skip.

required

Returns:

Type Description
Query[T]

The current Query instance for chaining.

Examples:

>>> query = User.select().offset(20)
>>> query._offset
20
all() async

Return all model instances that match the current query

Returns:

Type Description
list[T]

A list of model instances.

Examples:

>>> users = await User.where(User.active == True).all()
>>> isinstance(users, list)
True
first() async

Return the first matching record, or None

Returns:

Type Description
T | None

A model instance or None.

Examples:

>>> user = await User.select().order_by(User.id).first()
>>> user is None or isinstance(user, User)
True
count() async

Return the number of records that match the current query

Returns:

Type Description
int

The count of matching records.

Examples:

>>> total = await User.where(User.active == True).count()
>>> isinstance(total, int)
True
exists() async

Return whether at least one record matches the current query

Returns:

Type Description
bool

True if records exist, otherwise False.

Examples:

>>> found = await User.where(User.email == "a@b.com").exists()
>>> isinstance(found, bool)
True
update(**fields) async

Update all records matching the current query

Parameters:

Name Type Description Default
**fields

Field names and values to update.

{}

Returns:

Type Description
int

The number of records updated.

Examples:

>>> updated = await User.where(User.id == 1).update(name="Taylor")
>>> isinstance(updated, int)
True
delete() async

Delete all records matching the current query

Returns:

Type Description
int

The number of records deleted.

Examples:

>>> deleted = await User.where(User.disabled == True).delete()
>>> isinstance(deleted, int)
True

Relation

Relation is the lazy collection-relationship subclass of Query returned by BackRef and ManyToMany fields. It accepts the same three predicate styles on where.

Relation

Bases: Query[T]

Represent lazy collection relationship queries with typing support

Examples:

>>> class User(Model):
...     id: Annotated[int, FerroField(primary_key=True)]
...     name: str
...     posts: Relation[list["Post"]] = BackRef()
>>> class Post(Model):
...     id: Annotated[int, FerroField(primary_key=True)]
...     title: str
...     user: Annotated[User, ForeignKey(related_name="posts")]
>>> user = await User.get(1)
>>> posts = await user.posts.all()
>>> isinstance(posts, list)
True

Functions

where(node)
where(node: QueryNode) -> Relation[T]
where(node: Predicate[T]) -> Relation[T]
order_by(field, direction='asc')
limit(value)
offset(value)
all() async
all() -> list[E]
all() -> list[E]
first() async
first() -> E | None
first() -> E | None

col

Runtime-identity wrapper that statically narrows a model class attribute back to FieldProxy[T]. Use it when a single attribute on an existing chain trips your type checker; for new code prefer the lambda predicate style.

col(value)

Treat a model class attribute as a typed query column.

At runtime Ferro's metaclass replaces Model.field with a :class:FieldProxy, so Model.field is already a FieldProxy when accessed on the class. Static type checkers, however, see the field's Pydantic-annotated type (bool, int, ...). That makes expressions like Model.archived == False resolve to bool statically, even though the runtime value is a QueryNode.

col() is runtime-identity for FieldProxy inputs and statically narrows the return type to FieldProxy[T], so col(Model.archived) == False type-checks as QueryNode. Use it when a single attribute trips your type checker; for new code, prefer the lambda predicate API on :meth:Query.where.

Parameters:

Name Type Description Default
value TField

A model class attribute (already a FieldProxy at runtime).

required

Returns:

Type Description
FieldProxy[TField]

The same object, statically typed as FieldProxy[T].

Raises:

Type Description
TypeError

If value is not a FieldProxy. This guards against calling col() on a literal (e.g., col(False)), which is almost certainly a bug.

Examples:

>>> rows = await User.where(col(User.archived) == False).all()  # noqa: E712

FieldProxy

The typed proxy installed by Ferro's metaclass on every model class field. Generic over the column's Python type; operator overloads accept T | FieldProxy[T] and return QueryNode.

FieldProxy

Bases: Generic[TField]

Capture field comparisons and build query nodes

FieldProxy is generic over the column's Python type so that operator overloads carry that type into static analysis. At runtime the type parameter is erased and the proxy works identically for any column type.

Attributes:

Name Type Description
column

Database column name associated with the model field.

Examples:

>>> email_filter = User.email == "taylor@example.com"
>>> isinstance(email_filter, QueryNode)
True

Functions

in_(other)

Build an IN comparison node from an iterable

Parameters:

Name Type Description Default
other list[TField] | tuple[TField, ...] | set[TField]

Collection of values to match against the field.

required

Returns:

Type Description
QueryNode

A node using the SQL IN operator.

Raises:

Type Description
TypeError

If other is not a list, tuple, or set.

Examples:

>>> status_filter = User.status.in_(["active", "pending"])
>>> status_filter.operator
'IN'
like(pattern)

Build a LIKE comparison node

The self: FieldProxy[str] annotation prevents type checkers from accepting .like(...) on non-string columns; at runtime the method is available on any FieldProxy.

Parameters:

Name Type Description Default
pattern str

SQL LIKE pattern such as "%@example.com".

required

Returns:

Type Description
QueryNode

A node using the SQL LIKE operator.

Examples:

>>> email_filter = User.email.like("%@example.com")
>>> email_filter.operator
'LIKE'

QueryProxy

The attribute proxy passed to lambda predicates. Each attribute access returns a fresh FieldProxy for the accessed name.

QueryProxy

Bases: Generic[TModel]

Lazy attribute proxy used by lambda predicates passed to Query.where.

A fresh QueryProxy is constructed each time a lambda predicate is evaluated. Any attribute access returns a :class:FieldProxy for the accessed name, so lambda t: t.archived == False builds a :class:QueryNode without ever asking the model class what type archived is. The TModel type parameter exists so user-supplied lambdas can narrow t to a specific model in static analysis; the proxy itself ignores the parameter at runtime.

The proxy attribute return type is intentionally FieldProxy[Any] for now — wiring per-field types through a lambda parameter requires @dataclass_transform plumbing on the metaclass, which is outside this feature's scope.

Examples:

>>> rows = await User.where(lambda t: t.archived == False).all()  # noqa: E712

Attributes

__slots__ = () class-attribute instance-attribute

Functions

__getattr__(name)

Return a fresh FieldProxy for any attribute name.

QueryNode

The serializable AST node produced by every predicate style. You normally do not construct these directly.

QueryNode

Represent a node in the query expression tree

Attributes:

Name Type Description
column

Column name for leaf nodes.

operator

Comparison or logical operator.

value

Right-hand value for leaf comparisons.

left

Left child node for compound expressions.

right

Right child node for compound expressions.

is_compound

Flag indicating whether the node combines two child nodes.

Examples:

>>> active_filter = User.active == True
>>> admin_filter = User.role == "admin"
>>> expr = active_filter & admin_filter
>>> isinstance(expr, QueryNode)
True

Functions

to_dict()

Serialize the query node tree into a JSON-friendly dictionary

Returns:

Type Description
dict[str, Any]

A dictionary representation of the current node and its children.

Examples:

>>> expr = (User.active == True) & (User.id > 10)
>>> payload = expr.to_dict()
>>> payload["is_compound"]
True

Predicate

Type alias for lambda predicates accepted by Query.where, Relation.where, and Model.where.

Predicate[TModel] = Callable[[QueryProxy[TModel]], QueryNode]

See Also