Skip to content

API Reference

zspec.Specification

Bases: ABC

Abstract specification that can be combined with &, |, ~, ^.

Source code in src/zspec/specification.py
class Specification[T](ABC):
    """Abstract specification that can be combined with ``&``, ``|``, ``~``, ``^``."""

    __slots__: tuple[str, ...] = ()
    registry: ClassVar[dict[str, type[Specification[Any]]]] = {}

    def __init_subclass__(cls, **kwargs: object) -> None:
        """Auto-register subclass for :func:`~zspec.from_dict`."""
        super().__init_subclass__(**kwargs)
        cls.registry[cls.__name__] = cls

    @abstractmethod
    def is_satisfied_by(self, candidate: T) -> bool:
        """Check whether *candidate* satisfies this specification."""
        raise NotImplementedError

    def __and__(self, other: Specification[T]) -> Specification[T]:
        """Combine with *other* via logical AND."""
        if not isinstance(other, Specification):
            return NotImplemented
        if isinstance(self, FalseSpecification):
            return cast(Specification[T], FALSE_SPEC)
        if isinstance(other, FalseSpecification):
            return cast(Specification[T], FALSE_SPEC)
        if isinstance(self, TrueSpecification):
            return other
        if isinstance(other, TrueSpecification):
            return self
        return AndSpecification(left=self, right=other)

    def __or__(self, other: Specification[T]) -> Specification[T]:
        """Combine with *other* via logical OR."""
        if not isinstance(other, Specification):
            return NotImplemented
        if isinstance(self, TrueSpecification):
            return cast(Specification[T], TRUE_SPEC)
        if isinstance(other, TrueSpecification):
            return cast(Specification[T], TRUE_SPEC)
        if isinstance(self, FalseSpecification):
            return other
        if isinstance(other, FalseSpecification):
            return self
        return OrSpecification(self, other)

    def __xor__(self, other: Specification[T]) -> Specification[T]:
        """Combine with *other* via logical XOR."""
        if not isinstance(other, Specification):
            return NotImplemented
        if isinstance(self, TrueSpecification):
            return ~other
        if isinstance(other, TrueSpecification):
            return ~self
        if isinstance(self, FalseSpecification):
            return other
        if isinstance(other, FalseSpecification):
            return self
        return XorSpecification(self, other)

    def __invert__(self) -> Specification[T]:
        """Negate this specification."""
        return NotSpecification(self)

    @override
    def __repr__(self) -> str:
        """Return ``ClassName(attr=value, ...)`` for all slots."""
        args = ", ".join(
            f"{s}={getattr(self, s)!r}" for s in slots_of(self)
        )
        return f"{type(self).__name__}({args})"

    @override
    def __eq__(self, other: object) -> bool:
        if type(self) is not type(other):
            return NotImplemented
        return all(
            getattr(self, s) == getattr(other, s) for s in slots_of(self)
        )

    @override
    def __hash__(self) -> int:
        return hash(
            (type(self), tuple(getattr(self, s) for s in slots_of(self))),
        )

    @override
    def __str__(self) -> str:
        """Return the class name. Override in subclasses for custom rendering."""
        return type(self).__name__

    def __call__(self, candidate: T) -> bool:
        """Evaluate the specification against *candidate*."""
        return self.is_satisfied_by(candidate)

    def filter(self, candidates: Iterable[T]) -> Iterator[T]:
        """Yield candidates that satisfy this specification."""
        return (c for c in candidates if self(c))

    def reject(self, candidates: Iterable[T]) -> Iterator[T]:
        """Yield candidates that do **not** satisfy this specification."""
        return (c for c in candidates if not self(c))

    def count(self, candidates: Iterable[T]) -> int:
        """Count how many candidates satisfy this specification."""
        return sum(1 for c in candidates if self(c))

    def partition(
        self,
        candidates: Iterable[T],
    ) -> tuple[list[T], list[T]]:
        """Split into ``(passed, failed)`` lists."""
        passed: list[T] = []
        failed: list[T] = []
        for c in candidates:
            if self(c):
                passed.append(c)
            else:
                failed.append(c)
        return passed, failed

    @classmethod
    def of(cls, fn: Callable[[T], bool]) -> Specification[T]:
        """Create a specification from *fn*.

        Usage::

            adult = Specification.of(lambda u: u.age >= 18)
        """
        name = getattr(fn, "__name__", "fn")
        spec_type = type(
            f"Of({name})",
            (Specification,),
            {
                "__slots__": (),
                "is_satisfied_by": staticmethod(fn),
            },
        )
        return cast(Specification[T], spec_type())

    @classmethod
    def true(cls) -> Specification[T]:
        """Return a specification satisfied by **any** candidate."""
        return cast(Specification[T], TRUE_SPEC)

    @classmethod
    def false(cls) -> Specification[T]:
        """Return a specification satisfied by **no** candidate."""
        return cast(Specification[T], FALSE_SPEC)

    @classmethod
    def all_of(
        cls,
        *specs: Specification[T],
        default: Specification[T] | None = None,
    ) -> Specification[T] | None:
        """Return a specification satisfied when **all** of *specs* are.

        Accepts individual specs::

            Specification.all_of(a, b, c)
            Specification.all_of(*my_list)

        Returns *default* when empty.
        """
        if not specs:
            return default
        return reduce(and_, specs)

    @classmethod
    def any_of(
        cls,
        *specs: Specification[T],
        default: Specification[T] | None = None,
    ) -> Specification[T] | None:
        """Return a specification satisfied when **any** of *specs* are.

        Accepts individual specs::

            Specification.any_of(a, b, c)
            Specification.any_of(*my_list)

        Returns *default* when empty.
        """
        if not specs:
            return default
        return reduce(or_, specs)

    @classmethod
    def matching(
        cls,
        *predicates: Specification[T] | Callable[[T], bool],
        **kwargs: object,
    ) -> Specification[T]:
        """Create a specification from field comparisons and/or predicates.

        Positional args accept lambdas or field proxies from :func:`fields`::

            F = fields(Product)
            spec = Specification[Product].matching(
                F.price >= 100,
                lambda p: p.in_stock,
            )

        Keyword args use ``field__op`` syntax::

            spec = Specification[Product].matching(price__gte=100, in_stock=True)

        Supported keyword operators: ``eq``, ``ne``, ``gt``, ``gte``, ``lt``, ``lte``.
        A plain field name without ``__op`` defaults to ``eq``.
        """
        specs: list[Specification[T]] = []
        for p in predicates:
            if isinstance(p, Specification):
                specs.append(p)
            else:
                specs.append(cls.of(p))
        for key, value in kwargs.items():
            field, found, op = key.rpartition("__")
            if not found:
                field, op = key, "eq"
            specs.append(
                cast(Specification[T], FieldSpec(field, op, value)),
            )
        if not specs:
            return cls.true()
        if len(specs) == 1:
            return specs[0]
        return cast(Specification[T], cls.all_of(*specs))

    @classmethod
    def excluding(
        cls,
        *predicates: Specification[T] | Callable[[T], bool],
        **kwargs: object,
    ) -> Specification[T]:
        """Negated :meth:`matching` — exclude anything that matches.

        ::

            spec = Specification[Product].excluding(price__gte=100)
            # equivalent to ~Specification[Product].matching(price__gte=100)
        """
        spec = cls.matching(*predicates, **kwargs)
        if spec is cls.true():
            return cls.true()
        return ~spec

__slots__ = () class-attribute instance-attribute

registry = {} class-attribute

__and__(other)

Combine with other via logical AND.

Source code in src/zspec/specification.py
def __and__(self, other: Specification[T]) -> Specification[T]:
    """Combine with *other* via logical AND."""
    if not isinstance(other, Specification):
        return NotImplemented
    if isinstance(self, FalseSpecification):
        return cast(Specification[T], FALSE_SPEC)
    if isinstance(other, FalseSpecification):
        return cast(Specification[T], FALSE_SPEC)
    if isinstance(self, TrueSpecification):
        return other
    if isinstance(other, TrueSpecification):
        return self
    return AndSpecification(left=self, right=other)

__call__(candidate)

Evaluate the specification against candidate.

Source code in src/zspec/specification.py
def __call__(self, candidate: T) -> bool:
    """Evaluate the specification against *candidate*."""
    return self.is_satisfied_by(candidate)

__eq__(other)

