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
core_10x/roman_number.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from core_10x.named_constant import Enum, NamedConstantTable
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# ruff: noqa: E741
|
|
5
|
+
class R:
|
|
6
|
+
# fmt: off
|
|
7
|
+
class Symbol(Enum):
|
|
8
|
+
I = ()
|
|
9
|
+
V = ()
|
|
10
|
+
X = ()
|
|
11
|
+
L = ()
|
|
12
|
+
C = ()
|
|
13
|
+
D = ()
|
|
14
|
+
M = ()
|
|
15
|
+
|
|
16
|
+
class Attr(Enum):
|
|
17
|
+
VALUE = ()
|
|
18
|
+
REPEAT = ()
|
|
19
|
+
SUBTRACTABLE = ()
|
|
20
|
+
|
|
21
|
+
SYMBOL_TABLE = NamedConstantTable(Symbol, Attr,
|
|
22
|
+
# VALUE REPEAT SUBTRACTABLE
|
|
23
|
+
I = ( 1, 3, True ),
|
|
24
|
+
V = ( 5, 1, False ),
|
|
25
|
+
X = ( 10, 3, True ),
|
|
26
|
+
L = ( 50, 1, False ),
|
|
27
|
+
C = ( 100, 3, True ),
|
|
28
|
+
D = ( 500, 1, False ),
|
|
29
|
+
M = ( 1000, 3, True ),
|
|
30
|
+
)
|
|
31
|
+
# fmt: on
|
|
32
|
+
def __init__(self, text: str):
|
|
33
|
+
text = text.upper()
|
|
34
|
+
x = 0
|
|
35
|
+
prev_values = [0, 0, 0]
|
|
36
|
+
for i, symbol in enumerate(reversed(text)):
|
|
37
|
+
ref = R.SYMBOL_TABLE[symbol]
|
|
38
|
+
v = ref.VALUE
|
|
39
|
+
|
|
40
|
+
# -- Rules
|
|
41
|
+
repeat = ref.REPEAT
|
|
42
|
+
over_repeated = all(v == prev_values[i] for i in range(repeat))
|
|
43
|
+
assert not over_repeated, f"'{symbol}' is repeated more than {repeat} times"
|
|
44
|
+
|
|
45
|
+
subtractable = ref.SUBTRACTABLE
|
|
46
|
+
if not subtractable and v < prev_values[0]:
|
|
47
|
+
raise AssertionError(f"'{symbol}' may not occur before '{text[-i]}'")
|
|
48
|
+
|
|
49
|
+
if v >= prev_values[0]:
|
|
50
|
+
x += v
|
|
51
|
+
else:
|
|
52
|
+
x -= v
|
|
53
|
+
|
|
54
|
+
prev_values[2] = prev_values[1]
|
|
55
|
+
prev_values[1] = prev_values[0]
|
|
56
|
+
prev_values[0] = v
|
|
57
|
+
|
|
58
|
+
self.roman = text
|
|
59
|
+
self.value = x
|
|
60
|
+
|
|
61
|
+
def __repr__(self):
|
|
62
|
+
return f'{self.roman} = {self.value}'
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if __name__ == '__main__':
|
|
66
|
+
x = R('IX')
|
|
67
|
+
y = R('MCMLXXXIV')
|
|
File without changes
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
In-memory TestStore for testing TraitableHistory and other storage functionality.
|
|
4
|
+
|
|
5
|
+
This provides a lightweight, in-memory implementation of the TsStore interface
|
|
6
|
+
that can be used for unit tests without external dependencies like MongoDB.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import copy
|
|
12
|
+
import re
|
|
13
|
+
from typing import TYPE_CHECKING
|
|
14
|
+
|
|
15
|
+
from py10x_core import BTraitable, BTraitableProcessor
|
|
16
|
+
|
|
17
|
+
from core_10x.nucleus import Nucleus
|
|
18
|
+
from core_10x.ts_store import TsCollection, TsDuplicateKeyError, TsStore
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from collections.abc import Iterable
|
|
22
|
+
|
|
23
|
+
from core_10x.trait_filter import f
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TestCollection(TsCollection):
|
|
27
|
+
"""In-memory collection implementation for testing."""
|
|
28
|
+
|
|
29
|
+
s_id_tag = '_id'
|
|
30
|
+
|
|
31
|
+
def __init__(self, store: TestStore, collection_name: str):
|
|
32
|
+
self.store = store
|
|
33
|
+
self._collection_name = collection_name
|
|
34
|
+
self._documents = {} # id -> document
|
|
35
|
+
self._indexes = {} # index_name -> index_info
|
|
36
|
+
|
|
37
|
+
def collection_name(self) -> str:
|
|
38
|
+
return self._collection_name
|
|
39
|
+
|
|
40
|
+
def id_exists(self, id_value: str) -> bool:
|
|
41
|
+
"""Check if a document with the given ID exists."""
|
|
42
|
+
return id_value in self._documents
|
|
43
|
+
|
|
44
|
+
def _eval(self, doc, query):
|
|
45
|
+
bclass = query.traitable_class
|
|
46
|
+
try:
|
|
47
|
+
return query.eval(doc)
|
|
48
|
+
except AttributeError:
|
|
49
|
+
# TODO: fix - need to deserialize as dictionary-based eval doesn't support nested objects
|
|
50
|
+
if not bclass:
|
|
51
|
+
raise # likely and ID query from load(ID)
|
|
52
|
+
coll = self._collection_name if bclass.is_custom_collection() else None
|
|
53
|
+
with BTraitableProcessor.create_root():
|
|
54
|
+
return query.eval(BTraitable.deserialize_object(bclass, coll, copy.copy(doc)))
|
|
55
|
+
|
|
56
|
+
def find(self, query: f = None, _at_most: int = 0, _order: dict = None) -> Iterable:
|
|
57
|
+
"""Find documents matching the query."""
|
|
58
|
+
documents = list(self._documents.values())
|
|
59
|
+
|
|
60
|
+
# Apply query filter if provided
|
|
61
|
+
if query:
|
|
62
|
+
documents = [doc for doc in documents if self._eval(doc, query)]
|
|
63
|
+
|
|
64
|
+
# Apply ordering if provided
|
|
65
|
+
if _order:
|
|
66
|
+
# Sort by all fields at once, with the last field taking precedence
|
|
67
|
+
# We need to reverse the order of fields so the last one is applied first
|
|
68
|
+
sort_fields = list(_order.items())
|
|
69
|
+
sort_fields.reverse() # Last field first
|
|
70
|
+
|
|
71
|
+
for field, direction in sort_fields:
|
|
72
|
+
documents.sort(key=lambda x: x.get(field, ''), reverse=(direction == -1))
|
|
73
|
+
|
|
74
|
+
# Apply limit if specified
|
|
75
|
+
if _at_most > 0:
|
|
76
|
+
documents = documents[:_at_most]
|
|
77
|
+
|
|
78
|
+
return (dict(doc) for doc in documents)
|
|
79
|
+
|
|
80
|
+
def count(self, query: f = None) -> int:
|
|
81
|
+
"""Count documents matching the query."""
|
|
82
|
+
return len(list(self.find(query)))
|
|
83
|
+
|
|
84
|
+
def save_new(self, serialized_traitable: dict, overwrite: bool = False) -> int:
|
|
85
|
+
"""Save a new document."""
|
|
86
|
+
|
|
87
|
+
# Handle MongoDB-style operations
|
|
88
|
+
if '$set' in serialized_traitable:
|
|
89
|
+
# This is a MongoDB-style update operation
|
|
90
|
+
from datetime import datetime
|
|
91
|
+
|
|
92
|
+
# Extract the data from $set
|
|
93
|
+
data = serialized_traitable['$set'].copy()
|
|
94
|
+
|
|
95
|
+
# Handle $currentDate operations
|
|
96
|
+
if '$currentDate' in serialized_traitable:
|
|
97
|
+
current_date_fields = serialized_traitable['$currentDate']
|
|
98
|
+
for field in current_date_fields:
|
|
99
|
+
if current_date_fields[field] is True:
|
|
100
|
+
data[field] = datetime.utcnow()
|
|
101
|
+
|
|
102
|
+
serialized_traitable = data
|
|
103
|
+
|
|
104
|
+
serialized_traitable[Nucleus.REVISION_TAG()] = 1
|
|
105
|
+
|
|
106
|
+
id_tag = self.s_id_tag
|
|
107
|
+
id_value = serialized_traitable.get(id_tag)
|
|
108
|
+
|
|
109
|
+
if not id_value:
|
|
110
|
+
return 0
|
|
111
|
+
|
|
112
|
+
if id_value in self._documents and not overwrite:
|
|
113
|
+
raise TsDuplicateKeyError(self.collection_name(), {id_tag: id_value})
|
|
114
|
+
|
|
115
|
+
self._documents[id_value] = serialized_traitable
|
|
116
|
+
return 1
|
|
117
|
+
|
|
118
|
+
def save(self, serialized_traitable: dict) -> int:
|
|
119
|
+
"""Save or update a document."""
|
|
120
|
+
rev_tag = Nucleus.REVISION_TAG()
|
|
121
|
+
revision = serialized_traitable[rev_tag]
|
|
122
|
+
if revision == 0:
|
|
123
|
+
return self.save_new(serialized_traitable)
|
|
124
|
+
|
|
125
|
+
undef_variable = next((k[1:] for k in serialized_traitable if k.startswith('$')), None)
|
|
126
|
+
if undef_variable:
|
|
127
|
+
raise RuntimeError(f'Use of undefined variable: {undef_variable}')
|
|
128
|
+
doc_id = serialized_traitable.get(self.s_id_tag)
|
|
129
|
+
assert doc_id
|
|
130
|
+
|
|
131
|
+
existing_doc = self._documents.get(doc_id)
|
|
132
|
+
existing_revision = existing_doc[rev_tag] if existing_doc else 0
|
|
133
|
+
assert revision == existing_revision
|
|
134
|
+
|
|
135
|
+
if existing_doc == serialized_traitable:
|
|
136
|
+
return existing_revision
|
|
137
|
+
|
|
138
|
+
revision += 1
|
|
139
|
+
self._documents[doc_id] = serialized_traitable | {rev_tag: revision}
|
|
140
|
+
|
|
141
|
+
return revision
|
|
142
|
+
|
|
143
|
+
def delete(self, id_value: str) -> bool:
|
|
144
|
+
"""Delete a document by ID."""
|
|
145
|
+
if id_value in self._documents:
|
|
146
|
+
del self._documents[id_value]
|
|
147
|
+
return True
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
def create_index(self, name: str, trait_name: str | list[tuple[str, int]], **index_args) -> str:
|
|
151
|
+
"""Create an index (in-memory implementation just stores the index info)."""
|
|
152
|
+
self._indexes[name] = {'trait_name': trait_name, 'args': index_args}
|
|
153
|
+
return name
|
|
154
|
+
|
|
155
|
+
def max(self, trait_name: str, filter: f = None) -> dict:
|
|
156
|
+
"""Find the document with the maximum value for the given trait."""
|
|
157
|
+
documents = list(self.find(filter))
|
|
158
|
+
if not documents:
|
|
159
|
+
return {}
|
|
160
|
+
|
|
161
|
+
max_doc = max(documents, key=lambda x: x.get(trait_name, ''))
|
|
162
|
+
return max_doc
|
|
163
|
+
|
|
164
|
+
def min(self, trait_name: str, filter: f = None) -> dict:
|
|
165
|
+
"""Find the document with the minimum value for the given trait."""
|
|
166
|
+
documents = list(self.find(filter))
|
|
167
|
+
if not documents:
|
|
168
|
+
return {}
|
|
169
|
+
|
|
170
|
+
min_doc = min(documents, key=lambda x: x.get(trait_name, ''))
|
|
171
|
+
return min_doc
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class TestStore(TsStore, resource_name='TEST_DB'):
|
|
175
|
+
"""In-memory store implementation for testing."""
|
|
176
|
+
|
|
177
|
+
s_driver_name = 'TestStore'
|
|
178
|
+
|
|
179
|
+
def __init__(self, *args, **kwargs):
|
|
180
|
+
super().__init__()
|
|
181
|
+
self._collections = {}
|
|
182
|
+
self._collection_names = set()
|
|
183
|
+
# Set a default username for testing
|
|
184
|
+
self.username = kwargs.get('username', 'test_user')
|
|
185
|
+
|
|
186
|
+
@classmethod
|
|
187
|
+
def new_instance(cls, *args, password: str = '', **kwargs) -> TestStore:
|
|
188
|
+
"""Create a new TestStore instance."""
|
|
189
|
+
return cls(*args, **kwargs)
|
|
190
|
+
|
|
191
|
+
def collection_names(self, regexp: str = None) -> list:
|
|
192
|
+
"""Get collection names, optionally filtered by regexp."""
|
|
193
|
+
names = list(self._collection_names)
|
|
194
|
+
|
|
195
|
+
if regexp:
|
|
196
|
+
pattern = re.compile(regexp)
|
|
197
|
+
names = [name for name in names if pattern.match(name)]
|
|
198
|
+
|
|
199
|
+
return sorted(names)
|
|
200
|
+
|
|
201
|
+
def collection(self, collection_name: str) -> TestCollection:
|
|
202
|
+
"""Get or create a collection."""
|
|
203
|
+
if collection_name not in self._collections:
|
|
204
|
+
self._collections[collection_name] = TestCollection(self, collection_name)
|
|
205
|
+
self._collection_names.add(collection_name)
|
|
206
|
+
|
|
207
|
+
return self._collections[collection_name]
|
|
208
|
+
|
|
209
|
+
def delete_collection(self, collection_name: str) -> bool:
|
|
210
|
+
"""Delete a collection."""
|
|
211
|
+
if collection_name in self._collections:
|
|
212
|
+
del self._collections[collection_name]
|
|
213
|
+
self._collection_names.discard(collection_name)
|
|
214
|
+
return True
|
|
215
|
+
return False
|
|
216
|
+
|
|
217
|
+
@classmethod
|
|
218
|
+
def is_running_with_auth(cls, host_name: str) -> tuple:
|
|
219
|
+
"""TestStore doesn't require authentication."""
|
|
220
|
+
return True, False
|
|
221
|
+
|
|
222
|
+
def clear(self):
|
|
223
|
+
"""Clear all collections (useful for test cleanup)."""
|
|
224
|
+
self._collections.clear()
|
|
225
|
+
self._collection_names.clear()
|
|
226
|
+
|
|
227
|
+
def get_document_count(self, collection_name: str) -> int:
|
|
228
|
+
"""Get the number of documents in a collection (for testing)."""
|
|
229
|
+
if collection_name in self._collections:
|
|
230
|
+
return len(self._collections[collection_name]._documents)
|
|
231
|
+
return 0
|
|
232
|
+
|
|
233
|
+
def get_documents(self, collection_name: str) -> list:
|
|
234
|
+
"""Get all documents in a collection (for testing)."""
|
|
235
|
+
if collection_name in self._collections:
|
|
236
|
+
return list(self._collections[collection_name]._documents.values())
|
|
237
|
+
return []
|
|
238
|
+
|
|
239
|
+
def auth_user(self) -> str | None:
|
|
240
|
+
return self.username
|