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,153 @@
1
+ import sys
2
+
3
+ from core_10x.traitable import RC, RC_TRUE, Traitable
4
+
5
+
6
+ class TraitableCli(Traitable):
7
+ s_master = None
8
+ s_switch = {}
9
+
10
+ def __init_subclass__(cls, _command: str = None, **kwargs):
11
+ cls.s_switch = {}
12
+ if cls.s_master is None: # -- master parser
13
+ assert _command is None, 'master may not have a command'
14
+ cls.s_master = cls
15
+ else: # -- subordinate parser
16
+ assert _command and _command.isidentifier(), 'command must be a valid identifier'
17
+ cls.s_master.s_switch[_command] = cls
18
+ cls.s_master = cls
19
+
20
+ super().__init_subclass__(**kwargs)
21
+
22
+ @classmethod
23
+ def from_command_line(cls) -> tuple: # -- (RC, Traitable)
24
+ """
25
+ Creates an instance of target traitable parsing positional args to obtain the right target class and then taking trait values
26
+ in the name = value pairs form.
27
+ One can use any spacing around symbol '=', i.e.: name=value name= value name =value or name = value
28
+
29
+ :return: (rc, traitable) - RC and a traitable according to args and trait values parsed from sys.argv.
30
+ rc holds error message(s) if instantiation fails for any reason (traitable is set to None)
31
+ """
32
+ _script, *args = sys.argv
33
+ return cls.instance_from_args(args)
34
+
35
+ @classmethod
36
+ def instance_from_args(cls, input_args: tuple) -> tuple:
37
+ args = []
38
+ trait_values = {}
39
+ rc = cls.parse(input_args, args, trait_values)
40
+ if not rc:
41
+ return rc, None
42
+
43
+ return cls.instantiate(args, trait_values)
44
+
45
+ @classmethod
46
+ def instantiate(cls, args, trait_values: dict) -> tuple: # -- (RC, target_traitable)
47
+ if not args:
48
+ try:
49
+ res = cls()
50
+ for name, value in trait_values.items():
51
+ trait = cls.trait(name)
52
+ if not trait:
53
+ return RC(False, f'unknown attribute {name}\nValid attributes: {", ".join(cls.s_dir)}'), None
54
+
55
+ rc = res.set_value(trait, value)
56
+ if not rc:
57
+ return RC(False, f'{name}: {rc.err()}'), None
58
+
59
+ return RC_TRUE, res
60
+
61
+ except Exception as ex:
62
+ return RC(False, str(ex)), None
63
+
64
+ command, *args = args
65
+ parser = cls.s_switch.get(command)
66
+ if not parser or not issubclass(parser, TraitableCli):
67
+ return RC(False, f'Unknown argument {command}'), None
68
+
69
+ return parser.instantiate(args, trait_values)
70
+
71
+ @classmethod
72
+ def parse(cls, input_args: tuple, args: list, trait_values: dict) -> RC: # noqa: C901
73
+ if not input_args:
74
+ return RC_TRUE
75
+
76
+ trait_name = None
77
+ deal_with_args = True
78
+ new_vp = True
79
+ eq_expected = False
80
+ for i, arg in enumerate(input_args):
81
+ if deal_with_args:
82
+ parts = arg.split('=', 1)
83
+ n = len(parts)
84
+ if n == 1:
85
+ args.append(arg)
86
+ else:
87
+ deal_with_args = False
88
+ eq_expected = False
89
+ first, second = parts
90
+ if not first and not second: # -- just '='
91
+ if i:
92
+ trait_name = args.pop(-1)
93
+ new_vp = False
94
+ else:
95
+ return RC(False, 'May not start with "="')
96
+
97
+ elif first:
98
+ if not second: # -- xxx=
99
+ trait_name = first
100
+ new_vp = False
101
+ else: # -- xxx=yyy
102
+ trait_values[first] = second
103
+ new_vp = True
104
+ else: # -- =yyy
105
+ if i:
106
+ trait_name = args.pop(-1)
107
+ trait_values[trait_name] = second
108
+ new_vp = True
109
+ else:
110
+ return RC(False, 'May not start with "="')
111
+
112
+ else:
113
+ parts = arg.split('=', 1)
114
+ n = len(parts)
115
+ if new_vp:
116
+ trait_name = parts[0]
117
+ if n == 1:
118
+ eq_expected = True
119
+ new_vp = False
120
+ else:
121
+ value = parts[1]
122
+ if value:
123
+ trait_values[trait_name] = value
124
+ new_vp = True
125
+ else:
126
+ eq_expected = False
127
+ new_vp = False
128
+ else:
129
+ if not eq_expected:
130
+ if n == 2:
131
+ return RC(False, f'Invalid "=": {input_args[i - 1]} {arg}')
132
+
133
+ trait_values[trait_name] = parts[0]
134
+ new_vp = True
135
+
136
+ else:
137
+ if n == 1:
138
+ return RC(False, f'"=" is expected before {arg}')
139
+
140
+ if parts[0]:
141
+ return RC(False, f'{input_args[i - 1]} {arg}')
142
+
143
+ if parts[1]:
144
+ trait_values[trait_name] = parts[1]
145
+ new_vp = True
146
+
147
+ else:
148
+ eq_expected = False
149
+
150
+ if not new_vp:
151
+ return RC(False, f'Value is missing for {trait_name} = ')
152
+
153
+ return RC_TRUE
@@ -0,0 +1,33 @@
1
+ from core_10x.traitable import Traitable, Trait, T, RT, RC, XNone
2
+
3
+ class TraitableHeir(Traitable):
4
+ _grantor: Traitable = T()
5
+
6
+ s_grantor_traits = {}
7
+ def __init_subclass__(cls, **kwargs):
8
+ super().__init_subclass__(**kwargs)
9
+
10
+ cls.s_grantor_traits = dir = {}
11
+ grantor_trait = cls.trait('_grantor')
12
+ trait: Trait
13
+ for trait in cls.traits():
14
+ if trait is grantor_trait or trait.flags_on(T.RESERVED):
15
+ continue
16
+ if not trait.has_custom_getter() and trait.default is XNone: #-- trait has neither getter nor default value
17
+ trait.set_f_get(lambda self, trait_name = trait.name: self.heir_getter(trait_name), False)
18
+ dir[trait.name] = trait
19
+
20
+ def heir_getter(self, trait_name: str):
21
+ grantor = self._grantor
22
+ if not grantor:
23
+ return XNone
24
+ trait = grantor.__class__.trait(trait_name)
25
+ return grantor.get_value(trait) if trait else XNone
26
+
27
+ def serialize_object(self, save_references = False) -> dict:
28
+ serialized_data = super().serialize_object(save_references = save_references)
29
+ for name, trait in self.__class__.s_grantor_traits.items():
30
+ if not self.is_set(trait):
31
+ del serialized_data[name]
32
+
33
+ return serialized_data
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ from functools import total_ordering
4
+
5
+
6
+ @total_ordering
7
+ class ID:
8
+ __slots__ = ('collection_name', 'value')
9
+
10
+ def __init__(self, id_value: str = None, collection_name: str = None):
11
+ self.value = id_value
12
+ self.collection_name = collection_name
13
+
14
+ def __eq__(self, other: ID):
15
+ if not isinstance(other, ID):
16
+ return NotImplemented
17
+ return self.value == other.value and self.collection_name == other.collection_name
18
+
19
+ def __bool__(self):
20
+ return bool(self.value)
21
+
22
+ def __repr__(self):
23
+ return f'{self.value}' if not self.collection_name else f'{self.collection_name}/{self.value}'
24
+
25
+ def __hash__(self):
26
+ return hash((self.value, self.collection_name))
27
+
28
+ def __lt__(self, other: ID) -> bool:
29
+ if not isinstance(other, ID):
30
+ return NotImplemented
31
+ return (self.collection_name, self.value) < (other.collection_name, other.value)
core_10x/ts_store.py ADDED
@@ -0,0 +1,172 @@
1
+ from __future__ import annotations
2
+
3
+ import abc
4
+ from typing import TYPE_CHECKING
5
+
6
+ from core_10x.exec_control import ProcessContext
7
+ from core_10x.global_cache import standard_key
8
+ from core_10x.py_class import PyClass
9
+ from core_10x.rc import RC
10
+ from core_10x.resource import TS_STORE, Resource, ResourceSpec
11
+ from core_10x.trait_filter import f
12
+ from core_10x.ts_store_type import TS_STORE_TYPE
13
+
14
+ if TYPE_CHECKING:
15
+ from collections.abc import Iterable
16
+
17
+
18
+ class TsDuplicateKeyError(Exception):
19
+ """Raised when attempting to insert a document with a duplicate key."""
20
+
21
+ def __init__(self, collection_name: str, duplicate_key: dict):
22
+ super().__init__(f'Duplicate key error collection {collection_name} dup key: {duplicate_key} was found while insert was attempted.')
23
+
24
+
25
+ class TsCollection(abc.ABC):
26
+ s_id_tag: str = None
27
+
28
+ @abc.abstractmethod
29
+ def collection_name(self) -> str: ...
30
+ @abc.abstractmethod
31
+ def id_exists(self, id_value: str) -> bool: ...
32
+ @abc.abstractmethod
33
+ def find(self, query: f = None, _at_most: int = 0, _order: dict = None) -> Iterable: ...
34
+ @abc.abstractmethod
35
+ def count(self, query: f = None) -> int: ...
36
+ @abc.abstractmethod
37
+ def save_new(self, serialized_traitable: dict, overwrite: bool = False) -> int: ...
38
+ @abc.abstractmethod
39
+ def save(self, serialized_traitable: dict) -> int: ...
40
+ @abc.abstractmethod
41
+ def delete(self, id_value: str) -> bool: ...
42
+ @abc.abstractmethod
43
+ def create_index(self, name: str, trait_name: str | list[tuple[str, int]], **index_args) -> str: ...
44
+ @abc.abstractmethod
45
+ def max(self, trait_name: str, filter: f = None) -> dict: ...
46
+ @abc.abstractmethod
47
+ def min(self, trait_name: str, filter: f = None) -> dict: ...
48
+
49
+ def exists(self, query: f) -> bool:
50
+ return self.count(query) > 0
51
+
52
+ def load(self, id_value: str) -> dict | None:
53
+ for data in self.find(f(**{self.s_id_tag: id_value})):
54
+ return data
55
+
56
+ def copy_to(self, to_coll: TsCollection, overwrite: bool = False) -> RC:
57
+ """Copy all documents from this collection to another collection."""
58
+ rc = RC(True)
59
+
60
+ for doc in self.find():
61
+ try:
62
+ if not to_coll.save_new(doc, overwrite=overwrite):
63
+ rc.add_error(f'Failed to save {doc.get(to_coll.s_id_tag)} to {to_coll.collection_name()}')
64
+ except TsDuplicateKeyError:
65
+ if overwrite:
66
+ raise # -- we do not expect an exception in case of overwrite, so raise
67
+
68
+ return rc
69
+
70
+
71
+ class TsStore(Resource, resource_type=TS_STORE):
72
+ PROTOCOL = ''
73
+
74
+ def __init_subclass__(cls, **kwargs):
75
+ super().__init_subclass__(**kwargs)
76
+ assert cls.__mro__[1] is TsStore, 'TsStore must be the first base class'
77
+ cls.s_instance_kwargs_map = {**TsStore.s_instance_kwargs_map, **cls.s_instance_kwargs_map}
78
+
79
+ @staticmethod
80
+ def store_class(store_class_name: str):
81
+ cls = PyClass.find(store_class_name, TsStore)
82
+ assert cls, f'Unknown TsStore class {store_class_name}'
83
+ return cls
84
+
85
+ @classmethod
86
+ def standard_key(cls, *args, **kwargs) -> tuple:
87
+ return standard_key(args, kwargs)
88
+
89
+ s_instances = {}
90
+
91
+ @classmethod
92
+ def spec_from_uri(cls, uri: str) -> ResourceSpec:
93
+ parts = uri.split(':', maxsplit=1)
94
+ protocol = parts[0]
95
+ ts_class = TS_STORE_TYPE.ts_store_class(protocol)
96
+ return ResourceSpec(ts_class, ts_class.parse_uri(uri))
97
+
98
+ @classmethod
99
+ def instance(cls, *args, password: str = '', _cache: bool = True, **kwargs) -> TsStore:
100
+ translated_kwargs = cls.translate_kwargs(kwargs)
101
+ try:
102
+ if not _cache:
103
+ return cls.new_instance(*args, password=password, **translated_kwargs)
104
+
105
+ instance_key = cls.standard_key(*args, **kwargs)
106
+ store = cls.s_instances.get(instance_key)
107
+ if not store:
108
+ store = cls.new_instance(*args, password=password, **translated_kwargs)
109
+ cls.s_instances[instance_key] = store
110
+
111
+ return store
112
+
113
+ except Exception as e:
114
+ raise OSError(f'Failed to connect to {cls.s_driver_name}({args}, {translated_kwargs})\nOriginal Exception:\n{e!s}') from e
115
+
116
+ # fmt: off
117
+ s_instance_kwargs_map = {
118
+ Resource.HOSTNAME_TAG: (Resource.HOSTNAME_TAG, None),
119
+ Resource.USERNAME_TAG: (Resource.USERNAME_TAG, None),
120
+ Resource.DBNAME_TAG: (Resource.DBNAME_TAG, None),
121
+ Resource.PORT_TAG: (Resource.PORT_TAG, None),
122
+ Resource.SSL_TAG: (Resource.SSL_TAG, True),
123
+ 'sst': ('sst', 1000),
124
+ }
125
+ # fmt: on
126
+
127
+ @classmethod
128
+ def translate_kwargs(cls, kwargs: dict) -> dict:
129
+ kwargs_map = cls.s_instance_kwargs_map
130
+ def_kwargs = {name: def_value for name, (real_name, def_value) in kwargs_map.items()}
131
+ def_kwargs.update(kwargs)
132
+ return {kwargs_map[name][0]: value for name, value in def_kwargs.items()}
133
+
134
+ @classmethod
135
+ def new_instance(cls, *args, password: str, **kwargs) -> TsStore:
136
+ raise NotImplementedError
137
+
138
+ @classmethod
139
+ def parse_uri(cls, uri: str) -> dict:
140
+ raise NotImplementedError
141
+
142
+ def on_enter(self):
143
+ self.bpc_flags = ProcessContext.reset_flags(ProcessContext.CACHE_ONLY)
144
+
145
+ def on_exit(self):
146
+ ProcessContext.replace_flags(self.bpc_flags)
147
+
148
+ @abc.abstractmethod
149
+ def collection_names(self, regexp: str = None) -> list: ...
150
+
151
+ @abc.abstractmethod
152
+ def collection(self, collection_name: str) -> TsCollection: ...
153
+
154
+ @abc.abstractmethod
155
+ def delete_collection(self, collection_name: str) -> bool: ...
156
+
157
+ @classmethod
158
+ def is_running_with_auth(cls, host_name: str) -> tuple: ... # -- (is_running, with_auth)
159
+
160
+ @abc.abstractmethod
161
+ def auth_user(self) -> str | None: ...
162
+
163
+ def copy_to(self, to_store: TsStore, overwrite: bool = False) -> RC:
164
+ """Copy all collections from this store to another store."""
165
+ rc = RC(True)
166
+
167
+ for collection_name in self.collection_names():
168
+ from_coll = self.collection(collection_name)
169
+ to_coll = to_store.collection(collection_name)
170
+ rc += from_coll.copy_to(to_coll, overwrite=overwrite)
171
+
172
+ return rc
@@ -0,0 +1,26 @@
1
+ from core_10x.named_constant import NamedConstant
2
+ from core_10x.py_class import PyClass
3
+
4
+
5
+ class TS_STORE_TYPE(NamedConstant):
6
+ """
7
+ All known TsStore subclasses must be "registered" here.
8
+ """
9
+
10
+ MONGODB = 'infra_10x.mongodb_store.MongoStore'
11
+ ...
12
+
13
+ @classmethod
14
+ def ts_store_class(cls, uri_protocol: str):
15
+ """
16
+ example: TS_STORE_TYPE.ts_store_class('mongodb')
17
+ """
18
+ symbol = cls.s_dir.get(uri_protocol.upper())
19
+ if symbol is None:
20
+ raise ValueError(f'Unknown URI protocol: {uri_protocol}')
21
+
22
+ ts_class = PyClass.find(symbol.value)
23
+ if not ts_class:
24
+ raise TypeError(f'{symbol.value} - unknown TsStore class')
25
+
26
+ return ts_class
core_10x/ts_union.py ADDED
@@ -0,0 +1,147 @@
1
+ from __future__ import annotations
2
+
3
+ import heapq
4
+ import itertools
5
+ import operator
6
+ from itertools import zip_longest
7
+ from typing import TYPE_CHECKING
8
+
9
+ from core_10x.nucleus import Nucleus
10
+ from core_10x.trait_filter import EQ, f
11
+ from core_10x.ts_store import TsCollection, TsStore
12
+
13
+ if TYPE_CHECKING:
14
+ from collections.abc import Iterable
15
+
16
+
17
+ class _OrderKey:
18
+ __slots__ = ('reverse', 'value')
19
+
20
+ @classmethod
21
+ def _dict_cmp(cls, d: dict, od: dict) -> int:
22
+ assert isinstance(od, dict), 'can only compare dict to dict'
23
+
24
+ for i, oi in zip_longest(d.items(), od.items()):
25
+ if i == oi:
26
+ continue
27
+ if i is None:
28
+ return -1 # all match, d is shorter
29
+ if oi is None:
30
+ return 1 # all match d is longer
31
+ k, v = i
32
+ ok, ov = oi
33
+ if k != ok:
34
+ return -1 if k < ok else 1 if k > ok else 0 # keys do not match
35
+ if isinstance(v, dict) and isinstance(ov, dict):
36
+ return cls._dict_cmp(v, ov)
37
+ # TODO: handle mismatching types..
38
+ return -1 if v < ov else 1 if v > ov else 0
39
+ return 0 # equals..
40
+
41
+ def __init__(self, value, reverse: bool):
42
+ self.value = value
43
+ self.reverse = reverse
44
+
45
+ def __lt__(self, other):
46
+ assert isinstance(other, _OrderKey), 'can only compare to order key'
47
+ v = self.value
48
+ ov = other.value
49
+ if isinstance(v, dict):
50
+ return self._dict_cmp(v, ov) < 0 if self.reverse else self._dict_cmp(ov, v) > 0
51
+ return ov < v if self.reverse else v < ov
52
+
53
+ def __eq__(self, other):
54
+ assert isinstance(other, _OrderKey), 'can only compare to order key'
55
+ v = self.value
56
+ ov = other.value
57
+ return not self._dict_cmp(v, ov) if isinstance(v, dict) else v == ov
58
+
59
+ @classmethod
60
+ def key(cls, item, order):
61
+ return tuple(cls(value=item.get(k), reverse=v < 0) for k, v in order)
62
+
63
+
64
+ class TsUnionCollection(TsCollection):
65
+ def __init__(self, *collections: TsCollection):
66
+ self.collections = collections
67
+
68
+ def collection_name(self) -> str:
69
+ return self.collections[0].collection_name() if self.collections else ''
70
+
71
+ def find(self, query: f = None, _at_most: int = 0, _order: dict = None) -> Iterable:
72
+ order = tuple((_order or {'_id': 1}).items()) # FIX: assumes _id is always there!
73
+ order_key = _OrderKey.key
74
+ iterables = (collection.find(query, _at_most=_at_most, _order=_order) for collection in self.collections)
75
+ keyed_iterables = (((order_key(item, order), item) for item in iterable) for iterable in iterables if iterable is not None)
76
+ results = (item for _, item in heapq.merge(*keyed_iterables, key=operator.itemgetter(0)))
77
+ return (item for i, item in enumerate(results) if i < _at_most) if _at_most else results
78
+
79
+ def save_new(self, serialized_traitable: dict, overwrite: bool = False) -> int:
80
+ return self.collections[0].save_new(serialized_traitable, overwrite=overwrite)
81
+
82
+ def save(self, serialized_traitable):
83
+ # if serialized_traitable was not loaded from the union head, we need to call save_new
84
+ id_tag = Nucleus.ID_TAG()
85
+ if not self.collections[0].exists(f(**{id_tag: EQ(serialized_traitable[id_tag])})):
86
+ # TODO: optimize by introducing save with overwrite flag to bypass the "inappropriate restore from deleted" check
87
+ return self.collections[0].save_new(serialized_traitable)
88
+ return self.collections[0].save(serialized_traitable)
89
+
90
+ def delete(self, id_value):
91
+ # if id_value exists in the union tail, return False as the object wasn't fully deleted
92
+ return self.collections[0].delete(id_value) and not self.exists(f(**{Nucleus.ID_TAG(): EQ(id_value)}))
93
+
94
+ def create_index(self, name: str, trait_name: str | list[tuple[str, int]], **index_args) -> str:
95
+ # TODO: verify that index exists in the union tail
96
+ return self.collections[0].create_index(name, trait_name, **index_args)
97
+
98
+ def max(self, trait_name: str, filter: f = None) -> dict:
99
+ results = (result for result in (collection.max(trait_name, filter) for collection in self.collections) if result is not None)
100
+ return max(results, key=operator.itemgetter(trait_name), default=None)
101
+
102
+ def min(self, trait_name: str, filter: f = None) -> dict:
103
+ results = (result for result in (collection.min(trait_name, filter) for collection in self.collections) if result is not None)
104
+ return min(results, key=operator.itemgetter(trait_name), default=None)
105
+
106
+ def id_exists(self, id_value: str) -> bool:
107
+ return any(collection.id_exists(id_value) for collection in self.collections)
108
+
109
+ def count(self, query: f = None) -> int:
110
+ return sum(collection.count(query) for collection in self.collections)
111
+
112
+ def load(self, id_value: str) -> dict | None:
113
+ for collection in self.collections:
114
+ data = collection.load(id_value)
115
+ if data is not None:
116
+ return data
117
+ return None
118
+
119
+
120
+ class TsUnion(TsStore, resource_name='TS_UNION'):
121
+ @classmethod
122
+ def is_running_with_auth(cls, host_name: str) -> tuple: # -- (is_running, with_auth)
123
+ raise NotImplementedError
124
+
125
+ @classmethod
126
+ def standard_key(cls, *args) -> tuple:
127
+ return tuple(cls.s_resource_type.resource_driver(kw['driver_name']).standard_key(**kw) for kw in args)
128
+
129
+ @classmethod
130
+ def new_instance(cls, *args, **kwargs) -> TsUnion:
131
+ stores = [cls.s_resource_type.resource_driver(kw.pop('driver_name')).instance(**kw) for kw in args]
132
+ return TsUnion(*stores)
133
+
134
+ def __init__(self, *stores: TsStore):
135
+ self.stores = stores
136
+
137
+ def collection_names(self, regexp: str = None) -> list:
138
+ return list(set(itertools.chain(*(store.collection_names(regexp) for store in self.stores))))
139
+
140
+ def collection(self, collection_name):
141
+ return TsUnionCollection(*(store.collection(collection_name) for store in self.stores))
142
+
143
+ def delete_collection(self, collection_name: str) -> bool:
144
+ return self.stores[0].delete_collection(collection_name) if self.stores else False
145
+
146
+ def auth_user(self) -> str | None:
147
+ return self.stores[0].auth_user() if self.stores else None