Encapsulate filters as objects that can then be combined logically (using `&` and `|`).
| 39 | |
| 40 | |
| 41 | class Q(tree.Node): |
| 42 | """ |
| 43 | Encapsulate filters as objects that can then be combined logically (using |
| 44 | `&` and `|`). |
| 45 | """ |
| 46 | |
| 47 | # Connection types |
| 48 | AND = "AND" |
| 49 | OR = "OR" |
| 50 | XOR = "XOR" |
| 51 | default = AND |
| 52 | conditional = True |
| 53 | connectors = (None, AND, OR, XOR) |
| 54 | |
| 55 | def __init__(self, *args, _connector=None, _negated=False, **kwargs): |
| 56 | self._check_connector(_connector) |
| 57 | super().__init__( |
| 58 | children=[*args, *sorted(kwargs.items())], |
| 59 | connector=_connector, |
| 60 | negated=_negated, |
| 61 | ) |
| 62 | |
| 63 | @classmethod |
| 64 | def create(cls, children=None, connector=None, negated=False): |
| 65 | cls._check_connector(connector) |
| 66 | return super().create(children=children, connector=connector, negated=negated) |
| 67 | |
| 68 | @classmethod |
| 69 | def _check_connector(cls, connector): |
| 70 | if connector not in cls.connectors: |
| 71 | connector_reprs = ", ".join(f"{conn!r}" for conn in cls.connectors[1:]) |
| 72 | raise ValueError(f"connector must be one of {connector_reprs}, or None.") |
| 73 | |
| 74 | def _combine(self, other, conn): |
| 75 | if getattr(other, "conditional", False) is False: |
| 76 | raise TypeError(other) |
| 77 | if not self: |
| 78 | return other.copy() |
| 79 | if not other and isinstance(other, Q): |
| 80 | return self.copy() |
| 81 | |
| 82 | obj = self.create(connector=conn) |
| 83 | obj.add(self, conn) |
| 84 | obj.add(other, conn) |
| 85 | return obj |
| 86 | |
| 87 | def __or__(self, other): |
| 88 | return self._combine(other, self.OR) |
| 89 | |
| 90 | def __and__(self, other): |
| 91 | return self._combine(other, self.AND) |
| 92 | |
| 93 | def __xor__(self, other): |
| 94 | return self._combine(other, self.XOR) |
| 95 | |
| 96 | def __invert__(self): |
| 97 | obj = self.copy() |
| 98 | obj.negate() |
no outgoing calls