Source code in src/zspec/specification.py
@override
def __eq__(self, other: object) -> bool:
    if type(self) is not type(other):
        return NotImplemented
    return all(
        getattr(self, s) == getattr(other, s) for s in slots_of(self)
    )

__hash__()

Source code in src/zspec/specification.py
@override
def __hash__(self) -> int:
    return hash(
        (type(self), tuple(getattr(self, s) for s in slots_of(self))),
    )

__init_subclass__(**kwargs)

Auto-register subclass for :func:~zspec.from_dict.

Source code in src/zspec/specification.py
def __init_subclass__(cls, **kwargs: object) -> None:
    """Auto-register subclass for :func:`~zspec.from_dict`."""
    super().__init_subclass__(**kwargs)
    cls.registry[cls.__name__] = cls

__invert__()

Negate this specification.

Source code in src/zspec/specification.py
def __invert__(self) -> Specification[T]:
    """Negate this specification."""
    return NotSpecification(self)

__or__(other)

Combine with other via logical OR.

Source code in src/zspec/specification.py
def __or__(self, other: Specification[T]) -> Specification[T]:
    """Combine with *other* via logical OR."""
    if not isinstance(other, Specification):
        return NotImplemented
    if isinstance(self, TrueSpecification):
        return cast(Specification[T], TRUE_SPEC)
    if isinstance(other, TrueSpecification):
        return cast(Specification[T], TRUE_SPEC)
    if isinstance(self, FalseSpecification):
        return other
    if isinstance(other, FalseSpecification):
        return self
    return OrSpecification(self, other)

__repr__()

Return ClassName(attr=value, ...) for all slots.

Source code in src/zspec/specification.py
@override
def __repr__(self) -> str:
    """Return ``ClassName(attr=value, ...)`` for all slots."""
    args = ", ".join(
        f"{s}={getattr(self, s)!r}" for s in slots_of(self)
    )
    return f"{type(self).__name__}({args})"

__str__()

Return the class name. Override in subclasses for custom rendering.

Source code in src/zspec/specification.py
@override
def __str__(self) -> str:
    """Return the class name. Override in subclasses for custom rendering."""
    return type(self).__name__

__xor__(other)

Combine with other via logical XOR.

Source code in src/zspec/specification.py
def __xor__(self, other: Specification[T]) -> Specification[T]:
    """Combine with *other* via logical XOR."""
    if not isinstance(other, Specification):
        return NotImplemented
    if isinstance(self, TrueSpecification):
        return ~other
    if isinstance(other, TrueSpecification):
        return ~self
    if isinstance(self, FalseSpecification):
        return other
    if isinstance(other, FalseSpecification):
        return self
    return XorSpecification(self, other)

all_of(*specs, default=None) classmethod

Return a specification satisfied when all of specs are.

Accepts individual specs::

Specification.all_of(a, b, c)
Specification.all_of(*my_list)

Returns default when empty.

Source code in src/zspec/specification.py
@classmethod
def all_of(
    cls,
    *specs: Specification[T],
    default: Specification[T] | None = None,
) -> Specification[T] | None:
    """Return a specification satisfied when **all** of *specs* are.

    Accepts individual specs::

        Specification.all_of(a, b, c)
        Specification.all_of(*my_list)

    Returns *default* when empty.
    """
    if not specs:
        return default
    return reduce(and_, specs)

any_of(*specs, default=None) classmethod

Return a specification satisfied when any of specs are.

Accepts individual specs::

Specification.any_of(a, b, c)
Specification.any_of(*my_list)

Returns default when empty.

Source code in src/zspec/specification.py
@classmethod
def any_of(
    cls,
    *specs: Specification[T],
    default: Specification[T] | None = None,
) -> Specification[T] | None:
    """Return a specification satisfied when **any** of *specs* are.

    Accepts individual specs::

        Specification.any_of(a, b, c)
        Specification.any_of(*my_list)

    Returns *default* when empty.
    """
    if not specs:
        return default
    return reduce(or_, specs)

count(candidates)

Count how many candidates satisfy this specification.

Source code in src/zspec/specification.py
def count(self, candidates: Iterable[T]) -> int:
    """Count how many candidates satisfy this specification."""
    return sum(1 for c in candidates if self(c))

excluding(*predicates, **kwargs) classmethod

Negated :meth:matching — exclude anything that matches.

::

spec = Specification[Product].excluding(price__gte=100)
# equivalent to ~Specification[Product].matching(price__gte=100)
Source code in src/zspec/specification.py
@classmethod
def excluding(
    cls,
    *predicates: Specification[T] | Callable[[T], bool],
    **kwargs: object,
) -> Specification[T]:
    """Negated :meth:`matching` — exclude anything that matches.

    ::

        spec = Specification[Product].excluding(price__gte=100)
        # equivalent to ~Specification[Product].matching(price__gte=100)
    """
    spec = cls.matching(*predicates, **kwargs)
    if spec is cls.true():
        return cls.true()
    return ~spec

false() classmethod

Return a specification satisfied by no candidate.

Source code in src/zspec/specification.py
@classmethod
def false(cls) -> Specification[T]:
    """Return a specification satisfied by **no** candidate."""
    return cast(Specification[T], FALSE_SPEC)

filter(candidates)

Yield candidates that satisfy this specification.

Source code in src/zspec/specification.py
def filter(self, candidates: Iterable[T]) -> Iterator[T]:
    """Yield candidates that satisfy this specification."""
    return (c for c in candidates if self(c))

is_satisfied_by(candidate) abstractmethod

Check whether candidate satisfies this specification.

Source code in src/zspec/specification.py
@abstractmethod
def is_satisfied_by(self, candidate: T) -> bool:
    """Check whether *candidate* satisfies this specification."""
    raise NotImplementedError

matching(*predicates, **kwargs) classmethod

Create a specification from field comparisons and/or predicates.

Positional args accept lambdas or field proxies from :func:fields::

F = fields(Product)
spec = Specification[Product].matching(
    F.price >= 100,
    lambda p: p.in_stock,
)

Keyword args use field__op syntax::

spec = Specification[Product].matching(price__gte=100, in_stock=True)

Supported keyword operators: eq, ne, gt, gte, lt, lte. A plain field name without __op defaults to eq.

Source code in src/zspec/specification.py
@classmethod
def matching(
    cls,
    *predicates: Specification[T] | Callable[[T], bool],
    **kwargs: object,
) -> Specification[T]:
    """Create a specification from field comparisons and/or predicates.

    Positional args accept lambdas or field proxies from :func:`fields`::

        F = fields(Product)
        spec = Specification[Product].matching(
            F.price >= 100,
            lambda p: p.in_stock,
        )

    Keyword args use ``field__op`` syntax::

        spec = Specification[Product].matching(price__gte=100, in_stock=True)

    Supported keyword operators: ``eq``, ``ne``, ``gt``, ``gte``, ``lt``, ``lte``.
    A plain field name without ``__op`` defaults to ``eq``.
    """
    specs: list[Specification[T]] = []
    for p in predicates:
        if isinstance(p, Specification):
            specs.append(p)
        else:
            specs.append(cls.of(p))
    for key, value in kwargs.items():
        field, found, op = key.rpartition("__")
        if not found:
            field, op = key, "eq"
        specs.append(
            cast(Specification[T], FieldSpec(field, op, value)),
        )
    if not specs:
        return cls.true()
    if len(specs) == 1:
        return specs[0]
    return cast(Specification[T], cls.all_of(*specs))

of(fn) classmethod

Create a specification from fn.

Usage::

adult = Specification.of(lambda u: u.age >= 18)
Source code in src/zspec/specification.py
@classmethod
def of(cls, fn: Callable[[T], bool]) -> Specification[T]:
    """Create a specification from *fn*.

    Usage::

        adult = Specification.of(lambda u: u.age >= 18)
    """
    name = getattr(fn, "__name__", "fn")
    spec_type = type(
        f"Of({name})",
        (Specification,),
        {
            "__slots__": (),
            "is_satisfied_by": staticmethod(fn),
        },
    )
    return cast(Specification[T], spec_type())

partition(candidates)

Split into (passed, failed) lists.

Source code in src/zspec/specification.py
def partition(
    self,
    candidates: Iterable[T],
) -> tuple[list[T], list[T]]:
    """Split into ``(passed, failed)`` lists."""
    passed: list[T] = []
    failed: list[T] = []
    for c in candidates:
        if self(c):
            passed.append(c)
        else:
            failed.append(c)
    return passed, failed

