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
ui_10x/trait_widget.py ADDED
@@ -0,0 +1,131 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from core_10x.rc import RC, RC_TRUE
6
+ from core_10x.trait import T, Ui
7
+
8
+ from ui_10x.utils import UxStyleSheet, ux
9
+
10
+ if TYPE_CHECKING:
11
+ from core_10x.trait import Trait
12
+
13
+
14
+ class TraitWidget:
15
+ # __slots__ = ('program_edit', 'refresh_context', 'style_sheet', 'trait', 'trait_editor')
16
+ s_hinted_widgets = {}
17
+
18
+ def __init_subclass__(cls, widget_type: Ui.WIDGET_TYPE = None, **kwargs):
19
+ if widget_type:
20
+ TraitWidget.s_hinted_widgets[widget_type] = cls
21
+
22
+ @staticmethod
23
+ def widget_class(widget_type: Ui.WIDGET_TYPE):
24
+ w_cls = TraitWidget.s_hinted_widgets.get(widget_type)
25
+ assert w_cls, f'Unknown trait widget type: {widget_type}'
26
+ return w_cls
27
+
28
+ @staticmethod
29
+ def instance(trait_editor) -> TraitWidget | None:
30
+ w_type = trait_editor.ui_hint.widget_type
31
+ if w_type is not Ui.WIDGET_TYPE.NONE:
32
+ w_cls = TraitWidget.widget_class(w_type)
33
+ return w_cls(trait_editor)
34
+ return None
35
+
36
+ def __init__(self, trait_editor):
37
+ self.trait_editor = trait_editor
38
+ self.trait: Trait = trait_editor.trait
39
+ self.program_edit = False
40
+
41
+ self._create()
42
+
43
+ if self.trait_editor.is_read_only():
44
+ self._set_read_only(True)
45
+
46
+ self.set_tool_tip(self.trait.ui_hint.tip)
47
+
48
+ entity = self.trait_editor.entity
49
+
50
+ self.style_sheet = sheet = UxStyleSheet(self)
51
+ sh = entity.get_style_sheet(self.trait)
52
+ sheet.update(sh)
53
+
54
+ value = entity.get_value(self.trait)
55
+ self.set_widget_value(value)
56
+
57
+ self.refresh_context = ctx = RefreshContext(self)
58
+ entity.create_ui_node(self.trait, ctx.emit_signal)
59
+
60
+ def refresh(self):
61
+ entity = self.trait_editor.entity
62
+ trait = self.trait
63
+ entity.update_ui_node(self.trait)
64
+
65
+ if not trait.flags_on(T.EXPENSIVE):
66
+ value = entity.get_value(trait)
67
+ self.set_widget_value(value)
68
+ else:
69
+ sh = entity.get_style_sheet(trait)
70
+ self.style_sheet.update(sh)
71
+
72
+ if not entity.is_valid(trait):
73
+ self.style_sheet.update({Ui.BG_COLOR: 'lightblue'}, _system=True)
74
+
75
+ def widget_value(self):
76
+ return self._value()
77
+
78
+ def set_widget_value(self, value) -> bool:
79
+ self.program_edit = True
80
+ try:
81
+ self._set_value(value)
82
+ except Exception: # -- the real widget is gone (so far, has encountered this only in Qt on Mac OS)
83
+ # self.clean() #-- TODO: fix!
84
+ self.program_edit = False
85
+ return False
86
+
87
+ sh = self.trait_editor.entity.get_style_sheet(self.trait)
88
+ self.style_sheet.update(sh)
89
+ self.program_edit = False
90
+ return True
91
+
92
+ def update_trait_value(self, value: Any = None, invalidate: bool = False):
93
+ if not self.program_edit:
94
+ entity = self.trait_editor.entity
95
+ if invalidate:
96
+ entity.invalidate_value(self.trait)
97
+ rc = RC_TRUE
98
+ else:
99
+ if value is None:
100
+ value = self.widget_value()
101
+
102
+ try:
103
+ rc = entity.set_value(self.trait, value)
104
+ except Exception as e:
105
+ rc = RC(False, str(e))
106
+
107
+ if not rc:
108
+ self.set_tool_tip(rc.error())
109
+ self.style_sheet.update({Ui.BG_COLOR: 'orange'}, _system=True)
110
+ else:
111
+ self.set_tool_tip(self.trait.ui_hint.tip)
112
+ self.style_sheet.restore()
113
+
114
+ # fmt: off
115
+ def _create(self): raise NotImplementedError
116
+ def _set_read_only(self, flag): raise NotImplementedError
117
+ def _value(self): raise NotImplementedError
118
+ def _set_value(self, value): raise NotImplementedError
119
+ # fmt: on
120
+
121
+
122
+ class RefreshContext(ux.Object):
123
+ REFRESH = ux.signal_decl(object)
124
+
125
+ def __init__(self, trait_widget: TraitWidget):
126
+ super().__init__()
127
+ self.trait_widget = trait_widget
128
+ self.REFRESH.connect(TraitWidget.refresh, type=ux.QueuedConnection)
129
+
130
+ def emit_signal(self):
131
+ self.REFRESH.emit(self.trait_widget)
@@ -0,0 +1,200 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from core_10x.exec_control import INTERACTIVE
6
+ from core_10x.global_cache import cache
7
+ from core_10x.py_class import PyClass
8
+ from core_10x.rc import RC_TRUE
9
+ from core_10x.trait import Ui
10
+ from core_10x.traitable import Traitable
11
+
12
+ from ui_10x.trait_editor import TraitEditor
13
+ from ui_10x.traitable_view import TraitableView
14
+ from ui_10x.utils import UxDialog, ux, ux_warning
15
+
16
+ if TYPE_CHECKING:
17
+ from collections.abc import Callable
18
+
19
+ from core_10x.rc import RC
20
+
21
+
22
+ class TraitableEditor:
23
+ """
24
+ For many Traitables, a default Editor is sufficient.
25
+ A custom Editor, if needed, should be in a particular class, module and package.
26
+ Suppose your traitable class is abc.zyz.messages.TextMessage
27
+ - the custom editor class must be TextMessageEditor
28
+ - its module name must be messages_ui
29
+ - the module may be in either abc.xyz, abc.xyz.ui or alternative packages
30
+ - an editor class is searched first in alternative packages, if any; then in abc.xyz and abc.xyz.ui
31
+ - if a custom class is not found, the default TraitableEditor is used.
32
+ """
33
+
34
+ @staticmethod
35
+ @cache
36
+ def find_editor_class(
37
+ traitable_class: type[Traitable], *alternative_packages, traitable_class_parent: type[Traitable] = None, verify_custom_class: bool = True
38
+ ):
39
+ assert issubclass(traitable_class, Traitable), f'{traitable_class} is not a subclass of Traitable'
40
+ assert not traitable_class_parent or issubclass(traitable_class, traitable_class_parent), (
41
+ f'{traitable_class} is not a subclass of {traitable_class_parent}'
42
+ )
43
+
44
+ found = PyClass.find_related_class(traitable_class, 'ui', 'Editor', *alternative_packages, alternative_parent_class=traitable_class_parent)
45
+ if found:
46
+ if verify_custom_class:
47
+ assert issubclass(found, TraitableEditor), (
48
+ f'{traitable_class} has a custom editor class {found} which is not a subclass of TraitableEditor'
49
+ )
50
+
51
+ return found
52
+
53
+ return TraitableEditor
54
+
55
+ # fmt: off
56
+ @staticmethod
57
+ def editor(
58
+ entity: Traitable,
59
+ *alternative_packages, #-- if custom editor class should be looked up somewhere else
60
+ traitable_class_parent = None, #-- if a custom editor of entity's parent class could be used if it's missing for the entity.__class__
61
+ verify_custom_class: bool = True, #-- if a custom editor class should be verified if exists
62
+ view: TraitableView = None, #-- if a specific view for the entity should be used
63
+ read_only: bool = False #-- browser if True
64
+ ) -> TraitableEditor:
65
+ # fmt: on
66
+ editor_class = TraitableEditor.find_editor_class(
67
+ entity.__class__,
68
+ *alternative_packages,
69
+ traitable_class_parent = traitable_class_parent,
70
+ verify_custom_class = verify_custom_class
71
+ )
72
+ return editor_class(entity, view = view, read_only = read_only, _confirm = True)
73
+
74
+ def __init__(self, entity: Traitable, view: TraitableView = None, read_only = False, _confirm = False):
75
+ assert _confirm, 'Do not call TraitableEditor() directly, use TraitableEditor.editor() instead'
76
+ assert isinstance(entity, Traitable), 'entity must be an instance of Traitable'
77
+
78
+ entity_class = entity.__class__
79
+ if view is None:
80
+ view = TraitableView.default(entity_class, read_only = read_only)
81
+ else:
82
+ assert issubclass(entity_class, view.cls), f'Given view is not for {entity_class}'
83
+
84
+ self.entity = entity
85
+ self.traitable_processor = None
86
+
87
+ trait_dir = entity_class.s_dir
88
+ self.trait_hints = trait_hints = {trait_dir[trait_name]: ui_hint for trait_name, ui_hint in view.ui_hints.items() if not ui_hint.flags_on(Ui.HIDDEN)}
89
+
90
+ self.main_w: ux.Widget|None = None
91
+ self.callbacks_for_traits = {}
92
+ self.init()
93
+
94
+ callbacks = self.callbacks_for_traits
95
+ self.trait_editors = {
96
+ trait.name: TraitEditor(entity, trait, ui_hint, custom_callback = callbacks.get(trait.name), traitable_processor = lambda : self.traitable_processor)
97
+ for trait, ui_hint in trait_hints.items()
98
+ }
99
+
100
+ def init(self):
101
+ ...
102
+
103
+ def set_callback_for_trait(self, trait_name: str, bound_method):
104
+ self.callbacks_for_traits[trait_name] = bound_method
105
+
106
+ def callback_for_trait(self, trait_name: str):
107
+ return self.callbacks_for_traits.get(trait_name)
108
+
109
+ def main_layout(self) -> ux.Layout:
110
+ lay = ux.FormLayout()
111
+
112
+ te: TraitEditor
113
+ for te in self.trait_editors.values():
114
+ label = te.new_label()
115
+ w = te.new_widget()
116
+ lay.add_row(label, w)
117
+ if te.ui_hint.flags_on(Ui.SEPARATOR):
118
+ lay.add_row(ux.separator())
119
+
120
+ return lay
121
+
122
+ def row_layout(self) -> ux.Layout:
123
+ row = ux.HBoxLayout()
124
+
125
+ stretched_trait_found = False
126
+ for te in self.trait_editors.values():
127
+ stretch = te.ui_hint.param('stretch', 0)
128
+ if stretch:
129
+ stretched_trait_found = True
130
+
131
+ row.add_widget(te.new_label())
132
+ row.add_widget(te.new_widget(), stretch=stretch)
133
+
134
+ if te.ui_hint.flags_on(Ui.SEPARATOR):
135
+ row.add_widget(ux.separator(horizontal = False))
136
+
137
+ if stretched_trait_found:
138
+ row.add_widget(ux.Label(), stretch = 1)
139
+
140
+ return row
141
+
142
+ def main_widget(self) -> ux.Widget:
143
+ self.main_w = w = ux.Widget()
144
+ lay = self.main_layout()
145
+ w.set_layout(lay)
146
+ return w
147
+
148
+ def _cleanup_tp(self, apply: bool):
149
+ self.main_w = None
150
+ if self.traitable_processor:
151
+ if apply:
152
+ self.traitable_processor.export_nodes()
153
+ self.traitable_processor=None
154
+
155
+ def _dialog(self, layout: ux.Layout, title: str, ok: str, min_width: int, on_accept: Callable[[], RC]) -> UxDialog:
156
+ ux.init()
157
+ if layout is not None:
158
+ w = self.main_w = ux.Widget()
159
+ w.set_layout(layout)
160
+ else:
161
+ w = self.main_widget()
162
+
163
+ def accept_callback():
164
+ self._cleanup_tp(True)
165
+ rc = self.entity.verify()
166
+ if rc:
167
+ return on_accept()
168
+ return rc
169
+
170
+ def cancel_callback():
171
+ self._cleanup_tp(False)
172
+
173
+ return UxDialog(w, title = title, accept_callback = accept_callback, cancel_callback = cancel_callback, ok = ok, min_width = min_width)
174
+
175
+ def dialog(self, layout: ux.Layout = None, copy_entity: bool = True, title: str = '', save: bool = False, accept_hook: Callable[[RC],None] = None, min_width: int = 0) -> UxDialog:
176
+ if title is None:
177
+ title = ''
178
+ elif not title:
179
+ title = self.entity.__class__.__name__
180
+
181
+ ok = 'Save' if save else 'Ok'
182
+
183
+ def on_accept():
184
+ rc = self.entity.save() if save else RC_TRUE
185
+ if not rc:
186
+ self.warning(rc.error())
187
+ if accept_hook:
188
+ accept_hook(rc)
189
+ return rc
190
+
191
+ if copy_entity:
192
+ self.traitable_processor = INTERACTIVE()
193
+
194
+ return self._dialog(layout, title, ok, min_width, on_accept=on_accept)
195
+
196
+ def popup(self, layout: ux.Layout = None, copy_entity = True, title: str = '', save: bool = False, accept_hook: Callable[[RC],None] = None, min_width: int = 0) -> None:
197
+ self.dialog(layout = layout, copy_entity = copy_entity, title = title, save = save, accept_hook = accept_hook, min_width = min_width).exec()
198
+
199
+ def warning(self, msg: str, title: str = ''):
200
+ ux_warning(msg, parent = self.main_w, title = title, on_close=lambda ctx: None)
@@ -0,0 +1,131 @@
1
+ from __future__ import annotations
2
+
3
+ import copy
4
+ import inspect
5
+ from typing import TYPE_CHECKING
6
+
7
+ from core_10x.global_cache import cache
8
+ from core_10x.trait import T
9
+ from core_10x.traitable import Traitable
10
+ from core_10x.ui_hint import Ui, UiMod
11
+
12
+ if TYPE_CHECKING:
13
+ from core_10x.trait import Trait
14
+
15
+
16
+ class TraitableView:
17
+ """
18
+ To create an instance of TraitableView you need to pass the following args:
19
+ 1) entity_or_class: a subclass of Entity or an instance of it
20
+ 2) _header_data: a dictionary with each item as follows:
21
+ either
22
+ <trait-name>: <label> (if label is empty, the trait's label will be used)
23
+ or
24
+ <group-label>: <header> (_header like dict)
25
+ 3) named_traits_to_use: (kwargs) with each item as follows:
26
+ <trait-name>: Ui(...)
27
+
28
+ Such named_traits specify either traits to use (slice) or the ones to modify (modify)
29
+ """
30
+
31
+ @classmethod
32
+ def _check(cls, hint_mod, trait_name: str) -> bool:
33
+ assert isinstance(hint_mod, UiMod), f'{trait_name} = {hint_mod} - UiMod is expected'
34
+ return True
35
+
36
+ @classmethod
37
+ def suitable_record(cls, trait: Trait = None, trait_dir: dict = None, trait_name: str = None, _skip_reserved: bool = True) -> Trait | None:
38
+ if not trait:
39
+ assert trait_dir and trait_name, 'trait is None, so both trait_dir and trait_name must be provided'
40
+ trait = trait_dir.get(trait_name)
41
+ if not trait:
42
+ return None
43
+
44
+ flags = T.HIDDEN | T.RESERVED if _skip_reserved else T.HIDDEN
45
+ return None if trait.flags_on(flags) or trait.getter_params or trait.s_ui_hint.widget_type == Ui.WIDGET_TYPE.NONE else trait
46
+
47
+ @classmethod
48
+ def slice(cls, traitable_class, _header_data: dict = None, **named_ui_hint_changes) -> TraitableView:
49
+ assert inspect.isclass(traitable_class) and issubclass(traitable_class, Traitable), f'{traitable_class} is not a Traitable'
50
+
51
+ trait_dir = traitable_class.s_dir
52
+ trait: Trait
53
+ ui_hints = {
54
+ name: hint_change.apply(trait.ui_hint) if cls._check(hint_change, trait.name) else None
55
+ for name, hint_change in named_ui_hint_changes.items()
56
+ if (trait := cls.suitable_record(trait_dir=trait_dir, trait_name=name))
57
+ }
58
+ return TraitableView(traitable_class, ui_hints, _header_data)
59
+
60
+ @classmethod
61
+ def modify(
62
+ cls, traitable_class: type[Traitable], _header_data: dict = None, _skip_reserved: bool = True, **named_ui_hint_changes
63
+ ) -> TraitableView:
64
+ assert inspect.isclass(traitable_class) and issubclass(traitable_class, Traitable), f'{traitable_class} is not a Traitable'
65
+
66
+ ui_hints = {
67
+ trait_name: trait.ui_hint
68
+ for trait_name, trait in traitable_class.s_dir.items()
69
+ if TraitableView.suitable_record(trait=trait, _skip_reserved=_skip_reserved)
70
+ }
71
+
72
+ for name, hint_change in named_ui_hint_changes.items():
73
+ cls._check(hint_change, name)
74
+ hint = ui_hints.get(name)
75
+ if hint is not None:
76
+ ui_hints[name] = hint_change.apply(hint)
77
+
78
+ return TraitableView(traitable_class, ui_hints, _header_data)
79
+
80
+ @staticmethod
81
+ @cache
82
+ def default(traitable_class: type[Traitable], read_only: bool = False) -> TraitableView:
83
+ return TraitableView.modify(traitable_class).make_read_only(read_only, clone=True)
84
+
85
+ def make_read_only(self, flag: bool, clone=False) -> TraitableView:
86
+ view = self if not clone else copy.deepcopy(self)
87
+
88
+ to_set = Ui.READ_ONLY if flag else 0x0
89
+ to_reset = 0x0 if flag else Ui.READ_ONLY
90
+ for ui_hint in view.ui_hints.values():
91
+ ui_hint.set_reset_flags(to_set, to_reset)
92
+ return view
93
+
94
+ def __init__(self, traitable_class, ui_hints: dict, header_data: dict):
95
+ self.cls = traitable_class
96
+ self.ui_hints = ui_hints
97
+ self.header = self.create_header(ui_hints, header_data)
98
+
99
+ def all_hints(self) -> dict:
100
+ return self.ui_hints
101
+
102
+ def ui_hint(self, trait_name: str) -> Ui:
103
+ return self.ui_hints[trait_name]
104
+
105
+ @staticmethod
106
+ def _process_tree(ui_hints: dict, tree: dict) -> dict:
107
+ header = {}
108
+ for subtree_name, subtree in tree.items():
109
+ if isinstance(subtree, str): # -- must be a trait name
110
+ label = subtree
111
+ hint = ui_hints.get(subtree_name)
112
+ if not hint: # -- ignore an unknown or unused trait
113
+ continue
114
+ if not label: # -- no label overwrite
115
+ label = hint.label
116
+ header[subtree_name] = label
117
+
118
+ elif isinstance(subtree, dict): # -- must be a real subtree
119
+ header[subtree_name] = TraitableView._process_tree(ui_hints, subtree)
120
+
121
+ else:
122
+ continue # -- ignore anything else
123
+
124
+ return header
125
+
126
+ @staticmethod
127
+ def create_header(ui_hints: dict, header_data: dict) -> dict:
128
+ if not header_data:
129
+ return {trait_name: ui.label for trait_name, ui in ui_hints.items()}
130
+
131
+ return TraitableView._process_tree(ui_hints, header_data)
@@ -0,0 +1,8 @@
1
+ import pytest
2
+
3
+
4
+ @pytest.fixture(autouse=True)
5
+ def setup_ui_platform(monkeypatch):
6
+ monkeypatch.setenv('UI_PLATFORM', 'Unknown')
7
+ yield
8
+ monkeypatch.undo()
@@ -0,0 +1,9 @@
1
+ import sys
2
+
3
+ import pytest
4
+
5
+
6
+ def test_platform_import_raises(monkeypatch):
7
+ monkeypatch.delitem(sys.modules, 'ui_10x.platform', raising=False)
8
+ with pytest.raises(ImportError): # raises due to incorrect UI_PLATFORM from conftest
9
+ import ui_10x.platform