py10x-universe 0.1.3__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 (214) hide show
  1. core_10x/__init__.py +42 -0
  2. core_10x/backbone/__init__.py +0 -0
  3. core_10x/backbone/backbone_store.py +59 -0
  4. core_10x/backbone/backbone_traitable.py +30 -0
  5. core_10x/backbone/backbone_user.py +66 -0
  6. core_10x/backbone/bound_data_domain.py +49 -0
  7. core_10x/backbone/namespace.py +101 -0
  8. core_10x/backbone/vault.py +38 -0
  9. core_10x/code_samples/__init__.py +0 -0
  10. core_10x/code_samples/_package_manifest.py +3 -0
  11. core_10x/code_samples/directories.py +181 -0
  12. core_10x/code_samples/person.py +76 -0
  13. core_10x/concrete_traits.py +356 -0
  14. core_10x/conftest.py +12 -0
  15. core_10x/curve.py +321 -0
  16. core_10x/data_domain.py +48 -0
  17. core_10x/data_domain_binder.py +45 -0
  18. core_10x/directory.py +250 -0
  19. core_10x/entity.py +8 -0
  20. core_10x/entity_filter.py +5 -0
  21. core_10x/environment_variables.py +147 -0
  22. core_10x/exec_control.py +84 -0
  23. core_10x/experimental/__init__.py +0 -0
  24. core_10x/experimental/data_protocol_ex.py +34 -0
  25. core_10x/global_cache.py +121 -0
  26. core_10x/manual_tests/__init__.py +0 -0
  27. core_10x/manual_tests/calendar_test.py +35 -0
  28. core_10x/manual_tests/ctor_update_bug.py +58 -0
  29. core_10x/manual_tests/debug_graph_on.py +17 -0
  30. core_10x/manual_tests/debug_graphoff_inside_graph_on.py +28 -0
  31. core_10x/manual_tests/enum_bits_test.py +17 -0
  32. core_10x/manual_tests/env_vars_trivial_test.py +12 -0
  33. core_10x/manual_tests/existing_traitable.py +33 -0
  34. core_10x/manual_tests/k10x_test1.py +13 -0
  35. core_10x/manual_tests/named_constant_test.py +121 -0
  36. core_10x/manual_tests/nucleus_trivial_test.py +42 -0
  37. core_10x/manual_tests/polars_test.py +14 -0
  38. core_10x/manual_tests/py_class_test.py +4 -0
  39. core_10x/manual_tests/rc_test.py +42 -0
  40. core_10x/manual_tests/rdate_test.py +12 -0
  41. core_10x/manual_tests/reference_serialization_bug.py +19 -0
  42. core_10x/manual_tests/resource_trivial_test.py +10 -0
  43. core_10x/manual_tests/store_uri_test.py +6 -0
  44. core_10x/manual_tests/trait_definition_test.py +19 -0
  45. core_10x/manual_tests/trait_filter_test.py +15 -0
  46. core_10x/manual_tests/trait_flag_modification_test.py +42 -0
  47. core_10x/manual_tests/trait_modification_bug.py +26 -0
  48. core_10x/manual_tests/traitable_as_of_test.py +82 -0
  49. core_10x/manual_tests/traitable_heir_test.py +39 -0
  50. core_10x/manual_tests/traitable_history_test.py +41 -0
  51. core_10x/manual_tests/traitable_serialization_test.py +54 -0
  52. core_10x/manual_tests/traitable_trivial_test.py +71 -0
  53. core_10x/manual_tests/trivial_graph_test.py +16 -0
  54. core_10x/manual_tests/ts_class_association_test.py +64 -0
  55. core_10x/manual_tests/ts_trivial_test.py +35 -0
  56. core_10x/named_constant.py +425 -0
  57. core_10x/nucleus.py +81 -0
  58. core_10x/package_manifest.py +85 -0
  59. core_10x/package_refactoring.py +153 -0
  60. core_10x/py_class.py +431 -0
  61. core_10x/rc.py +155 -0
  62. core_10x/rdate.py +339 -0
  63. core_10x/resource.py +189 -0
  64. core_10x/roman_number.py +67 -0
  65. core_10x/testlib/__init__.py +0 -0
  66. core_10x/testlib/test_store.py +240 -0
  67. core_10x/testlib/traitable_history_tests.py +787 -0
  68. core_10x/testlib/ts_tests.py +280 -0
  69. core_10x/trait.py +377 -0
  70. core_10x/trait_definition.py +176 -0
  71. core_10x/trait_filter.py +205 -0
  72. core_10x/trait_method_error.py +36 -0
  73. core_10x/traitable.py +1082 -0
  74. core_10x/traitable_cli.py +153 -0
  75. core_10x/traitable_heir.py +33 -0
  76. core_10x/traitable_id.py +31 -0
  77. core_10x/ts_store.py +172 -0
  78. core_10x/ts_store_type.py +26 -0
  79. core_10x/ts_union.py +147 -0
  80. core_10x/ui_hint.py +153 -0
  81. core_10x/unit_tests/test_concrete_traits.py +156 -0
  82. core_10x/unit_tests/test_converters.py +51 -0
  83. core_10x/unit_tests/test_curve.py +157 -0
  84. core_10x/unit_tests/test_directory.py +54 -0
  85. core_10x/unit_tests/test_documentation.py +172 -0
  86. core_10x/unit_tests/test_environment_variables.py +15 -0
  87. core_10x/unit_tests/test_filters.py +239 -0
  88. core_10x/unit_tests/test_graph.py +348 -0
  89. core_10x/unit_tests/test_named_constant.py +98 -0
  90. core_10x/unit_tests/test_rc.py +11 -0
  91. core_10x/unit_tests/test_rdate.py +484 -0
  92. core_10x/unit_tests/test_trait_method_error.py +80 -0
  93. core_10x/unit_tests/test_trait_modification.py +19 -0
  94. core_10x/unit_tests/test_traitable.py +959 -0
  95. core_10x/unit_tests/test_traitable_history.py +1 -0
  96. core_10x/unit_tests/test_ts_store.py +1 -0
  97. core_10x/unit_tests/test_ts_union.py +369 -0
  98. core_10x/unit_tests/test_ui_nodes.py +81 -0
  99. core_10x/unit_tests/test_xxcalendar.py +471 -0
  100. core_10x/vault/__init__.py +0 -0
  101. core_10x/vault/sec_keys.py +133 -0
  102. core_10x/vault/security_keys_old.py +168 -0
  103. core_10x/vault/vault.py +56 -0
  104. core_10x/vault/vault_traitable.py +56 -0
  105. core_10x/vault/vault_user.py +70 -0
  106. core_10x/xdate_time.py +136 -0
  107. core_10x/xnone.py +71 -0
  108. core_10x/xxcalendar.py +228 -0
  109. infra_10x/__init__.py +0 -0
  110. infra_10x/manual_tests/__init__.py +0 -0
  111. infra_10x/manual_tests/test_misc.py +16 -0
  112. infra_10x/manual_tests/test_prepare_filter_and_pipeline.py +25 -0
  113. infra_10x/mongodb_admin.py +111 -0
  114. infra_10x/mongodb_store.py +346 -0
  115. infra_10x/mongodb_utils.py +129 -0
  116. infra_10x/unit_tests/conftest.py +13 -0
  117. infra_10x/unit_tests/test_mongo_db.py +36 -0
  118. infra_10x/unit_tests/test_mongo_history.py +1 -0
  119. py10x_universe-0.1.3.dist-info/METADATA +406 -0
  120. py10x_universe-0.1.3.dist-info/RECORD +214 -0
  121. py10x_universe-0.1.3.dist-info/WHEEL +4 -0
  122. py10x_universe-0.1.3.dist-info/licenses/LICENSE +21 -0
  123. ui_10x/__init__.py +0 -0
  124. ui_10x/apps/__init__.py +0 -0
  125. ui_10x/apps/collection_editor_app.py +100 -0
  126. ui_10x/choice.py +212 -0
  127. ui_10x/collection_editor.py +135 -0
  128. ui_10x/concrete_trait_widgets.py +220 -0
  129. ui_10x/conftest.py +8 -0
  130. ui_10x/entity_stocker.py +173 -0
  131. ui_10x/examples/__init__.py +0 -0
  132. ui_10x/examples/_guess_word_data.py +14076 -0
  133. ui_10x/examples/collection_editor.py +17 -0
  134. ui_10x/examples/date_selector.py +14 -0
  135. ui_10x/examples/entity_stocker.py +18 -0
  136. ui_10x/examples/guess_word.py +392 -0
  137. ui_10x/examples/message_box.py +20 -0
  138. ui_10x/examples/multi_choice.py +17 -0
  139. ui_10x/examples/py_data_browser.py +66 -0
  140. ui_10x/examples/radiobox.py +29 -0
  141. ui_10x/examples/single_choice.py +31 -0
  142. ui_10x/examples/style_sheet.py +47 -0
  143. ui_10x/examples/trivial_entity_editor.py +18 -0
  144. ui_10x/platform.py +20 -0
  145. ui_10x/platform_interface.py +517 -0
  146. ui_10x/py_data_browser.py +249 -0
  147. ui_10x/qt6/__init__.py +0 -0
  148. ui_10x/qt6/conftest.py +8 -0
  149. ui_10x/qt6/manual_tests/__init__.py +0 -0
  150. ui_10x/qt6/manual_tests/basic_test.py +35 -0
  151. ui_10x/qt6/platform_implementation.py +275 -0
  152. ui_10x/qt6/utils.py +665 -0
  153. ui_10x/rio/__init__.py +0 -0
  154. ui_10x/rio/apps/examples/examples/__init__.py +22 -0
  155. ui_10x/rio/apps/examples/examples/components/__init__.py +3 -0
  156. ui_10x/rio/apps/examples/examples/components/collection_editor.py +15 -0
  157. ui_10x/rio/apps/examples/examples/pages/collection_editor.py +21 -0
  158. ui_10x/rio/apps/examples/examples/pages/login_page.py +88 -0
  159. ui_10x/rio/apps/examples/examples/pages/style_sheet.py +21 -0
  160. ui_10x/rio/apps/examples/rio.toml +14 -0
  161. ui_10x/rio/component_builder.py +497 -0
  162. ui_10x/rio/components/__init__.py +9 -0
  163. ui_10x/rio/components/group_box.py +31 -0
  164. ui_10x/rio/components/labeled_checkbox.py +18 -0
  165. ui_10x/rio/components/line_edit.py +37 -0
  166. ui_10x/rio/components/radio_button.py +32 -0
  167. ui_10x/rio/components/separator.py +24 -0
  168. ui_10x/rio/components/splitter.py +121 -0
  169. ui_10x/rio/components/tree_view.py +75 -0
  170. ui_10x/rio/conftest.py +35 -0
  171. ui_10x/rio/internals/__init__.py +0 -0
  172. ui_10x/rio/internals/app.py +192 -0
  173. ui_10x/rio/manual_tests/__init__.py +0 -0
  174. ui_10x/rio/manual_tests/basic_test.py +24 -0
  175. ui_10x/rio/manual_tests/splitter.py +27 -0
  176. ui_10x/rio/platform_implementation.py +91 -0
  177. ui_10x/rio/style_sheet.py +53 -0
  178. ui_10x/rio/unit_tests/test_collection_editor.py +68 -0
  179. ui_10x/rio/unit_tests/test_internals.py +630 -0
  180. ui_10x/rio/unit_tests/test_style_sheet.py +37 -0
  181. ui_10x/rio/widgets/__init__.py +46 -0
  182. ui_10x/rio/widgets/application.py +109 -0
  183. ui_10x/rio/widgets/button.py +48 -0
  184. ui_10x/rio/widgets/button_group.py +60 -0
  185. ui_10x/rio/widgets/calendar.py +23 -0
  186. ui_10x/rio/widgets/checkbox.py +24 -0
  187. ui_10x/rio/widgets/dialog.py +137 -0
  188. ui_10x/rio/widgets/group_box.py +27 -0
  189. ui_10x/rio/widgets/layout.py +34 -0
  190. ui_10x/rio/widgets/line_edit.py +37 -0
  191. ui_10x/rio/widgets/list.py +105 -0
  192. ui_10x/rio/widgets/message_box.py +70 -0
  193. ui_10x/rio/widgets/scroll_area.py +31 -0
  194. ui_10x/rio/widgets/spacer.py +6 -0
  195. ui_10x/rio/widgets/splitter.py +45 -0
  196. ui_10x/rio/widgets/text_edit.py +28 -0
  197. ui_10x/rio/widgets/tree.py +89 -0
  198. ui_10x/rio/widgets/unit_tests/test_button.py +101 -0
  199. ui_10x/rio/widgets/unit_tests/test_button_group.py +33 -0
  200. ui_10x/rio/widgets/unit_tests/test_calendar.py +114 -0
  201. ui_10x/rio/widgets/unit_tests/test_checkbox.py +109 -0
  202. ui_10x/rio/widgets/unit_tests/test_group_box.py +158 -0
  203. ui_10x/rio/widgets/unit_tests/test_label.py +43 -0
  204. ui_10x/rio/widgets/unit_tests/test_line_edit.py +140 -0
  205. ui_10x/rio/widgets/unit_tests/test_list.py +146 -0
  206. ui_10x/table_header_view.py +305 -0
  207. ui_10x/table_view.py +174 -0
  208. ui_10x/trait_editor.py +189 -0
  209. ui_10x/trait_widget.py +131 -0
  210. ui_10x/traitable_editor.py +200 -0
  211. ui_10x/traitable_view.py +131 -0
  212. ui_10x/unit_tests/conftest.py +8 -0
  213. ui_10x/unit_tests/test_platform.py +9 -0
  214. ui_10x/utils.py +661 -0
