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/rc.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import traceback
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from core_10x.named_constant import Enum, ErrorCode
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from types import TracebackType
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RC:
|
|
14
|
+
"""
|
|
15
|
+
Stands for 'Return Code'. Main usage is for functions returning 'success' or 'error' with an optional payload.
|
|
16
|
+
|
|
17
|
+
For just a 'success':
|
|
18
|
+
RC_TRUE (a constant RC(True) instance)
|
|
19
|
+
return RC_TRUE
|
|
20
|
+
|
|
21
|
+
For 'success' with a single data:
|
|
22
|
+
return RC(True, data)
|
|
23
|
+
RC(enum, data) - an instance of Enum subclass with a positive value
|
|
24
|
+
|
|
25
|
+
For 'success' with multiple data:
|
|
26
|
+
rc = RC(True)
|
|
27
|
+
...
|
|
28
|
+
rc.add_data(data)
|
|
29
|
+
...
|
|
30
|
+
return rc
|
|
31
|
+
|
|
32
|
+
For a single error:
|
|
33
|
+
return RC(False) - if you are in except: to catch an exception with stack info
|
|
34
|
+
return RC(False, message)
|
|
35
|
+
return RC(error, message) - an instance of ErrorCode subclass (with a non-positive value)
|
|
36
|
+
|
|
37
|
+
For multiple errors:
|
|
38
|
+
rc = RC(True)
|
|
39
|
+
...
|
|
40
|
+
rc.add_error(message)
|
|
41
|
+
...
|
|
42
|
+
return rc
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
__slots__ = ('payload', 'rc')
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def show_exception_info(cls, ex_info: tuple[type[BaseException], BaseException, TracebackType] = None) -> str:
|
|
49
|
+
if not ex_info:
|
|
50
|
+
ex_info = sys.exc_info()
|
|
51
|
+
|
|
52
|
+
assert isinstance(ex_info, tuple) and len(ex_info) == 3, f'Invalid ex_info: {ex_info}'
|
|
53
|
+
|
|
54
|
+
ss = traceback.StackSummary.extract(traceback.walk_tb(ex_info[2]))
|
|
55
|
+
return f'{ex_info[0]} ({ex_info[1]})\n{"".join(ss.format())}'
|
|
56
|
+
|
|
57
|
+
def __init__(self, rc, data=None):
|
|
58
|
+
self.rc = rc
|
|
59
|
+
self.payload = [data] if data is not None else []
|
|
60
|
+
|
|
61
|
+
def __bool__(self):
|
|
62
|
+
rc = self.rc
|
|
63
|
+
dt = type(rc)
|
|
64
|
+
if dt is bool:
|
|
65
|
+
return rc
|
|
66
|
+
|
|
67
|
+
if issubclass(dt, Enum):
|
|
68
|
+
rc = rc.value
|
|
69
|
+
else:
|
|
70
|
+
assert dt is int, f'Invalid rc: {rc}'
|
|
71
|
+
|
|
72
|
+
return rc > 0
|
|
73
|
+
|
|
74
|
+
def __repr__(self):
|
|
75
|
+
if self.__bool__():
|
|
76
|
+
data = self.payload
|
|
77
|
+
else:
|
|
78
|
+
data = self.error()
|
|
79
|
+
return f'{self.rc}: {data}'
|
|
80
|
+
|
|
81
|
+
def __iadd__(self, err):
|
|
82
|
+
return self.add_error(err)
|
|
83
|
+
|
|
84
|
+
def __ilshift__(self, err):
|
|
85
|
+
return self.add_error(err)
|
|
86
|
+
|
|
87
|
+
def unwrap(self) -> tuple: # -- ( rc, payload)
|
|
88
|
+
return (self.rc, self.payload)
|
|
89
|
+
|
|
90
|
+
def data(self):
|
|
91
|
+
payload = self.payload
|
|
92
|
+
return payload if len(payload) > 1 else payload[0]
|
|
93
|
+
|
|
94
|
+
def error(self) -> str:
|
|
95
|
+
if self.__bool__():
|
|
96
|
+
return ''
|
|
97
|
+
|
|
98
|
+
payload = self.payload
|
|
99
|
+
n = len(payload)
|
|
100
|
+
if n == 0:
|
|
101
|
+
return self.__class__.show_exception_info()
|
|
102
|
+
|
|
103
|
+
if n == 1:
|
|
104
|
+
data = payload[0]
|
|
105
|
+
if isinstance(self.rc, ErrorCode):
|
|
106
|
+
return self.rc(**data)
|
|
107
|
+
return data
|
|
108
|
+
|
|
109
|
+
# -- multiple errors
|
|
110
|
+
return '\n'.join(payload)
|
|
111
|
+
|
|
112
|
+
def add_error(self, err) -> RC:
|
|
113
|
+
dt = type(err)
|
|
114
|
+
if dt is str:
|
|
115
|
+
self.payload.append(err)
|
|
116
|
+
|
|
117
|
+
elif dt is RC:
|
|
118
|
+
if err:
|
|
119
|
+
return self
|
|
120
|
+
self.payload.extend(err.payload)
|
|
121
|
+
|
|
122
|
+
else:
|
|
123
|
+
raise ValueError(f'Expected str or RC; got {type(err)}')
|
|
124
|
+
|
|
125
|
+
if self.__bool__():
|
|
126
|
+
self.rc = False
|
|
127
|
+
|
|
128
|
+
return self
|
|
129
|
+
|
|
130
|
+
def add_data(self, data) -> RC:
|
|
131
|
+
assert self.__bool__(), 'May not add_data() to an "error" RC'
|
|
132
|
+
self.payload.append(data)
|
|
133
|
+
return self
|
|
134
|
+
|
|
135
|
+
def throw(self, exc: type[Exception] = RuntimeError):
|
|
136
|
+
err = self.error()
|
|
137
|
+
if err:
|
|
138
|
+
raise exc(err)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class _RcTrue(RC):
|
|
142
|
+
def __init__(self):
|
|
143
|
+
super().__init__(True)
|
|
144
|
+
|
|
145
|
+
def new_rc(self) -> RC:
|
|
146
|
+
return RC(True)
|
|
147
|
+
|
|
148
|
+
def add_error(self, err: str = ''):
|
|
149
|
+
raise ValueError('May not add error to a constant RC_TRUE')
|
|
150
|
+
|
|
151
|
+
def add_data(self, data):
|
|
152
|
+
raise ValueError('May not add error to a constant RC_TRUE')
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
RC_TRUE = _RcTrue()
|
core_10x/rdate.py
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
import re
|
|
5
|
+
from datetime import date
|
|
6
|
+
|
|
7
|
+
from dateutil.relativedelta import relativedelta
|
|
8
|
+
|
|
9
|
+
from core_10x.named_constant import NamedConstant, NamedConstantTable
|
|
10
|
+
from core_10x.nucleus import Nucleus
|
|
11
|
+
from core_10x.xxcalendar import Calendar
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# =====
|
|
15
|
+
# Biz Day Roll Rules
|
|
16
|
+
# 1. Preceding (rolls backwards): go to the prev biz day, if not already
|
|
17
|
+
# 2. Following (rolls forward): go to the next biz day, if not already
|
|
18
|
+
# 3. Modified following: go to the next biz day unless the next biz day is in the next month, otherwise go to 1.
|
|
19
|
+
# 4. Modified preceding: go to the prev biz day unless the prev biz day is in the prev month, otherwise go to 2.
|
|
20
|
+
# =====
|
|
21
|
+
def bizday_roll_preceding(d: date, cal: Calendar) -> date:
|
|
22
|
+
if cal.is_bizday(d):
|
|
23
|
+
return d
|
|
24
|
+
|
|
25
|
+
return cal.prev_bizday(d)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def bizday_roll_following(d: date, cal: Calendar) -> date:
|
|
29
|
+
if cal.is_bizday(d):
|
|
30
|
+
return d
|
|
31
|
+
|
|
32
|
+
return cal.next_bizday(d)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def bizday_roll_mod_following(d: date, cal: Calendar) -> date:
|
|
36
|
+
if cal.is_bizday(d):
|
|
37
|
+
return d
|
|
38
|
+
|
|
39
|
+
res = cal.next_bizday(d)
|
|
40
|
+
return res if res.month == d.month else cal.prev_bizday(d)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def bizday_roll_mod_preceding(d: date, cal: Calendar) -> date:
|
|
44
|
+
if cal.is_bizday(d):
|
|
45
|
+
return d
|
|
46
|
+
|
|
47
|
+
res = cal.prev_bizday(d)
|
|
48
|
+
return res if res.month == d.month else cal.next_bizday(d)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# fmt: off
|
|
52
|
+
class BIZDAY_ROLL_RULE(NamedConstant):
|
|
53
|
+
PRECEDING = bizday_roll_preceding
|
|
54
|
+
FOLLOWING = bizday_roll_following
|
|
55
|
+
MOD_FOLLOWING = bizday_roll_mod_following
|
|
56
|
+
MOD_PRECEDING = bizday_roll_mod_preceding
|
|
57
|
+
NO_ROLL = lambda d, cal: d
|
|
58
|
+
|
|
59
|
+
class TENOR_FREQUENCY(NamedConstant):
|
|
60
|
+
BIZDAY = ()
|
|
61
|
+
CALDAY = ()
|
|
62
|
+
WEEK = ()
|
|
63
|
+
MONTH = ()
|
|
64
|
+
QUARTER = ()
|
|
65
|
+
HALF_YEAR = ()
|
|
66
|
+
YEAR = ()
|
|
67
|
+
#IMM_QUARTER = () # TODO - use later
|
|
68
|
+
|
|
69
|
+
class TENOR_PARAMS(NamedConstant):
|
|
70
|
+
CHAR = ()
|
|
71
|
+
RELATIVE_DELTA = ()
|
|
72
|
+
CONVERSIONS = ()
|
|
73
|
+
MIN_FREQUENCY = ()
|
|
74
|
+
|
|
75
|
+
FREQUENCY_TABLE = NamedConstantTable(TENOR_FREQUENCY, TENOR_PARAMS,
|
|
76
|
+
# CHAR RELATIVE_DELTA CONVERSIONS MIN_FREQUENCY
|
|
77
|
+
BIZDAY = ('B', None, None, None),
|
|
78
|
+
CALDAY = ('C', lambda n: relativedelta(days = n), dict(C = 1, W = 1/7), TENOR_FREQUENCY.CALDAY),
|
|
79
|
+
WEEK = ('W', lambda n: relativedelta(weeks = n), dict(C = 7, W = 1), TENOR_FREQUENCY.CALDAY),
|
|
80
|
+
MONTH = ('M', lambda n: relativedelta(months = n), dict(M = 1, Q = 1/3, S = 1/6, Y = 1/12), TENOR_FREQUENCY.MONTH),
|
|
81
|
+
QUARTER = ('Q', lambda n: relativedelta(months = n * 3), dict(M = 3, Q = 1, S = 0.5, Y = 0.25), TENOR_FREQUENCY.MONTH),
|
|
82
|
+
HALF_YEAR = ('S', lambda n: relativedelta(months = n * 6), dict(M = 6, Q = 2, S = 1, Y = 0.5), TENOR_FREQUENCY.MONTH),
|
|
83
|
+
YEAR = ('Y', lambda n: relativedelta(years = n), dict(M = 12, Q = 4, S = 2, Y = 1), TENOR_FREQUENCY.MONTH),
|
|
84
|
+
|
|
85
|
+
#IMM_QUARTER = ( 'IMM', lambda d, n: IMMQuarter.which( d ).ith( n ).last(), None, None ),
|
|
86
|
+
)
|
|
87
|
+
# fmt: on
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class PROPAGATE_DATES(NamedConstant):
|
|
91
|
+
BACKWARD = () ## BACKWARD period propagation is more standard for G10 interest rate swaps
|
|
92
|
+
FORWARD = ()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class RDate(Nucleus):
|
|
96
|
+
"""
|
|
97
|
+
rd = RDate('1Y')
|
|
98
|
+
dt = date(2025, 1, 1)
|
|
99
|
+
rd.apply(dt, Calendar.existing_instance_by_id('FB'), BIZDAY_ROLL_RULE.FOLLOWING)
|
|
100
|
+
-> date(2026, 1, 1)
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
# s_spot_tenors = { 'ON', 'TN', 'SN' }
|
|
104
|
+
|
|
105
|
+
# -- RDate('3M') or RDate(TENOR_FREQUENCY.MONTH, 3)
|
|
106
|
+
# def __init__(self, symbol_or_frequency = None, count: int = None):
|
|
107
|
+
def __init__(self, symbol: str = None, freq: TENOR_FREQUENCY = None, count: int = None):
|
|
108
|
+
if symbol is not None:
|
|
109
|
+
match = re.match(r'(-?\d+)([a-zA-Z]+)', symbol)
|
|
110
|
+
count = int(match.group(1))
|
|
111
|
+
letter = match.group(2)
|
|
112
|
+
|
|
113
|
+
freq = FREQUENCY_TABLE.primary_key(TENOR_PARAMS.CHAR, letter.upper())
|
|
114
|
+
if freq is None:
|
|
115
|
+
raise AttributeError(f"Invalid tenor symbol '{symbol}'")
|
|
116
|
+
|
|
117
|
+
else:
|
|
118
|
+
assert freq is not None, 'freq must be a valid TENOR_FREQUENCY'
|
|
119
|
+
if count is None:
|
|
120
|
+
count = 1
|
|
121
|
+
|
|
122
|
+
self.freq = freq
|
|
123
|
+
self.count = count
|
|
124
|
+
|
|
125
|
+
def symbol(self) -> str:
|
|
126
|
+
return f'{self.count}{FREQUENCY_TABLE[self.freq][TENOR_PARAMS.CHAR]}'
|
|
127
|
+
|
|
128
|
+
# ---- Nucleus methods
|
|
129
|
+
def to_str(self) -> str:
|
|
130
|
+
return self.symbol()
|
|
131
|
+
|
|
132
|
+
def serialize(self, embed: bool):
|
|
133
|
+
return self.symbol()
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def deserialize(cls, serialized_data) -> Nucleus:
|
|
137
|
+
return cls(symbol=serialized_data)
|
|
138
|
+
|
|
139
|
+
@classmethod
|
|
140
|
+
def from_str(cls, s: str) -> Nucleus:
|
|
141
|
+
return cls(symbol=s)
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def from_any_xstr(cls, value) -> Nucleus:
|
|
145
|
+
if isinstance(value, cls):
|
|
146
|
+
return cls(freq=value.freq, count=value.count)
|
|
147
|
+
|
|
148
|
+
if isinstance(value, tuple):
|
|
149
|
+
assert len(value) == 2, 'tenor frequency and count are expected'
|
|
150
|
+
return cls(freq=value[0], count=value[1])
|
|
151
|
+
|
|
152
|
+
raise AssertionError('unexpected type')
|
|
153
|
+
|
|
154
|
+
@classmethod
|
|
155
|
+
def same_values(cls, value1, value2) -> bool:
|
|
156
|
+
return value1.freq == value2.freq and value1.count == value2.count
|
|
157
|
+
|
|
158
|
+
# ----
|
|
159
|
+
|
|
160
|
+
def apply(self, d: date, cal: Calendar, roll_rule: BIZDAY_ROLL_RULE) -> date:
|
|
161
|
+
if self.freq is TENOR_FREQUENCY.BIZDAY:
|
|
162
|
+
not_rolled = cal.advance_bizdays(d, self.count)
|
|
163
|
+
else:
|
|
164
|
+
fn = FREQUENCY_TABLE[self.freq][TENOR_PARAMS.RELATIVE_DELTA]
|
|
165
|
+
not_rolled = d + fn(self.count)
|
|
166
|
+
|
|
167
|
+
return roll_rule(not_rolled, cal)
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
def apply_rule(cls, d: date, cal: Calendar, roll_rule: BIZDAY_ROLL_RULE, comma_separated_rdates_str: str) -> date:
|
|
171
|
+
rdates = comma_separated_rdates_str.split(',')
|
|
172
|
+
for rdate_symbol in rdates:
|
|
173
|
+
rdate = RDate(symbol=rdate_symbol)
|
|
174
|
+
d = rdate.apply(d, cal, roll_rule)
|
|
175
|
+
return d
|
|
176
|
+
|
|
177
|
+
def conversion_freq_multiplier(self, other_freq: TENOR_FREQUENCY) -> float:
|
|
178
|
+
my_freq = self.freq
|
|
179
|
+
if my_freq is other_freq:
|
|
180
|
+
return 1.0
|
|
181
|
+
|
|
182
|
+
dont_handle_freqs = (TENOR_FREQUENCY.BIZDAY, TENOR_FREQUENCY.CALDAY, TENOR_FREQUENCY.WEEK)
|
|
183
|
+
if my_freq in dont_handle_freqs or other_freq in dont_handle_freqs:
|
|
184
|
+
raise ValueError(f'cannot convert {my_freq} to {other_freq}')
|
|
185
|
+
|
|
186
|
+
multiplier = FREQUENCY_TABLE[my_freq][TENOR_PARAMS.CONVERSIONS].get(FREQUENCY_TABLE[other_freq][TENOR_PARAMS.CHAR])
|
|
187
|
+
if not multiplier:
|
|
188
|
+
raise ValueError(f'cannot get a conversion multiple from {my_freq} to {other_freq}')
|
|
189
|
+
|
|
190
|
+
return multiplier
|
|
191
|
+
|
|
192
|
+
def equate_freq(self, other: RDate) -> tuple:
|
|
193
|
+
if self.freq is other.freq:
|
|
194
|
+
return (self, other)
|
|
195
|
+
|
|
196
|
+
mult = self.conversion_freq_multiplier(other.freq) # -- either int or 1/int
|
|
197
|
+
mult_i = int(mult)
|
|
198
|
+
if mult_i >= 1:
|
|
199
|
+
return (RDate(freq=other.freq, count=self.count * mult_i), other)
|
|
200
|
+
|
|
201
|
+
return (self, RDate(freq=self.freq, count=other.count * int(1 / mult)))
|
|
202
|
+
|
|
203
|
+
def _fract_count_to_RDate(self, count: float, freq: TENOR_FREQUENCY, freq_to_try: TENOR_FREQUENCY) -> RDate:
|
|
204
|
+
if math.isclose(count, round(count)):
|
|
205
|
+
return RDate(freq=freq, count=round(count))
|
|
206
|
+
|
|
207
|
+
conv_to_min_freq = self.conversion_freq_multiplier(freq_to_try)
|
|
208
|
+
return RDate(freq=freq_to_try, count=round(conv_to_min_freq * count))
|
|
209
|
+
|
|
210
|
+
def multadd(self, mult: float, add, freq_to_try_for_fract_count=None) -> RDate:
|
|
211
|
+
if isinstance(add, (int, float)):
|
|
212
|
+
if add:
|
|
213
|
+
raise ValueError(f'cannot add a non-zero number {add} to RDate {self}: the result is ambiguous')
|
|
214
|
+
|
|
215
|
+
return self._fract_count_to_RDate(mult * self.count, self.freq, freq_to_try_for_fract_count)
|
|
216
|
+
|
|
217
|
+
if isinstance(add, str):
|
|
218
|
+
try:
|
|
219
|
+
add = RDate(add)
|
|
220
|
+
except Exception as e:
|
|
221
|
+
raise ValueError(f'cannot convert adder {add} to RDate') from e
|
|
222
|
+
|
|
223
|
+
if isinstance(add, RDate):
|
|
224
|
+
rd1, rd2 = self.equate_freq(add)
|
|
225
|
+
return self._fract_count_to_RDate(mult * rd1.count + rd2.count, rd1.freq, freq_to_try_for_fract_count)
|
|
226
|
+
|
|
227
|
+
raise ValueError(f'cannot calc a linear combination of {mult} * {self} + {add}')
|
|
228
|
+
|
|
229
|
+
def __mul__(self, other: float) -> RDate:
|
|
230
|
+
return self.multadd(other, 0, freq_to_try_for_fract_count=FREQUENCY_TABLE[self.freq][TENOR_PARAMS.MIN_FREQUENCY])
|
|
231
|
+
|
|
232
|
+
def __rmul__(self, other: float) -> RDate:
|
|
233
|
+
return self.multadd(other, 0, freq_to_try_for_fract_count=FREQUENCY_TABLE[self.freq][TENOR_PARAMS.MIN_FREQUENCY])
|
|
234
|
+
|
|
235
|
+
def __truediv__(self, other):
|
|
236
|
+
if isinstance(other, (int, float)):
|
|
237
|
+
return self.multadd(1 / other, 0, freq_to_try_for_fract_count=FREQUENCY_TABLE[self.freq][TENOR_PARAMS.MIN_FREQUENCY])
|
|
238
|
+
|
|
239
|
+
rd1, rd2 = self.equate_freq(other)
|
|
240
|
+
return rd1.count / rd2.count
|
|
241
|
+
|
|
242
|
+
def __add__(self, other) -> RDate:
|
|
243
|
+
return self.multadd(1.0, other, freq_to_try_for_fract_count=FREQUENCY_TABLE[self.freq][TENOR_PARAMS.MIN_FREQUENCY])
|
|
244
|
+
|
|
245
|
+
@classmethod
|
|
246
|
+
def add_bizdays(cls, d: date, biz_days: int, cal: Calendar, roll_rule: BIZDAY_ROLL_RULE) -> date:
|
|
247
|
+
not_rolled = cal.advance_bizdays(d, biz_days)
|
|
248
|
+
return roll_rule(not_rolled, cal)
|
|
249
|
+
|
|
250
|
+
@classmethod
|
|
251
|
+
def roll_to_bizday(cls, d: date, cal: Calendar, roll_rule: BIZDAY_ROLL_RULE) -> date:
|
|
252
|
+
return roll_rule(d, cal)
|
|
253
|
+
|
|
254
|
+
class RELOP(NamedConstant):
|
|
255
|
+
LT = date.__lt__
|
|
256
|
+
GT = date.__gt__
|
|
257
|
+
EQ = date.__eq__
|
|
258
|
+
NE = date.__ne__
|
|
259
|
+
LE = date.__le__
|
|
260
|
+
GE = date.__ge__
|
|
261
|
+
|
|
262
|
+
@classmethod
|
|
263
|
+
def relop(cls, rel_op: RELOP, rd1: RDate, rd2: RDate, d: date, cal: Calendar, roll_rule: BIZDAY_ROLL_RULE) -> bool:
|
|
264
|
+
d1 = rd1.apply(d, cal, roll_rule)
|
|
265
|
+
d2 = rd2.apply(d, cal, roll_rule)
|
|
266
|
+
return rel_op(d1, d2)
|
|
267
|
+
|
|
268
|
+
@classmethod
|
|
269
|
+
def from_tenors(cls, tenors_str: str, delim=',') -> list:
|
|
270
|
+
tenors = tenors_str.split(delim)
|
|
271
|
+
return [RDate(tenor.strip()) for tenor in tenors]
|
|
272
|
+
|
|
273
|
+
def dates_schedule(
|
|
274
|
+
self, start: date, end: date, calendar: Calendar, roll_rule: BIZDAY_ROLL_RULE, date_propagation: PROPAGATE_DATES, allow_stub=True
|
|
275
|
+
) -> list:
|
|
276
|
+
rolled_start = roll_rule(start, calendar)
|
|
277
|
+
assert rolled_start >= start, (
|
|
278
|
+
f'rolled start date {rolled_start} is before non-working start date {start} according to calendar {calendar.name} and roll rule {roll_rule.name}'
|
|
279
|
+
)
|
|
280
|
+
rolled_end = roll_rule(end, calendar)
|
|
281
|
+
assert rolled_end <= end, (
|
|
282
|
+
f'rolled end date {rolled_end} is after non-working end date {end} according to calendar {calendar.name} and roll rule {roll_rule.name}'
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
if date_propagation == PROPAGATE_DATES.BACKWARD:
|
|
286
|
+
begin = rolled_end
|
|
287
|
+
finish = rolled_start
|
|
288
|
+
dir = -1
|
|
289
|
+
exceed = lambda x, y: x < y
|
|
290
|
+
ex_or_eq = lambda x, y: x <= y
|
|
291
|
+
else:
|
|
292
|
+
begin = rolled_start
|
|
293
|
+
finish = rolled_end
|
|
294
|
+
dir = 1
|
|
295
|
+
exceed = lambda x, y: x > y
|
|
296
|
+
ex_or_eq = lambda x, y: x >= y
|
|
297
|
+
|
|
298
|
+
step = self * dir
|
|
299
|
+
|
|
300
|
+
prev_rolled_date = begin
|
|
301
|
+
rolled_date = begin
|
|
302
|
+
non_rolled_date = begin
|
|
303
|
+
|
|
304
|
+
all_dates = []
|
|
305
|
+
while ex_or_eq(finish, rolled_date):
|
|
306
|
+
all_dates.append(rolled_date)
|
|
307
|
+
non_rolled_date = step.apply(non_rolled_date, calendar, BIZDAY_ROLL_RULE.NO_ROLL)
|
|
308
|
+
rolled_date = roll_rule(non_rolled_date, calendar)
|
|
309
|
+
assert exceed(rolled_date, prev_rolled_date), (
|
|
310
|
+
f'infinite loop: next date {rolled_date} vs previous date {prev_rolled_date} for date_propagation = {date_propagation.name}'
|
|
311
|
+
)
|
|
312
|
+
if exceed(rolled_date, finish):
|
|
313
|
+
break
|
|
314
|
+
prev_rolled_date = rolled_date
|
|
315
|
+
|
|
316
|
+
if not allow_stub:
|
|
317
|
+
assert prev_rolled_date == finish, f'the date sequence has a stub period bound by {prev_rolled_date} and {finish}'
|
|
318
|
+
|
|
319
|
+
all_dates.sort()
|
|
320
|
+
return all_dates
|
|
321
|
+
|
|
322
|
+
def period_dates(
|
|
323
|
+
self, start: date, end: date, calendar: Calendar, roll_rule: BIZDAY_ROLL_RULE, date_propagation: PROPAGATE_DATES, allow_stub=True
|
|
324
|
+
) -> tuple:
|
|
325
|
+
all_dates = self.dates_schedule(start, end, calendar, roll_rule, date_propagation, allow_stub)
|
|
326
|
+
start_dates = all_dates[:-1]
|
|
327
|
+
end_dates = all_dates[1:]
|
|
328
|
+
|
|
329
|
+
return start_dates, end_dates, all_dates
|
|
330
|
+
|
|
331
|
+
## if the tenor is not an integral multiple of the frequency (e.g., 30-month annual swap: tenor = 5S, freq = YEAR)
|
|
332
|
+
## then there would be a period "shorter than the frequency" (e.g., half-year in the 30-month annual swap)
|
|
333
|
+
## call such a period a STUB. it maybe in the front (1st period) for BACKWARD propagation or in the back (last period) for FORWARD
|
|
334
|
+
def period_dates_for_tenor(
|
|
335
|
+
self, start: date, tenor: RDate, calendar: Calendar, roll_rule: BIZDAY_ROLL_RULE, date_propagation: PROPAGATE_DATES, allow_stub=True
|
|
336
|
+
) -> tuple:
|
|
337
|
+
start = roll_rule(start, calendar)
|
|
338
|
+
end = tenor.apply(start, calendar, roll_rule)
|
|
339
|
+
return self.period_dates(start, end, calendar, roll_rule, date_propagation, allow_stub)
|
core_10x/resource.py
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import abc
|
|
4
|
+
import inspect
|
|
5
|
+
from collections import deque
|
|
6
|
+
|
|
7
|
+
# ===================================================================================================================================
|
|
8
|
+
# We'd like to do the following:
|
|
9
|
+
#
|
|
10
|
+
# class MongodbStore(Resource, resource_type = TS_STORE, name = 'MONGO'):
|
|
11
|
+
# ...
|
|
12
|
+
#
|
|
13
|
+
# class RayCluster(Resource, resource_type = CLOUD_CLUSTER, name = 'RAY_CLUSTER'):
|
|
14
|
+
# ...
|
|
15
|
+
#
|
|
16
|
+
# class MDU(DataDomain):
|
|
17
|
+
# GENERAL = TS_STORE() #-- ResourceRequirements
|
|
18
|
+
# SYMBOLOGY = REL_DB() #--
|
|
19
|
+
# MKT_CLOSE_CLUSTER = CLOUD_CLUSTER(...) #--
|
|
20
|
+
#
|
|
21
|
+
# MDU.bind(
|
|
22
|
+
# GENERAL = R(TS_STORE.MONGO_DB, hostname = 'dev.mongo.general.io', tsl = True, ...),
|
|
23
|
+
# SYMBOLOGY = R(REL_DB.ORACLE_DB, hostname = 'dev.oracle.io', a = '...', b = '...')
|
|
24
|
+
# MKT_CLOSE_CLUSTER = R(CLOUD_CLUSTER.RAY_CLUSTER, hostname = 'dev.ray1.io', ...)
|
|
25
|
+
# )
|
|
26
|
+
#
|
|
27
|
+
# ===================================================================================================================================
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ResourceRequirements:
|
|
31
|
+
def __init__(self, resource_type, *args, **kwargs):
|
|
32
|
+
self.domain = None
|
|
33
|
+
self.category: str = None
|
|
34
|
+
self.resource_type = resource_type
|
|
35
|
+
self.args = args
|
|
36
|
+
self.kwargs = kwargs
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ResourceBinding:
|
|
40
|
+
def __init__(
|
|
41
|
+
self, _resource_class: type = None, _resource_type: ResourceType = None, _resource_name: str = None, _driver_name: str = None, **kwargs
|
|
42
|
+
):
|
|
43
|
+
if _resource_class:
|
|
44
|
+
assert issubclass(_resource_class, Resource), f'{_resource_class} is not a subclass of Resource'
|
|
45
|
+
else:
|
|
46
|
+
if _resource_name:
|
|
47
|
+
_resource_type = ResourceType.instance(_resource_name)
|
|
48
|
+
else:
|
|
49
|
+
assert _resource_type and isinstance(_resource_type, ResourceType), '_resource_type must be an instance of ResourceType'
|
|
50
|
+
|
|
51
|
+
_resource_class = _resource_type.resource_drivers.get(_driver_name)
|
|
52
|
+
assert _resource_class, f'Unknown _driver_name {_driver_name}'
|
|
53
|
+
|
|
54
|
+
self.resource_class = _resource_class
|
|
55
|
+
self.kwargs = kwargs
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
R = ResourceBinding
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ResourceType:
|
|
62
|
+
s_dir = {}
|
|
63
|
+
|
|
64
|
+
def __init__(self, name: str):
|
|
65
|
+
xrt = self.s_dir.get(name)
|
|
66
|
+
assert xrt is None, f"Resource type '{name}' has already been created"
|
|
67
|
+
self.s_dir[name] = xrt
|
|
68
|
+
|
|
69
|
+
self.name = name
|
|
70
|
+
self.resource_stack = deque()
|
|
71
|
+
self.resource_drivers = {}
|
|
72
|
+
|
|
73
|
+
def __call__(self, *args, **kwargs):
|
|
74
|
+
return ResourceRequirements(self, *args, **kwargs)
|
|
75
|
+
|
|
76
|
+
def __getattr__(self, driver_name: str):
|
|
77
|
+
return self.resource_drivers.get(driver_name)
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def instance(name: str, throw: bool = True):
|
|
81
|
+
rt = ResourceType.s_dir.get(name)
|
|
82
|
+
if not rt and throw:
|
|
83
|
+
raise ValueError(f"Unknown Resource type '{name}'")
|
|
84
|
+
|
|
85
|
+
return rt
|
|
86
|
+
|
|
87
|
+
def register_driver(self, name: str, driver_class):
|
|
88
|
+
existing_driver = self.resource_drivers.get(name)
|
|
89
|
+
assert not existing_driver, f"Resource '{name}' is already registered in {existing_driver}"
|
|
90
|
+
assert inspect.isclass(driver_class) and issubclass(driver_class, Resource), 'driver_class must be a subclass of Resource'
|
|
91
|
+
self.resource_drivers[name] = driver_class
|
|
92
|
+
|
|
93
|
+
def resource_driver(self, name: str, throw: bool = True):
|
|
94
|
+
r = self.resource_drivers.get(name)
|
|
95
|
+
if not r and throw:
|
|
96
|
+
raise ValueError(f"Unknown resource '{name}'")
|
|
97
|
+
|
|
98
|
+
return r
|
|
99
|
+
|
|
100
|
+
def begin_using(self, resource, last: bool = True):
|
|
101
|
+
self.resource_stack.append(resource) if last else self.resource_stack.appendleft(resource)
|
|
102
|
+
|
|
103
|
+
def end_using(self):
|
|
104
|
+
self.resource_stack.pop() # -- will throw if begin_using() hasn't been called
|
|
105
|
+
|
|
106
|
+
def current_resource(self):
|
|
107
|
+
stack = self.resource_stack
|
|
108
|
+
return stack[-1] if stack else None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class ResourceSpec:
|
|
112
|
+
def __init__(self, resource_class, kwargs: dict):
|
|
113
|
+
self.resource_class = resource_class
|
|
114
|
+
self.kwargs = kwargs
|
|
115
|
+
|
|
116
|
+
def set_credentials(self, username: str = None, password: str = None):
|
|
117
|
+
if username is not None:
|
|
118
|
+
self.kwargs[self.resource_class.USERNAME_TAG] = username
|
|
119
|
+
if password is not None:
|
|
120
|
+
self.kwargs[self.resource_class.PASSWORD_TAG] = password
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class Resource(abc.ABC):
|
|
124
|
+
# fmt: off
|
|
125
|
+
HOSTNAME_TAG = 'hostname'
|
|
126
|
+
PORT_TAG = 'port'
|
|
127
|
+
USERNAME_TAG = 'username'
|
|
128
|
+
DBNAME_TAG = 'dbname'
|
|
129
|
+
PASSWORD_TAG = 'password'
|
|
130
|
+
SSL_TAG = 'ssl'
|
|
131
|
+
# fmt: on
|
|
132
|
+
s_resource_type: ResourceType = None
|
|
133
|
+
s_driver_name: str = None
|
|
134
|
+
|
|
135
|
+
def __init_subclass__(cls, resource_type: ResourceType = None, resource_name: str = None, **kwargs):
|
|
136
|
+
if cls.s_resource_type is None: # -- must be a top class of a particular resource type, e.g. TsStore
|
|
137
|
+
assert resource_type and isinstance(resource_type, ResourceType), 'instance of ResourceType is expected'
|
|
138
|
+
assert resource_name is None, f'May not define Resource name for top class of Resource Type: {resource_type}'
|
|
139
|
+
cls.s_resource_type = resource_type
|
|
140
|
+
|
|
141
|
+
else: # -- a Resource of a particular resource type
|
|
142
|
+
assert resource_type is None, f'resource_type is already set: {cls.s_resource_type}'
|
|
143
|
+
assert resource_name and isinstance(resource_name, str), 'a unique Resource name is expected'
|
|
144
|
+
cls.s_resource_type.register_driver(resource_name, cls)
|
|
145
|
+
cls.s_driver_name = resource_name
|
|
146
|
+
|
|
147
|
+
def __enter__(self):
|
|
148
|
+
return self.begin_using()
|
|
149
|
+
|
|
150
|
+
def __exit__(self, *args):
|
|
151
|
+
self.end_using()
|
|
152
|
+
|
|
153
|
+
def begin_using(self):
|
|
154
|
+
rt = self.__class__.s_resource_type
|
|
155
|
+
rt.begin_using(self)
|
|
156
|
+
self.on_enter()
|
|
157
|
+
return rt
|
|
158
|
+
|
|
159
|
+
def end_using(self):
|
|
160
|
+
self.__class__.s_resource_type.end_using()
|
|
161
|
+
self.on_exit()
|
|
162
|
+
|
|
163
|
+
@classmethod
|
|
164
|
+
def instance_from_uri(cls, uri: str, username: str = None, password: str = None) -> Resource:
|
|
165
|
+
spec = cls.spec_from_uri(uri)
|
|
166
|
+
spec.set_credentials(username=username, password=password)
|
|
167
|
+
return spec.resource_class.instance(**spec.kwargs)
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
@abc.abstractmethod
|
|
171
|
+
def spec_from_uri(cls, uri: str) -> ResourceSpec: ...
|
|
172
|
+
|
|
173
|
+
@classmethod
|
|
174
|
+
@abc.abstractmethod
|
|
175
|
+
def instance(cls, *args, **kwargs) -> Resource: ...
|
|
176
|
+
|
|
177
|
+
@abc.abstractmethod
|
|
178
|
+
def on_enter(self): ...
|
|
179
|
+
|
|
180
|
+
@abc.abstractmethod
|
|
181
|
+
def on_exit(self): ...
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# =========== Known Resource Types
|
|
185
|
+
# fmt: off
|
|
186
|
+
TS_STORE = ResourceType('TS_STORE')
|
|
187
|
+
REL_DB = ResourceType('REL_DB')
|
|
188
|
+
CLOUD_CLUSTER = ResourceType('CLOUD_CLUSTER')
|
|
189
|
+
# fmt: on
|