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.
- core_10x/__init__.py +42 -0
- core_10x/backbone/__init__.py +0 -0
- core_10x/backbone/backbone_store.py +59 -0
- core_10x/backbone/backbone_traitable.py +30 -0
- core_10x/backbone/backbone_user.py +66 -0
- core_10x/backbone/bound_data_domain.py +49 -0
- core_10x/backbone/namespace.py +101 -0
- core_10x/backbone/vault.py +38 -0
- core_10x/code_samples/__init__.py +0 -0
- core_10x/code_samples/_package_manifest.py +3 -0
- core_10x/code_samples/directories.py +181 -0
- core_10x/code_samples/person.py +76 -0
- core_10x/concrete_traits.py +356 -0
- core_10x/conftest.py +12 -0
- core_10x/curve.py +321 -0
- core_10x/data_domain.py +48 -0
- core_10x/data_domain_binder.py +45 -0
- core_10x/directory.py +250 -0
- core_10x/entity.py +8 -0
- core_10x/entity_filter.py +5 -0
- core_10x/environment_variables.py +147 -0
- core_10x/exec_control.py +84 -0
- core_10x/experimental/__init__.py +0 -0
- core_10x/experimental/data_protocol_ex.py +34 -0
- core_10x/global_cache.py +121 -0
- core_10x/manual_tests/__init__.py +0 -0
- core_10x/manual_tests/calendar_test.py +35 -0
- core_10x/manual_tests/ctor_update_bug.py +58 -0
- core_10x/manual_tests/debug_graph_on.py +17 -0
- core_10x/manual_tests/debug_graphoff_inside_graph_on.py +28 -0
- core_10x/manual_tests/enum_bits_test.py +17 -0
- core_10x/manual_tests/env_vars_trivial_test.py +12 -0
- core_10x/manual_tests/existing_traitable.py +33 -0
- core_10x/manual_tests/k10x_test1.py +13 -0
- core_10x/manual_tests/named_constant_test.py +121 -0
- core_10x/manual_tests/nucleus_trivial_test.py +42 -0
- core_10x/manual_tests/polars_test.py +14 -0
- core_10x/manual_tests/py_class_test.py +4 -0
- core_10x/manual_tests/rc_test.py +42 -0
- core_10x/manual_tests/rdate_test.py +12 -0
- core_10x/manual_tests/reference_serialization_bug.py +19 -0
- core_10x/manual_tests/resource_trivial_test.py +10 -0
- core_10x/manual_tests/store_uri_test.py +6 -0
- core_10x/manual_tests/trait_definition_test.py +19 -0
- core_10x/manual_tests/trait_filter_test.py +15 -0
- core_10x/manual_tests/trait_flag_modification_test.py +42 -0
- core_10x/manual_tests/trait_modification_bug.py +26 -0
- core_10x/manual_tests/traitable_as_of_test.py +82 -0
- core_10x/manual_tests/traitable_heir_test.py +39 -0
- core_10x/manual_tests/traitable_history_test.py +41 -0
- core_10x/manual_tests/traitable_serialization_test.py +54 -0
- core_10x/manual_tests/traitable_trivial_test.py +71 -0
- core_10x/manual_tests/trivial_graph_test.py +16 -0
- core_10x/manual_tests/ts_class_association_test.py +64 -0
- core_10x/manual_tests/ts_trivial_test.py +35 -0
- core_10x/named_constant.py +425 -0
- core_10x/nucleus.py +81 -0
- core_10x/package_manifest.py +85 -0
- core_10x/package_refactoring.py +153 -0
- core_10x/py_class.py +431 -0
- core_10x/rc.py +155 -0
- core_10x/rdate.py +339 -0
- core_10x/resource.py +189 -0
- core_10x/roman_number.py +67 -0
- core_10x/testlib/__init__.py +0 -0
- core_10x/testlib/test_store.py +240 -0
- core_10x/testlib/traitable_history_tests.py +787 -0
- core_10x/testlib/ts_tests.py +280 -0
- core_10x/trait.py +377 -0
- core_10x/trait_definition.py +176 -0
- core_10x/trait_filter.py +205 -0
- core_10x/trait_method_error.py +36 -0
- core_10x/traitable.py +1082 -0
- core_10x/traitable_cli.py +153 -0
- core_10x/traitable_heir.py +33 -0
- core_10x/traitable_id.py +31 -0
- core_10x/ts_store.py +172 -0
- core_10x/ts_store_type.py +26 -0
- core_10x/ts_union.py +147 -0
- core_10x/ui_hint.py +153 -0
- core_10x/unit_tests/test_concrete_traits.py +156 -0
- core_10x/unit_tests/test_converters.py +51 -0
- core_10x/unit_tests/test_curve.py +157 -0
- core_10x/unit_tests/test_directory.py +54 -0
- core_10x/unit_tests/test_documentation.py +172 -0
- core_10x/unit_tests/test_environment_variables.py +15 -0
- core_10x/unit_tests/test_filters.py +239 -0
- core_10x/unit_tests/test_graph.py +348 -0
- core_10x/unit_tests/test_named_constant.py +98 -0
- core_10x/unit_tests/test_rc.py +11 -0
- core_10x/unit_tests/test_rdate.py +484 -0
- core_10x/unit_tests/test_trait_method_error.py +80 -0
- core_10x/unit_tests/test_trait_modification.py +19 -0
- core_10x/unit_tests/test_traitable.py +959 -0
- core_10x/unit_tests/test_traitable_history.py +1 -0
- core_10x/unit_tests/test_ts_store.py +1 -0
- core_10x/unit_tests/test_ts_union.py +369 -0
- core_10x/unit_tests/test_ui_nodes.py +81 -0
- core_10x/unit_tests/test_xxcalendar.py +471 -0
- core_10x/vault/__init__.py +0 -0
- core_10x/vault/sec_keys.py +133 -0
- core_10x/vault/security_keys_old.py +168 -0
- core_10x/vault/vault.py +56 -0
- core_10x/vault/vault_traitable.py +56 -0
- core_10x/vault/vault_user.py +70 -0
- core_10x/xdate_time.py +136 -0
- core_10x/xnone.py +71 -0
- core_10x/xxcalendar.py +228 -0
- infra_10x/__init__.py +0 -0
- infra_10x/manual_tests/__init__.py +0 -0
- infra_10x/manual_tests/test_misc.py +16 -0
- infra_10x/manual_tests/test_prepare_filter_and_pipeline.py +25 -0
- infra_10x/mongodb_admin.py +111 -0
- infra_10x/mongodb_store.py +346 -0
- infra_10x/mongodb_utils.py +129 -0
- infra_10x/unit_tests/conftest.py +13 -0
- infra_10x/unit_tests/test_mongo_db.py +36 -0
- infra_10x/unit_tests/test_mongo_history.py +1 -0
- py10x_universe-0.1.3.dist-info/METADATA +406 -0
- py10x_universe-0.1.3.dist-info/RECORD +214 -0
- py10x_universe-0.1.3.dist-info/WHEEL +4 -0
- py10x_universe-0.1.3.dist-info/licenses/LICENSE +21 -0
- ui_10x/__init__.py +0 -0
- ui_10x/apps/__init__.py +0 -0
- ui_10x/apps/collection_editor_app.py +100 -0
- ui_10x/choice.py +212 -0
- ui_10x/collection_editor.py +135 -0
- ui_10x/concrete_trait_widgets.py +220 -0
- ui_10x/conftest.py +8 -0
- ui_10x/entity_stocker.py +173 -0
- ui_10x/examples/__init__.py +0 -0
- ui_10x/examples/_guess_word_data.py +14076 -0
- ui_10x/examples/collection_editor.py +17 -0
- ui_10x/examples/date_selector.py +14 -0
- ui_10x/examples/entity_stocker.py +18 -0
- ui_10x/examples/guess_word.py +392 -0
- ui_10x/examples/message_box.py +20 -0
- ui_10x/examples/multi_choice.py +17 -0
- ui_10x/examples/py_data_browser.py +66 -0
- ui_10x/examples/radiobox.py +29 -0
- ui_10x/examples/single_choice.py +31 -0
- ui_10x/examples/style_sheet.py +47 -0
- ui_10x/examples/trivial_entity_editor.py +18 -0
- ui_10x/platform.py +20 -0
- ui_10x/platform_interface.py +517 -0
- ui_10x/py_data_browser.py +249 -0
- ui_10x/qt6/__init__.py +0 -0
- ui_10x/qt6/conftest.py +8 -0
- ui_10x/qt6/manual_tests/__init__.py +0 -0
- ui_10x/qt6/manual_tests/basic_test.py +35 -0
- ui_10x/qt6/platform_implementation.py +275 -0
- ui_10x/qt6/utils.py +665 -0
- ui_10x/rio/__init__.py +0 -0
- ui_10x/rio/apps/examples/examples/__init__.py +22 -0
- ui_10x/rio/apps/examples/examples/components/__init__.py +3 -0
- ui_10x/rio/apps/examples/examples/components/collection_editor.py +15 -0
- ui_10x/rio/apps/examples/examples/pages/collection_editor.py +21 -0
- ui_10x/rio/apps/examples/examples/pages/login_page.py +88 -0
- ui_10x/rio/apps/examples/examples/pages/style_sheet.py +21 -0
- ui_10x/rio/apps/examples/rio.toml +14 -0
- ui_10x/rio/component_builder.py +497 -0
- ui_10x/rio/components/__init__.py +9 -0
- ui_10x/rio/components/group_box.py +31 -0
- ui_10x/rio/components/labeled_checkbox.py +18 -0
- ui_10x/rio/components/line_edit.py +37 -0
- ui_10x/rio/components/radio_button.py +32 -0
- ui_10x/rio/components/separator.py +24 -0
- ui_10x/rio/components/splitter.py +121 -0
- ui_10x/rio/components/tree_view.py +75 -0
- ui_10x/rio/conftest.py +35 -0
- ui_10x/rio/internals/__init__.py +0 -0
- ui_10x/rio/internals/app.py +192 -0
- ui_10x/rio/manual_tests/__init__.py +0 -0
- ui_10x/rio/manual_tests/basic_test.py +24 -0
- ui_10x/rio/manual_tests/splitter.py +27 -0
- ui_10x/rio/platform_implementation.py +91 -0
- ui_10x/rio/style_sheet.py +53 -0
- ui_10x/rio/unit_tests/test_collection_editor.py +68 -0
- ui_10x/rio/unit_tests/test_internals.py +630 -0
- ui_10x/rio/unit_tests/test_style_sheet.py +37 -0
- ui_10x/rio/widgets/__init__.py +46 -0
- ui_10x/rio/widgets/application.py +109 -0
- ui_10x/rio/widgets/button.py +48 -0
- ui_10x/rio/widgets/button_group.py +60 -0
- ui_10x/rio/widgets/calendar.py +23 -0
- ui_10x/rio/widgets/checkbox.py +24 -0
- ui_10x/rio/widgets/dialog.py +137 -0
- ui_10x/rio/widgets/group_box.py +27 -0
- ui_10x/rio/widgets/layout.py +34 -0
- ui_10x/rio/widgets/line_edit.py +37 -0
- ui_10x/rio/widgets/list.py +105 -0
- ui_10x/rio/widgets/message_box.py +70 -0
- ui_10x/rio/widgets/scroll_area.py +31 -0
- ui_10x/rio/widgets/spacer.py +6 -0
- ui_10x/rio/widgets/splitter.py +45 -0
- ui_10x/rio/widgets/text_edit.py +28 -0
- ui_10x/rio/widgets/tree.py +89 -0
- ui_10x/rio/widgets/unit_tests/test_button.py +101 -0
- ui_10x/rio/widgets/unit_tests/test_button_group.py +33 -0
- ui_10x/rio/widgets/unit_tests/test_calendar.py +114 -0
- ui_10x/rio/widgets/unit_tests/test_checkbox.py +109 -0
- ui_10x/rio/widgets/unit_tests/test_group_box.py +158 -0
- ui_10x/rio/widgets/unit_tests/test_label.py +43 -0
- ui_10x/rio/widgets/unit_tests/test_line_edit.py +140 -0
- ui_10x/rio/widgets/unit_tests/test_list.py +146 -0
- ui_10x/table_header_view.py +305 -0
- ui_10x/table_view.py +174 -0
- ui_10x/trait_editor.py +189 -0
- ui_10x/trait_widget.py +131 -0
- ui_10x/traitable_editor.py +200 -0
- ui_10x/traitable_view.py +131 -0
- ui_10x/unit_tests/conftest.py +8 -0
- ui_10x/unit_tests/test_platform.py +9 -0
- 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
|
+
}
|