reject(candidates)

Yield candidates that do not satisfy this specification.

Source code in src/zspec/specification.py
def reject(self, candidates: Iterable[T]) -> Iterator[T]:
    """Yield candidates that do **not** satisfy this specification."""
    return (c for c in candidates if not self(c))

true() classmethod

Return a specification satisfied by any candidate.

Source code in src/zspec/specification.py
@classmethod
def true(cls) -> Specification[T]:
    """Return a specification satisfied by **any** candidate."""
    return cast(Specification[T], TRUE_SPEC)

zspec.AndSpecification

Bases: Specification[T]

Conjunction of two specifications (produced by &).

Source code in src/zspec/specification.py
class AndSpecification[T](Specification[T]):
    """Conjunction of two specifications (produced by ``&``)."""

    __slots__ = ("left", "right")

    def __init__(self, left: Specification[T], right: Specification[T]) -> None:
        """Initialize with *left* and *right* specifications."""
        self.left = left
        self.right = right

    @override
    def is_satisfied_by(self, candidate: T) -> bool:
        """Check whether *candidate* satisfies both specifications."""
        return self.left.is_satisfied_by(
            candidate,
        ) and self.right.is_satisfied_by(candidate)

    @override
    def __str__(self) -> str:
        return f"({self.left} AND {self.right})"

__slots__ = ('left', 'right') class-attribute instance-attribute

left = left instance-attribute

right = right instance-attribute

__init__(left, right)

Initialize with left and right specifications.

Source code in src/zspec/specification.py
def __init__(self, left: Specification[T], right: Specification[T]) -> None:
    """Initialize with *left* and *right* specifications."""
    self.left = left
    self.right = right

__str__()

Source code in src/zspec/specification.py
@override
def __str__(self) -> str:
    return f"({self.left} AND {self.right})"

is_satisfied_by(candidate)

Check whether candidate satisfies both specifications.

Source code in src/zspec/specification.py
@override
def is_satisfied_by(self, candidate: T) -> bool:
    """Check whether *candidate* satisfies both specifications."""
    return self.left.is_satisfied_by(
        candidate,
    ) and self.right.is_satisfied_by(candidate)

zspec.OrSpecification

Bases: Specification[T]

Disjunction of two specifications (produced by |).

Source code in src/zspec/specification.py
class OrSpecification[T](Specification[T]):
    """Disjunction of two specifications (produced by ``|``)."""

    __slots__ = ("left", "right")

    def __init__(self, left: Specification[T], right: Specification[T]) -> None:
        """Initialize with *left* and *right* specifications."""
        self.left = left
        self.right = right

    @override
    def is_satisfied_by(self, candidate: T) -> bool:
        """Check whether *candidate* satisfies at least one specification."""
        return (
            self.left.is_satisfied_by(candidate)
            or self.right.is_satisfied_by(candidate)
        )

    @override
    def __str__(self) -> str:
        return f"({self.left} OR {self.right})"

__slots__ = ('left', 'right') class-attribute instance-attribute

left = left instance-attribute

right = right instance-attribute

__init__(left, right)

Initialize with left and right specifications.

Source code in src/zspec/specification.py
def __init__(self, left: Specification[T], right: Specification[T]) -> None:
    """Initialize with *left* and *right* specifications."""
    self.left = left
    self.right = right

__str__()

Source code in src/zspec/specification.py
@override
def __str__(self) -> str:
    return f"({self.left} OR {self.right})"

is_satisfied_by(candidate)

Check whether candidate satisfies at least one specification.

Source code in src/zspec/specification.py
@override
def is_satisfied_by(self, candidate: T) -> bool:
    """Check whether *candidate* satisfies at least one specification."""
    return (
        self.left.is_satisfied_by(candidate)
        or self.right.is_satisfied_by(candidate)
    )

zspec.NotSpecification

Bases: Specification[T]

Negation of a specification (produced by ~).

Source code in src/zspec/specification.py
class NotSpecification[T](Specification[T]):
    """Negation of a specification (produced by ``~``)."""

    __slots__ = ("spec",)

    def __init__(self, spec: Specification[T]) -> None:
        """Initialize with *spec* to negate."""
        self.spec = spec

    @override
    def is_satisfied_by(self, candidate: T) -> bool:
        """Check whether *candidate* does **not** satisfy the specification."""
        return not self.spec.is_satisfied_by(candidate)

    @override
    def __invert__(self) -> Specification[T]:
        """Negate — double negation returns the inner spec."""
        return self.spec

    @override
    def __str__(self) -> str:
        return f"NOT ({self.spec})"

__slots__ = ('spec',) class-attribute instance-attribute

spec = spec instance-attribute

__init__(spec)

Initialize with spec to negate.

Source code in src/zspec/specification.py
def __init__(self, spec: Specification[T]) -> None:
    """Initialize with *spec* to negate."""
    self.spec = spec

__invert__()

Negate — double negation returns the inner spec.

Source code in src/zspec/specification.py
@override
def __invert__(self) -> Specification[T]:
    """Negate — double negation returns the inner spec."""
    return self.spec

__str__()

Source code in src/zspec/specification.py
@override
def __str__(self) -> str:
    return f"NOT ({self.spec})"

is_satisfied_by(candidate)

Check whether candidate does not satisfy the specification.

Source code in src/zspec/specification.py
@override
def is_satisfied_by(self, candidate: T) -> bool:
    """Check whether *candidate* does **not** satisfy the specification."""
    return not self.spec.is_satisfied_by(candidate)

zspec.Translator

Bases: ABC

Walk a specification tree and translate it into TResult.

Source code in src/zspec/translator.py
class Translator[TResult](ABC):
    """Walk a specification tree and translate it into *TResult*."""

    def translate(self, spec: Specification[Any]) -> TResult:
        """Recursively translate *spec* into *TResult*."""
        match spec:
            case AndSpecification(left=left, right=right):
                return self._and(self.translate(left), self.translate(right))
            case OrSpecification(left=left, right=right):
                return self._or(self.translate(left), self.translate(right))
            case NotSpecification(spec=inner):
                return self._not(self.translate(inner))
            case XorSpecification(left=left, right=right):
                left_result = self.translate(left)
                right_result = self.translate(right)
                return self._xor(left_result, right_result)
            case _:
                return self._translate(spec)

    @abstractmethod
    def _translate(self, spec: Specification[Any]) -> TResult:
        msg = f"Specification {type(spec).__name__} is not supported"
        raise NotImplementedError(msg)

    @abstractmethod
    def _and(self, left: TResult, right: TResult) -> TResult:
        raise NotImplementedError

    @abstractmethod
    def _or(self, left: TResult, right: TResult) -> TResult:
        raise NotImplementedError

    @abstractmethod
    def _not(self, operand: TResult) -> TResult:
        raise NotImplementedError

    def _xor(self, left: TResult, right: TResult) -> TResult:
        """Combine with XOR — defaults to ``(A OR B) AND NOT (A AND B)``.

        Override for backend-specific optimization.
        """
        return self._and(
            self._or(left, right),
            self._not(self._and(left, right)),
        )

_and(left, right) abstractmethod

Source code in src/zspec/translator.py
@abstractmethod
def _and(self, left: TResult, right: TResult) -> TResult:
    raise NotImplementedError

_not(operand) abstractmethod

Source code in src/zspec/translator.py
@abstractmethod
def _not(self, operand: TResult) -> TResult:
    raise NotImplementedError

_or(left, right) abstractmethod

Source code in src/zspec/translator.py
@abstractmethod
def _or(self, left: TResult, right: TResult) -> TResult:
    raise NotImplementedError

_translate(spec) abstractmethod

Source code in src/zspec/translator.py
@abstractmethod
def _translate(self, spec: Specification[Any]) -> TResult:
    msg = f"Specification {type(spec).__name__} is not supported"
    raise NotImplementedError(msg)

_xor(left, right)

Combine with XOR — defaults to (A OR B) AND NOT (A AND B).

Override for backend-specific optimization.

Source code in src/zspec/translator.py
def _xor(self, left: TResult, right: TResult) -> TResult:
    """Combine with XOR — defaults to ``(A OR B) AND NOT (A AND B)``.

    Override for backend-specific optimization.
    """
    return self._and(
        self._or(left, right),
        self._not(self._and(left, right)),
    )

translate(spec)

Recursively translate spec into TResult.

