Source code for alchemiq.query.soft_delete
"""Soft-delete predicates, deleted-mode constants, and ``version_of`` accessor."""
from __future__ import annotations
from typing import Any, Final, Literal
from alchemiq.exceptions import ConfigError
EXCLUDE: Final = "exclude"
INCLUDE: Final = "include"
ONLY: Final = "only"
DeletedMode = Literal["exclude", "include", "only"]
def is_soft_delete(model: type) -> bool:
"""Return True if *model* was declared with ``Meta.soft_delete = True``."""
meta = getattr(model, "__alchemiq_meta__", None)
return bool(meta is not None and meta.soft_delete)
def is_versioned(model: type) -> bool:
"""Return True if *model* was declared with ``Meta.versioned = True``."""
meta = getattr(model, "__alchemiq_meta__", None)
return bool(meta is not None and meta.versioned)
[docs]
def version_of(obj: Any) -> int:
"""Return the optimistic-lock ``_version`` counter of a versioned model instance.
Read this value before an update to pass as ``expected_version`` for
optimistic concurrency control.
E.g.::
user = await repo.get(id=1)
version = version_of(user)
await repo.update(1, expected_version=version, name="Ada")
:param obj: a model instance whose class has ``Meta.versioned = True``.
:return: the current ``_version`` integer.
:raises ConfigError: if the model was not declared with ``Meta.versioned = True``.
.. seealso:: :meth:`.Repository.update` - accepts ``expected_version``.
"""
if not is_versioned(type(obj)):
raise ConfigError(f"{type(obj).__name__} is not versioned; version_of() is unavailable")
return obj._version
def deleted_predicate(model: type, mode: DeletedMode) -> Any | None:
"""Return the deleted_at SQL clause for *mode*, or None if no filter applies."""
if mode == INCLUDE or not is_soft_delete(model):
return None
column = model.deleted_at # ty: ignore[unresolved-attribute]
return column.is_(None) if mode == EXCLUDE else column.is_not(None)