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,425 @@
1
+ from typing import List, cast
2
+
3
+ from exdrf.filter import (
4
+ FieldFilter,
5
+ FilterType,
6
+ FilterVisitor,
7
+ LogicAndType,
8
+ LogicNotType,
9
+ LogicOrType,
10
+ SearchType,
11
+ insert_quick_search,
12
+ validate_filter,
13
+ )
14
+
15
+
16
+ class FilterVisitorCollector(FilterVisitor):
17
+ """Test visitor that collects visited items for verification."""
18
+
19
+ def __init__(self, filter: FilterType):
20
+ super().__init__(filter)
21
+ self.visited_and: List[LogicAndType] = []
22
+ self.visited_or: List[LogicOrType] = []
23
+ self.visited_not: List[LogicNotType] = []
24
+ self.visited_field: List[FieldFilter] = []
25
+
26
+ def visit_and(self, filter: LogicAndType):
27
+ self.visited_and.append(filter)
28
+
29
+ def visit_or(self, filter: LogicOrType):
30
+ self.visited_or.append(filter)
31
+
32
+ def visit_not(self, filter: LogicNotType):
33
+ self.visited_not.append(filter)
34
+
35
+ def visit_field(self, filter: FieldFilter):
36
+ self.visited_field.append(filter)
37
+
38
+
39
+ def test_field_filter_creation():
40
+ """Test creating a FieldFilter instance."""
41
+ filter = FieldFilter(fld="name", op="eq", vl="test")
42
+ assert filter.fld == "name"
43
+ assert filter.op == "eq"
44
+ assert filter.vl == "test"
45
+
46
+
47
+ def test_simple_field_filter_visitor() -> None:
48
+ """Test visiting a simple field filter."""
49
+ filter: FilterType = [FieldFilter(fld="name", op="eq", vl="test")]
50
+ visitor = FilterVisitorCollector(filter)
51
+ visitor.run(filter)
52
+
53
+ assert len(visitor.visited_field) == 1
54
+ assert visitor.visited_field[0].fld == "name"
55
+ assert visitor.visited_field[0].op == "eq"
56
+ assert visitor.visited_field[0].vl == "test"
57
+
58
+
59
+ def test_and_filter_visitor() -> None:
60
+ """Test visiting an AND filter."""
61
+ filter: FilterType = cast(
62
+ FilterType,
63
+ [
64
+ "and",
65
+ [
66
+ FieldFilter(fld="id", op="eq", vl=1),
67
+ FieldFilter(fld="name", op="eq", vl="test"),
68
+ ],
69
+ ],
70
+ )
71
+ visitor = FilterVisitorCollector(filter)
72
+ visitor.run(filter)
73
+
74
+ assert len(visitor.visited_and) == 1
75
+ assert len(visitor.visited_field) == 2
76
+ assert visitor.visited_field[0].fld == "id"
77
+ assert visitor.visited_field[1].fld == "name"
78
+
79
+
80
+ def test_or_filter_visitor() -> None:
81
+ """Test visiting an OR filter."""
82
+ filter: FilterType = cast(
83
+ FilterType,
84
+ [
85
+ "or",
86
+ [
87
+ FieldFilter(fld="id", op="eq", vl=1),
88
+ FieldFilter(fld="name", op="eq", vl="test"),
89
+ ],
90
+ ],
91
+ )
92
+ visitor = FilterVisitorCollector(filter)
93
+ visitor.run(filter)
94
+
95
+ assert len(visitor.visited_or) == 1
96
+ assert len(visitor.visited_field) == 2
97
+ assert visitor.visited_field[0].fld == "id"
98
+ assert visitor.visited_field[1].fld == "name"
99
+
100
+
101
+ def test_not_filter_visitor() -> None:
102
+ """Test visiting a NOT filter."""
103
+ filter: FilterType = cast(FilterType, ["not", FieldFilter(fld="id", op="eq", vl=1)])
104
+ visitor = FilterVisitorCollector(filter)
105
+ visitor.run(filter)
106
+
107
+ assert len(visitor.visited_not) == 1
108
+ assert len(visitor.visited_field) == 1
109
+ assert visitor.visited_field[0].fld == "id"
110
+
111
+
112
+ def test_nested_filter_visitor() -> None:
113
+ """Test visiting a nested filter structure."""
114
+ filter: FilterType = cast(
115
+ FilterType,
116
+ [
117
+ "and",
118
+ [
119
+ FieldFilter(fld="id", op="eq", vl=1),
120
+ [
121
+ "or",
122
+ [
123
+ FieldFilter(fld="name", op="eq", vl="test"),
124
+ ["not", FieldFilter(fld="active", op="eq", vl=True)],
125
+ ],
126
+ ],
127
+ ],
128
+ ],
129
+ )
130
+ visitor = FilterVisitorCollector(filter)
131
+ visitor.run(filter)
132
+
133
+ assert len(visitor.visited_and) == 1
134
+ assert len(visitor.visited_or) == 1
135
+ assert len(visitor.visited_not) == 1
136
+ assert len(visitor.visited_field) == 3
137
+
138
+
139
+ def test_validate_filter_valid() -> None:
140
+ """Test validation of valid filters."""
141
+ # Test single field filter
142
+ assert validate_filter([FieldFilter(fld="name", op="eq", vl="test")]) == []
143
+
144
+ # Test AND filter
145
+ and_filter: FilterType = cast(
146
+ FilterType,
147
+ [
148
+ "and",
149
+ [
150
+ FieldFilter(fld="id", op="eq", vl=1),
151
+ FieldFilter(fld="name", op="eq", vl="test"),
152
+ ],
153
+ ],
154
+ )
155
+ assert validate_filter(and_filter) == []
156
+
157
+ # Test OR filter
158
+ or_filter: FilterType = cast(
159
+ FilterType,
160
+ [
161
+ "or",
162
+ [
163
+ FieldFilter(fld="id", op="eq", vl=1),
164
+ FieldFilter(fld="name", op="eq", vl="test"),
165
+ ],
166
+ ],
167
+ )
168
+ assert validate_filter(or_filter) == []
169
+
170
+ # Test NOT filter
171
+ not_filter: FilterType = cast(
172
+ FilterType, ["not", FieldFilter(fld="id", op="eq", vl=1)]
173
+ )
174
+ assert validate_filter(not_filter) == []
175
+
176
+
177
+ def test_validate_filter_invalid() -> None:
178
+ """Test validation of invalid filters."""
179
+ # Test invalid field filter
180
+ invalid_filter: FilterType = cast(FilterType, [{"invalid": "field"}])
181
+ assert validate_filter(invalid_filter) == ["invalid_field_filter", "and[0]"]
182
+
183
+ # Test invalid logic operator
184
+ assert validate_filter(cast(FilterType, ["invalid", []])) == [
185
+ "unknown_logic_operator"
186
+ ]
187
+
188
+ # Test invalid logic argument
189
+ assert validate_filter(cast(FilterType, ["and", "not_a_list"])) == [
190
+ "logic_arg_not_a_list",
191
+ "and",
192
+ ]
193
+
194
+ # Test invalid logic group length
195
+ assert validate_filter(cast(FilterType, ["and", []])) == []
196
+ single_filter: FilterType = cast(
197
+ FilterType, ["and", [FieldFilter(fld="id", op="eq", vl=1)]]
198
+ )
199
+ assert validate_filter(single_filter) == []
200
+
201
+ # Test invalid filter type
202
+ assert validate_filter(cast(FilterType, 123)) == ["unknown_filter_type"]
203
+
204
+ # Test None filter
205
+ assert validate_filter(cast(FilterType, None)) == ["none"]
206
+
207
+
208
+ def test_validate_filter_complex() -> None:
209
+ """Test validation of complex nested filters."""
210
+ complex_filter: FilterType = cast(
211
+ FilterType,
212
+ [
213
+ "and",
214
+ [
215
+ FieldFilter(fld="id", op="eq", vl=1),
216
+ [
217
+ "or",
218
+ [
219
+ FieldFilter(fld="name", op="eq", vl="test"),
220
+ ["not", FieldFilter(fld="active", op="eq", vl=True)],
221
+ ],
222
+ ],
223
+ ],
224
+ ],
225
+ )
226
+ assert validate_filter(complex_filter) == []
227
+
228
+
229
+ def test_validate_filter_edge_cases() -> None:
230
+ """Test validation of edge cases."""
231
+ # Empty list is valid
232
+ assert validate_filter([]) == []
233
+
234
+ # Empty logic group is valid
235
+ assert validate_filter(cast(FilterType, ["and", []])) == []
236
+ assert validate_filter(cast(FilterType, ["or", []])) == []
237
+
238
+ # Single field filter as dict is valid
239
+ dict_filter: FilterType = cast(FilterType, {"fld": "id", "op": "eq", "vl": 1})
240
+ assert validate_filter(dict_filter) == []
241
+
242
+ # Single field filter as FieldFilter instance is valid
243
+ field_filter: FilterType = cast(FilterType, [FieldFilter(fld="id", op="eq", vl=1)])
244
+ assert validate_filter(field_filter) == []
245
+
246
+
247
+ def test_insert_quick_search_basic() -> None:
248
+ """Test basic quick search insertion."""
249
+ # Test with None filter
250
+ result = insert_quick_search("name", "test", search_type=SearchType.EXTENDED)
251
+ assert isinstance(result, list)
252
+ assert len(result) == 1
253
+ assert isinstance(result[0], FieldFilter)
254
+ assert result[0].fld == "name"
255
+ assert result[0].op == "ilike"
256
+ assert result[0].vl == "%test%"
257
+
258
+ # Test with empty list
259
+ result = insert_quick_search(
260
+ "name", "test", cast(FilterType, []), search_type=SearchType.EXTENDED
261
+ )
262
+ assert isinstance(result, list)
263
+ assert len(result) == 1
264
+ assert isinstance(result[0], FieldFilter)
265
+ assert result[0].fld == "name"
266
+ assert result[0].op == "ilike"
267
+ assert result[0].vl == "%test%"
268
+
269
+ # Test with existing filter
270
+ existing_filter = cast(FilterType, [FieldFilter(fld="id", op="eq", vl=1)])
271
+ result = insert_quick_search(
272
+ "name", "test", existing_filter, search_type=SearchType.EXTENDED
273
+ )
274
+ assert isinstance(result, list)
275
+ assert len(result) == 2
276
+ assert existing_filter[0] in result
277
+ assert isinstance(result[0], FieldFilter)
278
+ assert result[0].fld == "name"
279
+ assert result[0].op == "ilike"
280
+ assert result[0].vl == "%test%"
281
+
282
+
283
+ def test_insert_quick_search_exact() -> None:
284
+ """Test exact quick search insertion."""
285
+ # Test exact search
286
+ result = insert_quick_search("name", "test", search_type=SearchType.EXACT)
287
+ assert isinstance(result, list)
288
+ assert len(result) == 1
289
+ assert isinstance(result[0], FieldFilter)
290
+ assert result[0].fld == "name"
291
+ assert result[0].op == "eq"
292
+ assert result[0].vl == "test"
293
+
294
+
295
+ def test_insert_quick_search_wildcards() -> None:
296
+ """Test quick search with wildcards and spaces."""
297
+ # Test with spaces
298
+ result = insert_quick_search("name", "test value", search_type=SearchType.EXTENDED)
299
+ assert isinstance(result, list)
300
+ assert len(result) == 1
301
+ assert isinstance(result[0], FieldFilter)
302
+ assert result[0].vl == "%test%value%"
303
+
304
+ # Test with asterisks
305
+ result = insert_quick_search("name", "test*value", search_type=SearchType.EXTENDED)
306
+ assert isinstance(result, list)
307
+ assert len(result) == 1
308
+ assert isinstance(result[0], FieldFilter)
309
+ assert result[0].vl == "test%value"
310
+
311
+ # Test with mixed wildcards
312
+ result = insert_quick_search("name", "test* value", search_type=SearchType.EXTENDED)
313
+ assert isinstance(result, list)
314
+ assert len(result) == 1
315
+ assert isinstance(result[0], FieldFilter)
316
+ assert result[0].vl == "test%value"
317
+
318
+
319
+ def test_insert_quick_search_replace() -> None:
320
+ """Test replacing existing quick searches."""
321
+ # Test replacing existing quick search
322
+ existing_filter = cast(
323
+ FilterType, [FieldFilter(fld="name", op="ilike", vl="%old%")]
324
+ )
325
+ result = insert_quick_search(
326
+ "name", "new", existing_filter, search_type=SearchType.EXTENDED
327
+ )
328
+ assert isinstance(result, list)
329
+ assert len(result) == 1
330
+ assert isinstance(result[0], FieldFilter)
331
+ assert result[0].vl == "%new%"
332
+
333
+ # Test replacing in AND group
334
+ and_filter = cast(
335
+ FilterType,
336
+ [
337
+ "and",
338
+ [
339
+ FieldFilter(fld="name", op="ilike", vl="%old%"),
340
+ FieldFilter(fld="id", op="eq", vl=1),
341
+ ],
342
+ ],
343
+ )
344
+ result = insert_quick_search(
345
+ "name", "new", and_filter, search_type=SearchType.EXTENDED
346
+ )
347
+ assert isinstance(result, list)
348
+ assert len(result) == 2
349
+ assert result[0] == "and"
350
+ assert isinstance(result[1], list)
351
+ assert len(result[1]) == 2
352
+ field_filter = result[1][0]
353
+ assert isinstance(field_filter, FieldFilter)
354
+ assert field_filter.vl == "%new%"
355
+ id_filter = result[1][1]
356
+ assert isinstance(id_filter, FieldFilter)
357
+ assert id_filter.fld == "id"
358
+
359
+
360
+ def test_insert_quick_search_empty() -> None:
361
+ """Test quick search with empty values."""
362
+ # Test with empty string
363
+ result = insert_quick_search("name", "")
364
+ assert isinstance(result, list)
365
+ assert len(result) == 0
366
+
367
+ # Test with whitespace only
368
+ result = insert_quick_search("name", " ")
369
+ assert isinstance(result, list)
370
+ assert len(result) == 0
371
+
372
+ # Test with empty string and existing filter
373
+ existing_filter = cast(FilterType, [FieldFilter(fld="id", op="eq", vl=1)])
374
+ result = insert_quick_search("name", "", existing_filter)
375
+ assert result == existing_filter
376
+
377
+
378
+ def test_insert_quick_search_edge_cases() -> None:
379
+ """Test edge cases for quick search insertion."""
380
+ # Test with None value
381
+ result = insert_quick_search("name", None) # type: ignore
382
+ assert isinstance(result, list)
383
+ assert len(result) == 0
384
+
385
+ # Test with existing quick search in dict form
386
+ dict_filter = cast(FilterType, [{"fld": "name", "op": "ilike", "vl": "%old%"}])
387
+ result = insert_quick_search(
388
+ "name", "new", dict_filter, search_type=SearchType.EXTENDED
389
+ )
390
+ assert isinstance(result, list)
391
+ assert len(result) == 1
392
+ assert isinstance(result[0], FieldFilter)
393
+ assert result[0].vl == "%new%"
394
+
395
+ # Test with complex nested filter
396
+ complex_filter = cast(
397
+ FilterType,
398
+ [
399
+ "and",
400
+ [
401
+ FieldFilter(fld="name", op="ilike", vl="%old%"),
402
+ [
403
+ "or",
404
+ [
405
+ FieldFilter(fld="id", op="eq", vl=1),
406
+ FieldFilter(fld="active", op="eq", vl=True),
407
+ ],
408
+ ],
409
+ ],
410
+ ],
411
+ )
412
+ result = insert_quick_search(
413
+ "name", "new", complex_filter, search_type=SearchType.EXTENDED
414
+ )
415
+ assert isinstance(result, list)
416
+ assert len(result) == 2
417
+ assert result[0] == "and"
418
+ assert isinstance(result[1], list)
419
+ assert len(result[1]) == 2
420
+ field_filter = result[1][0]
421
+ assert isinstance(field_filter, FieldFilter)
422
+ assert field_filter.vl == "%new%"
423
+ or_group = result[1][1]
424
+ assert isinstance(or_group, list)
425
+ assert or_group[0] == "or"