Source code in src/zspec/translator.py
def translate(self, spec: Specification[Any]) -> TResult:
    """Recursively translate *spec* into *TResult*."""
    match spec:
        case AndSpecification(left=left, right=right):
            return self._and(self.translate(left), self.translate(right))
        case OrSpecification(left=left, right=right):
            return self._or(self.translate(left), self.translate(right))
        case NotSpecification(spec=inner):
            return self._not(self.translate(inner))
        case XorSpecification(left=left, right=right):
            left_result = self.translate(left)
            right_result = self.translate(right)
            return self._xor(left_result, right_result)
        case _:
            return self._translate(spec)

zspec.XorSpecification

Bases: Specification[T]

Exclusive disjunction of two specifications (produced by ^).

Source code in src/zspec/specification.py
class XorSpecification[T](Specification[T]):
    """Exclusive disjunction of two specifications (produced by ``^``)."""

    __slots__ = ("left", "right")

    def __init__(self, left: Specification[T], right: Specification[T]) -> None:
        """Initialize with *left* and *right* specifications."""
        self.left = left
        self.right = right

    @override
    def is_satisfied_by(self, candidate: T) -> bool:
        """Check whether exactly one specification is satisfied."""
        return self.left(candidate) != self.right(candidate)

    @override
    def __str__(self) -> str:
        return f"({self.left} XOR {self.right})"

__slots__ = ('left', 'right') class-attribute instance-attribute

left = left instance-attribute

right = right instance-attribute

__init__(left, right)

Initialize with left and right specifications.

Source code in src/zspec/specification.py
def __init__(self, left: Specification[T], right: Specification[T]) -> None:
    """Initialize with *left* and *right* specifications."""
    self.left = left
    self.right = right

__str__()

Source code in src/zspec/specification.py
@override
def __str__(self) -> str:
    return f"({self.left} XOR {self.right})"

is_satisfied_by(candidate)

Check whether exactly one specification is satisfied.

Source code in src/zspec/specification.py
@override
def is_satisfied_by(self, candidate: T) -> bool:
    """Check whether exactly one specification is satisfied."""
    return self.left(candidate) != self.right(candidate)

zspec.TrueSpecification

Bases: Specification[T]

Internal: specification that is always satisfied.

Source code in src/zspec/specification.py
class TrueSpecification[T](Specification[T]):
    """Internal: specification that is always satisfied."""

    __slots__ = ()

    @override
    def is_satisfied_by(self, candidate: T) -> bool:
        return True

    @override
    def __invert__(self) -> Specification[T]:
        return cast(Specification[T], FALSE_SPEC)

    @override
    def __str__(self) -> str:
        return "TRUE"

__slots__ = () class-attribute instance-attribute

__invert__()

Source code in src/zspec/specification.py
@override
def __invert__(self) -> Specification[T]:
    return cast(Specification[T], FALSE_SPEC)

__str__()

Source code in src/zspec/specification.py
@override
def __str__(self) -> str:
    return "TRUE"

is_satisfied_by(candidate)

Source code in src/zspec/specification.py
@override
def is_satisfied_by(self, candidate: T) -> bool:
    return True

zspec.FalseSpecification

Bases: Specification[T]

Internal: specification that is never satisfied.

Source code in src/zspec/specification.py
class FalseSpecification[T](Specification[T]):
    """Internal: specification that is never satisfied."""

    __slots__ = ()

    @override
    def is_satisfied_by(self, candidate: T) -> bool:
        return False

    @override
    def __invert__(self) -> Specification[T]:
        return cast(Specification[T], TRUE_SPEC)

    @override
    def __str__(self) -> str:
        return "FALSE"

__slots__ = () class-attribute instance-attribute

__invert__()

Source code in src/zspec/specification.py
@override
def __invert__(self) -> Specification[T]:
    return cast(Specification[T], TRUE_SPEC)

__str__()

Source code in src/zspec/specification.py
@override
def __str__(self) -> str:
    return "FALSE"

is_satisfied_by(candidate)

Source code in src/zspec/specification.py
@override
def is_satisfied_by(self, candidate: T) -> bool:
    return False

zspec.FieldSpec

Bases: Specification[T]

Internal: attribute comparison created by matching.

Source code in src/zspec/specification.py
class FieldSpec[T](Specification[T]):
    """Internal: attribute comparison created by ``matching``."""

    __slots__ = ("field", "op", "value")

    def __init__(self, field: str, op: str, value: object) -> None:
        """Initialize with *field* name, *op* code (``"gte"``), and *value*."""
        self.field = field
        self.op = op
        self.value = value

    @override
    def is_satisfied_by(self, candidate: T) -> bool:
        actual = getattr(candidate, self.field)
        return bool(_OPERATORS[self.op](cast(Any, actual), cast(Any, self.value)))

    @override
    def __str__(self) -> str:
        symbol = _OPERATOR_SYMBOLS.get(self.op, self.op)
        return f"{self.field} {symbol} {self.value!r}"

__slots__ = ('field', 'op', 'value') class-attribute instance-attribute

field = field instance-attribute

op = op instance-attribute

value = value instance-attribute

__init__(field, op, value)

Initialize with field name, op code ("gte"), and value.

Source code in src/zspec/specification.py
def __init__(self, field: str, op: str, value: object) -> None:
    """Initialize with *field* name, *op* code (``"gte"``), and *value*."""
    self.field = field
    self.op = op
    self.value = value

__str__()

Source code in src/zspec/specification.py
@override
def __str__(self) -> str:
    symbol = _OPERATOR_SYMBOLS.get(self.op, self.op)
    return f"{self.field} {symbol} {self.value!r}"

is_satisfied_by(candidate)

Source code in src/zspec/specification.py
@override
def is_satisfied_by(self, candidate: T) -> bool:
    actual = getattr(candidate, self.field)
    return bool(_OPERATORS[self.op](cast(Any, actual), cast(Any, self.value)))

zspec.explain

Explain and visualize specification trees.

AndSpecification

Bases: Specification[T]

Conjunction of two specifications (produced by &).

Source code in src/zspec/specification.py
class AndSpecification[T](Specification[T]):
    """Conjunction of two specifications (produced by ``&``)."""

    __slots__ = ("left", "right")

    def __init__(self, left: Specification[T], right: Specification[T]) -> None:
        """Initialize with *left* and *right* specifications."""
        self.left = left
        self.right = right

    @override
    def is_satisfied_by(self, candidate: T) -> bool:
        """Check whether *candidate* satisfies both specifications."""
        return self.left.is_satisfied_by(
            candidate,
        ) and self.right.is_satisfied_by(candidate)

    @override
    def __str__(self) -> str:
        return f"({self.left} AND {self.right})"

__init__(left, right)

Initialize with left and right specifications.

Source code in src/zspec/specification.py
def __init__(self, left: Specification[T], right: Specification[T]) -> None:
    """Initialize with *left* and *right* specifications."""
    self.left = left
    self.right = right

is_satisfied_by(candidate)

Check whether candidate satisfies both specifications.

Source code in src/zspec/specification.py
@override
def is_satisfied_by(self, candidate: T) -> bool:
    """Check whether *candidate* satisfies both specifications."""
    return self.left.is_satisfied_by(
        candidate,
    ) and self.right.is_satisfied_by(candidate)

ExplainNode dataclass

Result of a specification check on a candidate.

The __str__ renders a tree with PASS / FAIL markers::

AND FAIL
├── InStock PASS
└── price >= 100 FAIL
Source code in src/zspec/explain.py
@dataclass
class ExplainNode:
    """Result of a specification check on a candidate.

    The ``__str__`` renders a tree with PASS / FAIL markers::

        AND FAIL
        ├── InStock PASS
        └── price >= 100 FAIL
    """

    passed: bool
    spec: str
    children: list[ExplainNode] = field(default_factory=list)

    @override
    def __str__(self) -> str:
        """Return an ASCII tree with PASS / FAIL for each node."""
        return "\n".join(
            _render_tree(
                self,
                label=lambda n: f"{n.spec} {'PASS' if n.passed else 'FAIL'}",
                children=lambda n: n.children,
            ),
        )

__str__()

Return an ASCII tree with PASS / FAIL for each node.

Source code in src/zspec/explain.py
@override
def __str__(self) -> str:
    """Return an ASCII tree with PASS / FAIL for each node."""
    return "\n".join(
        _render_tree(
            self,
            label=lambda n: f"{n.spec} {'PASS' if n.passed else 'FAIL'}",
            children=lambda n: n.children,
        ),
    )

