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,356 @@
1
+ import ast
2
+ import inspect
3
+
4
+ from core_10x.named_constant import EnumBits, NamedConstant
5
+ from core_10x.nucleus import Nucleus
6
+ from core_10x.package_refactoring import PackageRefactoring
7
+ from core_10x.py_class import PyClass
8
+ from core_10x.trait import T, Trait
9
+ from core_10x.ui_hint import Ui
10
+ from core_10x.xdate_time import XDateTime, date, datetime
11
+ from core_10x.xnone import XNone, XNoneType
12
+
13
+
14
+ class primitive_trait(Trait, register=False):
15
+ s_ui_hint = Ui.NONE
16
+
17
+ def default_value(self):
18
+ return self.default
19
+
20
+ def same_values(self, value1, value2) -> bool:
21
+ return value1 == value2
22
+
23
+ def from_str(self, s: str):
24
+ try:
25
+ v = ast.literal_eval(s)
26
+ dt = self.data_type
27
+ return v if type(v) is dt else dt(v)
28
+ except Exception:
29
+ return XNone
30
+
31
+ def from_any_xstr(self, value):
32
+ return self.data_type(value)
33
+
34
+ # def to_str(self, v) -> str:
35
+ # return str(v)
36
+
37
+ def to_id(self, v) -> str:
38
+ return str(v)
39
+
40
+ def serialize(self, value):
41
+ return value
42
+
43
+ def deserialize(self, value):
44
+ return value
45
+
46
+
47
+ class bool_trait(primitive_trait, data_type=bool):
48
+ s_ui_hint = Ui.check()
49
+
50
+ # fmt: off
51
+ FORMATS = [
52
+ ('yes',''),
53
+ ('yes','no'),
54
+ ('true','false'),
55
+ ('on','off')
56
+ ]
57
+ fmt = FORMATS[0]
58
+ # fmt: on
59
+
60
+ def to_id(self, value: bool) -> str:
61
+ return '0' if value else '1'
62
+
63
+ def from_str(self, s: str):
64
+ res = super().from_str(s)
65
+ if res is not XNone:
66
+ return res
67
+ s = s.strip().lower()
68
+ return next((f[0] == s for f in self.FORMATS if s in f), XNone)
69
+
70
+
71
+ class int_trait(primitive_trait, data_type=int):
72
+ s_ui_hint = Ui.line()
73
+
74
+ fmt = ','
75
+
76
+
77
+ class float_trait(primitive_trait, data_type=float):
78
+ s_ui_hint = Ui.line()
79
+
80
+ fmt = ',.2f'
81
+
82
+ # def from_str(self, s: str) -> RC:
83
+ # return RC(True, locale.atof(s))
84
+
85
+ def is_acceptable_type(self, data_type: type) -> bool:
86
+ return data_type is float or data_type is int
87
+
88
+
89
+ class str_trait(primitive_trait, data_type=str):
90
+ s_ui_hint = Ui.line(align_h=-1)
91
+
92
+ # pattern = ''
93
+ # placeholder = ''
94
+
95
+ def from_str(self, s: str):
96
+ return s
97
+
98
+ def to_str(self, v) -> str:
99
+ return v
100
+
101
+ def to_id(self, value: str) -> str:
102
+ return value
103
+
104
+
105
+ # class bin_trait(Trait, data_type = bytes):
106
+ # w = 0 #-- max width, if any and relevant
107
+ # h = 0 #-- max height, if any and relevant
108
+ #
109
+ # def pixmap(*args, **kwargs):
110
+ # tdef: TraitDefinition = T(bytes, *args, **kwargs)
111
+ # tdef.set_widget_type(Ui.WIDGET_TYPE.PIXMAP)
112
+ # return tdef
113
+
114
+
115
+ class datetime_trait(Trait, data_type=datetime):
116
+ s_ui_hint = Ui.line(align_h=0)
117
+
118
+ def from_str(self, s: str):
119
+ dt = XDateTime.str_to_datetime(s)
120
+ if dt is None:
121
+ return XNone
122
+ return dt
123
+
124
+ def from_any_xstr(self, value):
125
+ dt = XDateTime.to_datetime(value)
126
+ if dt is None:
127
+ return XNone
128
+ return dt
129
+
130
+ def to_str(self, v: datetime) -> str:
131
+ return XDateTime.datetime_to_str(v)
132
+
133
+ # s_acceptable_types = { datetime, date, int, str }
134
+ # def is_acceptable_type(self, data_type: type) -> bool:
135
+ # return data_type in self.s_acceptable_types
136
+
137
+ # -- NOTES:
138
+ # -- 1) we believe datetime is mostly acceptable for a storage, e.g., Mongo
139
+ # -- 2) it is in UTC, so no locale's datetime issues
140
+
141
+ def serialize(self, value: datetime):
142
+ return value
143
+
144
+ def deserialize(self, value: datetime):
145
+ return value
146
+
147
+ def to_id(self, value) -> str:
148
+ return XDateTime.datetime_to_str(value, with_ms=True)
149
+
150
+
151
+ class date_trait(Trait, data_type=date):
152
+ s_ui_hint = Ui.line(min_width=10, align_h=0)
153
+
154
+ def from_str(self, s: str):
155
+ dt = XDateTime.str_to_date(s)
156
+ if dt is None:
157
+ return XNone
158
+ return dt
159
+
160
+ def from_any_xstr(self, value):
161
+ dt = XDateTime.to_date(value)
162
+ if dt is None:
163
+ return XNone
164
+ return dt
165
+
166
+ def to_str(self, v: date) -> str:
167
+ return XDateTime.date_to_str(v)
168
+
169
+ # s_acceptable_types = { datetime, date, int, str }
170
+ # def is_acceptable_type(self, data_type: type) -> bool:
171
+ # return data_type in self.s_acceptable_types
172
+
173
+ def serialize(self, value: date):
174
+ return XDateTime.date_to_str(value, format=XDateTime.FORMAT_X10)
175
+
176
+ def deserialize(self, value):
177
+ return XDateTime.str_to_date(value, format=XDateTime.FORMAT_X10)
178
+
179
+ def to_id(self, value) -> str:
180
+ return XDateTime.date_to_str(value)
181
+
182
+
183
+ class bytes_trait(Trait, data_type=bytes):
184
+ s_ui_hint = Ui.NONE
185
+
186
+ def to_str(self, value):
187
+ return value.decode(encoding='utf-8')
188
+
189
+ def to_id(self, value) -> str:
190
+ return self.to_str(value)
191
+
192
+ def same_values(self, value1, value2) -> bool:
193
+ return value1 == value2
194
+
195
+ def from_str(self, s: str):
196
+ return bytes(s, encoding='utf-8')
197
+
198
+ def from_any_xstr(self, value):
199
+ return XNone # may not be called!
200
+
201
+ # def is_acceptable_type(self, data_type: type) -> bool:
202
+ # ...
203
+
204
+ def serialize(self, value):
205
+ return value
206
+
207
+ def deserialize(self, value):
208
+ return value
209
+
210
+
211
+ class class_trait(Trait, data_type=type):
212
+ s_ui_hint = Ui.line(align_h=-1)
213
+
214
+ def to_str(self, value):
215
+ return PyClass.name(value)
216
+
217
+ def to_id(self, value) -> str:
218
+ return PackageRefactoring.find_class_id(value)
219
+
220
+ def same_values(self, value1, value2) -> bool:
221
+ return value1 is value2
222
+
223
+ def from_str(self, s: str):
224
+ return PackageRefactoring.find_class(s)
225
+
226
+ def from_any_xstr(self, value):
227
+ return XNone # may not be called!
228
+
229
+ def is_acceptable_type(self, data_type: type) -> bool:
230
+ return inspect.isclass(data_type)
231
+
232
+ def serialize(self, value):
233
+ return PackageRefactoring.find_class_id(value)
234
+
235
+ def deserialize(self, value: str):
236
+ return PackageRefactoring.find_class(value)
237
+
238
+
239
+ class list_trait(Trait, data_type=list):
240
+ s_ui_hint = Ui.line()
241
+
242
+ def to_str(self, v) -> str:
243
+ return str(v)
244
+
245
+ def to_id(self, value) -> str:
246
+ return str(value)
247
+
248
+ def default_value(self) -> list:
249
+ return list(self.default)
250
+
251
+ def serialize(self, value: list):
252
+ return Nucleus.serialize_list(value, self.flags_on(T.EMBEDDED))
253
+
254
+ def deserialize(self, value: list):
255
+ return Nucleus.deserialize_list(value)
256
+
257
+
258
+ class dict_trait(Trait, data_type=dict):
259
+ s_ui_hint = Ui.line(flags=Ui.SELECT_ONLY)
260
+
261
+ def use_format_str(self, fmt: str, value) -> str:
262
+ return str(value) if not fmt else fmt.join(f"'{key}' -> '{val}'" for key, val in value.items())
263
+
264
+ def to_str(self, v) -> str:
265
+ return str(v)
266
+
267
+ def to_id(self, value) -> str:
268
+ return str(value)
269
+
270
+ def default_value(self) -> dict:
271
+ return dict(self.default)
272
+
273
+ def serialize(self, value: dict):
274
+ return Nucleus.serialize_dict(value, self.flags_on(T.EMBEDDED))
275
+
276
+ def deserialize(self, value: dict):
277
+ return Nucleus.deserialize_dict(value)
278
+
279
+
280
+ class any_trait(Trait, data_type=XNoneType): # -- any
281
+ s_ui_hint = Ui.NONE
282
+
283
+ def to_str(self, v) -> str:
284
+ return str(v)
285
+
286
+ def to_id(self, value) -> str:
287
+ return str(value)
288
+
289
+ def from_str(self, s: str):
290
+ return None
291
+
292
+ def from_any_xstr(self, value):
293
+ return value
294
+
295
+ def is_acceptable_type(self, data_type: type) -> bool:
296
+ return True
297
+
298
+ def serialize(self, value):
299
+ return Nucleus.serialize_any(value, self.flags_on(T.EMBEDDED))
300
+
301
+ def deserialize(self, value):
302
+ return Nucleus.deserialize_any(value, None) # expected class unknown
303
+
304
+ def same_values(self, value1, value2) -> bool:
305
+ return value1 is value2
306
+
307
+
308
+ class nucleus_trait(Trait, data_type=Nucleus, base_class=True):
309
+ s_ui_hint = Ui.NONE
310
+
311
+ def to_str(self, v) -> str:
312
+ return self.data_type.to_str(v)
313
+
314
+ def to_id(self, v) -> str:
315
+ return self.data_type.to_id(v)
316
+
317
+ def from_str(self, s: str) -> Nucleus:
318
+ return self.data_type.from_str(s)
319
+
320
+ def from_any_xstr(self, value) -> Nucleus:
321
+ return self.data_type.from_any_xstr(value)
322
+
323
+ def is_acceptable_type(self, data_type: type) -> bool:
324
+ return issubclass(data_type, self.data_type)
325
+
326
+ def same_values(self, value1, value2) -> bool:
327
+ return self.data_type.same_values(value1, value2)
328
+
329
+ def serialize(self, value: Nucleus):
330
+ if type(value) is self.data_type:
331
+ return value.serialize(self.flags_on(T.EMBEDDED))
332
+
333
+ return Nucleus.serialize_any(value, self.flags_on(T.EMBEDDED))
334
+
335
+ def deserialize(self, serialized_value) -> Nucleus:
336
+ if isinstance(serialized_value, dict) and (value := Nucleus.deserialize_record(serialized_value)):
337
+ return value
338
+
339
+ return self.data_type.deserialize(serialized_value)
340
+
341
+ def choices(self):
342
+ return self.data_type.choose_from()
343
+
344
+
345
+ class named_constant_trait(nucleus_trait, data_type=NamedConstant, base_class=True):
346
+ s_ui_hint = Ui.choice(flags=Ui.SELECT_ONLY)
347
+
348
+ def is_acceptable_type(self, data_type: type) -> bool:
349
+ return data_type is self.data_type
350
+
351
+
352
+ class flags_trait(nucleus_trait, data_type=EnumBits, base_class=True):
353
+ s_ui_hint = Ui.line()
354
+
355
+ def is_acceptable_type(self, data_type: type) -> bool:
356
+ return data_type is self.data_type
core_10x/conftest.py ADDED
@@ -0,0 +1,12 @@
1
+ import pytest
2
+
3
+ from core_10x.ts_store import TsStore
4
+
5
+
6
+ @pytest.fixture(scope='module')
7
+ def ts_instance():
8
+ from core_10x.testlib.test_store import TestStore
9
+
10
+ assert not TsStore.s_instances
11
+ yield TestStore.instance()
12
+ TsStore.s_instances.clear()
core_10x/curve.py ADDED
@@ -0,0 +1,321 @@
1
+ from __future__ import annotations
2
+
3
+ import bisect
4
+ import math
5
+ from datetime import date, timedelta
6
+ from typing import Any
7
+
8
+ from scipy import interpolate
9
+
10
+ from core_10x.named_constant import NamedConstant
11
+ from core_10x.traitable import RC, RC_TRUE, RT, AnonymousTraitable, M, T, Traitable
12
+
13
+
14
+ class IP_KIND(NamedConstant, lowercase_values=True):
15
+ # fmt: off
16
+ #-- NOTE: enum labels specify the minimum number of points required!
17
+ LINEAR = ( 2, )
18
+ NEAREST = ( 2, )
19
+ NEAREST_UP = ( 2, )
20
+ PREVIOUS = ( 2, ) #-- return the previous value of the point
21
+ NEXT = ( 2, ) #-- ------ the next ----
22
+ ZERO = ( 1, ) #-- spline interp of zeroth order
23
+ SLINEAR = ( 2, ) #-- spline interp of first order
24
+ QUADRATIC = ( 3, ) #-- spline interp of second order
25
+ CUBIC = ( 4, ) #-- spline interp of third order
26
+
27
+ NO_INTERP = ( 0, ) #-- NO interp outside of given nodes
28
+ # fmt: on
29
+
30
+ # @classmethod
31
+ # def labelFromValue( cls, value ) -> int:
32
+ # return int( super().labelFromValue( value ) )
33
+ #
34
+ # @classmethod
35
+ # def defaultValueForName( cls, name: str ) -> str:
36
+ # return name.replace( '_', '-' ).lower()
37
+
38
+
39
+ # -- TODO: it must be derived from AnonymousTraitable. Had to make it RT and derive from Traitable due to a bug - trying to load the embedded portion from its own collection
40
+ class CurveParams(Traitable):
41
+ # fmt: off
42
+ DEFAULT_INTERPOLATOR = interpolate.interp1d
43
+
44
+ interpolator: Any = RT(default = DEFAULT_INTERPOLATOR)
45
+ ip_kind: IP_KIND = RT(IP_KIND.LINEAR)
46
+ assume_sorted: bool = RT(True)
47
+ copy: bool = RT(False)
48
+ fill_value: str = RT('extrapolate')
49
+ bounds_error: bool = RT(False)
50
+ # fmt: on
51
+
52
+
53
+ class Curve(AnonymousTraitable):
54
+ # fmt: off
55
+ times: list = T([]) #-- only ints or floats are allowed
56
+ values: list = T([])
57
+ params: CurveParams = RT() #-- TODO: must be T(T.EMBEDDED)
58
+
59
+ beginning_of_time: Any = T(None) #-- may be float or int
60
+
61
+ interpolator: Any = RT()
62
+ min_curve_size: int = RT()
63
+ # fmt: on
64
+
65
+ def params_get(self) -> CurveParams:
66
+ return CurveParams()
67
+
68
+ def min_curve_size_get(self) -> int:
69
+ return self.params.ip_kind.label
70
+
71
+ def start_time(self):
72
+ times = self.times
73
+ return times[0] if times else None
74
+
75
+ def end_time(self):
76
+ times = self.times
77
+ return times[-1] if times else None
78
+
79
+ def update(self, t, value, reset=True):
80
+ if type(value) is not float: # -- TODO: we sometimes have np.floats
81
+ value = float(value)
82
+
83
+ times = self.times
84
+ values = self.values
85
+ i = bisect.bisect_left(times, t)
86
+ if i >= len(times):
87
+ times.append(t)
88
+ values.append(value)
89
+ else:
90
+ if times[i] == t:
91
+ values[i] = value
92
+ else:
93
+ times.insert(i, t)
94
+ values.insert(i, value)
95
+
96
+ self.times = times
97
+ self.values = values
98
+ if reset:
99
+ self.reset()
100
+
101
+ def update_many(self, times, values, reset=True):
102
+ assert len(times) == len(values), 'times and values size mismatch'
103
+ for i, t in enumerate(times):
104
+ self.update(t, values[i], reset=False)
105
+
106
+ if reset:
107
+ self.reset()
108
+
109
+ def remove(self, t, reset=True) -> bool:
110
+ times = self.times
111
+ if t not in times:
112
+ return False
113
+
114
+ values = self.values
115
+ i = times.index(t)
116
+ times.pop(i)
117
+ values.pop(i)
118
+ if reset:
119
+ self.reset()
120
+
121
+ return True
122
+
123
+ def perturb(self, t, new_value, perturb_existing_only=False):
124
+ if perturb_existing_only:
125
+ assert t in self.times, f't = {t} is not in the curve'
126
+
127
+ values = self.values
128
+ self.invalidate_value('values')
129
+ self.values = values
130
+ self.update(t, new_value)
131
+
132
+ def perturb_shift(self, t, value_shift, perturb_existing_only=False):
133
+ self.perturb(t, self.value(t) + value_shift, perturb_existing_only)
134
+
135
+ def perturb_proportional(self, t, value_shift_mult, perturb_existing_only=False):
136
+ self.perturb(t, self.value(t) * value_shift_mult, perturb_existing_only)
137
+
138
+ def interpolator_get(self):
139
+ params = self.params
140
+ # fmt: off
141
+ return params.interpolator(
142
+ self.times, self.values,
143
+ kind = params.ip_kind.value,
144
+ assume_sorted = params.assume_sorted,
145
+ copy = params.copy,
146
+ fill_value = params.fill_value,
147
+ bounds_error = params.bounds_error,
148
+ )
149
+ # fmt: on
150
+
151
+ def value(self, t) -> float:
152
+ times = self.times
153
+
154
+ if self.params.ip_kind is IP_KIND.NO_INTERP:
155
+ return self.values[times.index(t)] if t in times else math.nan
156
+
157
+ if len(times) < self.min_curve_size:
158
+ if t in times:
159
+ return self.values[times.index(t)]
160
+
161
+ bot = self.beginning_of_time
162
+ return float(self.interpolator(t)) if (bot is not None) or (t >= bot) else math.nan
163
+
164
+ def values_at(self, dates) -> tuple:
165
+ return tuple(self.value(d) for d in dates)
166
+
167
+ def reset(self):
168
+ self.invalidate_value('interpolator')
169
+
170
+ # @classmethod
171
+ # def _uniqueTimesValues(cls, times: list, values: list, keep_last_update: bool) -> tuple:
172
+ # assert len(times) == len(values), f'{len(times)} != {len(values)}: sizes of times and values must be equal'
173
+ #
174
+ # last_t = times[0]
175
+ # last_i = 0
176
+ # times_unique = [last_t]
177
+ # values_unique = [values[0]]
178
+ #
179
+ # for t, v in zip(times, values, strict=True):
180
+ # assert t >= last_t, f'times must be a non-decreasing list, but {t} < {last_t}'
181
+ #
182
+ # if t > last_t:
183
+ # last_t = t
184
+ # last_i += 1
185
+ # times_unique.append(t)
186
+ # values_unique.append(v)
187
+ # else:
188
+ # if keep_last_update:
189
+ # values_unique[last_i] = v
190
+ #
191
+ # return (times_unique, values_unique)
192
+ #
193
+ # def uniquePointsCurve(self, keep_last_update=True, copy_curve=False) -> Curve:
194
+ # times_unique, values_unique = self._uniqueTimesValues(self.times(), self.values(), keep_last_update)
195
+ # if copy_curve:
196
+ # return self.clone(times=times_unique, values=values_unique)
197
+ #
198
+ # self.times = times_unique
199
+ # self.values = values_unique
200
+ # self.reset()
201
+ # return self
202
+
203
+
204
+ class TwoFuncInterpolator:
205
+ def __init__(self, in_func, out_func, in_func_on_arrays=None, _interpolator=interpolate.interp1d):
206
+ if in_func:
207
+ in_func_on_arrays = lambda list_x, list_y: [in_func(x, list_y[i]) for i, x in enumerate(list_x)]
208
+
209
+ assert in_func_on_arrays, 'either in_func or in_func_on_arrays must be provided'
210
+
211
+ self.in_func = in_func_on_arrays
212
+ self.out_func = out_func
213
+ self.interpolator = _interpolator
214
+
215
+ def __call__(self, x, y, **kwargs):
216
+ values = self.in_func(x, y)
217
+ ip_method = self.interpolator(x, values, **kwargs)
218
+ out_func = self.out_func
219
+
220
+ def _method(t):
221
+ v = ip_method(t)
222
+ return out_func(t, v)
223
+
224
+ return _method
225
+
226
+
227
+ class DateCurve(Curve):
228
+ beginning_of_time: int = M() # -- TODO: looks like Any trait fails to get deserialized - bug
229
+ dates: list = RT()
230
+
231
+ s_epoch_date = date(1970, 1, 1)
232
+
233
+ @classmethod
234
+ def _to_number(cls, d) -> int:
235
+ if isinstance(d, date):
236
+ return (d - cls.s_epoch_date).days
237
+ elif isinstance(d, int):
238
+ return d
239
+
240
+ raise ValueError(f'Unexpected value {d}')
241
+
242
+ @classmethod
243
+ def _from_number(cls, x: int) -> date:
244
+ return cls.s_epoch_date + timedelta(days=x)
245
+
246
+ def dates_get(self) -> list:
247
+ f = self._from_number
248
+ return [f(x) for x in self.times]
249
+
250
+ def dates_set(self, trait, value) -> RC:
251
+ f = self._to_number # -- TODO: possibly improve performance by using a different f (which doesn't check the type)
252
+ times = [f(d) for d in value]
253
+ return self.set_value('times', times)
254
+
255
+ def beginning_of_time_set(self, trait, d) -> RC:
256
+ t = self._to_number(d)
257
+ self.raw_set_value(trait, t)
258
+ return RC_TRUE
259
+
260
+ def beginning_of_time_as_date(self) -> date:
261
+ return self._from_number(self.beginning_of_time)
262
+
263
+ def start_time(self) -> date:
264
+ times = self.dates
265
+ return times[0] if times else None
266
+
267
+ def end_time(self) -> date:
268
+ times = self.dates
269
+ return times[-1] if times else None
270
+
271
+ def update(self, d: date, value, reset=True):
272
+ x = self._to_number(d)
273
+ super().update(x, value, reset=reset)
274
+
275
+ def reset(self):
276
+ super().reset()
277
+ self.invalidate_value('dates')
278
+
279
+ def value(self, d: date) -> float:
280
+ x = self._to_number(d)
281
+ return super().value(x)
282
+
283
+ def remove(self, d, reset=True):
284
+ t = self._to_number(d)
285
+ if super().remove(t, reset=reset):
286
+ self.invalidate_value('dates')
287
+
288
+ def perturb(self, d: date, new_value, perturb_existing_only=False):
289
+ if perturb_existing_only:
290
+ assert d in self.dates, f'{d} is not in the curve'
291
+
292
+ values = self.values
293
+ self.invalidate_value('values')
294
+ self.values = values
295
+ self.update(d, new_value)
296
+
297
+ # def bracketDateNodes(self, d: date) -> tuple:
298
+ # t = self._to_number(d)
299
+ #
300
+ # times: list = self.times
301
+ # last_time = times[-1]
302
+ # first_time = times[0]
303
+ # if t > last_time:
304
+ # return (self._from_number(last_time), None)
305
+ #
306
+ # if t < first_time:
307
+ # return (None, self._from_number(first_time))
308
+ #
309
+ # i = bisect.bisect_left(times, t)
310
+ # if times[i] == t:
311
+ # d = self._from_number(times[i])
312
+ # return (d, d)
313
+ #
314
+ # d_left = self._from_number(times[i - 1])
315
+ # d_right = self._from_number(times[i])
316
+ # return (d_left, d_right)
317
+
318
+ def dates_values(self, min_date: date = None, max_date: date = None) -> list:
319
+ dates = self.dates
320
+ values = self.values
321
+ return [(d, v) for d, v in zip(dates, values, strict=True) if (not min_date or d >= min_date) and (not max_date or d <= max_date)]