deepfos 1.1.60__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 (175) hide show
  1. deepfos/__init__.py +6 -0
  2. deepfos/_version.py +21 -0
  3. deepfos/algo/__init__.py +0 -0
  4. deepfos/algo/graph.py +171 -0
  5. deepfos/algo/segtree.py +31 -0
  6. deepfos/api/V1_1/__init__.py +0 -0
  7. deepfos/api/V1_1/business_model.py +119 -0
  8. deepfos/api/V1_1/dimension.py +599 -0
  9. deepfos/api/V1_1/models/__init__.py +0 -0
  10. deepfos/api/V1_1/models/business_model.py +1033 -0
  11. deepfos/api/V1_1/models/dimension.py +2768 -0
  12. deepfos/api/V1_2/__init__.py +0 -0
  13. deepfos/api/V1_2/dimension.py +285 -0
  14. deepfos/api/V1_2/models/__init__.py +0 -0
  15. deepfos/api/V1_2/models/dimension.py +2923 -0
  16. deepfos/api/__init__.py +0 -0
  17. deepfos/api/account.py +167 -0
  18. deepfos/api/accounting_engines.py +147 -0
  19. deepfos/api/app.py +626 -0
  20. deepfos/api/approval_process.py +198 -0
  21. deepfos/api/base.py +983 -0
  22. deepfos/api/business_model.py +160 -0
  23. deepfos/api/consolidation.py +129 -0
  24. deepfos/api/consolidation_process.py +106 -0
  25. deepfos/api/datatable.py +341 -0
  26. deepfos/api/deep_pipeline.py +61 -0
  27. deepfos/api/deepconnector.py +36 -0
  28. deepfos/api/deepfos_task.py +92 -0
  29. deepfos/api/deepmodel.py +188 -0
  30. deepfos/api/dimension.py +486 -0
  31. deepfos/api/financial_model.py +319 -0
  32. deepfos/api/journal_model.py +119 -0
  33. deepfos/api/journal_template.py +132 -0
  34. deepfos/api/memory_financial_model.py +98 -0
  35. deepfos/api/models/__init__.py +3 -0
  36. deepfos/api/models/account.py +483 -0
  37. deepfos/api/models/accounting_engines.py +756 -0
  38. deepfos/api/models/app.py +1338 -0
  39. deepfos/api/models/approval_process.py +1043 -0
  40. deepfos/api/models/base.py +234 -0
  41. deepfos/api/models/business_model.py +805 -0
  42. deepfos/api/models/consolidation.py +711 -0
  43. deepfos/api/models/consolidation_process.py +248 -0
  44. deepfos/api/models/datatable_mysql.py +427 -0
  45. deepfos/api/models/deep_pipeline.py +55 -0
  46. deepfos/api/models/deepconnector.py +28 -0
  47. deepfos/api/models/deepfos_task.py +386 -0
  48. deepfos/api/models/deepmodel.py +308 -0
  49. deepfos/api/models/dimension.py +1576 -0
  50. deepfos/api/models/financial_model.py +1796 -0
  51. deepfos/api/models/journal_model.py +341 -0
  52. deepfos/api/models/journal_template.py +854 -0
  53. deepfos/api/models/memory_financial_model.py +478 -0
  54. deepfos/api/models/platform.py +178 -0
  55. deepfos/api/models/python.py +221 -0
  56. deepfos/api/models/reconciliation_engine.py +411 -0
  57. deepfos/api/models/reconciliation_report.py +161 -0
  58. deepfos/api/models/role_strategy.py +884 -0
  59. deepfos/api/models/smartlist.py +237 -0
  60. deepfos/api/models/space.py +1137 -0
  61. deepfos/api/models/system.py +1065 -0
  62. deepfos/api/models/variable.py +463 -0
  63. deepfos/api/models/workflow.py +946 -0
  64. deepfos/api/platform.py +199 -0
  65. deepfos/api/python.py +90 -0
  66. deepfos/api/reconciliation_engine.py +181 -0
  67. deepfos/api/reconciliation_report.py +64 -0
  68. deepfos/api/role_strategy.py +234 -0
  69. deepfos/api/smartlist.py +69 -0
  70. deepfos/api/space.py +582 -0
  71. deepfos/api/system.py +372 -0
  72. deepfos/api/variable.py +154 -0
  73. deepfos/api/workflow.py +264 -0
  74. deepfos/boost/__init__.py +6 -0
  75. deepfos/boost/py_jstream.py +89 -0
  76. deepfos/boost/py_pandas.py +20 -0
  77. deepfos/cache.py +121 -0
  78. deepfos/config.py +6 -0
  79. deepfos/core/__init__.py +27 -0
  80. deepfos/core/cube/__init__.py +10 -0
  81. deepfos/core/cube/_base.py +462 -0
  82. deepfos/core/cube/constants.py +21 -0
  83. deepfos/core/cube/cube.py +408 -0
  84. deepfos/core/cube/formula.py +707 -0
  85. deepfos/core/cube/syscube.py +532 -0
  86. deepfos/core/cube/typing.py +7 -0
  87. deepfos/core/cube/utils.py +238 -0
  88. deepfos/core/dimension/__init__.py +11 -0
  89. deepfos/core/dimension/_base.py +506 -0
  90. deepfos/core/dimension/dimcreator.py +184 -0
  91. deepfos/core/dimension/dimension.py +472 -0
  92. deepfos/core/dimension/dimexpr.py +271 -0
  93. deepfos/core/dimension/dimmember.py +155 -0
  94. deepfos/core/dimension/eledimension.py +22 -0
  95. deepfos/core/dimension/filters.py +99 -0
  96. deepfos/core/dimension/sysdimension.py +168 -0
  97. deepfos/core/logictable/__init__.py +5 -0
  98. deepfos/core/logictable/_cache.py +141 -0
  99. deepfos/core/logictable/_operator.py +663 -0
  100. deepfos/core/logictable/nodemixin.py +673 -0
  101. deepfos/core/logictable/sqlcondition.py +609 -0
  102. deepfos/core/logictable/tablemodel.py +497 -0
  103. deepfos/db/__init__.py +36 -0
  104. deepfos/db/cipher.py +660 -0
  105. deepfos/db/clickhouse.py +191 -0
  106. deepfos/db/connector.py +195 -0
  107. deepfos/db/daclickhouse.py +171 -0
  108. deepfos/db/dameng.py +101 -0
  109. deepfos/db/damysql.py +189 -0
  110. deepfos/db/dbkits.py +358 -0
  111. deepfos/db/deepengine.py +99 -0
  112. deepfos/db/deepmodel.py +82 -0
  113. deepfos/db/deepmodel_kingbase.py +83 -0
  114. deepfos/db/edb.py +214 -0
  115. deepfos/db/gauss.py +83 -0
  116. deepfos/db/kingbase.py +83 -0
  117. deepfos/db/mysql.py +184 -0
  118. deepfos/db/oracle.py +131 -0
  119. deepfos/db/postgresql.py +192 -0
  120. deepfos/db/sqlserver.py +99 -0
  121. deepfos/db/utils.py +135 -0
  122. deepfos/element/__init__.py +89 -0
  123. deepfos/element/accounting.py +348 -0
  124. deepfos/element/apvlprocess.py +215 -0
  125. deepfos/element/base.py +398 -0
  126. deepfos/element/bizmodel.py +1269 -0
  127. deepfos/element/datatable.py +2467 -0
  128. deepfos/element/deep_pipeline.py +186 -0
  129. deepfos/element/deepconnector.py +59 -0
  130. deepfos/element/deepmodel.py +1806 -0
  131. deepfos/element/dimension.py +1254 -0
  132. deepfos/element/fact_table.py +427 -0
  133. deepfos/element/finmodel.py +1485 -0
  134. deepfos/element/journal.py +840 -0
  135. deepfos/element/journal_template.py +943 -0
  136. deepfos/element/pyscript.py +412 -0
  137. deepfos/element/reconciliation.py +553 -0
  138. deepfos/element/rolestrategy.py +243 -0
  139. deepfos/element/smartlist.py +457 -0
  140. deepfos/element/variable.py +756 -0
  141. deepfos/element/workflow.py +560 -0
  142. deepfos/exceptions/__init__.py +239 -0
  143. deepfos/exceptions/hook.py +86 -0
  144. deepfos/lazy.py +104 -0
  145. deepfos/lazy_import.py +84 -0
  146. deepfos/lib/__init__.py +0 -0
  147. deepfos/lib/_javaobj.py +366 -0
  148. deepfos/lib/asynchronous.py +879 -0
  149. deepfos/lib/concurrency.py +107 -0
  150. deepfos/lib/constant.py +39 -0
  151. deepfos/lib/decorator.py +310 -0
  152. deepfos/lib/deepchart.py +778 -0
  153. deepfos/lib/deepux.py +477 -0
  154. deepfos/lib/discovery.py +273 -0
  155. deepfos/lib/edb_lexer.py +789 -0
  156. deepfos/lib/eureka.py +156 -0
  157. deepfos/lib/filterparser.py +751 -0
  158. deepfos/lib/httpcli.py +106 -0
  159. deepfos/lib/jsonstreamer.py +80 -0
  160. deepfos/lib/msg.py +394 -0
  161. deepfos/lib/nacos.py +225 -0
  162. deepfos/lib/patch.py +92 -0
  163. deepfos/lib/redis.py +241 -0
  164. deepfos/lib/serutils.py +181 -0
  165. deepfos/lib/stopwatch.py +99 -0
  166. deepfos/lib/subtask.py +572 -0
  167. deepfos/lib/sysutils.py +703 -0
  168. deepfos/lib/utils.py +1003 -0
  169. deepfos/local.py +160 -0
  170. deepfos/options.py +670 -0
  171. deepfos/translation.py +237 -0
  172. deepfos-1.1.60.dist-info/METADATA +33 -0
  173. deepfos-1.1.60.dist-info/RECORD +175 -0
  174. deepfos-1.1.60.dist-info/WHEEL +5 -0
  175. deepfos-1.1.60.dist-info/top_level.txt +1 -0