NotSpecification

Bases: Specification[T]

Negation of a specification (produced by ~).

Source code in src/zspec/specification.py
class NotSpecification[T](Specification[T]):
    """Negation of a specification (produced by ``~``)."""

    __slots__ = ("spec",)

    def __init__(self, spec: Specification[T]) -> None:
        """Initialize with *spec* to negate."""
        self.spec = spec

    @override
    def is_satisfied_by(self, candidate: T) -> bool:
        """Check whether *candidate* does **not** satisfy the specification."""
        return not self.spec.is_satisfied_by(candidate)

    @override
    def __invert__(self) -> Specification[T]:
        """Negate — double negation returns the inner spec."""
        return self.spec

    @override
    def __str__(self) -> str:
        return f"NOT ({self.spec})"

__init__(spec)

Initialize with spec to negate.

Source code in src/zspec/specification.py
def __init__(self, spec: Specification[T]) -> None:
    """Initialize with *spec* to negate."""
    self.spec = spec

__invert__()

Negate — double negation returns the inner spec.

Source code in src/zspec/specification.py
@override
def __invert__(self) -> Specification[T]:
    """Negate — double negation returns the inner spec."""
    return self.spec

is_satisfied_by(candidate)

Check whether candidate does not satisfy the specification.

Source code in src/zspec/specification.py
@override
def is_satisfied_by(self, candidate: T) -> bool:
    """Check whether *candidate* does **not** satisfy the specification."""
    return not self.spec.is_satisfied_by(candidate)

OrSpecification

Bases: Specification[T]

Disjunction of two specifications (produced by |).

Source code in src/zspec/specification.py
class OrSpecification[T](Specification[T]):
    """Disjunction of two specifications (produced by ``|``)."""

    __slots__ = ("left", "right")

    def __init__(self, left: Specification[T], right: Specification[T]) -> None:
        """Initialize with *left* and *right* specifications."""
        self.left = left
        self.right = right

    @override
    def is_satisfied_by(self, candidate: T) -> bool:
        """Check whether *candidate* satisfies at least one specification."""
        return (
            self.left.is_satisfied_by(candidate)
            or self.right.is_satisfied_by(candidate)
        )

    @override
    def __str__(self) -> str:
        return f"({self.left} OR {self.right})"

__init__(left, right)

Initialize with left and right specifications.

Source code in src/zspec/specification.py
def __init__(self, left: Specification[T], right: Specification[T]) -> None:
    """Initialize with *left* and *right* specifications."""
    self.left = left
    self.right = right

is_satisfied_by(candidate)

Check whether candidate satisfies at least one specification.

Source code in src/zspec/specification.py
@override
def is_satisfied_by(self, candidate: T) -> bool:
    """Check whether *candidate* satisfies at least one specification."""
    return (
        self.left.is_satisfied_by(candidate)
        or self.right.is_satisfied_by(candidate)
    )

Specification

Bases: ABC

Abstract specification that can be combined with &, |, ~, ^.

Source code in src/zspec/specification.py
class Specification[T](ABC):
    """Abstract specification that can be combined with ``&``, ``|``, ``~``, ``^``."""

    __slots__: tuple[str, ...] = ()
    registry: ClassVar[dict[str, type[Specification[Any]]]] = {}

    def __init_subclass__(cls, **kwargs: object) -> None:
        """Auto-register subclass for :func:`~zspec.from_dict`."""
        super().__init_subclass__(**kwargs)
        cls.registry[cls.__name__] = cls

    @abstractmethod
    def is_satisfied_by(self, candidate: T) -> bool:
        """Check whether *candidate* satisfies this specification."""
        raise NotImplementedError

    def __and__(self, other: Specification[T]) -> Specification[T]:
        """Combine with *other* via logical AND."""
        if not isinstance(other, Specification):
            return NotImplemented
        if isinstance(self, FalseSpecification):
            return cast(Specification[T], FALSE_SPEC)
        if isinstance(other, FalseSpecification):
            return cast(Specification[T], FALSE_SPEC)
        if isinstance(self, TrueSpecification):
            return other
        if isinstance(other, TrueSpecification):
            return self
        return AndSpecification(left=self, right=other)

    def __or__(self, other: Specification[T]) -> Specification[T]:
        """Combine with *other* via logical OR."""
        if not isinstance(other, Specification):
            return NotImplemented
        if isinstance(self, TrueSpecification):
            return cast(Specification[T], TRUE_SPEC)
        if isinstance(other, TrueSpecification):
            return cast(Specification[T], TRUE_SPEC)
        if isinstance(self, FalseSpecification):
            return other
        if isinstance(other, FalseSpecification):
            return self
        return OrSpecification(self, other)

    def __xor__(self, other: Specification[T]) -> Specification[T]:
        """Combine with *other* via logical XOR."""
        if not isinstance(other, Specification):
            return NotImplemented
        if isinstance(self, TrueSpecification):
            return ~other
        if isinstance(other, TrueSpecification):
            return ~self
        if isinstance(self, FalseSpecification):
            return other
        if isinstance(other, FalseSpecification):
            return self
        return XorSpecification(self, other)

    def __invert__(self) -> Specification[T]:
        """Negate this specification."""
        return NotSpecification(self)

    @override
    def __repr__(self) -> str:
        """Return ``ClassName(attr=value, ...)`` for all slots."""
        args = ", ".join(
            f"{s}={getattr(self, s)!r}" for s in slots_of(self)
        )
        return f"{type(self).__name__}({args})"

    @override
    def __eq__(self, other: object) -> bool:
        if type(self) is not type(other):
            return NotImplemented
        return all(
            getattr(self, s) == getattr(other, s) for s in slots_of(self)
        )

    @override
    def __hash__(self) -> int:
        return hash(
            (type(self), tuple(getattr(self, s) for s in slots_of(self))),
        )

    @override
    def __str__(self) -> str:
        """Return the class name. Override in subclasses for custom rendering."""
        return type(self).__name__

    def __call__(self, candidate: T) -> bool:
        """Evaluate the specification against *candidate*."""
        return self.is_satisfied_by(candidate)

    def filter(self, candidates: Iterable[T]) -> Iterator[T]:
        """Yield candidates that satisfy this specification."""
        return (c for c in candidates if self(c))

    def reject(self, candidates: Iterable[T]) -> Iterator[T]:
        """Yield candidates that do **not** satisfy this specification."""
        return (c for c in candidates if not self(c))

    def count(self, candidates: Iterable[T]) -> int:
        """Count how many candidates satisfy this specification."""
        return sum(1 for c in candidates if self(c))

    def partition(
        self,
        candidates: Iterable[T],
    ) -> tuple[list[T], list[T]]:
        """Split into ``(passed, failed)`` lists."""
        passed: list[T] = []
        failed: list[T] = []
        for c in candidates:
            if self(c):
                passed.append(c)
            else:
                failed.append(c)
        return passed, failed

    @classmethod
    def of(cls, fn: Callable[[T], bool]) -> Specification[T]:
        """Create a specification from *fn*.

        Usage::

            adult = Specification.of(lambda u: u.age >= 18)
        """
        name = getattr(fn, "__name__", "fn")
        spec_type = type(
            f"Of({name})",
            (Specification,),
            {
                "__slots__": (),
                "is_satisfied_by": staticmethod(fn),
            },
        )
        return cast(Specification[T], spec_type())

    @classmethod
    def true(cls) -> Specification[T]:
        """Return a specification satisfied by **any** candidate."""
        return cast(Specification[T], TRUE_SPEC)

    @classmethod
    def false(cls) -> Specification[T]:
        """Return a specification satisfied by **no** candidate."""
        return cast(Specification[T], FALSE_SPEC)

    @classmethod
    def all_of(
        cls,
        *specs: Specification[T],
        default: Specification[T] | None = None,
    ) -> Specification[T] | None:
        """Return a specification satisfied when **all** of *specs* are.

        Accepts individual specs::

            Specification.all_of(a, b, c)
            Specification.all_of(*my_list)

        Returns *default* when empty.
        """
        if not specs:
            return default
        return reduce(and_, specs)

    @classmethod
    def any_of(
        cls,
        *specs: Specification[T],
        default: Specification[T] | None = None,
    ) -> Specification[T] | None:
        """Return a specification satisfied when **any** of *specs* are.

        Accepts individual specs::

            Specification.any_of(a, b, c)
            Specification.any_of(*my_list)

        Returns *default* when empty.
        """
        if not specs:
            return default
        return reduce(or_, specs)

    @classmethod
    def matching(
        cls,
        *predicates: Specification[T] | Callable[[T], bool],
        **kwargs: object,
    ) -> Specification[T]:
        """Create a specification from field comparisons and/or predicates.

        Positional args accept lambdas or field proxies from :func:`fields`::

            F = fields(Product)
            spec = Specification[Product].matching(
                F.price >= 100,
                lambda p: p.in_stock,
            )

        Keyword args use ``field__op`` syntax::

            spec = Specification[Product].matching(price__gte=100, in_stock=True)

        Supported keyword operators: ``eq``, ``ne``, ``gt``, ``gte``, ``lt``, ``lte``.
        A plain field name without ``__op`` defaults to ``eq``.
        """
        specs: list[Specification[T]] = []
        for p in predicates:
            if isinstance(p, Specification):
                specs.append(p)
            else:
                specs.append(cls.of(p))
        for key, value in kwargs.items():
            field, found, op = key.rpartition("__")
            if not found:
                field, op = key, "eq"
            specs.append(
                cast(Specification[T], FieldSpec(field, op, value)),
            )
        if not specs:
            return cls.true()
        if len(specs) == 1:
            return specs[0]
        return cast(Specification[T], cls.all_of(*specs))

    @classmethod
    def excluding(
        cls,
        *predicates: Specification[T] | Callable[[T], bool],
        **kwargs: object,
    ) -> Specification[T]:
        """Negated :meth:`matching` — exclude anything that matches.

        ::

            spec = Specification[Product].excluding(price__gte=100)
            # equivalent to ~Specification[Product].matching(price__gte=100)
        """
        spec = cls.matching(*predicates, **kwargs)
        if spec is cls.true():
            return cls.true()
        return ~spec

