exdrf 0.0.1.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. exdrf/__init__.py +0 -0
  2. exdrf/__version__.py +24 -0
  3. exdrf/api.py +51 -0
  4. exdrf/constants.py +30 -0
  5. exdrf/dataset.py +197 -0
  6. exdrf/field.py +554 -0
  7. exdrf/field_types/__init__.py +0 -0
  8. exdrf/field_types/api.py +78 -0
  9. exdrf/field_types/blob_field.py +44 -0
  10. exdrf/field_types/bool_field.py +47 -0
  11. exdrf/field_types/date_field.py +49 -0
  12. exdrf/field_types/date_time.py +52 -0
  13. exdrf/field_types/dur_field.py +44 -0
  14. exdrf/field_types/enum_field.py +41 -0
  15. exdrf/field_types/filter_field.py +11 -0
  16. exdrf/field_types/float_field.py +85 -0
  17. exdrf/field_types/float_list.py +18 -0
  18. exdrf/field_types/formatted.py +39 -0
  19. exdrf/field_types/int_field.py +70 -0
  20. exdrf/field_types/int_list.py +18 -0
  21. exdrf/field_types/ref_base.py +105 -0
  22. exdrf/field_types/ref_m2m.py +39 -0
  23. exdrf/field_types/ref_m2o.py +23 -0
  24. exdrf/field_types/ref_o2m.py +36 -0
  25. exdrf/field_types/ref_o2o.py +32 -0
  26. exdrf/field_types/sort_field.py +18 -0
  27. exdrf/field_types/str_field.py +77 -0
  28. exdrf/field_types/str_list.py +18 -0
  29. exdrf/field_types/time_field.py +49 -0
  30. exdrf/filter.py +653 -0
  31. exdrf/filter_dsl.py +950 -0
  32. exdrf/filter_op_catalog.py +222 -0
  33. exdrf/label_dsl.py +691 -0
  34. exdrf/moment.py +496 -0
  35. exdrf/py.typed +0 -0
  36. exdrf/py_support.py +21 -0
  37. exdrf/resource.py +901 -0
  38. exdrf/sa_fi_item.py +69 -0
  39. exdrf/sa_filter_op.py +324 -0
  40. exdrf/utils.py +17 -0
  41. exdrf/validator.py +45 -0
  42. exdrf/var_bag.py +328 -0
  43. exdrf/visitor.py +58 -0
  44. exdrf-0.0.1.dev0.dist-info/METADATA +42 -0
  45. exdrf-0.0.1.dev0.dist-info/RECORD +57 -0
  46. exdrf-0.0.1.dev0.dist-info/WHEEL +5 -0
  47. exdrf-0.0.1.dev0.dist-info/top_level.txt +3 -0
  48. exdrf_tests/__init__.py +0 -0
  49. exdrf_tests/test_dataset.py +422 -0
  50. exdrf_tests/test_field.py +109 -0
  51. exdrf_tests/test_filter.py +425 -0
  52. exdrf_tests/test_filter_dsl.py +556 -0
  53. exdrf_tests/test_label_dsl.py +234 -0
  54. exdrf_tests/test_resource.py +107 -0
  55. exdrf_tests/test_utils.py +43 -0
  56. exdrf_tests/test_visitor.py +31 -0
  57. exdrf_tests/var_bag_test.py +502 -0
