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/xxcalendar.py
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections import deque
|
|
4
|
+
from datetime import date, timedelta
|
|
5
|
+
|
|
6
|
+
from core_10x.traitable import RT, T, Traitable
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CalendarNameParser:
|
|
10
|
+
"""
|
|
11
|
+
calendar_name := just_name | operation_repr_list
|
|
12
|
+
operation_repr_list := operation_repr | operation_repr OP_CHAR operation_repr_list
|
|
13
|
+
operation_repr := op num_args | name_list op num_args
|
|
14
|
+
name_list := just_name MORE_CHAR just_name MORE_CHAR | just_name MORE_CHAR name_list
|
|
15
|
+
op := OR_CHAR | AND_CHAR
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
# fmt: off
|
|
19
|
+
MORE_CHAR = ','
|
|
20
|
+
OR_CHAR = '|'
|
|
21
|
+
AND_CHAR = '&'
|
|
22
|
+
OP_CHAR = '\n'
|
|
23
|
+
|
|
24
|
+
s_ops = {
|
|
25
|
+
OR_CHAR: set.update,
|
|
26
|
+
AND_CHAR: set.intersection_update
|
|
27
|
+
}
|
|
28
|
+
# fmt: on
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def combo_name(cls, name: str) -> bool:
|
|
32
|
+
return any(sym in name for sym in cls.s_ops.keys())
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def operation_repr(cls, calendar_cls, op_char: str, *calendars) -> str:
|
|
36
|
+
assert len(calendars) > 1, 'there must be at least 2 calendars'
|
|
37
|
+
assert op_char in cls.s_ops, f'Unknown op char = {op_char}'
|
|
38
|
+
|
|
39
|
+
cal_names = []
|
|
40
|
+
for cal_or_name in calendars:
|
|
41
|
+
if isinstance(cal_or_name, calendar_cls):
|
|
42
|
+
cname = cal_or_name.name
|
|
43
|
+
elif isinstance(cal_or_name, str):
|
|
44
|
+
cal = calendar_cls.existing_instance(name=cal_or_name)
|
|
45
|
+
assert cal, f"Unknown calendar '{cal_or_name}'"
|
|
46
|
+
cname = cal_or_name
|
|
47
|
+
else:
|
|
48
|
+
raise NameError(f"Invalid calendar/name '{cal_or_name}'")
|
|
49
|
+
|
|
50
|
+
cal_names.append(cname)
|
|
51
|
+
|
|
52
|
+
return f'{cls.MORE_CHAR.join(sorted(cal_names))}{cls.MORE_CHAR}{op_char}{len(cal_names)}{cls.OP_CHAR}'
|
|
53
|
+
|
|
54
|
+
def __init__(self):
|
|
55
|
+
self.stack = deque()
|
|
56
|
+
self.non_working_days = set()
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def parse(cls, calendar_cls, name: str) -> set:
|
|
60
|
+
parser = cls()
|
|
61
|
+
non_working_days = parser.non_working_days
|
|
62
|
+
stack = parser.stack
|
|
63
|
+
|
|
64
|
+
operation_repr_list = name.split(cls.OP_CHAR)
|
|
65
|
+
if len(operation_repr_list) == 1: # -- regular (stored) calendar
|
|
66
|
+
cal = calendar_cls.existing_instance(name=name)
|
|
67
|
+
return cal._non_working_days
|
|
68
|
+
|
|
69
|
+
for operation_repr in operation_repr_list:
|
|
70
|
+
if not operation_repr:
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
name_list_op_num_args = operation_repr.split(cls.MORE_CHAR)
|
|
74
|
+
if len(name_list_op_num_args) > 1: # -- name list followed by op with num_args
|
|
75
|
+
for cname in name_list_op_num_args[:-1]:
|
|
76
|
+
if cname:
|
|
77
|
+
cal = calendar_cls.existing_instance(name=cname)
|
|
78
|
+
assert cal, f"Unknown calendar '{cname}"
|
|
79
|
+
stack.append(cal._non_working_days)
|
|
80
|
+
|
|
81
|
+
op_with_num_args = name_list_op_num_args[-1]
|
|
82
|
+
op_char = op_with_num_args[0]
|
|
83
|
+
op = cls.s_ops.get(op_char)
|
|
84
|
+
assert op, f'Unknown op char {op_char}'
|
|
85
|
+
try:
|
|
86
|
+
num_args = int(op_with_num_args[1:])
|
|
87
|
+
except Exception as e:
|
|
88
|
+
raise RuntimeError(f'Invalid num_args = {op_with_num_args[1:]}') from e
|
|
89
|
+
|
|
90
|
+
for _ in range(num_args):
|
|
91
|
+
_non_working_days = stack.pop()
|
|
92
|
+
assert isinstance(_non_working_days, set)
|
|
93
|
+
|
|
94
|
+
if not non_working_days:
|
|
95
|
+
non_working_days.update(_non_working_days)
|
|
96
|
+
else:
|
|
97
|
+
op(non_working_days, _non_working_days)
|
|
98
|
+
|
|
99
|
+
stack.append(non_working_days) # -- push set of non_working_days of an intermediate calendar
|
|
100
|
+
|
|
101
|
+
return non_working_days
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class CalendarAdjustment(Traitable):
|
|
105
|
+
# fmt: off
|
|
106
|
+
name: str = T(T.ID)
|
|
107
|
+
add_days: list = T()
|
|
108
|
+
remove_days: list = T()
|
|
109
|
+
# fmt: on
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# -- TODO: _keep_history=True, _default_cache = True:
|
|
113
|
+
class Calendar(Traitable):
|
|
114
|
+
# fmt: off
|
|
115
|
+
name: str = T(T.ID)
|
|
116
|
+
adjusted_for: str = T(T.ID, default = '') // 'Name of a specific adjustment to this calendar, if any'
|
|
117
|
+
description: str = T(T.NOT_EMPTY) // 'Calendar Description'
|
|
118
|
+
non_working_days: list = T() // 'Non-Working Days'
|
|
119
|
+
|
|
120
|
+
_non_working_days: set = RT()
|
|
121
|
+
# fmt: on
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def AND(cls, *calendars) -> Calendar: # noqa: N802
|
|
125
|
+
if not calendars:
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
name = CalendarNameParser.operation_repr(cls, CalendarNameParser.AND_CHAR, *calendars)
|
|
129
|
+
return cls(name=name)
|
|
130
|
+
|
|
131
|
+
intersection = AND
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
def OR(cls, *calendars) -> Calendar: # noqa: N802
|
|
135
|
+
if not calendars:
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
name = CalendarNameParser.operation_repr(cls, CalendarNameParser.OR_CHAR, *calendars)
|
|
139
|
+
return cls(name=name)
|
|
140
|
+
|
|
141
|
+
@classmethod
|
|
142
|
+
def union(cls, *calendars) -> Calendar:
|
|
143
|
+
if len(calendars) > 1:
|
|
144
|
+
return cls.OR(*calendars)
|
|
145
|
+
assert calendars, 'At least one calendar is required for union()'
|
|
146
|
+
return cls(name=calendars[0])
|
|
147
|
+
|
|
148
|
+
def non_working_days_get(self) -> list:
|
|
149
|
+
non_working_days = CalendarNameParser.parse(self.__class__, self.name)
|
|
150
|
+
adjusted_for = self.adjusted_for
|
|
151
|
+
if adjusted_for:
|
|
152
|
+
ca = CalendarAdjustment.existing_instance_by_id(_id_value=adjusted_for, _throw=False)
|
|
153
|
+
if ca:
|
|
154
|
+
self.add_days(non_working_days, *ca.add_days)
|
|
155
|
+
self.remove_days(non_working_days, *ca.remove_days)
|
|
156
|
+
|
|
157
|
+
return sorted(non_working_days) if non_working_days else []
|
|
158
|
+
|
|
159
|
+
def _non_working_days_get(self) -> set:
|
|
160
|
+
return set(self.non_working_days)
|
|
161
|
+
|
|
162
|
+
@classmethod
|
|
163
|
+
def add_days(cls, days: set, *days_to_add) -> bool:
|
|
164
|
+
if not days_to_add:
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
all_dates = all(type(hd) is date for hd in days_to_add)
|
|
168
|
+
if not all_dates:
|
|
169
|
+
raise TypeError('Every day to add must be a date')
|
|
170
|
+
|
|
171
|
+
ndays = len(days)
|
|
172
|
+
days.update(days_to_add)
|
|
173
|
+
return len(days) > ndays
|
|
174
|
+
|
|
175
|
+
@classmethod
|
|
176
|
+
def remove_days(cls, days: set, *days_to_remove) -> bool:
|
|
177
|
+
if not days_to_remove:
|
|
178
|
+
return False
|
|
179
|
+
|
|
180
|
+
all_dates = all(type(hd) is date for hd in days_to_remove)
|
|
181
|
+
if not all_dates:
|
|
182
|
+
raise TypeError('Every day to remove must be a date')
|
|
183
|
+
|
|
184
|
+
ndays = len(days)
|
|
185
|
+
days.difference_update(days_to_remove)
|
|
186
|
+
return len(days) < ndays
|
|
187
|
+
|
|
188
|
+
def add_non_working_days(self, *days_to_add):
|
|
189
|
+
non_working_days = set(self.non_working_days)
|
|
190
|
+
if self.add_days(non_working_days, *days_to_add):
|
|
191
|
+
self.non_working_days = list(non_working_days)
|
|
192
|
+
|
|
193
|
+
def remove_non_working_days(self, *days_to_remove):
|
|
194
|
+
non_working_days = set(self.non_working_days)
|
|
195
|
+
if self.remove_days(non_working_days, *days_to_remove):
|
|
196
|
+
self.non_working_days = list(non_working_days)
|
|
197
|
+
|
|
198
|
+
def is_bizday(self, d: date) -> bool:
|
|
199
|
+
return d not in self._non_working_days
|
|
200
|
+
|
|
201
|
+
def next_bizday(self, d: date) -> date:
|
|
202
|
+
dt = timedelta(days=1)
|
|
203
|
+
d += dt
|
|
204
|
+
while not self.is_bizday(d):
|
|
205
|
+
d += dt
|
|
206
|
+
|
|
207
|
+
return d
|
|
208
|
+
|
|
209
|
+
def prev_bizday(self, d: date) -> date:
|
|
210
|
+
dt = timedelta(days=-1)
|
|
211
|
+
d += dt
|
|
212
|
+
while not self.is_bizday(d):
|
|
213
|
+
d += dt
|
|
214
|
+
|
|
215
|
+
return d # -- TODO: if the cal has ALL days as holidays... :-)
|
|
216
|
+
|
|
217
|
+
def advance_bizdays(self, d: date, biz_days: int) -> date:
|
|
218
|
+
if not biz_days:
|
|
219
|
+
return d
|
|
220
|
+
|
|
221
|
+
if biz_days > 0:
|
|
222
|
+
for _ in range(biz_days):
|
|
223
|
+
d = self.next_bizday(d)
|
|
224
|
+
else:
|
|
225
|
+
for _ in range(-biz_days):
|
|
226
|
+
d = self.prev_bizday(d)
|
|
227
|
+
|
|
228
|
+
return d
|
infra_10x/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
|
|
3
|
+
from core_10x.code_samples.person import Person
|
|
4
|
+
from core_10x.exec_control import GRAPH_ON
|
|
5
|
+
|
|
6
|
+
from infra_10x.mongodb_store import MongoStore
|
|
7
|
+
|
|
8
|
+
if __name__ == '__main__':
|
|
9
|
+
with MongoStore.instance(hostname='localhost', dbname='test', username='', password=''):
|
|
10
|
+
p = Person(first_name='Ilya', last_name='Pevzner')
|
|
11
|
+
p.dob = date(1971, 7, 1)
|
|
12
|
+
p.save()
|
|
13
|
+
p.dob = date(1971, 7, 3)
|
|
14
|
+
assert p.dob == date(1971, 7, 3)
|
|
15
|
+
with GRAPH_ON():
|
|
16
|
+
assert p.dob == date(1971, 7, 3) # FIX: fails here!
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
if __name__ == '__main__':
|
|
2
|
+
from infra_10x.mongodb_store import MongoCollectionHelper, MongoStore
|
|
3
|
+
|
|
4
|
+
store = MongoStore.instance('localhost', 'test', '', '')
|
|
5
|
+
|
|
6
|
+
id_value = 'AAAA'
|
|
7
|
+
rev = 10
|
|
8
|
+
|
|
9
|
+
serialized_traitable = dict(_id=id_value, _rev=rev, name='test', age=60)
|
|
10
|
+
|
|
11
|
+
coll = store.collection('test')
|
|
12
|
+
|
|
13
|
+
data1 = dict(serialized_traitable)
|
|
14
|
+
pipeline1 = []
|
|
15
|
+
filter1 = {}
|
|
16
|
+
coll.filter_and_pipeline('_id', '_rev', id_value, rev, data1, filter1, pipeline1)
|
|
17
|
+
|
|
18
|
+
data2 = dict(serialized_traitable)
|
|
19
|
+
pipeline2 = []
|
|
20
|
+
filter2 = {}
|
|
21
|
+
|
|
22
|
+
MongoCollectionHelper.prepare_filter_and_pipeline(data2, filter2, pipeline2)
|
|
23
|
+
|
|
24
|
+
assert filter1 == filter2
|
|
25
|
+
assert pipeline1 == pipeline2
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from infra_10x.mongodb_store import MongoStore
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MongodbAdmin:
|
|
5
|
+
def __init__(self, hostname: str, username: str, password: str):
|
|
6
|
+
self.client = MongoStore.connect(hostname=hostname, username=username, password=password, _cache=False)
|
|
7
|
+
self.db = self.client[MongoStore.ADMIN]
|
|
8
|
+
self.hostname = hostname
|
|
9
|
+
self.username = username
|
|
10
|
+
|
|
11
|
+
def user_exists(self, username: str) -> bool:
|
|
12
|
+
res = self.db.command('usersInfo', {'user': username, 'db': MongoStore.ADMIN})
|
|
13
|
+
return bool(res.get('users'))
|
|
14
|
+
|
|
15
|
+
def update_user(self, username: str, password: str, *role_names, keep_current_roles=True):
|
|
16
|
+
create = not self.user_exists(username)
|
|
17
|
+
if create:
|
|
18
|
+
if not password:
|
|
19
|
+
raise RuntimeError(f'User {username} does not exist and requires a non-empty password')
|
|
20
|
+
|
|
21
|
+
cmd = dict(createUser=username, pwd=password, roles=[*role_names])
|
|
22
|
+
|
|
23
|
+
else:
|
|
24
|
+
if not role_names and keep_current_roles:
|
|
25
|
+
role_names = self.user_role_names(username)
|
|
26
|
+
|
|
27
|
+
cmd = dict(updateUser=username)
|
|
28
|
+
if role_names:
|
|
29
|
+
cmd.update(roles=[*role_names])
|
|
30
|
+
if password:
|
|
31
|
+
cmd.update(pwd=password)
|
|
32
|
+
|
|
33
|
+
self.db.command(cmd)
|
|
34
|
+
|
|
35
|
+
def change_own_password(self, password: str):
|
|
36
|
+
self.db.command(dict(updateUser=self.username, pwd=password))
|
|
37
|
+
self.client = MongoStore.connect(hostname=self.hostname, username=self.username, password=password, _cache=False)
|
|
38
|
+
self.db = self.client[MongoStore.ADMIN]
|
|
39
|
+
|
|
40
|
+
# # Discover which user/db you're authenticated as (handy to avoid hardcoding)
|
|
41
|
+
# status = client.admin.command("connectionStatus", showPrivileges = False)
|
|
42
|
+
# auth = status[ "authInfo" ][ "authenticatedUsers" ]
|
|
43
|
+
# if not auth:
|
|
44
|
+
# raise RuntimeError("Not authenticated. Connect with your current username/password first.")
|
|
45
|
+
#
|
|
46
|
+
# username = auth[0]['user']
|
|
47
|
+
# user_db = auth[0]['db']
|
|
48
|
+
#
|
|
49
|
+
# # Change your password (roles remain unchanged because we don't pass 'roles')
|
|
50
|
+
# client[user_db].command("updateUser", username, pwd = "<NEW_STRONG_PASSWORD>")
|
|
51
|
+
#
|
|
52
|
+
# # Reconnect with the new password (recommended)
|
|
53
|
+
# client = MongoClient(f"mongodb://{username}:<NEW_STRONG_PASSWORD>@localhost:27017/?authSource={user_db}")
|
|
54
|
+
# client.admin.command("ping")
|
|
55
|
+
# print("Password changed and re-authenticated successfully.")
|
|
56
|
+
|
|
57
|
+
def update_role(self, role_name: str, **permissions_per_db):
|
|
58
|
+
"""
|
|
59
|
+
:param role_name: e.g., 'VISITOR'
|
|
60
|
+
:param permissions_per_db: a dict: each key is either a db name or db_name/collection_name, value is PERMISSION, e.g.
|
|
61
|
+
{ 'catalog': PERMISSION.READ_ONLY, 'logs/errors': PERMISSION.READ_WRITE }
|
|
62
|
+
:return:
|
|
63
|
+
"""
|
|
64
|
+
privileges = [
|
|
65
|
+
dict(resource=dict(db=resource_parts[0], collection=resource_parts[1] if len(resource_parts) == 2 else ''), actions=permission)
|
|
66
|
+
for db_name, permission in permissions_per_db.items()
|
|
67
|
+
if (resource_parts := db_name.split('/'))
|
|
68
|
+
]
|
|
69
|
+
method = 'createRole' if not self.role_exists(role_name) else 'updateRole'
|
|
70
|
+
self.db.command(method, role_name, privileges=privileges, roles=[])
|
|
71
|
+
|
|
72
|
+
def all_user_names(self) -> list:
|
|
73
|
+
listing = self.db.command('usersInfo')
|
|
74
|
+
return [doc['user'] for doc in listing['users']] if listing['ok'] else []
|
|
75
|
+
|
|
76
|
+
def all_role_names(self) -> list:
|
|
77
|
+
listing = self.db.command('rolesInfo')
|
|
78
|
+
return [doc['role'] for doc in listing['roles']] if listing['ok'] else []
|
|
79
|
+
|
|
80
|
+
def role_exists(self, role_name: str) -> bool:
|
|
81
|
+
res = self.db.command('rolesInfo', dict(role=role_name, db=MongoStore.ADMIN))
|
|
82
|
+
return bool(res['roles'])
|
|
83
|
+
|
|
84
|
+
def user_role_names(self, username: str) -> list:
|
|
85
|
+
res = self.db.command(dict(usersInfo=username))
|
|
86
|
+
if not res:
|
|
87
|
+
return []
|
|
88
|
+
|
|
89
|
+
return [r['role'] for r in res['users'][0]['roles']]
|
|
90
|
+
|
|
91
|
+
def delete_all_roles(self):
|
|
92
|
+
db = self.db
|
|
93
|
+
for role_name in self.all_role_names():
|
|
94
|
+
db.command('dropRole', role_name)
|
|
95
|
+
|
|
96
|
+
def delete_role(self, role_name: str):
|
|
97
|
+
if self.role_exists(role_name):
|
|
98
|
+
self.db.command('dropRole', role_name)
|
|
99
|
+
|
|
100
|
+
def delete_all_users(self):
|
|
101
|
+
db = self.db
|
|
102
|
+
me = self.username
|
|
103
|
+
for username in self.all_user_names():
|
|
104
|
+
if username != me:
|
|
105
|
+
db.command('dropUser', username)
|
|
106
|
+
|
|
107
|
+
def delete_user(self, username: str):
|
|
108
|
+
me = self.username
|
|
109
|
+
if me != username:
|
|
110
|
+
if self.user_exists(username):
|
|
111
|
+
self.db.command('dropUser', username)
|