Source code for djangordf.query

"""``Q`` objects for composing filter expressions with ``&``, ``|`` and ``~``.

A ``Q`` instance carries a connector (``AND`` or ``OR``), a negation
flag, and a list of children. Children are either other ``Q``
subtrees or ``(key, value)`` leaves built from kwargs at construction
time. The operator dunders return new ``Q`` instances; same-connector
chains are flattened so the resulting tree stays shallow.
"""
from __future__ import annotations

from typing import Iterable, List, Sequence, Tuple, Union


_Leaf = Tuple[str, object]


[docs] class Q: """Composable filter expression for :py:meth:`RDFManager.filter`.""" AND = "AND" OR = "OR" def __init__( self, *args: Union["Q", _Leaf], _connector: str = AND, _negated: bool = False, **kwargs: object, ) -> None: if _connector not in (Q.AND, Q.OR): raise ValueError( f"Q connector must be 'AND' or 'OR', got {_connector!r}" ) children: List[Union["Q", _Leaf]] = list(args) + list(kwargs.items()) if not children: raise ValueError( "Q requires at least one child (positional Q or kwarg)" ) self.connector = _connector self.negated = _negated self.children: List[Union["Q", _Leaf]] = children # -- operator surface --------------------------------------------------- def __or__(self, other: "Q") -> "Q": if not isinstance(other, Q): return NotImplemented return Q._combine(self, other, Q.OR) def __and__(self, other: "Q") -> "Q": if not isinstance(other, Q): return NotImplemented return Q._combine(self, other, Q.AND) def __invert__(self) -> "Q": clone = Q.__new__(Q) clone.connector = self.connector clone.negated = not self.negated clone.children = list(self.children) return clone def __bool__(self) -> bool: raise TypeError( "Q is not directly usable in boolean context; " "compose with &/|/~ or pass to filter()" ) def __repr__(self) -> str: prefix = "~" if self.negated else "" joiner = " | " if self.connector == Q.OR else " & " rendered = joiner.join( repr(child) if isinstance(child, Q) else f"({child[0]}={child[1]!r})" for child in self.children ) return f"{prefix}({rendered})" # -- helpers ------------------------------------------------------------ @staticmethod def _combine(left: "Q", right: "Q", connector: str) -> "Q": """Combine two Q instances under ``connector``. Same-connector children are flattened so the tree stays shallow, but a negated node is treated as a single opaque subtree to preserve its semantics.""" children: List[Union["Q", _Leaf]] = [] for side in (left, right): if ( isinstance(side, Q) and side.connector == connector and not side.negated ): children.extend(side.children) else: children.append(side) merged = Q.__new__(Q) merged.connector = connector merged.negated = False merged.children = children return merged @property def is_leaf_only(self) -> bool: """True when every child is a ``(key, value)`` leaf — useful for the byte-identical fast path in the flat filter case.""" return all(not isinstance(child, Q) for child in self.children)
[docs] def leaves(self) -> Iterable[_Leaf]: """Iterate only the direct ``(key, value)`` leaves (not Q children). Used by the flat path.""" for child in self.children: if not isinstance(child, Q): yield child
[docs] def subtrees(self) -> Sequence["Q"]: """Direct Q children (excluding leaves).""" return [child for child in self.children if isinstance(child, Q)]