@@ -0,0 +1,222 @@
1
+ """Catalog of filter operators and which ``FIELD_TYPE_*`` values support them.
2
+
3
+ This module is the single source of truth for:
4
+
5
+ - **Canonical operator ids** stored in JSON filters and used by
6
+ ``exdrf.sa_filter_op.filter_op_registry`` (for example ``eq``, ``not_eq``).
7
+ - **Surface aliases** accepted in the filter DSL or legacy payloads (for
8
+ example ``==``, ``ne``, ``>=``).
9
+ - **Allowed operators per field type** for validators and for backend/codegen
10
+ tools that emit OpenAPI or query parameters.
11
+
12
+ Generators (including those in application repos) can import this module and
13
+ call :func:`filter_ops_by_field_type_json` to embed the mapping in artifacts.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from types import MappingProxyType
19
+ from typing import Dict, Final, FrozenSet, Mapping, Optional
20
+
21
+ from exdrf.constants import (
22
+ FIELD_TYPE_BLOB,
23
+ FIELD_TYPE_BOOL,
24
+ FIELD_TYPE_DATE,
25
+ FIELD_TYPE_DT,
26
+ FIELD_TYPE_DURATION,
27
+ FIELD_TYPE_ENUM,
28
+ FIELD_TYPE_FLOAT,
29
+ FIELD_TYPE_FLOAT_LIST,
30
+ FIELD_TYPE_FORMATTED,
31
+ FIELD_TYPE_INT_LIST,
32
+ FIELD_TYPE_INTEGER,
33
+ FIELD_TYPE_REF_MANY_TO_MANY,
34
+ FIELD_TYPE_REF_MANY_TO_ONE,
35
+ FIELD_TYPE_REF_ONE_TO_MANY,
36
+ FIELD_TYPE_REF_ONE_TO_ONE,
37
+ FIELD_TYPE_STRING,
38
+ FIELD_TYPE_STRING_LIST,
39
+ FIELD_TYPE_TIME,
40
+ )
41
+
42
+ # Canonical ids match ``FiOp.uniq`` / keys in ``FiOpRegistry`` (exdrf-qt).
43
+ FILTER_OP_EQ: Final = "eq"
44
+ FILTER_OP_NOT_EQ: Final = "not_eq"
45
+ FILTER_OP_ILIKE: Final = "ilike"
46
+ FILTER_OP_REGEX: Final = "regex"
47
+ FILTER_OP_NONE: Final = "none"
48
+ FILTER_OP_GT: Final = "gt"
49
+ FILTER_OP_LT: Final = "lt"
50
+ FILTER_OP_GE: Final = "ge"
51
+ FILTER_OP_LE: Final = "le"
52
+ FILTER_OP_IN: Final = "in"
53
+
54
+ # All canonical operators known to the SQLAlchemy-backed registry.
55
+ ALL_CANONICAL_FILTER_OPS: Final[FrozenSet[str]] = frozenset(
56
+ {
57
+ FILTER_OP_EQ,
58
+ FILTER_OP_NOT_EQ,
59
+ FILTER_OP_ILIKE,
60
+ FILTER_OP_REGEX,
61
+ FILTER_OP_NONE,
62
+ FILTER_OP_GT,
63
+ FILTER_OP_LT,
64
+ FILTER_OP_GE,
65
+ FILTER_OP_LE,
66
+ FILTER_OP_IN,
67
+ }
68
+ )
69
+
70
+ # Comparison and membership operators (no pattern match).
71
+ _ORDER_AND_MEMBERSHIP_OPS: Final[FrozenSet[str]] = frozenset(
72
+ {
73
+ FILTER_OP_EQ,
74
+ FILTER_OP_NOT_EQ,
75
+ FILTER_OP_GT,
76
+ FILTER_OP_LT,
77
+ FILTER_OP_GE,
78
+ FILTER_OP_LE,
79
+ FILTER_OP_IN,
80
+ FILTER_OP_NONE,
81
+ }
82
+ )
83
+
84
+ # String-oriented column types: pattern match plus ordering.
85
+ _STRING_LIKE_OPS: Final[FrozenSet[str]] = frozenset(
86
+ _ORDER_AND_MEMBERSHIP_OPS | {FILTER_OP_ILIKE, FILTER_OP_REGEX}
87
+ )
88
+
89
+ # Map exdrf ``type_name`` -> allowed **canonical** operator ids.
90
+ _FILTER_OPS_BY_FIELD_TYPE: Dict[str, FrozenSet[str]] = {
91
+ FIELD_TYPE_STRING: _STRING_LIKE_OPS,
92
+ FIELD_TYPE_FORMATTED: _STRING_LIKE_OPS,
93
+ FIELD_TYPE_INTEGER: _ORDER_AND_MEMBERSHIP_OPS,
94
+ FIELD_TYPE_FLOAT: _ORDER_AND_MEMBERSHIP_OPS,
95
+ FIELD_TYPE_BOOL: frozenset({FILTER_OP_EQ, FILTER_OP_NOT_EQ, FILTER_OP_NONE}),
96
+ FIELD_TYPE_DATE: _ORDER_AND_MEMBERSHIP_OPS,
97
+ FIELD_TYPE_DT: _ORDER_AND_MEMBERSHIP_OPS,
98
+ FIELD_TYPE_TIME: _ORDER_AND_MEMBERSHIP_OPS,
99
+ FIELD_TYPE_DURATION: _ORDER_AND_MEMBERSHIP_OPS,
100
+ FIELD_TYPE_ENUM: frozenset(
101
+ {FILTER_OP_EQ, FILTER_OP_NOT_EQ, FILTER_OP_IN, FILTER_OP_NONE}
102
+ ),
103
+ FIELD_TYPE_BLOB: frozenset({FILTER_OP_EQ, FILTER_OP_NOT_EQ, FILTER_OP_NONE}),
104
+ FIELD_TYPE_STRING_LIST: frozenset(
105
+ {FILTER_OP_EQ, FILTER_OP_NOT_EQ, FILTER_OP_IN, FILTER_OP_NONE}
106
+ ),
107
+ FIELD_TYPE_INT_LIST: frozenset(
108
+ {FILTER_OP_EQ, FILTER_OP_NOT_EQ, FILTER_OP_IN, FILTER_OP_NONE}
109
+ ),
110
+ FIELD_TYPE_FLOAT_LIST: frozenset(
111
+ {FILTER_OP_EQ, FILTER_OP_NOT_EQ, FILTER_OP_IN, FILTER_OP_NONE}
112
+ ),
113
+ FIELD_TYPE_REF_MANY_TO_ONE: _ORDER_AND_MEMBERSHIP_OPS,
114
+ FIELD_TYPE_REF_ONE_TO_ONE: _ORDER_AND_MEMBERSHIP_OPS,
115
+ FIELD_TYPE_REF_ONE_TO_MANY: _ORDER_AND_MEMBERSHIP_OPS,
116
+ FIELD_TYPE_REF_MANY_TO_MANY: _ORDER_AND_MEMBERSHIP_OPS,
117
+ }
118
+
119
+ FILTER_OPS_BY_FIELD_TYPE: Final[Mapping[str, FrozenSet[str]]] = MappingProxyType(
120
+ _FILTER_OPS_BY_FIELD_TYPE
121
+ )
122
+
123
+ # Every surface form maps to exactly one canonical id.
124
+ _FILTER_OP_ALIASES: Dict[str, str] = {
125
+ # Canonical forms (identity).
126
+ FILTER_OP_EQ: FILTER_OP_EQ,
127
+ FILTER_OP_NOT_EQ: FILTER_OP_NOT_EQ,
128
+ FILTER_OP_ILIKE: FILTER_OP_ILIKE,
129
+ FILTER_OP_REGEX: FILTER_OP_REGEX,
130
+ FILTER_OP_NONE: FILTER_OP_NONE,
131
+ FILTER_OP_GT: FILTER_OP_GT,
132
+ FILTER_OP_LT: FILTER_OP_LT,
133
+ FILTER_OP_GE: FILTER_OP_GE,
134
+ FILTER_OP_LE: FILTER_OP_LE,
135
+ FILTER_OP_IN: FILTER_OP_IN,
136
+ # JSON / docs sometimes use ``ne`` instead of ``not_eq``.
137
+ "ne": FILTER_OP_NOT_EQ,
138
+ # Filter DSL symbolic tokens (see ``Tokenizer`` in ``filter_dsl``).
139
+ "==": FILTER_OP_EQ,
140
+ "!=": FILTER_OP_NOT_EQ,
141
+ ">": FILTER_OP_GT,
142
+ "<": FILTER_OP_LT,
143
+ ">=": FILTER_OP_GE,
144
+ "<=": FILTER_OP_LE,
145
+ # Historical / alternate spellings.
146
+ "gte": FILTER_OP_GE,
147
+ "lte": FILTER_OP_LE,
148
+ "~=": FILTER_OP_ILIKE,
149
+ }
150
+
151
+ FILTER_OP_ALIASES: Final[Mapping[str, str]] = MappingProxyType(_FILTER_OP_ALIASES)
152
+
153
+
154
+ def normalize_filter_op(op: str) -> Optional[str]:
155
+ """Resolve a filter operator string to its canonical id.
156
+
157
+ Matching is case-insensitive for the lookup key.
158
+
159
+ Args:
160
+ op: Operator as provided by JSON, the DSL, or another client.
161
+
162
+ Returns:
163
+ Canonical operator id, or ``None`` if the string is not recognized.
164
+ """
165
+ if not op:
166
+ return None
167
+ return _FILTER_OP_ALIASES.get(op.lower())
168
+
169
+
170
+ def canonical_filter_ops_for_type(type_name: str) -> FrozenSet[str]:
171
+ """Return the canonical operators allowed for an exdrf ``type_name``.
172
+
173
+ Unknown ``type_name`` values fall back to :data:`ALL_CANONICAL_FILTER_OPS`
174
+ so custom field types remain usable until they register a mapping.
175
+
176
+ Args:
177
+ type_name: Value of :attr:`exdrf.field.ExField.type_name`.
178
+
179
+ Returns:
180
+ Frozen set of canonical operator ids.
181
+ """
182
+ mapped = _FILTER_OPS_BY_FIELD_TYPE.get(type_name)
183
+ if mapped is not None:
184
+ return mapped
185
+ return ALL_CANONICAL_FILTER_OPS
186
+
187
+
188
+ def filter_op_allowed_for_type(type_name: str, op: str) -> bool:
189
+ """Return whether ``op`` is allowed for the given field ``type_name``.
190
+
191
+ Args:
192
+ type_name: Field ``type_name`` from metadata.
193
+ op: Operator in any supported surface form.
194
+
195
+ Returns:
196
+ ``True`` if the operator is known and allowed for that type.
197
+ """
198
+ canon = normalize_filter_op(op)
199
+ if canon is None:
200
+ return False
201
+ return canon in canonical_filter_ops_for_type(type_name)
202
+
203
+
204
+ def filter_ops_by_field_type_json() -> Dict[str, list[str]]:
205
+ """Build a JSON-friendly ``type_name -> [canonical_op, ...]`` mapping.
206
+
207
+ Returns:
208
+ Dict suitable for ``json.dumps`` or embedding in generated code.
209
+ """
210
+ result: Dict[str, list[str]] = {}
211
+ for type_name, ops in _FILTER_OPS_BY_FIELD_TYPE.items():
212
+ result[type_name] = sorted(ops)
213
+ return result
214
+
215
+
216
+ def filter_op_alias_map_json() -> Dict[str, str]:
217
+ """Return ``alias -> canonical`` as plain strings for generators.
218
+
219
+ Returns:
220
+ Dict of every alias (lowercase keys) to canonical operator id.
221
+ """
222
+ return dict(_FILTER_OP_ALIASES)