deepfos/options.py ADDED
@@ -0,0 +1,670 @@
1
+ import configparser
2
+ import sys
3
+ import warnings
4
+ from enum import Enum
5
+ from functools import partial
6
+ from typing import (
7
+ Iterable, TYPE_CHECKING, TypeVar, Optional,
8
+ Callable, Any, Tuple, Union, List, overload,
9
+ Generic, Type, Dict
10
+ )
11
+ from contextvars import ContextVar
12
+ import locale
13
+ import importlib
14
+ from loguru import logger
15
+
16
+ from deepfos.exceptions import (
17
+ OptionTypeError, OptionValueError,
18
+ OptionNotSetError, BaseOptionError,
19
+ )
20
+ from deepfos.config import USE_CONTEXT_OPTION
21
+
22
+
23
+ __all__ = ['OPTION', 'set_option', 'show_option']
24
+
25
+
26
+ # -----------------------------------------------------------------------------
27
+ # utils
28
+ class _Action(Enum):
29
+ PASS = 0
30
+ WARN = 1
31
+ RAISE = 2
32
+
33
+
34
+ T_Stub = TypeVar('T_Stub')
35
+ T_SingleOrMulti = Union[T_Stub, Iterable[T_Stub]]
36
+ T_Opt = TypeVar('T_Opt')
37
+ T_Category = TypeVar('T_Category', bound='_Category')
38
+
39
+
40
+ # -----------------------------------------------------------------------------
41
+ # extra check / trigger / convertors
42
+ def _check_number_range(number, minimum=None, maximum=None):
43
+ if minimum is not None:
44
+ if number < minimum:
45
+ raise ValueError(f"Value should be greater than {minimum}")
46
+ if maximum is not None:
47
+ if number > maximum:
48
+ raise ValueError(f"Value should be less than {maximum}")
49
+
50
+
51
+ def _reset_level(level: str):
52
+ logger.remove()
53
+ if level.upper() == 'DISABLED':
54
+ return
55
+ logger.configure(
56
+ handlers=[{
57
+ "level": level.upper(),
58
+ "sink": sys.stdout,
59
+ "format": "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
60
+ "<yellow>{process}</yellow> | "
61
+ "<yellow>{thread.name: <10}</yellow> | "
62
+ "<level>{level: <8}</level> | "
63
+ "<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan>"
64
+ " - "
65
+ "<level>{message}</level>",
66
+ }],
67
+ )
68
+
69
+
70
+ def _check_keys(value: dict, keys: Optional[Iterable[str]] = None):
71
+ missing = [
72
+ key for key in keys or []
73
+ if key not in value
74
+ ]
75
+ if missing:
76
+ raise KeyError(f'Missing keys: {missing}')
77
+
78
+
79
+ def _set_locale(value: dict):
80
+ if 'language' in value:
81
+ if _General.locale.unset:
82
+ OPTION.general.locale = value['language']
83
+
84
+
85
+ def _normalize_locale(loc: str) -> str:
86
+ normliazed_locale = locale.normalize(loc.replace('-', '_'))
87
+ return normliazed_locale.split('.', maxsplit=1)[0]
88
+
89
+
90
+ def _load_module(path: str):
91
+ if not path:
92
+ return
93
+ try:
94
+ if path.endswith('.py'):
95
+ path = path[:-3]
96
+ package, sep, module = path.rpartition('.')
97
+ if package:
98
+ importlib.import_module('.' + module, package=package)
99
+ else:
100
+ importlib.import_module(module)
101
+ except (ImportError, TypeError):
102
+ logger.debug(f'Translation file not found in {path}')
103
+
104
+
105
+ def _activate_cache(value: bool):
106
+ if value is True:
107
+ logger.info('Element info cache activated.')
108
+
109
+
110
+ def _ensure_discovery_server_is_set(value):
111
+ if not value:
112
+ return
113
+
114
+ impl = OPTION.discovery.implementation
115
+ server_opt = {
116
+ 'eureka': OPTION.server.eureka,
117
+ 'nacos': OPTION.nacos.server
118
+ }[impl]
119
+
120
+ if server_opt is None:
121
+ raise RuntimeError(f"Missing server configure for {impl}")
122
+
123
+ from deepfos.lib.discovery import ServiceDiscovery
124
+ from deepfos.lib.asynchronous import evloop
125
+ task = evloop.apply(ServiceDiscovery.start())
126
+ task.die_in_peace = True
127
+ # Will stop per python task because of evloop.stop
128
+ evloop.register_shutdown(ServiceDiscovery.stop, is_coro=True)
129
+
130
+
131
+ def _maybe_set_discovery_enabled(value):
132
+ if value:
133
+ OPTION.discovery.enabled = True
134
+
135
+
136
+ # -----------------------------------------------------------------------------
137
+ # Option class
138
+ class _Option(Generic[T_Opt]):
139
+ """
140
+ 配置项基类
141
+
142
+ Args:
143
+ default: 默认值
144
+ val_type: 允许的类型, 多类型时传入tuple of type
145
+ val_choices: 允许的取值
146
+ write_warning: 当配置项被设定为某些值时,抛出对应的warning。
147
+ on_set: 其他的校验,可传入callable,设定值将作为参数传入
148
+ if_unset: 当配置项未设置就被使用时,对应的行为。
149
+
150
+ - _Action.PASS: 忽略
151
+ - _Action.WARN: 抛出warning
152
+ - _Action.RAISE: 报错
153
+
154
+ depends: 使用该参数依赖的校验函数。
155
+ convertor: 配置值被真正设置前调用的转换函数
156
+ deprecated: 配置项是否已废弃
157
+ replacement: 当前配置项的替代配置,如果当前配置项被设置,
158
+ 替代配置也会同步该设置
159
+
160
+ Example:
161
+ .. code-block:: python
162
+
163
+ class _CubeOption(_Category):
164
+ key = _Option(False, val_type=bool,
165
+ write_warning=[(True: "warn!!!")])
166
+
167
+ `key` 默认值为 `False`, 只能设置为 `bool` 类型。
168
+ 并且在设置为 `True` 时会提示warning信息。
169
+
170
+ """
171
+ def __init__(
172
+ self,
173
+ default: Optional[T_Opt],
174
+ val_type: Optional[Type[T_Opt]] = None,
175
+ val_choices: Tuple = (),
176
+ write_warning: Optional[List[Tuple[Any, str]]] = None,
177
+ on_set: T_SingleOrMulti[Callable[[Any], None]] = (),
178
+ if_unset: _Action = _Action.PASS,
179
+ depends: T_SingleOrMulti[Callable[[], None]] = (),
180
+ convertor: Optional[Callable[[Any], Any]] = None,
181
+ deprecated: bool = False,
182
+ replacement: Optional['_Option'] = None,
183
+ ):
184
+ if isinstance(on_set, Iterable):
185
+ self.extra_check = on_set
186
+ else:
187
+ self.extra_check = (on_set,)
188
+
189
+ self.val_type = val_type
190
+ self.val_choices = val_choices
191
+ self.default = default
192
+ self.warning = write_warning or []
193
+ self.unset_action = if_unset
194
+ if isinstance(depends, Iterable):
195
+ self.depends = list(depends)
196
+ else:
197
+ self.depends = [depends]
198
+ self.convertor = convertor
199
+ self.unset = True
200
+ self.deprecated = deprecated
201
+ self.replacement = replacement
202
+ self._deprecattion_warned = False
203
+
204
+ def __set_name__(self, owner, name):
205
+ self._var_name = '__' + name
206
+ self._display_name = name
207
+
208
+ @overload
209
+ def __get__(self, instance: None, owner) -> '_Option':
210
+ ...
211
+
212
+ @overload
213
+ def __get__(self, instance: Any, owner) -> T_Opt:
214
+ ...
215
+
216
+ def __get__(self, instance: Optional[Any], owner) -> Union['_Option', T_Opt]:
217
+ if instance is None:
218
+ return self
219
+
220
+ if self.unset_action is _Action.RAISE:
221
+ raise OptionNotSetError(f"Option '{instance}.{self}' has not yet been set.")
222
+ elif self.unset_action is _Action.WARN:
223
+ warnings.warn(
224
+ f"Option '{instance}.{self}' has not yet been set.",
225
+ stacklevel=2
226
+ )
227
+
228
+ while self.depends:
229
+ self.depends.pop(-1)()
230
+
231
+ self.depends = []
232
+
233
+ try:
234
+ rslt = getattr(instance, self._var_name)
235
+ except AttributeError:
236
+ rslt = self.default
237
+ setattr(instance, self._var_name, rslt)
238
+ return rslt
239
+
240
+ def __set__(self, instance: Any, value: T_Opt):
241
+ if self.convertor is not None:
242
+ value = self.convertor(value)
243
+
244
+ if self.val_type is not None:
245
+ if not isinstance(value, self.val_type):
246
+ raise OptionTypeError(
247
+ f"'{instance}.{self}' can only be set as type "
248
+ f"'{self.val_type}', not '{type(value)}'.")
249
+
250
+ if self.val_choices:
251
+ if value not in self.val_choices:
252
+ raise OptionValueError(
253
+ f"'{instance}.{self}' can only be "
254
+ f"chosen from {self.val_type!r}.")
255
+
256
+ for do_check in self.extra_check:
257
+ do_check(value)
258
+
259
+ for match, warning_msg in self.warning:
260
+ if (callable(match) and match(value)) or value == match:
261
+ warnings.warn(warning_msg, stacklevel=2)
262
+ setattr(instance, self._var_name, value)
263
+ logger.info(f"Set option: {instance}.{self}={value}")
264
+
265
+ if self.deprecated and not self._deprecattion_warned:
266
+ msg = f"Option: '{instance}.{self}' is deprecated."
267
+ if self.replacement is not None:
268
+ msg += f" Use '{instance}.{self.replacement}' instead."
269
+ warnings.warn(msg, DeprecationWarning, stacklevel=2)
270
+ self._deprecattion_warned = True
271
+
272
+ if self.replacement is not None:
273
+ self.replacement.__set__(instance, value)
274
+
275
+ if self.unset_action is not _Action.PASS:
276
+ self.unset_action = _Action.PASS
277
+ self.unset = False
278
+
279
+ def __str__(self):
280
+ return self._display_name
281
+
282
+
283
+ # -----------------------------------------------------------------------------
284
+ # Nested Option
285
+ class _Category:
286
+ __id__ = ''
287
+
288
+ def __get__(self, instance, owner):
289
+ if instance is None:
290
+ return self
291
+ try:
292
+ inst = getattr(instance, self._var_name)
293
+ except AttributeError:
294
+ inst = self.__class__()
295
+ inst._name = self._name
296
+ setattr(instance, self._var_name, inst)
297
+ return inst
298
+
299
+ def __set__(self, instance, value):
300
+ raise TypeError("Category instance cannot be set.")
301
+
302
+ def __set_name__(self, owner, name):
303
+ self._name = name
304
+ self._var_name = '__' + name
305
+
306
+ def __str__(self):
307
+ return self._name
308
+
309
+ def set_default(self, env):
310
+ if (id_ := self.__id__) not in env:
311
+ return
312
+
313
+ self.load_dict(env[id_])
314
+
315
+ def load_dict(self, env: Dict[str, Any]):
316
+ for k, v in env.items():
317
+ if not hasattr(cls := self.__class__, k):
318
+ continue
319
+
320
+ option: _Option = getattr(cls, k)
321
+ if (
322
+ (val_type := option.val_type)
323
+ and val_type is not str
324
+ ):
325
+ v = val_type(v)
326
+ option.__set__(self, v)
327
+
328
+ @property
329
+ def _options(self):
330
+ return {
331
+ k: v for k, v in self.__class__.__dict__.items()
332
+ if isinstance(v, _Option)
333
+ }
334
+
335
+ def show_options(self, option=None):
336
+ name = self._name
337
+ options = self._options
338
+
339
+ if option is None:
340
+ print(f"{name}\n{len(name)*'-'}")
341
+ for opt in options:
342
+ print(f"{opt}: {self._get_option(opt)}")
343
+ print()
344
+ else:
345
+ if option not in options:
346
+ raise KeyError(f"No such option: '{name}.{option}'")
347
+ print(f"{name}.{option}: {self._get_option(option)}")
348
+
349
+ def _get_option(self, option) -> _Option:
350
+ try:
351
+ return getattr(self, option)
352
+ except BaseOptionError:
353
+ return getattr(self.__class__, option).default
354
+
355
+ def copy_from(self: T_Category, other: T_Category):
356
+ if type(other) is not self.__class__:
357
+ raise TypeError(
358
+ f"Cannot make copy from instance of {type(other)}, "
359
+ f"expect {self.__class__}.")
360
+
361
+ if not hasattr(other, '_name'):
362
+ raise ValueError(
363
+ "Could not make a copy because target category is not "
364
+ "properly initialized. Category is supposed to be a class variable.")
365
+
366
+ setattr(self, '_name', getattr(other, '_name'))
367
+ for attr, option in self._options.items():
368
+ try:
369
+ option.__set__(self, getattr(other, attr))
370
+ except BaseOptionError:
371
+ continue
372
+
373
+
374
+ class _Server(_Category):
375
+ __id__ = 'server_url'
376
+
377
+ base = _Option('http://', val_type=str)
378
+ app = _Option('http://app-server', val_type=str)
379
+ account = _Option('http://account-server', val_type=str)
380
+ system = _Option('http://system-server', val_type=str)
381
+ space = _Option('http://space-server', val_type=str)
382
+ platform_file = _Option('http://platform-file-server', val_type=str)
383
+ deepfos_task = _Option('http://deepfos-task-server', val_type=str)
384
+ eureka = _Option('http://eureka', val_type=str)
385
+
386
+ def __get__(self, instance, owner) -> '_Server':
387
+ """defined to help ide"""
388
+ return super().__get__(instance, owner)
389
+
390
+
391
+ class _API(_Category):
392
+ __id__ = 'api'
393
+
394
+ header = _Option({}, val_type=dict, on_set=_set_locale)
395
+ io_sync = _Option(True, val_type=bool)
396
+ verify_ssl = _Option(True, val_type=bool)
397
+ timeout = _Option(180, val_type=int, on_set=partial(
398
+ _check_number_range, minimum=0, maximum=7200))
399
+ dump_on_failure = _Option(False, val_type=bool)
400
+ dump_always = _Option(False, val_type=bool)
401
+ #: server clock offset (in micro seconds)
402
+ clock_offset = _Option(0, val_type=int)
403
+ cache_element_info = _Option(False, val_type=bool, on_set=_activate_cache)
404
+
405
+ def __get__(self, instance, owner) -> '_API':
406
+ """defined to help ide"""
407
+ return super().__get__(instance, owner)
408
+
409
+
410
+ class _General(_Category):
411
+ __id__ = 'general'
412
+
413
+ log_level = _Option(
414
+ 'DISABLED', val_type=str, on_set=_reset_level,
415
+ write_warning=[(lambda v: v.upper() == 'DISABLED', 'logger disabled!')])
416
+ for_server_use = _Option(False, val_type=bool)
417
+ use_eureka = _Option(True, val_type=bool, deprecated=True, on_set=_maybe_set_discovery_enabled)
418
+ coro_graceful_timeout = _Option(5, val_type=int)
419
+ task_info = _Option({}, val_type=dict, on_set=partial(_check_keys, keys=('task_id',)))
420
+ dev_mode = _Option(False, val_type=bool)
421
+ db_direct_access = _Option(False, val_type=bool)
422
+ locale = _Option("en_US", val_type=str, convertor=_normalize_locale)
423
+ preload_module = _Option(None, val_type=str, on_set=_load_module)
424
+ socket_communication = _Option(False, val_type=bool)
425
+ parallel_mode = _Option(False, val_type=bool)
426
+ socket_name = _Option(None, val_type=str)
427
+ preserve_concurrency = _Option(1, val_type=int)
428
+ response_display_length_on_error = _Option(20000, val_type=int)
429
+
430
+ def __get__(self, instance, owner) -> '_General':
431
+ """defined to help ide"""
432
+ return super().__get__(instance, owner)
433
+
434
+
435
+ class _Module(_Category):
436
+ __id__ = 'module'
437
+
438
+ src_celeryapp = _Option(None, val_type=str)
439
+ src_task = _Option(None, val_type=str)
440
+ src_options = _Option(None, val_type=str)
441
+ src_errors_classes = _Option(None, val_type=str)
442
+
443
+ def __get__(self, instance, owner) -> '_Module':
444
+ """defined to help ide"""
445
+ return super().__get__(instance, owner)
446
+
447
+
448
+ class _Redis(_Category):
449
+ __id__ = 'redis'
450
+
451
+ url = _Option('', val_type=str)
452
+
453
+ def __get__(self, instance, owner) -> '_Redis':
454
+ """defined to help ide"""
455
+ return super().__get__(instance, owner)
456
+
457
+
458
+ class _Edgedb(_Category):
459
+ __id__ = 'edgedb'
460
+
461
+ dsn = _Option('', val_type=str)
462
+ timeout = _Option(30, val_type=int)
463
+
464
+ def __get__(self, instance, owner) -> '_Edgedb':
465
+ """defined to help ide"""
466
+ return super().__get__(instance, owner)
467
+
468
+
469
+ class _Boost(_Category):
470
+ __id__ = 'boost'
471
+
472
+ skip_internal_existence_check = _Option(False, val_type=bool)
473
+
474
+ def __get__(self, instance, owner) -> '_Boost':
475
+ """defined to help ide"""
476
+ return super().__get__(instance, owner)
477
+
478
+
479
+ class _ServiceDiscovery(_Category):
480
+ __id__ = 'service_discovery'
481
+
482
+ #: 是否使用服务发现功能
483
+ enabled = _Option(False, val_type=bool, on_set=_ensure_discovery_server_is_set)
484
+ #: 服务注册发现使用的实现
485
+ implementation = _Option('eureka', val_type=str, val_choices=('eureka', 'nacos'))
486
+ #: 服务注册发现使用的缓存策略
487
+ cache_strategy = _Option(
488
+ 'ranked',
489
+ val_type=str,
490
+ val_choices=('ranked', 'roundrobin', 'random')
491
+ )
492
+
493
+ def __get__(self, instance, owner) -> '_ServiceDiscovery':
494
+ """defined to help ide"""
495
+ return super().__get__(instance, owner)
496
+
497
+
498
+ class _Nacos(_Category):
499
+ __id__ = 'nacos'
500
+
501
+ server = _Option(None, val_type=str)
502
+ cluster = _Option('DEFAULT', val_type=str)
503
+ namespace = _Option('public', val_type=str)
504
+ group = _Option('DEFAULT_GROUP', val_type=str)
505
+
506
+ def __get__(self, instance, owner) -> '_Nacos':
507
+ """defined to help ide"""
508
+ return super().__get__(instance, owner)
509
+
510
+
511
+ # -----------------------------------------------------------------------------
512
+ # Options
513
+ class _GlobalOptions:
514
+ general = _General()
515
+ server = _Server()
516
+ api = _API()
517
+ redis = _Redis()
518
+ module = _Module()
519
+ boost = _Boost()
520
+ discovery = _ServiceDiscovery()
521
+ nacos = _Nacos()
522
+ edgedb = _Edgedb()
523
+
524
+ def load_file(self, filepath):
525
+ parser = configparser.ConfigParser()
526
+ parser.read(filepath, encoding='utf8')
527
+ self.load_env(parser)
528
+
529
+ def load_env(self, env):
530
+ for attr in self._categories.values():
531
+ attr.set_default(env)
532
+
533
+ @property
534
+ def _categories(self):
535
+ return {
536
+ k: getattr(self, k) for k, v in self.__class__.__dict__.items()
537
+ if isinstance(v, _Category)
538
+ }
539
+
540
+ def show_options(self, category=None):
541
+ categories = self._categories
542
+
543
+ if category is None:
544
+ for ctgy in categories.values():
545
+ ctgy.show_options()
546
+ else:
547
+ if category not in categories:
548
+ raise KeyError(f"No such category: '{category}'.")
549
+ categories[category].show_options()
550
+
551
+ def __copy__(self):
552
+ cp = self.__class__()
553
+ for attr, category in self._categories.items():
554
+ getattr(cp, attr).copy_from(category)
555
+ return cp
556
+
557
+
558
+ def _get_nested(obj, nested_attr):
559
+ category = obj
560
+
561
+ *attrs, option = nested_attr.split('.')
562
+ for attr in attrs:
563
+ category = getattr(category, attr)
564
+
565
+ return getattr(category.__class__, option), category
566
+
567
+
568
+ # noinspection PyProtectedMember
569
+ def set_option(option, value):
570
+ """
571
+ 设定全局配置项。
572
+
573
+ Args:
574
+ option: 配置项名称
575
+ value: 设定值
576
+
577
+ Raises:
578
+ ValueError: 配置项不存在时
579
+ TypeError: 对目录值进行设定时
580
+
581
+ Example:
582
+ 全局配置为多层配置,设定时需传入设定值的完整名称。
583
+
584
+ .. code-block:: python
585
+
586
+ set_option('system.app_id', 2)
587
+ # 等价于
588
+ OPTION.system.app_id = 2
589
+
590
+ See Also:
591
+ :meth:`show_option`
592
+
593
+ """
594
+ if USE_CONTEXT_OPTION:
595
+ g_option = OPTION._option
596
+ else:
597
+ g_option = OPTION
598
+ try:
599
+ target, category = _get_nested(g_option, option)
600
+ target.__set__(category, value)
601
+ except AttributeError:
602
+ raise ValueError(f"Option: {option!r} is not available.") from None
603
+
604
+
605
+ # noinspection PyProtectedMember
606
+ def show_option(category_or_option=None):
607
+ """
608
+ 展示目前的配置项及其对应值
609
+
610
+ Args:
611
+ category_or_option: 需要展示目录或者具体的配置项,
612
+ 为 `None` 时显示全部配置项
613
+
614
+ See Also:
615
+ :meth:`set_option`
616
+
617
+ """
618
+ if category_or_option is None:
619
+ OPTION.show_options()
620
+ return
621
+ if USE_CONTEXT_OPTION:
622
+ assert isinstance(OPTION, _OptionCTX)
623
+ g_option = OPTION._option
624
+ else:
625
+ g_option = OPTION
626
+ try:
627
+ option, category = _get_nested(g_option, category_or_option)
628
+ category.show_options(str(option))
629
+ except AttributeError:
630
+ raise ValueError(f"Option: {category_or_option!r} is not available.") from None
631
+
632
+
633
+ _option_ctx: ContextVar[_GlobalOptions] = ContextVar('deepfos_global_option')
634
+
635
+
636
+ class _OptionCTX:
637
+ if TYPE_CHECKING:
638
+ general: _General
639
+ server: _Server
640
+ api: _API
641
+ redis: _Redis
642
+ module: _Module
643
+ boost: _Boost
644
+ discovery: _ServiceDiscovery
645
+ nacos: _Nacos
646
+
647
+ def show_options(self, category=None):
648
+ ...
649
+
650
+ def __init__(self):
651
+ self._token = _option_ctx.set(_GlobalOptions())
652
+
653
+ def create_local(self):
654
+ self._token = _option_ctx.set(_GlobalOptions())
655
+
656
+ @property
657
+ def _option(self) -> _GlobalOptions:
658
+ return _option_ctx.get()
659
+
660
+ def __getattr__(self, item):
661
+ return getattr(self._option, item)
662
+
663
+
664
+ OPTION: Union[_OptionCTX, _GlobalOptions]
665
+
666
+
667
+ if USE_CONTEXT_OPTION:
668
+ OPTION = _OptionCTX()
669
+ else:
670
+ OPTION = _GlobalOptions()