Query: Q, QuerySet and aggregates¶
- class alchemiq.Q(*args, **kwargs)[source]¶
Bases:
objectDjango-style boolean predicate tree of
(field__op, value)leaves.Combine predicates with
&(AND),|(OR), and~(NOT). Pass toQuerySet.filter()/Repository.filter()for type-safe filtering.Supported lookup suffixes:
__eq(default),__ne,__lt,__lte,__gt,__gte,__in,__not_in,__isnull,__contains,__icontains,__startswith,__endswith.E.g.:
>>> q1 = Q(status="active") & Q(age__gte=18) >>> q2 = ~Q(role__in=["admin", "staff"]) >>> q_or = Q(role="admin") | Q(role="staff") >>> q_or.connector 'OR' >>> neg = ~Q(deleted=True) >>> neg.negated True
Pass the composed predicate to
filter()for execution:active_adults = await ( Repository(User) .filter(Q(status="active") & Q(age__gte=18)) .all() )
- Parameters:
args (Any)
kwargs (Any)
- classmethod from_data(data, model, allow=None, deny=None)[source]¶
Deserialize a
Qfrom aQ.to_data()payload, validating fields against model.- Parameters:
- Raises:
DeserializationError – if the payload structure is invalid.
DisallowedFieldError – if a field is denied or not in the allow-list.
UnknownOperatorError – if the payload contains an unrecognised lookup operator.
- Return type:
- classmethod from_bytes(data, model, allow=None, deny=None)[source]¶
Deserialize a
Qfrom UTF-8 JSON bytes produced byto_bytes().
- class alchemiq.QuerySet(model, *, where=(), order=(), limit=None, offset=None, distinct=False, projection=None, select_related=(), prefetch_related=(), deleted='exclude', cache=None, cache_ttl=None)[source]¶
Bases:
objectImmutable, lazy query builder. Compiles to a SQLAlchemy Select (no I/O).
- Parameters:
Join-load the named relationships via a SQL JOIN in the same query.
Uses SQLAlchemy
joinedload; the related object is available on the result without triggering a lazy load. For collections, preferQuerySet.prefetch_related()(a separate SELECT avoids row duplication from the JOIN).E.g.:
track = await QuerySet(Track).select_related("artist").filter(id=1).first() print(track.artist.name) # no RelationNotLoaded
See also
QuerySet.prefetch_related()- uses selectinload (separate SELECT).
Eager-load the named relationships via a separate SELECT … IN (…) query.
Uses SQLAlchemy
selectinload; the related collection is populated without a JOIN (no row duplication). For to-one relationships where you need the foreign object in the same query, preferQuerySet.select_related().E.g.:
artist = await QuerySet(Artist).prefetch_related("tracks").filter(id=1).first() print([t.title for t in artist.tracks]) # no RelationNotLoaded
See also
QuerySet.select_related()- uses joinedload (same-query JOIN).
- with_deleted()[source]¶
Include soft-deleted (tombstoned) rows in results.
By default, soft-delete models exclude tombstone rows (
deleted_at IS NULL). Call this builder to include them as well.E.g.:
all_docs = await QuerySet(Document).with_deleted().all()
- Raises:
ConfigError – if the model does not have
Meta.soft_delete = True.- Return type:
See also
QuerySet.only_deleted()- restrict to tombstones only.
- only_deleted()[source]¶
Restrict results to soft-deleted rows only (
deleted_at IS NOT NULL).E.g.:
tombstones = await QuerySet(Document).only_deleted().all()
- Raises:
ConfigError – if the model does not have
Meta.soft_delete = True.- Return type:
See also
QuerySet.with_deleted()- include both live and deleted rows.
- filter(*args, **lookups)[source]¶
Narrow results by AND-ing the given
Qobjects and keyword lookups.
- exclude(*args, **lookups)[source]¶
Exclude rows matching the given
Qobjects or keyword lookups (NOT AND).
- async all()[source]¶
Execute the query and return all matching model instances.
E.g.:
users = await QuerySet(User).filter(status="active").all()
- async first()[source]¶
Execute with
LIMIT 1and return the first result, orNone.E.g.:
user = await QuerySet(User).order_by("created_at").first()
- Return type:
Any | None
- async count()[source]¶
Return the number of rows matching the current filters.
Strips
ORDER BY/LIMIT/OFFSETbefore wrapping inSELECT count(*) FROM (...) AS subquery.E.g.:
total = await QuerySet(User).filter(status="active").count()
- Return type:
- async aggregate(**exprs)[source]¶
Compute reduce-aggregates over the filtered set and return
{alias: value}.Each keyword argument is a
Count/Sum/Avg/Min/Maxexpression. Inherits the current filters, soft-delete predicate, and any traversal joins. NoGROUP BY; always one row.Sum/Avg/Min/Maxover an empty set returnNone;Countreturns0.E.g.:
result = await QuerySet(Order).filter(status="paid").aggregate( total=Sum("amount"), n=Count(), ) print(result["total"], result["n"])
- Parameters:
exprs (Aggregate) –
alias=Aggregate(...)pairs.- Returns:
dict mapping each alias to its computed value.
- Raises:
ValueError – if no expressions are given.
- Return type:
- async explain(*, analyze=False, format='text')[source]¶
Run
EXPLAINon the compiledSELECTand return the query plan.analyze=Trueexecutes the query for real timings (read-onlySELECTinside a rolled-back transaction on PostgreSQL).format="json"returns the parsed JSON plan (alist);format="text"returns the plan as astr. Always hits the database - never cached. PostgreSQL only.E.g.:
plan = await QuerySet(User).filter(status="active").explain(analyze=True) print(plan)
- async exists()[source]¶
Return
Trueif at least one row matches the current filters.E.g.:
if await QuerySet(User).filter(email="ada@x.io").exists(): ...
- Return type:
- async get(*args, **lookups)[source]¶
Filter and return the single matching instance.
Fetches at most 2 rows to detect duplicates without a full scan. Accepts the same
Qand keyword arguments asQuerySet.filter().E.g.:
user = await QuerySet(User).get(id=42)
- Raises:
NotFoundError – if no rows match the current filters.
MultipleResultsFound – if more than one row matches.
- Parameters:
- Return type:
- async get_or_none(*args, **lookups)[source]¶
Filter and return the single matching instance, or
Noneif not found.- Raises:
MultipleResultsFound – if more than one row matches.
- Parameters:
- Return type:
Any | None
- async paginate(page=1, size=20)[source]¶
Return a
Pageof results for page/size pagination.Issues two queries:
count()then a windowedall().E.g.:
page = await QuerySet(User).order_by("id").paginate(page=1, size=20) print(page.total, page.items)
- Parameters:
- Raises:
ValueError – if
page < 1orsize < 1.- Return type:
Page[Any]
Note
count()andall()run in two separate database sessions. A row inserted between the two queries may inflatetotalwithout appearing initems, or vice versa.See also
QuerySet.cursor_paginate()- keyset pagination with nototal.
- async cursor_paginate(*, size=20, after=None, before=None)[source]¶
Keyset (cursor) pagination with no total-count query.
Adds a PK tiebreaker to the effective order for a deterministic total order.
afterandbeforeare mutually exclusive opaque tokens fromCursorPage.next_cursor/CursorPage.prev_cursor.E.g.:
p1 = await QuerySet(User).order_by("id").cursor_paginate(size=20) if p1.has_next: p2 = await QuerySet(User).order_by("id").cursor_paginate( size=20, after=p1.next_cursor )
- Parameters:
- Raises:
ValueError – if
size < 1or bothafterandbeforeare given.InvalidCursorError – if a cursor token is malformed or does not match the current order.
- Return type:
CursorPage[Any]
See also
QuerySet.paginate()- offset pagination with atotalcount.
- async update(**changes)[source]¶
Execute a set-based
UPDATEon own-column filters. Returns rowcount.- Raises:
QueryError – if no filter is set; use
QuerySet.update_all()for an unguarded full-table update.- Parameters:
changes (Any)
- Return type:
Note
The automatic soft-delete predicate does not count as a user filter.
- async update_all(**changes)[source]¶
Execute a SET-BASED UPDATE over EVERY matching row (no user filter required).
The explicit, lexically-distinct full-table escape hatch -
update()refuses an unfiltered call so a missing.filter()cannot silently rewrite the table. Still honors the current deleted-mode (default EXCLUDE skips tombstones).
- async delete()[source]¶
Execute a set-based
DELETEon own-column filters. Returns rowcount.On a soft-delete model this stamps
deleted_at(anUPDATE) instead of a physicalDELETE.- Raises:
QueryError – if no filter is set; use
QuerySet.delete_all()for an unguarded full-table delete.- Return type:
- async delete_all()[source]¶
Execute a SET-BASED DELETE over EVERY matching row (no user filter required).
Soft-delete models stamp
deleted_at; others physically DELETE. The explicit full-table escape hatch -delete()refuses an unfiltered call. Honors the current deleted-mode (default EXCLUDE skips tombstones).- Return type:
- async hard_delete()[source]¶
Execute a physical SET-BASED DELETE on own-column filters. Returns rowcount.
Bypasses soft-delete: the rows are physically removed. Honors the current deleted-mode (default EXCLUDE purges live rows;
only_deleted().hard_delete()purges tombstones;with_deleted().hard_delete()purges both).- Return type:
- class alchemiq.Count(field='*', *, distinct=False)[source]¶
Bases:
AggregateRow count, optionally over a specific column with
distinctdeduplication.Count()orCount("*")emitscount(*);Count("field")emitscount(field);Count("field", distinct=True)emitscount(DISTINCT field). RaisesQueryErrorfor traversal fields (__paths).E.g.:
result = await QuerySet(Order).aggregate( n=Count(), unique_customers=Count("customer_id", distinct=True), )
See also
QuerySet.aggregate()- pass aggregate expressions by alias.
- class alchemiq.Sum(field)[source]¶
Bases:
AggregateSum of a numeric column.
- Parameters:
field (str)
- class alchemiq.Avg(field)[source]¶
Bases:
AggregateArithmetic mean of a numeric column.
- Parameters:
field (str)
- class alchemiq.Min(field)[source]¶
Bases:
AggregateMinimum value of a column.
- Parameters:
field (str)
- class alchemiq.Max(field)[source]¶
Bases:
AggregateMaximum value of a column.
- Parameters:
field (str)
- alchemiq.version_of(obj)[source]¶
Return the optimistic-lock
_versioncounter of a versioned model instance.Read this value before an update to pass as
expected_versionfor optimistic concurrency control.E.g.:
user = await repo.get(id=1) version = version_of(user) await repo.update(1, expected_version=version, name="Ada")
- Parameters:
obj (Any) – a model instance whose class has
Meta.versioned = True.- Returns:
the current
_versioninteger.- Raises:
ConfigError – if the model was not declared with
Meta.versioned = True.- Return type:
See also
Repository.update()- acceptsexpected_version.