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
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from cryptography.hazmat.backends import default_backend
|
|
6
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
|
7
|
+
from cryptography.hazmat.primitives.asymmetric import padding, rsa
|
|
8
|
+
from cryptography.hazmat.primitives.serialization import (
|
|
9
|
+
load_pem_private_key,
|
|
10
|
+
load_pem_public_key,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from core_10x.global_cache import cache
|
|
14
|
+
|
|
15
|
+
# fmt: off
|
|
16
|
+
ROOT = '.xx'
|
|
17
|
+
PUBLIC_EXP = 65537
|
|
18
|
+
KEY_SIZE = 2048
|
|
19
|
+
PRIVATE_KEY_FILE = 'private.pem'
|
|
20
|
+
PUBLIC_KEY_FILE = 'public.pem'
|
|
21
|
+
ENCODING = 'utf-8'
|
|
22
|
+
# fmt: on
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SecKeys:
|
|
26
|
+
@classmethod
|
|
27
|
+
@cache
|
|
28
|
+
def home_dir(cls) -> str:
|
|
29
|
+
dir = f'{os.path.expanduser("~")}/{ROOT}'
|
|
30
|
+
if os.path.exists(dir):
|
|
31
|
+
assert os.path.isdir(dir), f'{dir} is not a directory'
|
|
32
|
+
else:
|
|
33
|
+
os.mkdir(dir)
|
|
34
|
+
|
|
35
|
+
return dir
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def generate_keys(cls, pwd=None) -> tuple:
|
|
39
|
+
private_key = rsa.generate_private_key(public_exponent=PUBLIC_EXP, key_size=KEY_SIZE, backend=default_backend())
|
|
40
|
+
public_key = private_key.public_key()
|
|
41
|
+
|
|
42
|
+
if pwd:
|
|
43
|
+
format = serialization.PrivateFormat.PKCS8
|
|
44
|
+
algo = serialization.BestAvailableEncryption(bytes(pwd, encoding=ENCODING))
|
|
45
|
+
else:
|
|
46
|
+
format = serialization.PrivateFormat.TraditionalOpenSSL
|
|
47
|
+
algo = serialization.NoEncryption()
|
|
48
|
+
|
|
49
|
+
private_key_pem = private_key.private_bytes(encoding=serialization.Encoding.PEM, format=format, encryption_algorithm=algo)
|
|
50
|
+
public_key_pem = public_key.public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo)
|
|
51
|
+
|
|
52
|
+
return (private_key_pem, public_key_pem)
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def create_keys(cls, keys: tuple = None) -> tuple:
|
|
56
|
+
private_key, public_key = keys if keys else cls.generate_keys()
|
|
57
|
+
home_dir = cls.home_dir()
|
|
58
|
+
try:
|
|
59
|
+
with open(f'{home_dir}/{PRIVATE_KEY_FILE}', 'wb') as f:
|
|
60
|
+
f.write(private_key)
|
|
61
|
+
os.chmod(f.name, 0o600)
|
|
62
|
+
|
|
63
|
+
with open(f'{home_dir}/{PUBLIC_KEY_FILE}', 'wb') as f:
|
|
64
|
+
f.write(public_key)
|
|
65
|
+
os.chmod(f.name, 0o600)
|
|
66
|
+
|
|
67
|
+
cls.keys.clear() # -- cls.keys() will then read from the files
|
|
68
|
+
return (private_key, public_key)
|
|
69
|
+
|
|
70
|
+
except Exception:
|
|
71
|
+
return (None, None)
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
@cache
|
|
75
|
+
def keys(cls) -> tuple:
|
|
76
|
+
home_dir = cls.home_dir()
|
|
77
|
+
file = f'{home_dir}/{PRIVATE_KEY_FILE}'
|
|
78
|
+
if os.path.exists(file):
|
|
79
|
+
try:
|
|
80
|
+
with open(file, 'rb') as f:
|
|
81
|
+
private_key = f.read()
|
|
82
|
+
|
|
83
|
+
with open(f'{home_dir}/{PUBLIC_KEY_FILE}', 'rb') as f:
|
|
84
|
+
public_key = f.read()
|
|
85
|
+
|
|
86
|
+
return (private_key, public_key)
|
|
87
|
+
|
|
88
|
+
except Exception:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
return (None, None)
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def encrypt(cls, message, public_key: bytes = None) -> bytes | None:
|
|
95
|
+
if type(message) is str:
|
|
96
|
+
message = bytes(message, encoding=ENCODING)
|
|
97
|
+
|
|
98
|
+
if public_key is None:
|
|
99
|
+
_, public_key = cls.keys()
|
|
100
|
+
if public_key is None:
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
public_key = load_pem_public_key(public_key)
|
|
104
|
+
# fmt: off
|
|
105
|
+
return public_key.encrypt(
|
|
106
|
+
message,
|
|
107
|
+
padding.OAEP(
|
|
108
|
+
mgf = padding.MGF1(algorithm = hashes.SHA256()),
|
|
109
|
+
algorithm = hashes.SHA256(),
|
|
110
|
+
label = None
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
# fmt: on
|
|
114
|
+
|
|
115
|
+
@classmethod
|
|
116
|
+
def decrypt(cls, encrypted_message: bytes, to_str=True):
|
|
117
|
+
private_key, _ = cls.keys()
|
|
118
|
+
if private_key is None:
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
private_key = load_pem_private_key(private_key, password=None)
|
|
122
|
+
# fmt: off
|
|
123
|
+
res = private_key.decrypt(
|
|
124
|
+
encrypted_message,
|
|
125
|
+
padding.OAEP(
|
|
126
|
+
mgf = padding.MGF1(algorithm = hashes.SHA256()),
|
|
127
|
+
algorithm = hashes.SHA256(),
|
|
128
|
+
label = None
|
|
129
|
+
)
|
|
130
|
+
)
|
|
131
|
+
# fmt: on
|
|
132
|
+
|
|
133
|
+
if to_str:
|
|
134
|
+
res = res.decode(encoding=ENCODING)
|
|
135
|
+
|
|
136
|
+
return res
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
def encrypt_private_key(cls, password) -> bytes | None:
|
|
140
|
+
if type(password) is str:
|
|
141
|
+
password = bytes(password, encoding=ENCODING)
|
|
142
|
+
|
|
143
|
+
private_key, _ = cls.keys()
|
|
144
|
+
if private_key is None:
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
private_key = load_pem_private_key(private_key, password=None)
|
|
148
|
+
# fmt: off
|
|
149
|
+
return private_key.private_bytes(
|
|
150
|
+
encoding = serialization.Encoding.PEM,
|
|
151
|
+
format = serialization.PrivateFormat.PKCS8,
|
|
152
|
+
encryption_algorithm = serialization.BestAvailableEncryption(password),
|
|
153
|
+
)
|
|
154
|
+
# fmt: on
|
|
155
|
+
|
|
156
|
+
@classmethod
|
|
157
|
+
def decrypt_private_key(cls, private_key_with_password, password) -> bytes:
|
|
158
|
+
if type(password) is str:
|
|
159
|
+
password = bytes(password, encoding=ENCODING)
|
|
160
|
+
|
|
161
|
+
pk = load_pem_private_key(private_key_with_password, password=password)
|
|
162
|
+
# fmt: off
|
|
163
|
+
return pk.private_bytes(
|
|
164
|
+
encoding = serialization.Encoding.PEM,
|
|
165
|
+
format = serialization.PrivateFormat.TraditionalOpenSSL,
|
|
166
|
+
encryption_algorithm = serialization.NoEncryption(),
|
|
167
|
+
)
|
|
168
|
+
# fmt: on
|
core_10x/vault/vault.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
from core_10x.xnone import XNone
|
|
4
|
+
from core_10x.resource import Resource
|
|
5
|
+
from core_10x.vault.vault_traitable import VaultTraitable, T, RT
|
|
6
|
+
from core_10x.vault.vault_user import VaultUser
|
|
7
|
+
|
|
8
|
+
class ResourceAccessor(VaultTraitable):
|
|
9
|
+
# fmt: off
|
|
10
|
+
username: str = T(T.ID)
|
|
11
|
+
resource_uri: str = T(T.ID)
|
|
12
|
+
|
|
13
|
+
login: str = T()
|
|
14
|
+
password: bytes = T()
|
|
15
|
+
|
|
16
|
+
last_updated: datetime = T(T.EVAL_ONCE)
|
|
17
|
+
|
|
18
|
+
user: VaultUser = RT(T.EVAL_ONCE)
|
|
19
|
+
resource: Resource = RT(T.EVAL_ONCE)
|
|
20
|
+
# fmt: on
|
|
21
|
+
|
|
22
|
+
def username_get(self) -> str:
|
|
23
|
+
return VaultUser.myname()
|
|
24
|
+
|
|
25
|
+
def last_updated_get(self) -> datetime:
|
|
26
|
+
return datetime.utcnow()
|
|
27
|
+
|
|
28
|
+
def user_get(self) -> VaultUser:
|
|
29
|
+
return VaultUser.existing_instance(user_id = self.username)
|
|
30
|
+
|
|
31
|
+
def resource_get(self) -> Resource:
|
|
32
|
+
return Resource.instance_from_uri(
|
|
33
|
+
self.resource_uri,
|
|
34
|
+
username = self.username,
|
|
35
|
+
password = self.user.sec_keys.decrypt_text(self.password)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
class Vault:
|
|
39
|
+
@classmethod
|
|
40
|
+
def save_resource_accessor(cls, resource_uri: str, password: str, login: str = None, username: str = XNone):
|
|
41
|
+
if login is None:
|
|
42
|
+
login = username
|
|
43
|
+
|
|
44
|
+
ra = ResourceAccessor(username = username, resource_uri = resource_uri)
|
|
45
|
+
user = ra.user
|
|
46
|
+
ra.set_values(
|
|
47
|
+
login = login,
|
|
48
|
+
password = user.sec_keys.encrypt_text(password)
|
|
49
|
+
).throw()
|
|
50
|
+
|
|
51
|
+
ra.save().throw()
|
|
52
|
+
|
|
53
|
+
#-- strictly speaking this method isn't necessary as it just returning existing_instance()
|
|
54
|
+
@classmethod
|
|
55
|
+
def retrieve_resource_accessor(cls, resource_uri: str, username: str = XNone) -> ResourceAccessor:
|
|
56
|
+
return ResourceAccessor.existing_instance(username = username, resource_uri = resource_uri)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import keyring
|
|
2
|
+
|
|
3
|
+
from py10x_core import OsUser
|
|
4
|
+
|
|
5
|
+
from core_10x.traitable import Traitable, T, RT, RC, RC_TRUE, TsStore, cache
|
|
6
|
+
from core_10x.environment_variables import EnvVars
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class VaultTraitable(Traitable):
|
|
10
|
+
@classmethod
|
|
11
|
+
@cache
|
|
12
|
+
def retrieve_master_password(cls) -> str:
|
|
13
|
+
username = OsUser.me.name()
|
|
14
|
+
pwd = keyring.get_password(EnvVars.master_password_key, username)
|
|
15
|
+
if pwd is None:
|
|
16
|
+
raise OSError(f'XX MasterPassword for {username} is not found')
|
|
17
|
+
|
|
18
|
+
return pwd
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def keep_master_password(cls, password: str):
|
|
22
|
+
username = OsUser.me.name()
|
|
23
|
+
keyring.set_password(EnvVars.master_password_key, username, password)
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
@cache
|
|
27
|
+
def retrieve_vault_password(cls, vault_uri: str) -> str:
|
|
28
|
+
username = OsUser.me.name()
|
|
29
|
+
pwd = keyring.get_password(vault_uri, username)
|
|
30
|
+
if pwd is None:
|
|
31
|
+
raise OSError(f'Password for {username} @ {vault_uri} is not found')
|
|
32
|
+
|
|
33
|
+
return pwd
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def keep_vault_password(cls, vault_uri: str, password: str):
|
|
37
|
+
username = OsUser.me.name()
|
|
38
|
+
keyring.set_password(vault_uri, username, password)
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
@cache
|
|
42
|
+
def store_per_class(cls,) -> TsStore:
|
|
43
|
+
uri = EnvVars.assert_var.vault_ts_store_uri
|
|
44
|
+
spec = TsStore.spec_from_uri(uri)
|
|
45
|
+
kwargs = spec.kwargs
|
|
46
|
+
ts_class = spec.resource_class
|
|
47
|
+
hostname = kwargs[ts_class.HOSTNAME_TAG]
|
|
48
|
+
is_running, with_auth = ts_class.is_running_with_auth(hostname)
|
|
49
|
+
if not is_running or not with_auth:
|
|
50
|
+
raise OSError(f'Vault host {hostname} must be run with auth')
|
|
51
|
+
|
|
52
|
+
username = OsUser.me.name()
|
|
53
|
+
kwargs[ts_class.USERNAME_TAG] = username
|
|
54
|
+
kwargs[ts_class.PASSWORD_TAG] = cls.retrieve_master_password()
|
|
55
|
+
|
|
56
|
+
return ts_class.instance(**kwargs)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from core_10x.environment_variables import EnvVars
|
|
2
|
+
from core_10x.ts_store import TsStore
|
|
3
|
+
from core_10x.vault.vault_traitable import VaultTraitable, T, RT, RC, OsUser, cache
|
|
4
|
+
from core_10x.vault.sec_keys import SecKeys
|
|
5
|
+
|
|
6
|
+
class VaultUser(VaultTraitable):
|
|
7
|
+
# fmt: off
|
|
8
|
+
user_id: str = T(T.ID) // 'OS login'
|
|
9
|
+
suspended: bool = T(False)
|
|
10
|
+
|
|
11
|
+
private_key_encrypted: bytes = T()
|
|
12
|
+
public_key: bytes = T()
|
|
13
|
+
|
|
14
|
+
sec_keys: SecKeys = RT(T.EVAL_ONCE)
|
|
15
|
+
# fmt: on
|
|
16
|
+
|
|
17
|
+
def user_id_get(self) -> str:
|
|
18
|
+
return self.__class__.myname()
|
|
19
|
+
|
|
20
|
+
def sec_keys_get(self) -> SecKeys:
|
|
21
|
+
return SecKeys(self.private_key_encrypted, self.public_key, VaultTraitable.retrieve_master_password())
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def create_new(cls, master_password: str, save = True) -> 'VaultUser':
|
|
25
|
+
VaultTraitable.keep_master_password(master_password)
|
|
26
|
+
private_key_pem, public_key_pem = SecKeys.generate_keys()
|
|
27
|
+
me = VaultUser()
|
|
28
|
+
me.public_key = public_key_pem
|
|
29
|
+
me.private_key_encrypted = SecKeys.encrypt_private_key(private_key_pem, master_password)
|
|
30
|
+
|
|
31
|
+
if save:
|
|
32
|
+
me.save().throw()
|
|
33
|
+
|
|
34
|
+
return me
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def new_vault(cls, vault_uri: str, password: str, master_password: str = None):
|
|
38
|
+
username = cls.myname()
|
|
39
|
+
vault_db = TsStore.instance_from_uri(vault_uri, username = username, password = password)
|
|
40
|
+
with vault_db:
|
|
41
|
+
me = cls.existing_instance(user_id = username, _throw = False)
|
|
42
|
+
if not me:
|
|
43
|
+
if not master_password:
|
|
44
|
+
raise AssertionError('Master password is required')
|
|
45
|
+
me = cls.create_new(master_password)
|
|
46
|
+
else:
|
|
47
|
+
try:
|
|
48
|
+
existing_master_password = cls.retrieve_master_password()
|
|
49
|
+
except Exception:
|
|
50
|
+
existing_master_password = None
|
|
51
|
+
if not existing_master_password:
|
|
52
|
+
raise AssertionError('No existing MasterPassword found')
|
|
53
|
+
if existing_master_password != master_password:
|
|
54
|
+
raise AssertionError('MasterPassword provided does not match the stored one')
|
|
55
|
+
|
|
56
|
+
cls.keep_vault_password(vault_uri, password)
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
def is_functional_account(cls, user_id: str) -> bool:
|
|
60
|
+
return user_id.split('-', 1) == EnvVars.functional_account_prefix
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
@cache
|
|
64
|
+
def myname(cls) -> str:
|
|
65
|
+
return OsUser.me.name()
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
@cache
|
|
69
|
+
def me(cls) -> 'VaultUser':
|
|
70
|
+
return cls.existing_instance(user_id=cls.myname())
|
core_10x/xdate_time.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import date, datetime
|
|
4
|
+
|
|
5
|
+
import dateutil.parser
|
|
6
|
+
|
|
7
|
+
MIN_CANONICAL_DATE = 10000101
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class XDateTime:
|
|
11
|
+
"""
|
|
12
|
+
All datetime values are in UTC time zone!
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
def int_to_date(v: int) -> date:
|
|
17
|
+
"""
|
|
18
|
+
ordinal or canonical (<yyyy><mm><dd>)
|
|
19
|
+
"""
|
|
20
|
+
if v >= MIN_CANONICAL_DATE:
|
|
21
|
+
day = v % 100
|
|
22
|
+
ym = v // 100
|
|
23
|
+
|
|
24
|
+
month = ym % 100
|
|
25
|
+
year = ym // 100
|
|
26
|
+
|
|
27
|
+
return date(year=year, month=month, day=day)
|
|
28
|
+
|
|
29
|
+
return date.fromordinal(v)
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def date_to_int(v: date, ordinal: bool = True) -> int:
|
|
33
|
+
return v.toordinal() if ordinal else (10000 * v.year + 100 * v.month + v.day)
|
|
34
|
+
|
|
35
|
+
# fmt: off
|
|
36
|
+
FORMAT_X10 = '%Y%m%d'
|
|
37
|
+
FORMAT_ISO = '%Y-%m-%d'
|
|
38
|
+
FORMAT_US = '%m/%d/%Y'
|
|
39
|
+
FORMAT_EU = '%d.%m.%Y'
|
|
40
|
+
# fmt: on
|
|
41
|
+
|
|
42
|
+
s_default_format = FORMAT_X10
|
|
43
|
+
formats = [s_default_format, FORMAT_X10, FORMAT_ISO, FORMAT_US, FORMAT_EU]
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def set_default_format(cls, fmt: str):
|
|
47
|
+
cls.s_default_format = fmt
|
|
48
|
+
cls.formats[0] = fmt
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def str_to_date(v: str, format: str = '') -> date | None:
|
|
52
|
+
if format:
|
|
53
|
+
try:
|
|
54
|
+
return datetime.strptime(v, format).date()
|
|
55
|
+
except Exception:
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
for fmt in XDateTime.formats:
|
|
59
|
+
try:
|
|
60
|
+
return datetime.strptime(v, fmt).date()
|
|
61
|
+
except Exception:
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
return dateutil.parser.parse(v).date()
|
|
66
|
+
except Exception:
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
formats_to_str = (
|
|
70
|
+
f'{formats[0]} %H:%M:%S',
|
|
71
|
+
f'{formats[0]} %H:%M:%S.%f',
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def datetime_to_str(v: datetime, with_ms: bool = False) -> str:
|
|
76
|
+
fmt = XDateTime.formats_to_str[with_ms]
|
|
77
|
+
return v.strftime(fmt)
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def date_to_str(v: date, format: str = '') -> str:
|
|
81
|
+
if not format:
|
|
82
|
+
format = XDateTime.formats[0]
|
|
83
|
+
return v.strftime(format)
|
|
84
|
+
|
|
85
|
+
# fmt: off
|
|
86
|
+
date_converters = {
|
|
87
|
+
date: lambda v: v,
|
|
88
|
+
datetime: lambda v: date(v.year, v.month, v.day),
|
|
89
|
+
int: int_to_date,
|
|
90
|
+
str: str_to_date,
|
|
91
|
+
}
|
|
92
|
+
# fmt: on
|
|
93
|
+
@classmethod
|
|
94
|
+
def to_date(cls, v) -> date | None:
|
|
95
|
+
fn = cls.date_converters.get(type(v))
|
|
96
|
+
return fn(v) if fn else None
|
|
97
|
+
|
|
98
|
+
dt_format = ('%H:%M', '%H:%M:%S')
|
|
99
|
+
|
|
100
|
+
@staticmethod
|
|
101
|
+
def str_to_datetime(v: str) -> datetime | None:
|
|
102
|
+
parts = v.split(' ')
|
|
103
|
+
try:
|
|
104
|
+
date_part, time_part = parts
|
|
105
|
+
d = XDateTime.str_to_date(date_part)
|
|
106
|
+
if d is not None:
|
|
107
|
+
num_colons = time_part.count(':')
|
|
108
|
+
fmt = XDateTime.dt_format[num_colons]
|
|
109
|
+
if time_part.find('.') != -1:
|
|
110
|
+
fmt = fmt + '.%f'
|
|
111
|
+
return datetime.strptime(time_part, fmt)
|
|
112
|
+
except Exception:
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
return dateutil.parser.parse(v)
|
|
117
|
+
except Exception:
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def date_to_datetime(d: date) -> datetime:
|
|
122
|
+
return datetime(year=d.year, month=d.month, day=d.day)
|
|
123
|
+
|
|
124
|
+
# fmt: off
|
|
125
|
+
datetime_converters = {
|
|
126
|
+
datetime: lambda v: v,
|
|
127
|
+
date: lambda v: datetime(year = v.year, month = v.month, day = v.day),
|
|
128
|
+
int: lambda v: XDateTime.date_to_datetime(XDateTime.int_to_date(v)),
|
|
129
|
+
str: str_to_datetime,
|
|
130
|
+
}
|
|
131
|
+
# fmt: on
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
def to_datetime(cls, v) -> datetime:
|
|
135
|
+
fn = cls.datetime_converters.get(type(v))
|
|
136
|
+
return fn(v) if fn else None
|
core_10x/xnone.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
class XNoneType:
|
|
2
|
+
"""
|
|
3
|
+
XNone is the 10x alternative to None representing a special empty object.
|
|
4
|
+
As opposed to None, XNone may be used in many built-in python operations without extra checking, e.g.
|
|
5
|
+
|
|
6
|
+
a_dict = XNone
|
|
7
|
+
for key, value in a_dict.items():
|
|
8
|
+
...
|
|
9
|
+
|
|
10
|
+
The above loop will merely exit on the first iteration.
|
|
11
|
+
|
|
12
|
+
- all arithmetic operations with XNone will return XNone
|
|
13
|
+
- all relational operations with XNone will return True or False accordingly
|
|
14
|
+
- a call to XNone(*args, **kwargs) will return XNone
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __getattr__(self, item): return self
|
|
18
|
+
def __setattr__(self, key, value): raise AttributeError('May not setattr to XNone')
|
|
19
|
+
def __getitem__(self, item): return self
|
|
20
|
+
def __setitem__(self, key, value): raise TypeError('\'XNone\' object does not support item assignment')
|
|
21
|
+
def __call__(self, *args, **kwargs): return self
|
|
22
|
+
def __hash__(self): return id(self)
|
|
23
|
+
|
|
24
|
+
def __int__(self): return self
|
|
25
|
+
def __bool__(self): return False
|
|
26
|
+
def __float__(self): return self
|
|
27
|
+
|
|
28
|
+
def __repr__(self): return 'XNone'
|
|
29
|
+
def __str__(self): return ''
|
|
30
|
+
|
|
31
|
+
def __and__(self, other): return False
|
|
32
|
+
def __or__(self, other): return other
|
|
33
|
+
def __xor__(self, other): return other
|
|
34
|
+
def __invert__(self): return None #-- the opposite to XNone
|
|
35
|
+
def __lshift__(self, other): return self
|
|
36
|
+
def __rshift__(self, other): return self
|
|
37
|
+
|
|
38
|
+
def __len__(self): return 0
|
|
39
|
+
def __iter__(self): return self
|
|
40
|
+
def __next__(self): raise StopIteration
|
|
41
|
+
|
|
42
|
+
def keys(self): return self
|
|
43
|
+
def values(self): return self
|
|
44
|
+
def items(self): return self
|
|
45
|
+
|
|
46
|
+
def __eq__(self, other): return other is self
|
|
47
|
+
def __ne__(self, other): return other is not self
|
|
48
|
+
def __lt__(self, other): return other is not self
|
|
49
|
+
def __le__(self, other): return True
|
|
50
|
+
def __gt__(self, other): return False
|
|
51
|
+
def __ge__(self, other): return other is self
|
|
52
|
+
def __abs__(self): return self
|
|
53
|
+
def __neg__(self): return self
|
|
54
|
+
def __add__(self, other): return self
|
|
55
|
+
def __radd__(self, other): return self
|
|
56
|
+
def __sub__(self, other): return self
|
|
57
|
+
def __rsub__(self, other): return self
|
|
58
|
+
def __mul__(self, other): return self
|
|
59
|
+
def __rmul__(self, other): return self
|
|
60
|
+
def __pow__(self, power, modulo = None): return self
|
|
61
|
+
def __truediv__(self, other): return self
|
|
62
|
+
def __rtruediv__(self, other): return self
|
|
63
|
+
def __floordiv__(self, other): return self
|
|
64
|
+
def __rfloordiv__(self, other): return self
|
|
65
|
+
|
|
66
|
+
s_serialized = chr(8)
|
|
67
|
+
|
|
68
|
+
def __init_subclass__(cls, **kwargs):
|
|
69
|
+
raise TypeError('May not derive from XNone')
|
|
70
|
+
|
|
71
|
+
XNone = XNoneType()
|