__and__(other)

Combine with other via logical AND.

Source code in src/zspec/specification.py
def __and__(self, other: Specification[T]) -> Specification[T]:
    """Combine with *other* via logical AND."""
    if not isinstance(other, Specification):
        return NotImplemented
    if isinstance(self, FalseSpecification):
        return cast(Specification[T], FALSE_SPEC)
    if isinstance(other, FalseSpecification):
        return cast(Specification[T], FALSE_SPEC)
    if isinstance(self, TrueSpecification):
        return other
    if isinstance(other, TrueSpecification):
        return self
    return AndSpecification(left=self, right=other)

__call__(candidate)

Evaluate the specification against candidate.

Source code in src/zspec/specification.py
def __call__(self, candidate: T) -> bool:
    """Evaluate the specification against *candidate*."""
    return self.is_satisfied_by(candidate)

__init_subclass__(**kwargs)

Auto-register subclass for :func:~zspec.from_dict.

Source code in src/zspec/specification.py
def __init_subclass__(cls, **kwargs: object) -> None:
    """Auto-register subclass for :func:`~zspec.from_dict`."""
    super().__init_subclass__(**kwargs)
    cls.registry[cls.__name__] = cls

__invert__()

Negate this specification.

Source code in src/zspec/specification.py
def __invert__(self) -> Specification[T]:
    """Negate this specification."""
    return NotSpecification(self)

__or__(other)

Combine with other via logical OR.

Source code in src/zspec/specification.py
def __or__(self, other: Specification[T]) -> Specification[T]:
    """Combine with *other* via logical OR."""
    if not isinstance(other, Specification):
        return NotImplemented
    if isinstance(self, TrueSpecification):
        return cast(Specification[T], TRUE_SPEC)
    if isinstance(other, TrueSpecification):
        return cast(Specification[T], TRUE_SPEC)
    if isinstance(self, FalseSpecification):
        return other
    if isinstance(other, FalseSpecification):
        return self
    return OrSpecification(self, other)

__repr__()

Return ClassName(attr=value, ...) for all slots.

Source code in src/zspec/specification.py
@override
def __repr__(self) -> str:
    """Return ``ClassName(attr=value, ...)`` for all slots."""
    args = ", ".join(
        f"{s}={getattr(self, s)!r}" for s in slots_of(self)
    )
    return f"{type(self).__name__}({args})"

__str__()

Return the class name. Override in subclasses for custom rendering.

Source code in src/zspec/specification.py
@override
def __str__(self) -> str:
    """Return the class name. Override in subclasses for custom rendering."""
    return type(self).__name__

__xor__(other)

Combine with other via logical XOR.

Source code in src/zspec/specification.py
def __xor__(self, other: Specification[T]) -> Specification[T]:
    """Combine with *other* via logical XOR."""
    if not isinstance(other, Specification):
        return NotImplemented
    if isinstance(self, TrueSpecification):
        return ~other
    if isinstance(other, TrueSpecification):
        return ~self
    if isinstance(self, FalseSpecification):
        return other
    if isinstance(other, FalseSpecification):
        return self
    return XorSpecification(self, other)

all_of(*specs, default=None) classmethod

Return a specification satisfied when all of specs are.

Accepts individual specs::

Specification.all_of(a, b, c)
Specification.all_of(*my_list)

Returns default when empty.

Source code in src/zspec/specification.py
@classmethod
def all_of(
    cls,
    *specs: Specification[T],
    default: Specification[T] | None = None,
) -> Specification[T] | None:
    """Return a specification satisfied when **all** of *specs* are.

    Accepts individual specs::

        Specification.all_of(a, b, c)
        Specification.all_of(*my_list)

    Returns *default* when empty.
    """
    if not specs:
        return default
    return reduce(and_, specs)

any_of(*specs, default=None) classmethod

Return a specification satisfied when any of specs are.

Accepts individual specs::

Specification.any_of(a, b, c)
Specification.any_of(*my_list)

Returns default when empty.

Source code in src/zspec/specification.py
@classmethod
def any_of(
    cls,
    *specs: Specification[T],
    default: Specification[T] | None = None,
) -> Specification[T] | None:
    """Return a specification satisfied when **any** of *specs* are.

    Accepts individual specs::

        Specification.any_of(a, b, c)
        Specification.any_of(*my_list)

    Returns *default* when empty.
    """
    if not specs:
        return default
    return reduce(or_, specs)

count(candidates)

Count how many candidates satisfy this specification.

Source code in src/zspec/specification.py
def count(self, candidates: Iterable[T]) -> int:
    """Count how many candidates satisfy this specification."""
    return sum(1 for c in candidates if self(c))

excluding(*predicates, **kwargs) classmethod

Negated :meth:matching — exclude anything that matches.

::

spec = Specification[Product].excluding(price__gte=100)
# equivalent to ~Specification[Product].matching(price__gte=100)
Source code in src/zspec/specification.py
@classmethod
def excluding(
    cls,
    *predicates: Specification[T] | Callable[[T], bool],
    **kwargs: object,
) -> Specification[T]:
    """Negated :meth:`matching` — exclude anything that matches.

    ::

        spec = Specification[Product].excluding(price__gte=100)
        # equivalent to ~Specification[Product].matching(price__gte=100)
    """
    spec = cls.matching(*predicates, **kwargs)
    if spec is cls.true():
        return cls.true()
    return ~spec

false() classmethod

Return a specification satisfied by no candidate.

Source code in src/zspec/specification.py
@classmethod
def false(cls) -> Specification[T]:
    """Return a specification satisfied by **no** candidate."""
    return cast(Specification[T], FALSE_SPEC)

filter(candidates)

Yield candidates that satisfy this specification.

Source code in src/zspec/specification.py
def filter(self, candidates: Iterable[T]) -> Iterator[T]:
    """Yield candidates that satisfy this specification."""
    return (c for c in candidates if self(c))

is_satisfied_by(candidate) abstractmethod

Check whether candidate satisfies this specification.

Source code in src/zspec/specification.py
@abstractmethod
def is_satisfied_by(self, candidate: T) -> bool:
    """Check whether *candidate* satisfies this specification."""
    raise NotImplementedError

matching(*predicates, **kwargs) classmethod

Create a specification from field comparisons and/or predicates.

Positional args accept lambdas or field proxies from :func:fields::

F = fields(Product)
spec = Specification[Product].matching(
    F.price >= 100,
    lambda p: p.in_stock,
)

Keyword args use field__op syntax::

