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,234 @@
1
+ from datetime import datetime
2
+
3
+ import pytest
4
+
5
+ from exdrf.label_dsl import (
6
+ ParsedIdentifier,
7
+ ParsedLiteral,
8
+ ParsedOp,
9
+ evaluate,
10
+ generate_python_code,
11
+ generate_typescript_code,
12
+ get_used_fields,
13
+ parse_expr,
14
+ )
15
+
16
+
17
+ class DummyContext:
18
+ def __init__(self, **kwargs):
19
+ for key, value in kwargs.items():
20
+ setattr(self, key, value)
21
+
22
+
23
+ def test_parse_expr_concat():
24
+ expr = '(concat "Hello" "World")'
25
+ ast = parse_expr(expr)
26
+ # Check that the AST is a list starting with an operator
27
+ assert isinstance(ast, list)
28
+ op = ast[0]
29
+ assert isinstance(op, ParsedOp)
30
+ assert op.value == "concat"
31
+ # Remaining tokens should be literals
32
+ lit1 = ast[1]
33
+ lit2 = ast[2]
34
+ assert isinstance(lit1, ParsedLiteral)
35
+ assert lit1.value == "Hello"
36
+ assert isinstance(lit2, ParsedLiteral)
37
+ assert lit2.value == "World"
38
+
39
+
40
+ def test_evaluate_concat_literals():
41
+ expr = '(concat "Hello" " " "World")'
42
+ ast = parse_expr(expr)
43
+ # All arguments are literals so context is not used (pass None)
44
+ result = evaluate(ast, None)
45
+ assert result == "Hello World"
46
+
47
+
48
+ def test_evaluate_identifier_with_upper():
49
+ expr = "(upper name)"
50
+ ast = parse_expr(expr)
51
+ context = DummyContext(name="test")
52
+ result = evaluate(ast, context)
53
+ assert result == "TEST"
54
+
55
+
56
+ def test_generate_python_code():
57
+ expr = '(concat "foo" "bar")'
58
+ ast = parse_expr(expr)
59
+ code = generate_python_code(ast)
60
+ # The generated code should join the literals with a Python plus operator
61
+ assert " + " in code
62
+ assert '"foo"' in code
63
+ assert '"bar"' in code
64
+
65
+
66
+ def test_generate_typescript_code_with_identifier():
67
+ expr = '(concat "foo" bar)'
68
+ ast = parse_expr(expr)
69
+ ts_code = generate_typescript_code(ast)
70
+ assert '("foo" + record.bar)' == ts_code
71
+
72
+
73
+ def test_get_used_fields():
74
+ expr = "(concat first_name last.name)"
75
+ ast = parse_expr(expr)
76
+ fields = get_used_fields(ast)
77
+ # The current implementation collects ParsedOp values, so it returns the
78
+ # operator.
79
+ # This test checks that behavior.
80
+ assert fields == ["first_name", "last.name"]
81
+
82
+
83
+ def test_nested_expression_evaluate():
84
+ expr = "(concat (upper first_name) (lower last_name))"
85
+ ast = parse_expr(expr)
86
+ context = DummyContext(first_name="john", last_name="DOE")
87
+ result = evaluate(ast, context)
88
+ assert result == "JOHNdoe"
89
+
90
+
91
+ def test_eq_same_value_same_class():
92
+ op1 = ParsedOp("test")
93
+ op2 = ParsedOp("test")
94
+ assert op1 == op2
95
+ lit1 = ParsedLiteral("hello")
96
+ lit2 = ParsedLiteral("hello")
97
+ assert lit1 == lit2
98
+
99
+
100
+ def test_eq_same_value_different_subclasses():
101
+ # Even though they are different subclasses of Parsed,
102
+ # equality compares the 'value' attribute only.
103
+ op = ParsedOp("value")
104
+ lit = ParsedLiteral("value")
105
+ ident = ParsedIdentifier("value")
106
+ assert op == lit
107
+ assert op == ident
108
+ assert lit == ident
109
+
110
+
111
+ def test_eq_with_string():
112
+ op = ParsedOp("example")
113
+ lit = ParsedLiteral("example")
114
+ ident = ParsedIdentifier("example")
115
+ assert op == "example"
116
+ assert lit == "example"
117
+ assert ident == "example"
118
+
119
+
120
+ def test_eq_different_values():
121
+ op = ParsedOp("one")
122
+ lit = ParsedLiteral("two")
123
+ ident = ParsedIdentifier("three")
124
+ assert not (op == lit)
125
+ assert not (lit == ident)
126
+ assert not (op == ident)
127
+
128
+
129
+ def test_eq_non_parsed_object():
130
+ op = ParsedOp("data")
131
+ # When comparing with an object that is not an instance of Parsed or str,
132
+ # equality should return False.
133
+ assert not (op == 123)
134
+ assert op is not None
135
+
136
+
137
+ def test_if():
138
+ context = DummyContext(name="test")
139
+ expr = '(if name "Yes" "No")'
140
+ ast = parse_expr(expr)
141
+ result = evaluate(ast, context)
142
+ assert result == "Yes"
143
+
144
+ py_code = generate_python_code(ast)
145
+ assert '("Yes" if record.name else "No")' in py_code
146
+
147
+ ts_code = generate_typescript_code(ast)
148
+ assert '(record.name ? "Yes" : "No")' in ts_code
149
+
150
+
151
+ def test_upper():
152
+ context = DummyContext(name="test")
153
+ expr = "(upper name)"
154
+ ast = parse_expr(expr)
155
+ result = evaluate(ast, context)
156
+ assert result == "TEST"
157
+
158
+ py_code = generate_python_code(ast)
159
+ assert "str(record.name).upper()" in py_code
160
+
161
+ ts_code = generate_typescript_code(ast)
162
+ assert "String(record.name).toUpperCase()" in ts_code
163
+
164
+
165
+ def test_lower():
166
+ context = DummyContext(name="TEST")
167
+ expr = "(lower name)"
168
+ ast = parse_expr(expr)
169
+ result = evaluate(ast, context)
170
+ assert result == "test"
171
+
172
+ py_code = generate_python_code(ast)
173
+ assert "str(record.name).lower()" in py_code
174
+
175
+ ts_code = generate_typescript_code(ast)
176
+ assert "String(record.name).toLowerCase()" in ts_code
177
+
178
+
179
+ def test_is_none():
180
+ context = DummyContext(name=None)
181
+ expr = '(is_none name "Yes" "No")'
182
+ ast = parse_expr(expr)
183
+ result = evaluate(ast, context)
184
+ assert result == "Yes"
185
+
186
+ py_code = generate_python_code(ast)
187
+ assert '("Yes" if record.name is None else "No")' in py_code
188
+
189
+ ts_code = generate_typescript_code(ast)
190
+ assert '(record.name == null || record.name == undefined) ? "Yes" : "No"' in ts_code
191
+
192
+ context = DummyContext(name="test")
193
+ expr = '(is_none name "Yes" "No")'
194
+ ast = parse_expr(expr)
195
+ result = evaluate(ast, context)
196
+ assert result == "No"
197
+
198
+
199
+ def test_equals():
200
+ context = DummyContext(name="test")
201
+ expr = '(= name "test" "Yes" "No")'
202
+ ast = parse_expr(expr)
203
+ result = evaluate(ast, context)
204
+ assert result == "Yes"
205
+
206
+ py_code = generate_python_code(ast)
207
+ assert '("Yes" if record.name == "test" else "No")' in py_code
208
+
209
+ ts_code = generate_typescript_code(ast)
210
+ assert '((record.name == "test") ? "Yes" : "No")' in ts_code
211
+
212
+ context = DummyContext(name="not")
213
+ expr = '(= name "test" "Yes" "No")'
214
+ ast = parse_expr(expr)
215
+ result = evaluate(ast, context)
216
+ assert result == "No"
217
+
218
+
219
+ def test_date_str():
220
+ context = DummyContext(date=datetime(2023, 10, 1))
221
+ expr = '(date_str date "%Y-%m-%d")'
222
+ ast = parse_expr(expr)
223
+ result = evaluate(ast, context)
224
+ assert result == "2023-10-01"
225
+
226
+ py_code = generate_python_code(ast)
227
+ assert 'record.date.strftime("%Y-%m-%d")' in py_code
228
+
229
+ ts_code = generate_typescript_code(ast)
230
+ assert 'record.date.strftime("%Y-%m-%d")' in ts_code
231
+
232
+
233
+ if __name__ == "__main__":
234
+ pytest.main()
@@ -0,0 +1,107 @@
1
+ from unittest.mock import Mock
2
+
3
+ import pytest
4
+
5
+ from exdrf.resource import ExResource
6
+
7
+
8
+ def test_exresource_initialization():
9
+ resource = ExResource(name="TestResource")
10
+ assert resource.name == "TestResource"
11
+ assert resource.fields == []
12
+ assert resource.categories == []
13
+ assert resource.description == ""
14
+ assert resource.src is None
15
+ assert resource.label_ast is None
16
+
17
+
18
+ def test_exresource_repr():
19
+ resource = ExResource(name="TestResource")
20
+ assert repr(resource) == "<Resource TestResource (0 fields)>"
21
+
22
+
23
+ def test_exresource_add_field():
24
+ mock_field = Mock()
25
+ mock_field.name = "test_field"
26
+ mock_field.type_name = "string"
27
+ mock_field.category = None
28
+
29
+ resource = ExResource(name="TestResource")
30
+ resource.add_field(mock_field)
31
+
32
+ assert len(resource.fields) == 1
33
+ assert resource.fields[0] == mock_field
34
+ assert mock_field.resource == resource
35
+
36
+
37
+ def test_exresource_getitem_by_index():
38
+ mock_field = Mock()
39
+ mock_field.name = "test_field"
40
+ mock_field.type_name = "string"
41
+
42
+ resource = ExResource(name="TestResource")
43
+ resource.add_field(mock_field)
44
+
45
+ assert resource[0] == mock_field
46
+
47
+
48
+ def test_exresource_getitem_by_name():
49
+ mock_field = Mock()
50
+ mock_field.name = "test_field"
51
+ mock_field.type_name = "string"
52
+
53
+ resource = ExResource(name="TestResource")
54
+ resource.add_field(mock_field)
55
+
56
+ assert resource["test_field"] == mock_field
57
+
58
+
59
+ def test_exresource_getitem_keyerror():
60
+ resource = ExResource(name="TestResource")
61
+ with pytest.raises(KeyError):
62
+ _ = resource["nonexistent_field"]
63
+
64
+
65
+ def test_exresource_pascal_case_name():
66
+ resource = ExResource(name="TestResource")
67
+ assert resource.pascal_case_name == "TestResource"
68
+
69
+
70
+ def test_exresource_snake_case_name():
71
+ resource = ExResource(name="TestResource")
72
+ assert resource.snake_case_name == "test_resource"
73
+
74
+
75
+ def test_exresource_snake_case_name_plural():
76
+ resource = ExResource(name="TestResource")
77
+ assert resource.snake_case_name_plural == "test_resources"
78
+
79
+
80
+ def test_exresource_camel_case_name():
81
+ resource = ExResource(name="TestResource")
82
+ assert resource.camel_case_name == "testResource"
83
+
84
+
85
+ def test_exresource_text_name():
86
+ resource = ExResource(name="TestResource")
87
+ assert resource.text_name == "Test resource"
88
+
89
+
90
+ def test_exresource_doc_lines():
91
+ resource = ExResource(name="TestResource", description="This is a test resource.")
92
+ assert resource.doc_lines == ["This is a test resource."]
93
+
94
+
95
+ def test_exresource_get_dependencies():
96
+ mock_field = Mock()
97
+ mock_field.is_ref_type = True
98
+ mock_field.ref = Mock()
99
+ mock_field.ref.name = "DependencyResource"
100
+ mock_field.extra_ref = Mock(return_value=[])
101
+
102
+ resource = ExResource(name="TestResource")
103
+ resource.add_field(mock_field)
104
+
105
+ dependencies = resource.get_dependencies()
106
+ assert len(dependencies) == 1
107
+ assert list(dependencies)[0].name == "DependencyResource"
@@ -0,0 +1,43 @@
1
+ from exdrf.utils import doc_lines
2
+
3
+
4
+ def test_doc_lines_single_line():
5
+ text = "This is a single line of text."
6
+ expected = ["This is a single line of text."]
7
+ assert doc_lines(text) == expected
8
+
9
+
10
+ def test_doc_lines_multiple_lines():
11
+ text = "This is the first line.\nThis is the second line."
12
+ expected = ["This is the first line.", "", "This is the second line."]
13
+ assert doc_lines(text) == expected
14
+
15
+
16
+ def test_doc_lines_wrapping():
17
+ text = (
18
+ "This is a very long line of text that should be wrapped into "
19
+ "multiple lines because it exceeds the width limit."
20
+ )
21
+ expected = [
22
+ "This is a very long line of text that should be wrapped into multiple",
23
+ "lines because it exceeds the width limit.",
24
+ ]
25
+ assert doc_lines(text) == expected
26
+
27
+
28
+ def test_doc_lines_empty_string():
29
+ text = ""
30
+ expected = []
31
+ assert doc_lines(text) == expected
32
+
33
+
34
+ def test_doc_lines_with_leading_and_trailing_whitespace():
35
+ text = " This line has leading and trailing spaces. "
36
+ expected = ["This line has leading and trailing spaces."]
37
+ assert doc_lines(text) == expected
38
+
39
+
40
+ def test_doc_lines_with_blank_lines():
41
+ text = "Line one.\n\nLine three."
42
+ expected = ["Line one.", "", "", "Line three."]
43
+ assert doc_lines(text) == expected
@@ -0,0 +1,31 @@
1
+ from unittest.mock import MagicMock
2
+
3
+ from exdrf.visitor import ExVisitor
4
+
5
+
6
+ class TestExVisitor:
7
+ def test_visit_dataset(self):
8
+ visitor = ExVisitor()
9
+ mock_dataset = MagicMock()
10
+ result = visitor.visit_dataset(mock_dataset)
11
+ assert result is True
12
+
13
+ def test_visit_category(self):
14
+ visitor = ExVisitor()
15
+ name = "category_name"
16
+ level = 1
17
+ content = {"sub_category": {}, "resource": {}}
18
+ result = visitor.visit_category(name, level, content)
19
+ assert result is True
20
+
21
+ def test_visit_resource(self):
22
+ visitor = ExVisitor()
23
+ mock_resource = MagicMock()
24
+ result = visitor.visit_resource(mock_resource)
25
+ assert result is True
26
+
27
+ def test_visit_field(self):
28
+ visitor = ExVisitor()
29
+ mock_field = MagicMock()
30
+ result = visitor.visit_field(mock_field)
31
+ assert result is True