Skip to content

Constraint checkers reference

UniqueConstraintChecker

autocrud.resource_manager.unique_handler.UniqueConstraintChecker

Bases: IConstraintChecker

Checks that unique-annotated fields are not duplicated.

The checker owns the list of unique fields. When unique_fields is not given explicitly it is auto-detected from the model's Unique annotations. It also ensures every unique field is present in the :class:ResourceManager's indexed fields (auto-adding if missing).

PARAMETER DESCRIPTION
rm

The owning :class:ResourceManager (needed to query storage).

TYPE: ResourceManager

unique_fields

Explicit list of field names to enforce uniqueness on. When None (the default) the fields are auto-detected from Unique annotations on the model.

TYPE: list[str] | None DEFAULT: None

Source code in autocrud/resource_manager/unique_handler.py
class UniqueConstraintChecker(IConstraintChecker):
    """Checks that unique-annotated fields are not duplicated.

    The checker **owns** the list of unique fields.  When *unique_fields* is
    not given explicitly it is auto-detected from the model's ``Unique``
    annotations.  It also ensures every unique field is present in the
    :class:`ResourceManager`'s indexed fields (auto-adding if missing).

    Args:
        rm: The owning :class:`ResourceManager` (needed to query storage).
        unique_fields: Explicit list of field names to enforce uniqueness on.
            When ``None`` (the default) the fields are auto-detected from
            ``Unique`` annotations on the model.
    """

    def __init__(
        self,
        rm: ResourceManager,
        unique_fields: list[str] | None = None,
    ) -> None:
        self.rm: ResourceManager = rm
        self._unique_fields: list[str] = (
            list(unique_fields)
            if unique_fields is not None
            else extract_unique_fields(rm.resource_type)
        )
        # Auto-ensure every unique field is indexed.
        self._ensure_indexed()

    @property
    def unique_fields(self) -> list[str]:
        """The list of field names enforced by this checker."""
        return self._unique_fields

    # -- internal helpers ----------------------------------------------------

    def _ensure_indexed(self) -> None:
        """Add unique fields to RM's indexed fields if not already present."""
        for field_name in self._unique_fields:
            raw_type = get_field_raw_type(
                self.rm.resource_type, field_name, default=UNSET
            )
            self.rm.add_indexed_field(
                IndexableField(field_path=field_name, field_type=raw_type)
            )

    # -- IConstraintChecker --------------------------------------------------

    def check(
        self,
        data: Any,
        *,
        exclude_resource_id: str | None = None,
    ) -> None:
        """Raise :class:`UniqueConstraintError` if any unique-annotated field
        value is already used by another (non-deleted) resource.
        """
        if not self._unique_fields:
            return
        for field_path in self._unique_fields:
            value = getattr(data, field_path, None)
            if value is None:
                continue
            query = ResourceMetaSearchQuery(
                conditions=[
                    DataSearchCondition(
                        field_path=field_path,
                        operator=DataSearchOperator.equals,
                        value=value,
                    )
                ],
                is_deleted=False,
                limit=1,
                sorts=[
                    ResourceMetaSearchSort(
                        key=ResourceMetaSortKey.updated_time,
                        direction=ResourceMetaSortDirection.ascending,
                    )
                ],
            )
            matches: list[ResourceMeta] = self.rm.storage.search(query)
            if not matches:
                continue
            first: ResourceMeta = matches[0]
            if (
                exclude_resource_id is not None
                and first.resource_id == exclude_resource_id
            ):
                continue  # this resource is the earliest owner — no conflict
            raise UniqueConstraintError(field_path, value, first.resource_id)

    def data_relevant_changed(self, current_data: Any, new_data: Any) -> bool:
        """Return ``True`` if any unique-constrained field value differs."""
        if not self._unique_fields:
            return False
        return any(
            getattr(new_data, f, None) != getattr(current_data, f, None)
            for f in self._unique_fields
        )

Attributes

rm instance-attribute

rm: ResourceManager = rm

_unique_fields instance-attribute

_unique_fields: list[str] = (
    list(unique_fields)
    if unique_fields is not None
    else extract_unique_fields(resource_type)
)

unique_fields property

unique_fields: list[str]

The list of field names enforced by this checker.

Functions

__init__

__init__(
    rm: ResourceManager,
    unique_fields: list[str] | None = None,
) -> None
Source code in autocrud/resource_manager/unique_handler.py
def __init__(
    self,
    rm: ResourceManager,
    unique_fields: list[str] | None = None,
) -> None:
    self.rm: ResourceManager = rm
    self._unique_fields: list[str] = (
        list(unique_fields)
        if unique_fields is not None
        else extract_unique_fields(rm.resource_type)
    )
    # Auto-ensure every unique field is indexed.
    self._ensure_indexed()

_ensure_indexed

_ensure_indexed() -> None

Add unique fields to RM's indexed fields if not already present.

Source code in autocrud/resource_manager/unique_handler.py
def _ensure_indexed(self) -> None:
    """Add unique fields to RM's indexed fields if not already present."""
    for field_name in self._unique_fields:
        raw_type = get_field_raw_type(
            self.rm.resource_type, field_name, default=UNSET
        )
        self.rm.add_indexed_field(
            IndexableField(field_path=field_name, field_type=raw_type)
        )

check

check(
    data: Any, *, exclude_resource_id: str | None = None
) -> None

Raise :class:UniqueConstraintError if any unique-annotated field value is already used by another (non-deleted) resource.

Source code in autocrud/resource_manager/unique_handler.py
def check(
    self,
    data: Any,
    *,
    exclude_resource_id: str | None = None,
) -> None:
    """Raise :class:`UniqueConstraintError` if any unique-annotated field
    value is already used by another (non-deleted) resource.
    """
    if not self._unique_fields:
        return
    for field_path in self._unique_fields:
        value = getattr(data, field_path, None)
        if value is None:
            continue
        query = ResourceMetaSearchQuery(
            conditions=[
                DataSearchCondition(
                    field_path=field_path,
                    operator=DataSearchOperator.equals,
                    value=value,
                )
            ],
            is_deleted=False,
            limit=1,
            sorts=[
                ResourceMetaSearchSort(
                    key=ResourceMetaSortKey.updated_time,
                    direction=ResourceMetaSortDirection.ascending,
                )
            ],
        )
        matches: list[ResourceMeta] = self.rm.storage.search(query)
        if not matches:
            continue
        first: ResourceMeta = matches[0]
        if (
            exclude_resource_id is not None
            and first.resource_id == exclude_resource_id
        ):
            continue  # this resource is the earliest owner — no conflict
        raise UniqueConstraintError(field_path, value, first.resource_id)

data_relevant_changed

data_relevant_changed(
    current_data: Any, new_data: Any
) -> bool

Return True if any unique-constrained field value differs.

Source code in autocrud/resource_manager/unique_handler.py
def data_relevant_changed(self, current_data: Any, new_data: Any) -> bool:
    """Return ``True`` if any unique-constrained field value differs."""
    if not self._unique_fields:
        return False
    return any(
        getattr(new_data, f, None) != getattr(current_data, f, None)
        for f in self._unique_fields
    )