Source code for alchemiq.query.aggregates
"""Aggregate expression types for use with ``QuerySet.aggregate()``."""
from __future__ import annotations
from typing import Any
from sqlalchemy import distinct as sa_distinct
from sqlalchemy import func
from alchemiq.exceptions import QueryError
class Aggregate:
"""Base reduce-aggregate expression. ``resolve(model)`` builds a SQLAlchemy element."""
func_name: str = ""
def __init__(self, field: str) -> None:
self.field = field
def _column(self, model: type) -> Any:
if "__" in self.field:
raise QueryError(
f"aggregate over traversal {self.field!r} is not supported (own columns only)"
)
fields: dict[str, Any] = getattr(model, "__alchemiq_fields__", {})
if self.field not in fields:
raise QueryError(f"{model.__name__} has no field {self.field!r}")
return getattr(model, self.field)
def resolve(self, model: type) -> Any:
"""Build the SQLAlchemy aggregate element for *model*."""
return getattr(func, self.func_name)(self._column(model))
[docs]
class Sum(Aggregate):
"""Sum of a numeric column."""
func_name = "sum"
[docs]
class Avg(Aggregate):
"""Arithmetic mean of a numeric column."""
func_name = "avg"
[docs]
class Min(Aggregate):
"""Minimum value of a column."""
func_name = "min"
[docs]
class Max(Aggregate):
"""Maximum value of a column."""
func_name = "max"
[docs]
class Count(Aggregate):
"""Row count, optionally over a specific column with ``distinct`` deduplication.
``Count()`` or ``Count("*")`` emits ``count(*)``;
``Count("field")`` emits ``count(field)``;
``Count("field", distinct=True)`` emits ``count(DISTINCT field)``.
Raises ``QueryError`` for traversal fields (``__`` paths).
E.g.::
result = await QuerySet(Order).aggregate(
n=Count(),
unique_customers=Count("customer_id", distinct=True),
)
.. seealso:: :meth:`.QuerySet.aggregate` - pass aggregate expressions by alias.
"""
def __init__(self, field: str = "*", *, distinct: bool = False) -> None:
self.field = field
self.distinct = distinct
[docs]
def resolve(self, model: type) -> Any:
"""Build the SQLAlchemy count element for *model*."""
if self.field == "*":
return func.count()
column = self._column(model)
return func.count(sa_distinct(column) if self.distinct else column)