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.
- deepfos/__init__.py +6 -0
- deepfos/_version.py +21 -0
- deepfos/algo/__init__.py +0 -0
- deepfos/algo/graph.py +171 -0
- deepfos/algo/segtree.py +31 -0
- deepfos/api/V1_1/__init__.py +0 -0
- deepfos/api/V1_1/business_model.py +119 -0
- deepfos/api/V1_1/dimension.py +599 -0
- deepfos/api/V1_1/models/__init__.py +0 -0
- deepfos/api/V1_1/models/business_model.py +1033 -0
- deepfos/api/V1_1/models/dimension.py +2768 -0
- deepfos/api/V1_2/__init__.py +0 -0
- deepfos/api/V1_2/dimension.py +285 -0
- deepfos/api/V1_2/models/__init__.py +0 -0
- deepfos/api/V1_2/models/dimension.py +2923 -0
- deepfos/api/__init__.py +0 -0
- deepfos/api/account.py +167 -0
- deepfos/api/accounting_engines.py +147 -0
- deepfos/api/app.py +626 -0
- deepfos/api/approval_process.py +198 -0
- deepfos/api/base.py +983 -0
- deepfos/api/business_model.py +160 -0
- deepfos/api/consolidation.py +129 -0
- deepfos/api/consolidation_process.py +106 -0
- deepfos/api/datatable.py +341 -0
- deepfos/api/deep_pipeline.py +61 -0
- deepfos/api/deepconnector.py +36 -0
- deepfos/api/deepfos_task.py +92 -0
- deepfos/api/deepmodel.py +188 -0
- deepfos/api/dimension.py +486 -0
- deepfos/api/financial_model.py +319 -0
- deepfos/api/journal_model.py +119 -0
- deepfos/api/journal_template.py +132 -0
- deepfos/api/memory_financial_model.py +98 -0
- deepfos/api/models/__init__.py +3 -0
- deepfos/api/models/account.py +483 -0
- deepfos/api/models/accounting_engines.py +756 -0
- deepfos/api/models/app.py +1338 -0
- deepfos/api/models/approval_process.py +1043 -0
- deepfos/api/models/base.py +234 -0
- deepfos/api/models/business_model.py +805 -0
- deepfos/api/models/consolidation.py +711 -0
- deepfos/api/models/consolidation_process.py +248 -0
- deepfos/api/models/datatable_mysql.py +427 -0
- deepfos/api/models/deep_pipeline.py +55 -0
- deepfos/api/models/deepconnector.py +28 -0
- deepfos/api/models/deepfos_task.py +386 -0
- deepfos/api/models/deepmodel.py +308 -0
- deepfos/api/models/dimension.py +1576 -0
- deepfos/api/models/financial_model.py +1796 -0
- deepfos/api/models/journal_model.py +341 -0
- deepfos/api/models/journal_template.py +854 -0
- deepfos/api/models/memory_financial_model.py +478 -0
- deepfos/api/models/platform.py +178 -0
- deepfos/api/models/python.py +221 -0
- deepfos/api/models/reconciliation_engine.py +411 -0
- deepfos/api/models/reconciliation_report.py +161 -0
- deepfos/api/models/role_strategy.py +884 -0
- deepfos/api/models/smartlist.py +237 -0
- deepfos/api/models/space.py +1137 -0
- deepfos/api/models/system.py +1065 -0
- deepfos/api/models/variable.py +463 -0
- deepfos/api/models/workflow.py +946 -0
- deepfos/api/platform.py +199 -0
- deepfos/api/python.py +90 -0
- deepfos/api/reconciliation_engine.py +181 -0
- deepfos/api/reconciliation_report.py +64 -0
- deepfos/api/role_strategy.py +234 -0
- deepfos/api/smartlist.py +69 -0
- deepfos/api/space.py +582 -0
- deepfos/api/system.py +372 -0
- deepfos/api/variable.py +154 -0
- deepfos/api/workflow.py +264 -0
- deepfos/boost/__init__.py +6 -0
- deepfos/boost/py_jstream.py +89 -0
- deepfos/boost/py_pandas.py +20 -0
- deepfos/cache.py +121 -0
- deepfos/config.py +6 -0
- deepfos/core/__init__.py +27 -0
- deepfos/core/cube/__init__.py +10 -0
- deepfos/core/cube/_base.py +462 -0
- deepfos/core/cube/constants.py +21 -0
- deepfos/core/cube/cube.py +408 -0
- deepfos/core/cube/formula.py +707 -0
- deepfos/core/cube/syscube.py +532 -0
- deepfos/core/cube/typing.py +7 -0
- deepfos/core/cube/utils.py +238 -0
- deepfos/core/dimension/__init__.py +11 -0
- deepfos/core/dimension/_base.py +506 -0
- deepfos/core/dimension/dimcreator.py +184 -0
- deepfos/core/dimension/dimension.py +472 -0
- deepfos/core/dimension/dimexpr.py +271 -0
- deepfos/core/dimension/dimmember.py +155 -0
- deepfos/core/dimension/eledimension.py +22 -0
- deepfos/core/dimension/filters.py +99 -0
- deepfos/core/dimension/sysdimension.py +168 -0
- deepfos/core/logictable/__init__.py +5 -0
- deepfos/core/logictable/_cache.py +141 -0
- deepfos/core/logictable/_operator.py +663 -0
- deepfos/core/logictable/nodemixin.py +673 -0
- deepfos/core/logictable/sqlcondition.py +609 -0
- deepfos/core/logictable/tablemodel.py +497 -0
- deepfos/db/__init__.py +36 -0
- deepfos/db/cipher.py +660 -0
- deepfos/db/clickhouse.py +191 -0
- deepfos/db/connector.py +195 -0
- deepfos/db/daclickhouse.py +171 -0
- deepfos/db/dameng.py +101 -0
- deepfos/db/damysql.py +189 -0
- deepfos/db/dbkits.py +358 -0
- deepfos/db/deepengine.py +99 -0
- deepfos/db/deepmodel.py +82 -0
- deepfos/db/deepmodel_kingbase.py +83 -0
- deepfos/db/edb.py +214 -0
- deepfos/db/gauss.py +83 -0
- deepfos/db/kingbase.py +83 -0
- deepfos/db/mysql.py +184 -0
- deepfos/db/oracle.py +131 -0
- deepfos/db/postgresql.py +192 -0
- deepfos/db/sqlserver.py +99 -0
- deepfos/db/utils.py +135 -0
- deepfos/element/__init__.py +89 -0
- deepfos/element/accounting.py +348 -0
- deepfos/element/apvlprocess.py +215 -0
- deepfos/element/base.py +398 -0
- deepfos/element/bizmodel.py +1269 -0
- deepfos/element/datatable.py +2467 -0
- deepfos/element/deep_pipeline.py +186 -0
- deepfos/element/deepconnector.py +59 -0
- deepfos/element/deepmodel.py +1806 -0
- deepfos/element/dimension.py +1254 -0
- deepfos/element/fact_table.py +427 -0
- deepfos/element/finmodel.py +1485 -0
- deepfos/element/journal.py +840 -0
- deepfos/element/journal_template.py +943 -0
- deepfos/element/pyscript.py +412 -0
- deepfos/element/reconciliation.py +553 -0
- deepfos/element/rolestrategy.py +243 -0
- deepfos/element/smartlist.py +457 -0
- deepfos/element/variable.py +756 -0
- deepfos/element/workflow.py +560 -0
- deepfos/exceptions/__init__.py +239 -0
- deepfos/exceptions/hook.py +86 -0
- deepfos/lazy.py +104 -0
- deepfos/lazy_import.py +84 -0
- deepfos/lib/__init__.py +0 -0
- deepfos/lib/_javaobj.py +366 -0
- deepfos/lib/asynchronous.py +879 -0
- deepfos/lib/concurrency.py +107 -0
- deepfos/lib/constant.py +39 -0
- deepfos/lib/decorator.py +310 -0
- deepfos/lib/deepchart.py +778 -0
- deepfos/lib/deepux.py +477 -0
- deepfos/lib/discovery.py +273 -0
- deepfos/lib/edb_lexer.py +789 -0
- deepfos/lib/eureka.py +156 -0
- deepfos/lib/filterparser.py +751 -0
- deepfos/lib/httpcli.py +106 -0
- deepfos/lib/jsonstreamer.py +80 -0
- deepfos/lib/msg.py +394 -0
- deepfos/lib/nacos.py +225 -0
- deepfos/lib/patch.py +92 -0
- deepfos/lib/redis.py +241 -0
- deepfos/lib/serutils.py +181 -0
- deepfos/lib/stopwatch.py +99 -0
- deepfos/lib/subtask.py +572 -0
- deepfos/lib/sysutils.py +703 -0
- deepfos/lib/utils.py +1003 -0
- deepfos/local.py +160 -0
- deepfos/options.py +670 -0
- deepfos/translation.py +237 -0
- deepfos-1.1.60.dist-info/METADATA +33 -0
- deepfos-1.1.60.dist-info/RECORD +175 -0
- deepfos-1.1.60.dist-info/WHEEL +5 -0
- 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()
|