spec = Specification[Product].matching(price__gte=100, in_stock=True)

Supported keyword operators: eq, ne, gt, gte, lt, lte. A plain field name without __op defaults to eq.

Source code in src/zspec/specification.py
@classmethod
def matching(
    cls,
    *predicates: Specification[T] | Callable[[T], bool],
    **kwargs: object,
) -> Specification[T]:
    """Create a specification from field comparisons and/or predicates.

    Positional args accept lambdas or field proxies from :func:`fields`::

        F = fields(Product)
        spec = Specification[Product].matching(
            F.price >= 100,
            lambda p: p.in_stock,
        )

    Keyword args use ``field__op`` syntax::

        spec = Specification[Product].matching(price__gte=100, in_stock=True)

    Supported keyword operators: ``eq``, ``ne``, ``gt``, ``gte``, ``lt``, ``lte``.
    A plain field name without ``__op`` defaults to ``eq``.
    """
    specs: list[Specification[T]] = []
    for p in predicates:
        if isinstance(p, Specification):
            specs.append(p)
        else:
            specs.append(cls.of(p))
    for key, value in kwargs.items():
        field, found, op = key.rpartition("__")
        if not found:
            field, op = key, "eq"
        specs.append(
            cast(Specification[T], FieldSpec(field, op, value)),
        )
    if not specs:
        return cls.true()
    if len(specs) == 1:
        return specs[0]
    return cast(Specification[T], cls.all_of(*specs))

of(fn) classmethod

Create a specification from fn.

Usage::

adult = Specification.of(lambda u: u.age >= 18)
Source code in src/zspec/specification.py
@classmethod
def of(cls, fn: Callable[[T], bool]) -> Specification[T]:
    """Create a specification from *fn*.

    Usage::

        adult = Specification.of(lambda u: u.age >= 18)
    """
    name = getattr(fn, "__name__", "fn")
    spec_type = type(
        f"Of({name})",
        (Specification,),
        {
            "__slots__": (),
            "is_satisfied_by": staticmethod(fn),
        },
    )
    return cast(Specification[T], spec_type())

partition(candidates)

Split into (passed, failed) lists.

Source code in src/zspec/specification.py
def partition(
    self,
    candidates: Iterable[T],
) -> tuple[list[T], list[T]]:
    """Split into ``(passed, failed)`` lists."""
    passed: list[T] = []
    failed: list[T] = []
    for c in candidates:
        if self(c):
            passed.append(c)
        else:
            failed.append(c)
    return passed, failed

reject(candidates)

Yield candidates that do not satisfy this specification.

Source code in src/zspec/specification.py
def reject(self, candidates: Iterable[T]) -> Iterator[T]:
    """Yield candidates that do **not** satisfy this specification."""
    return (c for c in candidates if not self(c))

true() classmethod

Return a specification satisfied by any candidate.

Source code in src/zspec/specification.py
@classmethod
def true(cls) -> Specification[T]:
    """Return a specification satisfied by **any** candidate."""
    return cast(Specification[T], TRUE_SPEC)

XorSpecification

Bases: Specification[T]

Exclusive disjunction of two specifications (produced by ^).

Source code in src/zspec/specification.py
class XorSpecification[T](Specification[T]):
    """Exclusive disjunction of two specifications (produced by ``^``)."""

    __slots__ = ("left", "right")

    def __init__(self, left: Specification[T], right: Specification[T]) -> None:
        """Initialize with *left* and *right* specifications."""
        self.left = left
        self.right = right

    @override
    def is_satisfied_by(self, candidate: T) -> bool:
        """Check whether exactly one specification is satisfied."""
        return self.left(candidate) != self.right(candidate)

    @override
    def __str__(self) -> str:
        return f"({self.left} XOR {self.right})"

__init__(left, right)

Initialize with left and right specifications.

Source code in src/zspec/specification.py
def __init__(self, left: Specification[T], right: Specification[T]) -> None:
    """Initialize with *left* and *right* specifications."""
    self.left = left
    self.right = right

is_satisfied_by(candidate)

Check whether exactly one specification is satisfied.

Source code in src/zspec/specification.py
@override
def is_satisfied_by(self, candidate: T) -> bool:
    """Check whether exactly one specification is satisfied."""
    return self.left(candidate) != self.right(candidate)

_render_tree(root, *, label, children)

Render root as an ASCII tree, one string per line.

Source code in src/zspec/explain.py
def _render_tree[T](
    root: T,
    *,
    label: Callable[[T], str],
    children: Callable[[T], list[T]],
) -> list[str]:
    """Render *root* as an ASCII tree, one string per line."""
    lines = [label(root)]
    kids = children(root)
    for i, child in enumerate(kids):
        is_last = i == len(kids) - 1
        child_lines = _render_tree(child, label=label, children=children)
        connector = "└── " if is_last else "├── "
        continuation = "    " if is_last else "│   "
        lines.append(connector + child_lines[0])
        lines.extend(continuation + line for line in child_lines[1:])
    return lines

_spec_children(spec)

Source code in src/zspec/explain.py
def _spec_children(spec: Specification[Any]) -> list[Specification[Any]]:
    match spec:
        case AndSpecification(left=left, right=right):
            return [left, right]
        case OrSpecification(left=left, right=right):
            return [left, right]
        case XorSpecification(left=left, right=right):
            return [left, right]
        case NotSpecification(spec=inner):
            return [inner]
        case _:
            return []

explain(spec, candidate)

Evaluate spec against candidate and return a result tree.

Each leaf specification produces a node with no children. Composite specifications (AND, OR, NOT, XOR) produce nodes with children for each sub-specification.

Usage::

print(explain(eligible, product))
# AND FAIL
# ├── InStock PASS
# └── price >= 100 FAIL
Source code in src/zspec/explain.py
def explain(spec: Specification[Any], candidate: object) -> ExplainNode:
    """Evaluate *spec* against *candidate* and return a result tree.

    Each leaf specification produces a node with no children.
    Composite specifications (AND, OR, NOT, XOR) produce nodes
    with children for each sub-specification.

    Usage::

        print(explain(eligible, product))
        # AND FAIL
        # ├── InStock PASS
        # └── price >= 100 FAIL
    """
    match spec:
        case AndSpecification(left=left, right=right):
            left_result = explain(left, candidate)
            right_result = explain(right, candidate)
            return ExplainNode(
                passed=left_result.passed and right_result.passed,
                spec=str(spec),
                children=[left_result, right_result],
            )
        case OrSpecification(left=left, right=right):
            left_result = explain(left, candidate)
            right_result = explain(right, candidate)
            return ExplainNode(
                passed=left_result.passed or right_result.passed,
                spec=str(spec),
                children=[left_result, right_result],
            )
        case XorSpecification(left=left, right=right):
            left_result = explain(left, candidate)
            right_result = explain(right, candidate)
            return ExplainNode(
                passed=left_result.passed != right_result.passed,
                spec=str(spec),
                children=[left_result, right_result],
            )
        case NotSpecification(spec=inner):
            inner_result = explain(inner, candidate)
            return ExplainNode(
                passed=not inner_result.passed,
                spec=str(spec),
                children=[inner_result],
            )
        case _:
            return ExplainNode(
                passed=spec.is_satisfied_by(candidate),
                spec=str(spec),
            )

to_ascii(spec)

Return an ASCII tree visualization of spec.

Usage::

print(to_ascii(eligible))
# AND
# ├── price >= 100
# └── in_stock == True
Source code in src/zspec/explain.py
def to_ascii(spec: Specification[Any]) -> str:
    """Return an ASCII tree visualization of *spec*.

    Usage::

        print(to_ascii(eligible))
        # AND
        # ├── price >= 100
        # └── in_stock == True
    """
    return "\n".join(
        _render_tree(spec, label=str, children=_spec_children),
    )

zspec.SqlTranslator

Bases: Translator[SqlFragment]

Translate specifications into parameterized SQL.

Subclass and override _translate for each leaf specification::

class MyTranslator(SqlTranslator):
    def _translate(self, spec: Specification[Any]) -> SqlFragment:
        match spec:
            case InStock():
                return SqlFragment("in_stock = %s", (True,))
            case MinPrice(min_price=price):
                return SqlFragment("price >= %s", (price,))
            case _:
                return super()._translate(spec)
