py10x-universe 0.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. core_10x/__init__.py +42 -0
  2. core_10x/backbone/__init__.py +0 -0
  3. core_10x/backbone/backbone_store.py +59 -0
  4. core_10x/backbone/backbone_traitable.py +30 -0
  5. core_10x/backbone/backbone_user.py +66 -0
  6. core_10x/backbone/bound_data_domain.py +49 -0
  7. core_10x/backbone/namespace.py +101 -0
  8. core_10x/backbone/vault.py +38 -0
  9. core_10x/code_samples/__init__.py +0 -0
  10. core_10x/code_samples/_package_manifest.py +3 -0
  11. core_10x/code_samples/directories.py +181 -0
  12. core_10x/code_samples/person.py +76 -0
  13. core_10x/concrete_traits.py +356 -0
  14. core_10x/conftest.py +12 -0
  15. core_10x/curve.py +321 -0
  16. core_10x/data_domain.py +48 -0
  17. core_10x/data_domain_binder.py +45 -0
  18. core_10x/directory.py +250 -0
  19. core_10x/entity.py +8 -0
  20. core_10x/entity_filter.py +5 -0
  21. core_10x/environment_variables.py +147 -0
  22. core_10x/exec_control.py +84 -0
  23. core_10x/experimental/__init__.py +0 -0
  24. core_10x/experimental/data_protocol_ex.py +34 -0
  25. core_10x/global_cache.py +121 -0
  26. core_10x/manual_tests/__init__.py +0 -0
  27. core_10x/manual_tests/calendar_test.py +35 -0
  28. core_10x/manual_tests/ctor_update_bug.py +58 -0
  29. core_10x/manual_tests/debug_graph_on.py +17 -0
  30. core_10x/manual_tests/debug_graphoff_inside_graph_on.py +28 -0
  31. core_10x/manual_tests/enum_bits_test.py +17 -0
  32. core_10x/manual_tests/env_vars_trivial_test.py +12 -0
  33. core_10x/manual_tests/existing_traitable.py +33 -0
  34. core_10x/manual_tests/k10x_test1.py +13 -0
  35. core_10x/manual_tests/named_constant_test.py +121 -0
  36. core_10x/manual_tests/nucleus_trivial_test.py +42 -0
  37. core_10x/manual_tests/polars_test.py +14 -0
  38. core_10x/manual_tests/py_class_test.py +4 -0
  39. core_10x/manual_tests/rc_test.py +42 -0
  40. core_10x/manual_tests/rdate_test.py +12 -0
  41. core_10x/manual_tests/reference_serialization_bug.py +19 -0
  42. core_10x/manual_tests/resource_trivial_test.py +10 -0
  43. core_10x/manual_tests/store_uri_test.py +6 -0
  44. core_10x/manual_tests/trait_definition_test.py +19 -0
  45. core_10x/manual_tests/trait_filter_test.py +15 -0
  46. core_10x/manual_tests/trait_flag_modification_test.py +42 -0
  47. core_10x/manual_tests/trait_modification_bug.py +26 -0
  48. core_10x/manual_tests/traitable_as_of_test.py +82 -0
  49. core_10x/manual_tests/traitable_heir_test.py +39 -0
  50. core_10x/manual_tests/traitable_history_test.py +41 -0
  51. core_10x/manual_tests/traitable_serialization_test.py +54 -0
  52. core_10x/manual_tests/traitable_trivial_test.py +71 -0
  53. core_10x/manual_tests/trivial_graph_test.py +16 -0
  54. core_10x/manual_tests/ts_class_association_test.py +64 -0
  55. core_10x/manual_tests/ts_trivial_test.py +35 -0
  56. core_10x/named_constant.py +425 -0
  57. core_10x/nucleus.py +81 -0
  58. core_10x/package_manifest.py +85 -0
  59. core_10x/package_refactoring.py +153 -0
  60. core_10x/py_class.py +431 -0
  61. core_10x/rc.py +155 -0
  62. core_10x/rdate.py +339 -0
  63. core_10x/resource.py +189 -0
  64. core_10x/roman_number.py +67 -0
  65. core_10x/testlib/__init__.py +0 -0
  66. core_10x/testlib/test_store.py +240 -0
  67. core_10x/testlib/traitable_history_tests.py +787 -0
  68. core_10x/testlib/ts_tests.py +280 -0
  69. core_10x/trait.py +377 -0
  70. core_10x/trait_definition.py +176 -0
  71. core_10x/trait_filter.py +205 -0
  72. core_10x/trait_method_error.py +36 -0
  73. core_10x/traitable.py +1082 -0
  74. core_10x/traitable_cli.py +153 -0
  75. core_10x/traitable_heir.py +33 -0
  76. core_10x/traitable_id.py +31 -0
  77. core_10x/ts_store.py +172 -0
  78. core_10x/ts_store_type.py +26 -0
  79. core_10x/ts_union.py +147 -0
  80. core_10x/ui_hint.py +153 -0
  81. core_10x/unit_tests/test_concrete_traits.py +156 -0
  82. core_10x/unit_tests/test_converters.py +51 -0
  83. core_10x/unit_tests/test_curve.py +157 -0
  84. core_10x/unit_tests/test_directory.py +54 -0
  85. core_10x/unit_tests/test_documentation.py +172 -0
  86. core_10x/unit_tests/test_environment_variables.py +15 -0
  87. core_10x/unit_tests/test_filters.py +239 -0
  88. core_10x/unit_tests/test_graph.py +348 -0
  89. core_10x/unit_tests/test_named_constant.py +98 -0
  90. core_10x/unit_tests/test_rc.py +11 -0
  91. core_10x/unit_tests/test_rdate.py +484 -0
  92. core_10x/unit_tests/test_trait_method_error.py +80 -0
  93. core_10x/unit_tests/test_trait_modification.py +19 -0
  94. core_10x/unit_tests/test_traitable.py +959 -0
  95. core_10x/unit_tests/test_traitable_history.py +1 -0
  96. core_10x/unit_tests/test_ts_store.py +1 -0
  97. core_10x/unit_tests/test_ts_union.py +369 -0
  98. core_10x/unit_tests/test_ui_nodes.py +81 -0
  99. core_10x/unit_tests/test_xxcalendar.py +471 -0
  100. core_10x/vault/__init__.py +0 -0
  101. core_10x/vault/sec_keys.py +133 -0
  102. core_10x/vault/security_keys_old.py +168 -0
  103. core_10x/vault/vault.py +56 -0
  104. core_10x/vault/vault_traitable.py +56 -0
  105. core_10x/vault/vault_user.py +70 -0
  106. core_10x/xdate_time.py +136 -0
  107. core_10x/xnone.py +71 -0
  108. core_10x/xxcalendar.py +228 -0
  109. infra_10x/__init__.py +0 -0
  110. infra_10x/manual_tests/__init__.py +0 -0
  111. infra_10x/manual_tests/test_misc.py +16 -0
  112. infra_10x/manual_tests/test_prepare_filter_and_pipeline.py +25 -0
  113. infra_10x/mongodb_admin.py +111 -0
  114. infra_10x/mongodb_store.py +346 -0
  115. infra_10x/mongodb_utils.py +129 -0
  116. infra_10x/unit_tests/conftest.py +13 -0
  117. infra_10x/unit_tests/test_mongo_db.py +36 -0
  118. infra_10x/unit_tests/test_mongo_history.py +1 -0
  119. py10x_universe-0.1.3.dist-info/METADATA +406 -0
  120. py10x_universe-0.1.3.dist-info/RECORD +214 -0
  121. py10x_universe-0.1.3.dist-info/WHEEL +4 -0
  122. py10x_universe-0.1.3.dist-info/licenses/LICENSE +21 -0
  123. ui_10x/__init__.py +0 -0
  124. ui_10x/apps/__init__.py +0 -0
  125. ui_10x/apps/collection_editor_app.py +100 -0
  126. ui_10x/choice.py +212 -0
  127. ui_10x/collection_editor.py +135 -0
  128. ui_10x/concrete_trait_widgets.py +220 -0
  129. ui_10x/conftest.py +8 -0
  130. ui_10x/entity_stocker.py +173 -0
  131. ui_10x/examples/__init__.py +0 -0
  132. ui_10x/examples/_guess_word_data.py +14076 -0
  133. ui_10x/examples/collection_editor.py +17 -0
  134. ui_10x/examples/date_selector.py +14 -0
  135. ui_10x/examples/entity_stocker.py +18 -0
  136. ui_10x/examples/guess_word.py +392 -0
  137. ui_10x/examples/message_box.py +20 -0
  138. ui_10x/examples/multi_choice.py +17 -0
  139. ui_10x/examples/py_data_browser.py +66 -0
  140. ui_10x/examples/radiobox.py +29 -0
  141. ui_10x/examples/single_choice.py +31 -0
  142. ui_10x/examples/style_sheet.py +47 -0
  143. ui_10x/examples/trivial_entity_editor.py +18 -0
  144. ui_10x/platform.py +20 -0
  145. ui_10x/platform_interface.py +517 -0
  146. ui_10x/py_data_browser.py +249 -0
  147. ui_10x/qt6/__init__.py +0 -0
  148. ui_10x/qt6/conftest.py +8 -0
  149. ui_10x/qt6/manual_tests/__init__.py +0 -0
  150. ui_10x/qt6/manual_tests/basic_test.py +35 -0
  151. ui_10x/qt6/platform_implementation.py +275 -0
  152. ui_10x/qt6/utils.py +665 -0
  153. ui_10x/rio/__init__.py +0 -0
  154. ui_10x/rio/apps/examples/examples/__init__.py +22 -0
  155. ui_10x/rio/apps/examples/examples/components/__init__.py +3 -0
  156. ui_10x/rio/apps/examples/examples/components/collection_editor.py +15 -0
  157. ui_10x/rio/apps/examples/examples/pages/collection_editor.py +21 -0
  158. ui_10x/rio/apps/examples/examples/pages/login_page.py +88 -0
  159. ui_10x/rio/apps/examples/examples/pages/style_sheet.py +21 -0
  160. ui_10x/rio/apps/examples/rio.toml +14 -0
  161. ui_10x/rio/component_builder.py +497 -0
  162. ui_10x/rio/components/__init__.py +9 -0
  163. ui_10x/rio/components/group_box.py +31 -0
  164. ui_10x/rio/components/labeled_checkbox.py +18 -0
  165. ui_10x/rio/components/line_edit.py +37 -0
  166. ui_10x/rio/components/radio_button.py +32 -0
  167. ui_10x/rio/components/separator.py +24 -0
  168. ui_10x/rio/components/splitter.py +121 -0
  169. ui_10x/rio/components/tree_view.py +75 -0
  170. ui_10x/rio/conftest.py +35 -0
  171. ui_10x/rio/internals/__init__.py +0 -0
  172. ui_10x/rio/internals/app.py +192 -0
  173. ui_10x/rio/manual_tests/__init__.py +0 -0
  174. ui_10x/rio/manual_tests/basic_test.py +24 -0
  175. ui_10x/rio/manual_tests/splitter.py +27 -0
  176. ui_10x/rio/platform_implementation.py +91 -0
  177. ui_10x/rio/style_sheet.py +53 -0
  178. ui_10x/rio/unit_tests/test_collection_editor.py +68 -0
  179. ui_10x/rio/unit_tests/test_internals.py +630 -0
  180. ui_10x/rio/unit_tests/test_style_sheet.py +37 -0
  181. ui_10x/rio/widgets/__init__.py +46 -0
  182. ui_10x/rio/widgets/application.py +109 -0
  183. ui_10x/rio/widgets/button.py +48 -0
  184. ui_10x/rio/widgets/button_group.py +60 -0
  185. ui_10x/rio/widgets/calendar.py +23 -0
  186. ui_10x/rio/widgets/checkbox.py +24 -0
  187. ui_10x/rio/widgets/dialog.py +137 -0
  188. ui_10x/rio/widgets/group_box.py +27 -0
  189. ui_10x/rio/widgets/layout.py +34 -0
  190. ui_10x/rio/widgets/line_edit.py +37 -0
  191. ui_10x/rio/widgets/list.py +105 -0
  192. ui_10x/rio/widgets/message_box.py +70 -0
  193. ui_10x/rio/widgets/scroll_area.py +31 -0
  194. ui_10x/rio/widgets/spacer.py +6 -0
  195. ui_10x/rio/widgets/splitter.py +45 -0
  196. ui_10x/rio/widgets/text_edit.py +28 -0
  197. ui_10x/rio/widgets/tree.py +89 -0
  198. ui_10x/rio/widgets/unit_tests/test_button.py +101 -0
  199. ui_10x/rio/widgets/unit_tests/test_button_group.py +33 -0
  200. ui_10x/rio/widgets/unit_tests/test_calendar.py +114 -0
  201. ui_10x/rio/widgets/unit_tests/test_checkbox.py +109 -0
  202. ui_10x/rio/widgets/unit_tests/test_group_box.py +158 -0
  203. ui_10x/rio/widgets/unit_tests/test_label.py +43 -0
  204. ui_10x/rio/widgets/unit_tests/test_line_edit.py +140 -0
  205. ui_10x/rio/widgets/unit_tests/test_list.py +146 -0
  206. ui_10x/table_header_view.py +305 -0
  207. ui_10x/table_view.py +174 -0
  208. ui_10x/trait_editor.py +189 -0
  209. ui_10x/trait_widget.py +131 -0
  210. ui_10x/traitable_editor.py +200 -0
  211. ui_10x/traitable_view.py +131 -0
  212. ui_10x/unit_tests/conftest.py +8 -0
  213. ui_10x/unit_tests/test_platform.py +9 -0
  214. ui_10x/utils.py +661 -0
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