@@ -0,0 +1,172 @@
1
+ """
2
+ Test documentation examples by extracting and running code blocks at runtime.
3
+
4
+ This module automatically extracts Python code blocks from documentation files
5
+ and runs them as tests to ensure examples remain functional.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import ast
11
+ import re
12
+ import sys
13
+ from pathlib import Path
14
+ from typing import TYPE_CHECKING
15
+
16
+ import pytest
17
+ from core_10x.ts_store import TsStore
18
+
19
+ if TYPE_CHECKING:
20
+ from collections.abc import Generator
21
+
22
+ # Add the project root to Python path for imports
23
+ project_root = Path(__file__).parent.parent.parent
24
+ sys.path.insert(0, str(project_root))
25
+
26
+ # Module-level variable for documentation files to avoid duplication
27
+ DOCUMENTATION_FILES = [
28
+ 'README.md',
29
+ 'GETTING_STARTED.md',
30
+ 'INSTALLATION.md',
31
+ 'CONTRIBUTING.md',
32
+ ]
33
+
34
+
35
+ def extract_code_blocks_from_file(filepath: Path) -> Generator[tuple[str, str], None, None]:
36
+ """Extract Python code blocks from a markdown file."""
37
+ content = filepath.read_text(encoding='utf-8')
38
+
39
+ # Find code blocks (```python ... ```)
40
+ pattern = r'```python\s*\n(.*?)\n```'
41
+ matches = re.findall(pattern, content, re.DOTALL)
42
+
43
+ for i, code_block in enumerate(matches, 1):
44
+ # Clean up the code block
45
+ code_block = code_block.strip()
46
+
47
+ # Skip empty blocks
48
+ if not code_block:
49
+ continue
50
+
51
+ # Generate a test name from the file and block number
52
+ test_name = f'{filepath.stem}_block_{i}'
53
+ yield test_name, code_block
54
+
55
+
56
+ def extract_code_blocks_from_docs() -> Generator[tuple[str, str, str], None, None]:
57
+ """Extract all Python code blocks from documentation files."""
58
+ docs_dir = project_root
59
+
60
+ for doc_file in DOCUMENTATION_FILES:
61
+ filepath = docs_dir / doc_file
62
+ for test_name, code_block in extract_code_blocks_from_file(filepath):
63
+ yield f'{test_name}', code_block, doc_file
64
+
65
+
66
+ def is_ui_code_block(code_block: str) -> bool:
67
+ """Check if a code block contains UI-related imports or usage."""
68
+ ui_indicators = [
69
+ 'import ui_10x',
70
+ 'from ui_10x',
71
+ 'ui_10x.',
72
+ ]
73
+ return any(indicator in code_block for indicator in ui_indicators)
74
+
75
+
76
+ def validate_python_syntax(code: str) -> bool:
77
+ """Validate that code has valid Python syntax."""
78
+ try:
79
+ ast.parse(code)
80
+ return True
81
+ except SyntaxError:
82
+ return False
83
+
84
+
85
+ @pytest.mark.parametrize(
86
+ 'test_name,code_block,future_annotations',
87
+ [
88
+ (name, code, future_annotations)
89
+ for name, code, src in extract_code_blocks_from_docs()
90
+ for future_annotations in (True, False)
91
+ if not is_ui_code_block(code) # Skip UI code blocks - tested separately
92
+ ],
93
+ )
94
+ def test_documentation_code_block_execution(test_name: str, code_block: str, future_annotations: bool):
95
+ """Test that documentation code blocks can execute successfully."""
96
+ # Skip if code block is empty
97
+ if not code_block.strip():
98
+ pytest.skip('Empty code block')
99
+
100
+ # Validate syntax
101
+ assert validate_python_syntax(code_block), f'Syntax error in {test_name}'
102
+
103
+ doc_file_name = test_name.split('_block_')[0]
104
+ fake_module_name = f'__doc_test_{doc_file_name}__'
105
+ if fake_module_name not in sys.modules:
106
+ fake_module = type(sys)('module')
107
+ fake_module.__dict__.update(
108
+ {
109
+ '__name__': fake_module_name,
110
+ '__file__': f'<{doc_file_name}>',
111
+ '__builtins__': __builtins__,
112
+ }
113
+ )
114
+ sys.modules[fake_module_name] = fake_module
115
+ if future_annotations:
116
+ exec('from __future__ import annotations', fake_module.__dict__)
117
+
118
+ try:
119
+ exec(code_block, fake_module.__dict__)
120
+ finally:
121
+ # Clean up the fake module
122
+ if fake_module_name in sys.modules:
123
+ del sys.modules[fake_module_name]
124
+
125
+ TsStore.s_instances.clear()
126
+
127
+
128
+ @pytest.mark.parametrize(
129
+ 'test_name,code_block,source_file',
130
+ [
131
+ (f'{src}_{name}', code, src)
132
+ for name, code, src in extract_code_blocks_from_docs()
133
+ if is_ui_code_block(code) # Only test UI code blocks
134
+ ],
135
+ )
136
+ def test_documentation_ui_code_block_syntax(test_name: str, code_block: str, source_file: str):
137
+ """Test that UI documentation code blocks have valid syntax (execution tested separately in UI tests)."""
138
+ # Skip if code block is empty
139
+ if not code_block.strip():
140
+ pytest.skip('Empty code block')
141
+
142
+ # Validate syntax only for UI blocks (execution requires async UI environment)
143
+ assert validate_python_syntax(code_block), f'Syntax error in UI code block {source_file} {test_name}'
144
+
145
+
146
+ def test_documentation_files_and_code_blocks():
147
+ """Test that documentation files exist and contain Python code blocks."""
148
+ docs_dir = project_root
149
+ total_blocks = 0
150
+ ui_blocks = 0
151
+ core_blocks = 0
152
+
153
+ # Check that all expected documentation files exist
154
+ for filename in DOCUMENTATION_FILES:
155
+ filepath = docs_dir / filename
156
+ assert filepath.exists(), f'Documentation file {filename} not found'
157
+ assert filepath.stat().st_size > 0, f'Documentation file {filename} is empty'
158
+
159
+ # Check that files contain Python code blocks
160
+ for _name, code, _src in extract_code_blocks_from_docs():
161
+ total_blocks += 1
162
+ if is_ui_code_block(code):
163
+ ui_blocks += 1
164
+ else:
165
+ core_blocks += 1
166
+
167
+ # Ensure we found some code blocks
168
+ assert total_blocks > 0, 'No Python code blocks found in documentation'
169
+ assert core_blocks > 0, 'No core Python code blocks found in documentation'
170
+
171
+ # We expect some UI blocks but most should be core functionality
172
+ assert core_blocks >= ui_blocks, f'Expected more core blocks ({core_blocks}) than UI blocks ({ui_blocks})'
@@ -0,0 +1,15 @@
1
+ from core_10x.environment_variables import EnvVars
2
+ from core_10x.xdate_time import XDateTime, date
3
+
4
+
5
+ def test_env_vars_date_format_applies_to_xdatetime(monkeypatch):
6
+ # Change via environment variable and ensure it is applied on first access
7
+ custom_fmt = '%d/%m/%Y'
8
+ monkeypatch.setenv('XX_DATE_FORMAT', custom_fmt)
9
+
10
+ # Accessing EnvVars.date_format should convert and apply to XDateTime
11
+ fmt = EnvVars.date_format
12
+ assert fmt == custom_fmt
13
+
14
+ d = date(2024, 1, 2)
15
+ assert XDateTime.date_to_str(d) == '02/01/2024'
@@ -0,0 +1,239 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import date # noqa: TC003
4
+
5
+ import pytest
6
+ from core_10x.trait_filter import (
7
+ AND,
8
+ BETWEEN,
9
+ EQ,
10
+ GE,
11
+ GT,
12
+ IN,
13
+ LE,
14
+ LT,
15
+ NE,
16
+ NIN,
17
+ NOT_EMPTY,
18
+ OR,
19
+ f,
20
+ )
21
+ from core_10x.traitable import Traitable, XNone
22
+
23
+
24
+ class Person(Traitable):
25
+ first_name: str
26
+ last_name: str
27
+ age: int
28
+ dob: date
29
+
30
+
31
+ def test_filters():
32
+ p = Person(first_name='Sasha', last_name='Davidovich')
33
+
34
+ r = OR(f(age=BETWEEN(50, 70), first_name=NE('Sasha')), f(age=17))
35
+
36
+ assert r.prefix_notation() == {'$or': [{'age': {'$gte': 50, '$lte': 70}, 'first_name': {'$ne': 'Sasha'}}, {'age': {'$eq': 17}}]}
37
+ assert not r.eval(p)
38
+
39
+
40
+ def test_or():
41
+ p = Person(first_name='Sasha', last_name='Davidovich')
42
+ r1, r2 = f(age=BETWEEN(50, 70)), f(first_name=EQ('Sasha'))
43
+ r3 = OR(r1, r2)
44
+ r4 = OR(r1)
45
+ r5 = OR(r2)
46
+ r6 = OR()
47
+ assert r1.prefix_notation() == {'age': {'$gte': 50, '$lte': 70}}
48
+ assert r2.prefix_notation() == {'first_name': {'$eq': 'Sasha'}}
49
+ assert r3.prefix_notation() == {'$or': [r1.prefix_notation(), r2.prefix_notation()]}
50
+
51
+ assert r4.prefix_notation() == r1.prefix_notation()
52
+ assert r5.prefix_notation() == r2.prefix_notation()
53
+ assert r6.prefix_notation() == {'$in': []}
54
+
55
+ assert not r1.eval(p)
56
+ assert r2.eval(p)
57
+ assert r3.eval(p)
58
+ assert not r4.eval(p)
59
+ assert r5.eval(p)
60
+ assert not r6.eval(p)
61
+
62
+ r7 = OR(OR(), OR())
63
+ assert r7.prefix_notation() == {'$in': []}
64
+ assert not r7.eval(p)
65
+
66
+ r8 = OR(OR(), AND())
67
+ assert r7.prefix_notation() == {'$in': []}
68
+ assert r8.eval(p)
69
+
70
+
71
+ def test_and():
72
+ p = Person(first_name='Sasha', last_name='Davidovich')
73
+
74
+ r1, r2 = f(age=BETWEEN(50, 70)), f(first_name=EQ('Sasha'))
75
+ r3 = AND(r1, r2)
76
+ r4 = AND(r1)
77
+ r5 = AND(r2)
78
+ r6 = AND()
79
+ assert r1.prefix_notation() == {'age': {'$gte': 50, '$lte': 70}}
80
+ assert r2.prefix_notation() == {'first_name': {'$eq': 'Sasha'}}
81
+ assert r3.prefix_notation() == {'$and': [r1.prefix_notation(), r2.prefix_notation()]}
82
+
83
+ assert r4.prefix_notation() == r1.prefix_notation()
84
+ assert r5.prefix_notation() == r2.prefix_notation()
85
+ assert r6.prefix_notation() == {}
86
+
87
+ assert not r1.eval(p)
88
+ assert r2.eval(p)
89
+ assert not r3.eval(p)
90
+ assert not r4.eval(p)
91
+ assert r5.eval(p)
92
+ assert r6.eval(p)
93
+
94
+ r7 = AND(AND(), AND())
95
+ assert r7.prefix_notation() == {}
96
+ assert r7.eval(p)
97
+
98
+ r8 = AND(AND(), OR())
99
+ assert r8.prefix_notation() == {'$in': []}
100
+ assert not r8.eval(p)
101
+
102
+
103
+ def test_simple_ops_eval_and_prefix():
104
+ assert EQ(5).eval(5)
105
+ assert not EQ(5).eval(4)
106
+ assert EQ('x').prefix_notation() == {'$eq': 'x'}
107
+
108
+ assert NE(5).eval(4)
109
+ assert not NE(5).eval(5)
110
+ assert NE('x').prefix_notation() == {'$ne': 'x'}
111
+
112
+ assert GT(5).eval(6)
113
+ assert not GT(5).eval(5)
114
+ assert GT(5).prefix_notation() == {'$gt': 5}
115
+
116
+ assert GE(5).eval(5)
117
+ assert GE(5).eval(6)
118
+ assert not GE(5).eval(4)
119
+ assert GE(5).prefix_notation() == {'$gte': 5}
120
+
121
+ assert LT(5).eval(4)
122
+ assert not LT(5).eval(5)
123
+ assert LT(5).prefix_notation() == {'$lt': 5}
124
+
125
+ assert LE(5).eval(5)
126
+ assert LE(5).eval(4)
127
+ assert not LE(5).eval(6)
128
+ assert LE(5).prefix_notation() == {'$lte': 5}
129
+
130
+
131
+ def test_in_nin():
132
+ assert IN([1, 2, 3]).eval(2)
133
+ assert not IN([1, 2, 3]).eval(4)
134
+ assert IN((1, 2)).prefix_notation() == {'$in': (1, 2)}
135
+
136
+ assert NIN([1, 2, 3]).eval(4)
137
+ assert not NIN([1, 2, 3]).eval(2)
138
+ assert NIN([1, 2]).prefix_notation() == {'$nin': [1, 2]}
139
+
140
+
141
+ def test_between_bounds_and_prefix():
142
+ b = BETWEEN(1, 5)
143
+ assert b.eval(3)
144
+ assert b.eval(1)
145
+ assert b.eval(5)
146
+ assert not b.eval(0)
147
+ assert not b.eval(6)
148
+ assert b.prefix_notation() == {'$gte': 1, '$lte': 5}
149
+
150
+ b_ex = BETWEEN(1, 5, bounds=(False, False))
151
+ assert b_ex.eval(2)
152
+ assert not b_ex.eval(1)
153
+ assert not b_ex.eval(5)
154
+ assert b_ex.prefix_notation() == {'$gt': 1, '$lt': 5}
155
+
156
+
157
+ def test_not_empty_and_bool_ops_eval_and_prefix():
158
+ assert NOT_EMPTY().eval('abc')
159
+ assert not NOT_EMPTY().eval('')
160
+ with pytest.raises(NotImplementedError):
161
+ NOT_EMPTY().prefix_notation()
162
+
163
+ a = AND(EQ(5), GT(3))
164
+ assert a.eval(5)
165
+ assert not a.eval(3)
166
+ assert a.prefix_notation() == {'$and': [{'$eq': 5}, {'$gt': 3}]}
167
+
168
+ o = OR(EQ(1), EQ(2))
169
+ assert o.eval(1)
170
+ assert o.eval(2)
171
+ assert not o.eval(3)
172
+ assert o.prefix_notation() == {'$or': [{'$eq': 1}, {'$eq': 2}]}
173
+
174
+ # single-argument behaviors: prefix_notation returns inner dict
175
+ single_and = AND(EQ(7))
176
+ assert single_and.prefix_notation() == {'$eq': 7} or single_and.prefix_notation() == {'$eq': 7}
177
+
178
+ # empty BoolOp
179
+ assert OR().prefix_notation() == {'$in': []}
180
+ assert AND().prefix_notation() == {}
181
+
182
+
183
+ def test_f_named_expressions_eval_and_prefix():
184
+ d = Person(age=10, first_name='Bob', last_name='')
185
+
186
+ # named expressions may be raw values (wrapped to EQ) or filters
187
+ filt = f(age=EQ(10), first_name='Bob')
188
+ assert filt.eval(d)
189
+ assert filt.prefix_notation() == {'age': {'$eq': 10}, 'first_name': {'$eq': 'Bob'}}
190
+
191
+ # mismatch
192
+ filt2 = f(age=EQ(11), first_name='Bob')
193
+ assert not filt2.eval(d)
194
+
195
+ # ensure f uses Person.get_value for named fields
196
+ assert f(age=10).eval(d) # 10 wrapped as EQ(10) -> matches d.get_value('age') == 10
197
+ assert not f(age=9).eval(d)
198
+
199
+ # f with multiple named expressions
200
+ multi = f(age=EQ(10), first_name=NE('Alice'), last_name=NOT_EMPTY())
201
+ assert multi.eval(Person(age=10, first_name='Bob', last_name='Smith'))
202
+ assert not f(last_name=NOT_EMPTY()).eval(Person(last_name='')) # empty string -> NOT_EMPTY false
203
+
204
+
205
+ def test_named_serializers():
206
+ class P(Person):
207
+ @classmethod
208
+ def age_serialize(cls, t, v):
209
+ return f'age:{v}' # noinspection PyUnusedLocal
210
+
211
+ trait = P.trait('age')
212
+
213
+ assert trait is P.trait('age')
214
+
215
+ assert trait.serialize_value(5) == 'age:5'
216
+
217
+ assert EQ(5).prefix_notation(trait=trait, traitable_class=P.s_bclass) == {'$eq': 'age:5'}
218
+
219
+ assert BETWEEN(1, 5).prefix_notation(trait=trait, traitable_class=P.s_bclass) == {
220
+ '$gte': 'age:1',
221
+ '$lte': 'age:5',
222
+ }
223
+
224
+ x = OR(f(age=LE(70)), f(first_name=NE('Sasha')), f(last_name=XNone))
225
+ assert x.prefix_notation(traitable_class=P.s_bclass) == {
226
+ '$or': [{'age': {'$lte': 'age:70'}}, {'first_name': {'$ne': 'Sasha'}}, {'last_name': {'$eq': None}}]
227
+ }
228
+
229
+ x = f(age=BETWEEN(50, 70), first_name=NE('Sasha'))
230
+
231
+ assert f(x, P.s_bclass).prefix_notation() == x.prefix_notation(traitable_class=P.s_bclass)
232
+
233
+ r = OR(f(age=BETWEEN(50, 70), first_name=NE('Sasha')), f(age=17))
234
+ assert r.prefix_notation(traitable_class=P.s_bclass) == {
235
+ '$or': [
236
+ {'age': {'$gte': 'age:50', '$lte': 'age:70'}, 'first_name': {'$ne': 'Sasha'}},
237
+ {'age': {'$eq': 'age:17'}},
238
+ ]
239
+ }