Source code in src/zspec/translators.py
class SqlTranslator(Translator[SqlFragment]):
    """Translate specifications into parameterized SQL.

    Subclass and override ``_translate`` for each leaf specification::

        class MyTranslator(SqlTranslator):
            def _translate(self, spec: Specification[Any]) -> SqlFragment:
                match spec:
                    case InStock():
                        return SqlFragment("in_stock = %s", (True,))
                    case MinPrice(min_price=price):
                        return SqlFragment("price >= %s", (price,))
                    case _:
                        return super()._translate(spec)

    """

    @override
    def _and(self, left: SqlFragment, right: SqlFragment) -> SqlFragment:
        """Combine with AND."""
        return SqlFragment(
            f"({left.sql} AND {right.sql})",
            left.params + right.params,
        )

    @override
    def _or(self, left: SqlFragment, right: SqlFragment) -> SqlFragment:
        """Combine with OR."""
        return SqlFragment(
            f"({left.sql} OR {right.sql})",
            left.params + right.params,
        )

    @override
    def _not(self, operand: SqlFragment) -> SqlFragment:
        """Negate."""
        return SqlFragment(f"NOT ({operand.sql})", operand.params)

_and(left, right)

Combine with AND.

Source code in src/zspec/translators.py
@override
def _and(self, left: SqlFragment, right: SqlFragment) -> SqlFragment:
    """Combine with AND."""
    return SqlFragment(
        f"({left.sql} AND {right.sql})",
        left.params + right.params,
    )

_not(operand)

Negate.

Source code in src/zspec/translators.py
@override
def _not(self, operand: SqlFragment) -> SqlFragment:
    """Negate."""
    return SqlFragment(f"NOT ({operand.sql})", operand.params)

_or(left, right)

Combine with OR.

Source code in src/zspec/translators.py
@override
def _or(self, left: SqlFragment, right: SqlFragment) -> SqlFragment:
    """Combine with OR."""
    return SqlFragment(
        f"({left.sql} OR {right.sql})",
        left.params + right.params,
    )

zspec.MongoTranslator

Bases: Translator[dict[str, Any]]

Translate specifications into MongoDB filter documents.

Subclass and override _translate for each leaf specification::

class MyTranslator(MongoTranslator):
    def _translate(self, spec: Specification[Any]) -> dict[str, Any]:
        match spec:
            case InStock():
                return {"in_stock": True}
            case MinPrice(min_price=price):
                return {"price": {"$gte": price}}
            case _:
                return super()._translate(spec)

translator = MyTranslator()
results = collection.find(
    translator.translate(InStock() & MinPrice(100)),
)
Source code in src/zspec/translators.py
class MongoTranslator(Translator[dict[str, Any]]):
    """Translate specifications into MongoDB filter documents.

    Subclass and override ``_translate`` for each leaf specification::

        class MyTranslator(MongoTranslator):
            def _translate(self, spec: Specification[Any]) -> dict[str, Any]:
                match spec:
                    case InStock():
                        return {"in_stock": True}
                    case MinPrice(min_price=price):
                        return {"price": {"$gte": price}}
                    case _:
                        return super()._translate(spec)

        translator = MyTranslator()
        results = collection.find(
            translator.translate(InStock() & MinPrice(100)),
        )
    """

    @override
    def _and(
        self,
        left: dict[str, Any],
        right: dict[str, Any],
    ) -> dict[str, Any]:
        """Combine with ``$and``."""
        return {"$and": [left, right]}

    @override
    def _or(
        self,
        left: dict[str, Any],
        right: dict[str, Any],
    ) -> dict[str, Any]:
        """Combine with ``$or``."""
        return {"$or": [left, right]}

    @override
    def _not(self, operand: dict[str, Any]) -> dict[str, Any]:
        """Negate with ``$nor``."""
        return {"$nor": [operand]}

    @override
    def _xor(
        self,
        left: dict[str, Any],
        right: dict[str, Any],
    ) -> dict[str, Any]:
        """Combine with native ``$xor``."""
        return {"$xor": [left, right]}

_and(left, right)

Combine with $and.

Source code in src/zspec/translators.py
@override
def _and(
    self,
    left: dict[str, Any],
    right: dict[str, Any],
) -> dict[str, Any]:
    """Combine with ``$and``."""
    return {"$and": [left, right]}

_not(operand)

Negate with $nor.

Source code in src/zspec/translators.py
@override
def _not(self, operand: dict[str, Any]) -> dict[str, Any]:
    """Negate with ``$nor``."""
    return {"$nor": [operand]}

_or(left, right)

Combine with $or.

Source code in src/zspec/translators.py
@override
def _or(
    self,
    left: dict[str, Any],
    right: dict[str, Any],
) -> dict[str, Any]:
    """Combine with ``$or``."""
    return {"$or": [left, right]}

_xor(left, right)

Combine with native $xor.

Source code in src/zspec/translators.py
@override
def _xor(
    self,
    left: dict[str, Any],
    right: dict[str, Any],
) -> dict[str, Any]:
    """Combine with native ``$xor``."""
    return {"$xor": [left, right]}

zspec.fields(_model)

Return a namespace for building field-comparison specifications.

Usage::

F = fields(Product)
spec = F.price >= 100  # Specification[Product]
Source code in src/zspec/specification.py
def fields[Model](_model: type[Model]) -> _FieldNamespace[Model]:
    """Return a namespace for building field-comparison specifications.

    Usage::

        F = fields(Product)
        spec = F.price >= 100  # Specification[Product]
    """
    return cast(_FieldNamespace[Model], _FieldNamespace())

zspec.TRUE_SPEC = TrueSpecification() module-attribute

zspec.FALSE_SPEC = FalseSpecification() module-attribute

zspec.to_dict(spec)

Serialize spec to a plain dictionary.

Composite nodes are recursively serialized. Leaf specifications keep their class name and all slot values.

Usage::

data = to_dict(InStock() & MinPrice(100))
# {"type": "AndSpecification", "left": {...}, "right": {...}}

Specification.of() specs and unregistered user-defined types will raise :exc:TypeError.

Source code in src/zspec/serialize.py
def to_dict(spec: Specification[Any]) -> dict[str, object]:
    """Serialize *spec* to a plain dictionary.

    Composite nodes are recursively serialized.  Leaf specifications
    keep their class name and all slot values.

    Usage::

        data = to_dict(InStock() & MinPrice(100))
        # {"type": "AndSpecification", "left": {...}, "right": {...}}

    ``Specification.of()`` specs and unregistered user-defined types
    will raise :exc:`TypeError`.
    """
    if spec is TRUE_SPEC:
        return {"type": "TRUE"}
    if spec is FALSE_SPEC:
        return {"type": "FALSE"}

    match spec:
        case AndSpecification() | OrSpecification() | XorSpecification():
            return {
                "type": type(spec).__name__,
                "left": to_dict(spec.left),
                "right": to_dict(spec.right),
            }
        case NotSpecification():
            return {
                "type": "NotSpecification",
                "spec": to_dict(spec.spec),
            }
        case FieldSpec():
            return {
                "type": "FieldSpec",
                "field": spec.field,
                "op": spec.op,
                "value": spec.value,
            }
        case _:
            return _to_dict_leaf(spec)

zspec.from_dict(data, registry=None)

Deserialize a specification tree from data.

registry maps class names to Specification subclasses. Built-in types (composites, TRUE, FALSE, FieldSpec) are always recognized.

Usage::

spec = from_dict({"type": "MinPrice", "threshold": 100})
Source code in src/zspec/serialize.py
def from_dict(
    data: Mapping[str, object],
    registry: _Registry | None = None,
) -> Specification[Any]:
    """Deserialize a specification tree from *data*.

    *registry* maps class names to ``Specification`` subclasses.
    Built-in types (composites, ``TRUE``, ``FALSE``, ``FieldSpec``)
    are always recognized.

    Usage::

        spec = from_dict({"type": "MinPrice", "threshold": 100})
    """
    reg: dict[str, type[Specification[Any]]] = {**_BUILTINS}
    reg.update(Specification.registry)
    if registry is not None:
        reg.update(registry)

    type_name: object = data["type"]
    if not isinstance(type_name, str):
        msg = f"Invalid spec data: 'type' must be a string, got {type_name!r}"
        raise TypeError(msg)

    if type_name == "TRUE":
        return cast(Specification[Any], TRUE_SPEC)
    if type_name == "FALSE":
        return cast(Specification[Any], FALSE_SPEC)

    return _from_dict_node(type_name, data, reg, registry)