class ScalarCriteria(ModelGeneric[P]):
model_config = ConfigDict(
populate_by_name=True,
validate_by_alias=True,
validate_by_name=True,
arbitrary_types_allowed=True,
extra="forbid",
)
criteria_type_mapping: ClassVar[
dict[CriteriaValueType, type[BaseCriteria[Any]]]
] = {
str: TextCriteria[str],
UUID: NumericCriteria[UUID],
int: NumericCriteria[int],
float: NumericCriteria[float],
bool: NumericCriteria[bool],
datetime: NumericCriteria[datetime],
}
@classmethod
def __get_generic_model_value_annotations__(
cls, generic_model: TransmuterType
) -> dict[str, Any]:
"""Names and value annotations of criteria-eligible attributes.
Regular scalar fields plus ``@provided`` computed fields (keyed by
return type). Marking a computed field with :func:`arcanus.provided`
declares it is backed by a provider-side expression (e.g. a SQLAlchemy
``hybrid_property``), making it filterable and orderable like a plain
column; unmarked computed fields stay serialization-only.
"""
annotations = {
name: info.annotation
for name, info in generic_model.__pydantic_fields__.items()
if name not in generic_model.model_associations
}
for name, computed in provided_computed_fields(generic_model).items():
annotations.setdefault(name, computed.return_type)
return annotations
@classmethod
def __get_generic_model_field_definitions__(
cls, generic_model: TransmuterType
) -> dict[str, Any]:
scalar_fields: dict[str, Any] = {}
for name, annotation in cls.__get_generic_model_value_annotations__(
generic_model
).items():
# Normalize scalar field annotations before mapping them to criteria types.
origin = get_origin(annotation)
if origin is UnionType or str(origin) == "typing.Union":
annotation = next(
(arg for arg in get_args(annotation) if arg is not type(None)),
object,
)
origin = get_origin(annotation)
if origin is Literal:
literal_values = get_args(annotation)
if not literal_values:
continue
criteria_type = cast(
type[BaseCriteria[Any]], cast(Any, ExactCriteria)[annotation]
)
scalar_fields[name] = (
criteria_type | None,
Field(default=None),
)
continue
else:
try:
if is_association(cast(type[Any], annotation)):
annotation = object
except TypeError:
annotation = object
if isinstance(annotation, type) and annotation in cls.criteria_type_mapping:
criteria_value_type = cast(CriteriaValueType, annotation)
criteria_type = cast(
type[BaseCriteria[Any]],
cls.criteria_type_mapping[criteria_value_type],
)
scalar_fields[name] = (
criteria_type | None,
Field(default=None),
)
return scalar_fields
@classmethod
def __get_generic_model_examples__(
cls, generic_model: TransmuterType
) -> dict[str, CriteriaExample]:
scalar_examples: dict[str, CriteriaExample] = {}
def example_value(annotation: CriteriaValueType) -> CriteriaExampleValue:
if annotation is str:
return "string"
if annotation is UUID:
return "3fa85f64-5717-4562-b3fc-2c963f66afa6"
if annotation is bool:
return True
if annotation is datetime:
return "2026-05-25T16:59:31.252Z"
if annotation is int:
return 1
if annotation is float:
return 1.0
return "string"
def criteria_example(
criteria_type: type[BaseCriteria[Any]], value: CriteriaExampleValue
) -> CriteriaExample:
def attribute_example(attribute: str) -> Any:
if attribute in {"in_", "not_in"}:
return [value]
if attribute == "is_null":
return True
return value
return {
(criteria_type.model_fields[attribute].alias or attribute): (
attribute_example(attribute)
)
for attribute, _ in criteria_type.criteria_operators
}
for name, annotation in cls.__get_generic_model_value_annotations__(
generic_model
).items():
origin = get_origin(annotation)
if origin is UnionType or str(origin) == "typing.Union":
annotation = next(
(arg for arg in get_args(annotation) if arg is not type(None)),
object,
)
origin = get_origin(annotation)
if origin is Literal:
literal_values = get_args(annotation)
if not literal_values:
continue
criteria_type = cast(
type[BaseCriteria[Any]], cast(Any, ExactCriteria)[annotation]
)
example = literal_values[0]
scalar_examples[name] = criteria_example(
criteria_type,
example
if isinstance(example, (str, int, float, bool))
else "string",
)
continue
else:
try:
if is_association(cast(type[Any], annotation)):
annotation = object
except TypeError:
annotation = object
if isinstance(annotation, type) and annotation in cls.criteria_type_mapping:
criteria_value_type = cast(CriteriaValueType, annotation)
criteria_type = cast(
type[BaseCriteria[Any]],
cls.criteria_type_mapping[criteria_value_type],
)
scalar_examples[name] = criteria_example(
criteria_type, example_value(criteria_value_type)
)
return scalar_examples
@cached_property
def expressions(self) -> tuple[Expression[bool], ...]:
expressions: list[Expression[bool]] = []
generic_model = type(self).generic_model
reserved = {"limit", "offset", "order_by", "cursor"}
for name, value in self:
if value is None or name in reserved:
continue
if isinstance(value, BaseCriteria):
column = cast(
Column[CriteriaValue],
generic_model[name],
)
column_expressions = [
column.operate(operator, operator_value)
for attribute, operator in value.criteria_operators
if (operator_value := getattr(value, attribute)) is not None
]
expressions.extend(column_expressions)
return tuple(expressions)