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
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
from typing import List, Dict, Optional, Union, Tuple, TYPE_CHECKING, Any # noqa
|
|
2
|
+
|
|
3
|
+
from .base import ElementBase, SyncMeta
|
|
4
|
+
from .datatable import (DataTableMySQL, get_table_class, AsyncDataTableMySQL,
|
|
5
|
+
T_AsyncDatatableInstance, T_DatatableInstance)
|
|
6
|
+
from deepfos.lib.decorator import cached_property
|
|
7
|
+
from deepfos.lib.constant import UNSET
|
|
8
|
+
from deepfos.api.approval_process import ApprovalProcessAPI
|
|
9
|
+
from deepfos.api.models.approval_process import (
|
|
10
|
+
ProcessOperationDto, OperationRecordDto,
|
|
11
|
+
ApprovalRecordVo, QueryRecordDto, ProcessConfigureVo, OperationRecordVo
|
|
12
|
+
)
|
|
13
|
+
from deepfos.lib.asynchronous import future_property
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
'AsyncApprovalProcess',
|
|
18
|
+
'ApprovalProcess',
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AsyncApprovalProcess(ElementBase[ApprovalProcessAPI]):
|
|
23
|
+
"""审批流"""
|
|
24
|
+
@future_property
|
|
25
|
+
async def meta(self) -> ProcessConfigureVo:
|
|
26
|
+
"""审批流的元配置信息"""
|
|
27
|
+
api = await self.wait_for('async_api')
|
|
28
|
+
ele_info = await self.wait_for('element_info')
|
|
29
|
+
meta = await api.configure.query(
|
|
30
|
+
folderId=ele_info.folderId,
|
|
31
|
+
elementName=self.element_name
|
|
32
|
+
)
|
|
33
|
+
del meta.processInfo.controlInfo.style
|
|
34
|
+
return meta
|
|
35
|
+
|
|
36
|
+
@cached_property
|
|
37
|
+
def record_table(self) -> T_DatatableInstance:
|
|
38
|
+
"""审批记录表"""
|
|
39
|
+
ctrl_info = self.meta.processInfo.controlInfo
|
|
40
|
+
if (ele_type := ctrl_info.approvalRecordTableElementType) is None:
|
|
41
|
+
cls = DataTableMySQL
|
|
42
|
+
else:
|
|
43
|
+
cls = get_table_class(ele_type)
|
|
44
|
+
return cls(
|
|
45
|
+
element_name=ctrl_info.approvalRecordTableName,
|
|
46
|
+
folder_id=ctrl_info.approvalRecordTableFolderId,
|
|
47
|
+
path=ctrl_info.approvalRecordTablePath
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
@cached_property
|
|
51
|
+
def async_record_table(self) -> T_AsyncDatatableInstance:
|
|
52
|
+
"""审批记录表"""
|
|
53
|
+
ctrl_info = self.meta.processInfo.controlInfo
|
|
54
|
+
if (ele_type := ctrl_info.approvalRecordTableElementType) is None:
|
|
55
|
+
cls = AsyncDataTableMySQL
|
|
56
|
+
else:
|
|
57
|
+
cls = get_table_class(ele_type, sync=False)
|
|
58
|
+
return cls(
|
|
59
|
+
element_name=ctrl_info.approvalRecordTableName,
|
|
60
|
+
folder_id=ctrl_info.approvalRecordTableFolderId,
|
|
61
|
+
path=ctrl_info.approvalRecordTablePath
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
@cached_property
|
|
65
|
+
def _name2opinfo(self) -> Dict[str, ProcessOperationDto]:
|
|
66
|
+
return {
|
|
67
|
+
op_info.name: op_info
|
|
68
|
+
for op_info in self.meta.processInfo.operationInfo
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
def get_operation_id(
|
|
72
|
+
self,
|
|
73
|
+
operation: str,
|
|
74
|
+
default: Any = UNSET,
|
|
75
|
+
) -> str:
|
|
76
|
+
"""获取审批操作id
|
|
77
|
+
|
|
78
|
+
根据审批操作编码获取操作id,当查询的审批操作
|
|
79
|
+
不存在时,如果传入default,将返回default值,
|
|
80
|
+
否则抛出 `KeyError` 。
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
operation: 审批操作编码
|
|
84
|
+
default: 默认值
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
审批操作id
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
if operation not in self._name2opinfo:
|
|
91
|
+
if default is not UNSET:
|
|
92
|
+
return default
|
|
93
|
+
raise KeyError(
|
|
94
|
+
f"Unknown operation: {operation} "
|
|
95
|
+
f"for approval process: {self.element_name}")
|
|
96
|
+
return self._name2opinfo[operation].id
|
|
97
|
+
|
|
98
|
+
async def operate(
|
|
99
|
+
self,
|
|
100
|
+
primary_kv: Dict[str, Union[str, int]],
|
|
101
|
+
operation: str = None,
|
|
102
|
+
operation_id: str = None,
|
|
103
|
+
remark: str = '',
|
|
104
|
+
roles: List[str] = None,
|
|
105
|
+
origin_status: str = None,
|
|
106
|
+
) -> OperationRecordVo:
|
|
107
|
+
"""操作审批流
|
|
108
|
+
|
|
109
|
+
根据审批数据和审批操作来操作审批流,如果不提供初始审批状态,
|
|
110
|
+
会查询当前的审批状态作为初始状态。
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
primary_kv: 审批数据的键值对
|
|
114
|
+
operation: 审批操作编码(界面可获取)
|
|
115
|
+
operation_id: 审批操作id
|
|
116
|
+
remark: 备注
|
|
117
|
+
roles: 角色方案
|
|
118
|
+
origin_status: 初始审批状态
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
审批操作结果
|
|
122
|
+
|
|
123
|
+
"""
|
|
124
|
+
if operation_id is not None:
|
|
125
|
+
op_id = operation_id
|
|
126
|
+
elif operation is not None:
|
|
127
|
+
op_id = self.get_operation_id(operation)
|
|
128
|
+
else:
|
|
129
|
+
raise ValueError('None of argumnet [operation_id, operation] is set.')
|
|
130
|
+
|
|
131
|
+
if roles is None:
|
|
132
|
+
roles = [-1]
|
|
133
|
+
|
|
134
|
+
if origin_status is None:
|
|
135
|
+
records = await self.get_record(primary_kv, roles)
|
|
136
|
+
origin_status = records[0].result_status
|
|
137
|
+
|
|
138
|
+
return await self.async_api.operation.record(OperationRecordDto(
|
|
139
|
+
folderId=self.element_info.folderId,
|
|
140
|
+
pcName=self.element_name,
|
|
141
|
+
processOperationId=op_id,
|
|
142
|
+
primaryKeyValue=primary_kv,
|
|
143
|
+
remark=remark,
|
|
144
|
+
roles=roles,
|
|
145
|
+
originStatus=origin_status
|
|
146
|
+
))
|
|
147
|
+
|
|
148
|
+
async def get_record(
|
|
149
|
+
self,
|
|
150
|
+
primary_kv: Dict[str, Any],
|
|
151
|
+
roles: List[str] = None,
|
|
152
|
+
) -> List[ApprovalRecordVo]:
|
|
153
|
+
"""获取审批记录列表
|
|
154
|
+
|
|
155
|
+
根据审批数据获取所有审批记录,
|
|
156
|
+
审批记录按时间倒序排列
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
primary_kv: 审批数据键值对
|
|
160
|
+
roles: 角色
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
审批记录列表
|
|
164
|
+
|
|
165
|
+
"""
|
|
166
|
+
if roles is None:
|
|
167
|
+
roles = [-1]
|
|
168
|
+
|
|
169
|
+
return await self.async_api.operation.get_record(QueryRecordDto(
|
|
170
|
+
folderId=self.element_info.folderId,
|
|
171
|
+
pcName=self.element_name,
|
|
172
|
+
roles=roles,
|
|
173
|
+
primaryKeyValue=primary_kv
|
|
174
|
+
))
|
|
175
|
+
|
|
176
|
+
def get_operation_info(self, operation: str) -> ProcessOperationDto:
|
|
177
|
+
"""获取审批操作所有信息
|
|
178
|
+
|
|
179
|
+
根据审批操作编码获取操作信息,当查询的审批操作
|
|
180
|
+
不存在时,会报错
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
operation: 审批操作编码
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
审批操作id
|
|
187
|
+
|
|
188
|
+
"""
|
|
189
|
+
return self._name2opinfo[operation]
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class ApprovalProcess(AsyncApprovalProcess, metaclass=SyncMeta):
|
|
193
|
+
synchronize = (
|
|
194
|
+
'operate',
|
|
195
|
+
'get_record',
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
if TYPE_CHECKING:
|
|
199
|
+
def operate(
|
|
200
|
+
self,
|
|
201
|
+
primary_kv: Dict[str, Union[str, int]],
|
|
202
|
+
operation: str = None,
|
|
203
|
+
operation_id: str = None,
|
|
204
|
+
remark: str = '',
|
|
205
|
+
roles: List[str] = None,
|
|
206
|
+
origin_status: str = None,
|
|
207
|
+
) -> OperationRecordVo: # pragma: no cover
|
|
208
|
+
...
|
|
209
|
+
|
|
210
|
+
def get_record(
|
|
211
|
+
self,
|
|
212
|
+
primary_kv: Dict[str, Any],
|
|
213
|
+
roles: List[str] = None,
|
|
214
|
+
) -> List[ApprovalRecordVo]: # pragma: no cover
|
|
215
|
+
...
|
deepfos/element/base.py
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import contextvars
|
|
3
|
+
from functools import wraps
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from deepfos import OPTION
|
|
7
|
+
from deepfos.api.app import AppAPI
|
|
8
|
+
from deepfos.api.base import DynamicRootAPI
|
|
9
|
+
from deepfos.api.models.app import (
|
|
10
|
+
QueryElementInfoByTypeDto as ElementModel,
|
|
11
|
+
ElementRelationInfo, ModuleServerNameVO
|
|
12
|
+
)
|
|
13
|
+
from deepfos.exceptions import *
|
|
14
|
+
from deepfos.lib.asynchronous import evloop, future_property, FuturePropertyMeta
|
|
15
|
+
from deepfos.lib.constant import RE_SERVER_NAME_PARSER, RE_MODULEID_PARSER
|
|
16
|
+
from deepfos.lib.decorator import cached_property
|
|
17
|
+
from deepfos.lib.concurrency import ParallelProxy
|
|
18
|
+
from typing import (
|
|
19
|
+
Type, Union, Dict, List, Generic, TypeVar,
|
|
20
|
+
get_origin, get_args
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = ['ElementBase', 'SyncMeta', 'synchronize']
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_HINT = """
|
|
27
|
+
如果不提供folder_id和path,将会使用元素名和元素类型进行全局搜索。
|
|
28
|
+
如果找到 **唯一匹配** 的元素,那么一切正常,否则将会报错。
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
_ARGS = {
|
|
32
|
+
"element_name": "元素名",
|
|
33
|
+
"folder_id": "元素所在的文件夹id",
|
|
34
|
+
"path": "元素所在的文件夹绝对路径",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
T_ElementInfoWithServer = Union[ModuleServerNameVO, ElementRelationInfo]
|
|
38
|
+
T_ApiClass = TypeVar('T_ApiClass', bound=DynamicRootAPI)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class _ElementMeta(FuturePropertyMeta):
|
|
42
|
+
__mangle_docs__ = True
|
|
43
|
+
|
|
44
|
+
def __new__(mcs, name, bases, namespace, **kwargs):
|
|
45
|
+
if '__doc__' not in namespace:
|
|
46
|
+
for base in bases:
|
|
47
|
+
if base.__doc__ is not None:
|
|
48
|
+
namespace['__doc__'] = base.__doc__
|
|
49
|
+
break
|
|
50
|
+
|
|
51
|
+
cls = super().__new__(mcs, name, bases, namespace, **kwargs)
|
|
52
|
+
|
|
53
|
+
if cls.__mangle_docs__:
|
|
54
|
+
cls_doc = mcs._split_doc(cls.__doc__)
|
|
55
|
+
cls.__doc__ = mcs._recover_doc(cls_doc)
|
|
56
|
+
|
|
57
|
+
if "api_class" not in namespace and ElementBase in bases:
|
|
58
|
+
for base in cls.__orig_bases__: # noqa
|
|
59
|
+
if get_origin(base) is ElementBase:
|
|
60
|
+
cls.api_class = get_args(base)[0]
|
|
61
|
+
break
|
|
62
|
+
else:
|
|
63
|
+
raise RuntimeError(f'Cannot resolve api class for {cls}')
|
|
64
|
+
|
|
65
|
+
return cls
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def _split_doc(doc: str):
|
|
69
|
+
cur_directive = 'main'
|
|
70
|
+
parsed = {
|
|
71
|
+
cur_directive: []
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if not doc:
|
|
75
|
+
return {
|
|
76
|
+
**parsed,
|
|
77
|
+
"Args": _ARGS,
|
|
78
|
+
"Hint": [_HINT]
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
directives = ['Args:', 'Hint:', 'Warnings:', 'Note:']
|
|
82
|
+
|
|
83
|
+
in_args = False
|
|
84
|
+
cur_directive = 'main'
|
|
85
|
+
parsed = {
|
|
86
|
+
cur_directive: []
|
|
87
|
+
}
|
|
88
|
+
last_arg = ''
|
|
89
|
+
|
|
90
|
+
for line in doc.splitlines():
|
|
91
|
+
if (striped_line := line.strip()) in directives:
|
|
92
|
+
cur_directive = striped_line[:-1]
|
|
93
|
+
|
|
94
|
+
if cur_directive == 'Args':
|
|
95
|
+
in_args = True
|
|
96
|
+
parsed[cur_directive] = {}
|
|
97
|
+
|
|
98
|
+
else:
|
|
99
|
+
in_args = False
|
|
100
|
+
parsed[cur_directive] = []
|
|
101
|
+
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
if in_args:
|
|
105
|
+
if striped_line:
|
|
106
|
+
k, _, v = striped_line.partition(':')
|
|
107
|
+
if _:
|
|
108
|
+
parsed[cur_directive][k.strip()] = v.strip()
|
|
109
|
+
last_arg = k
|
|
110
|
+
else:
|
|
111
|
+
parsed[cur_directive][last_arg] += striped_line.strip()
|
|
112
|
+
else:
|
|
113
|
+
parsed[cur_directive].append(line)
|
|
114
|
+
|
|
115
|
+
args = parsed.pop('Args', {})
|
|
116
|
+
hint = parsed.pop('Hint', [])
|
|
117
|
+
parsed['Args'] = {**_ARGS, **args}
|
|
118
|
+
|
|
119
|
+
hint.append(_HINT)
|
|
120
|
+
parsed['Hint'] = hint
|
|
121
|
+
|
|
122
|
+
return parsed
|
|
123
|
+
|
|
124
|
+
@staticmethod
|
|
125
|
+
def _recover_doc(doc_dict: Dict):
|
|
126
|
+
doc: List[str] = doc_dict.pop('main')
|
|
127
|
+
doc.append('\n')
|
|
128
|
+
args: Dict[str, str] = doc_dict.pop('Args')
|
|
129
|
+
|
|
130
|
+
doc.append('Args:')
|
|
131
|
+
doc.extend(f" {k}: {v}" for k, v in args.items())
|
|
132
|
+
doc.append('\n')
|
|
133
|
+
|
|
134
|
+
for k, v in doc_dict.items():
|
|
135
|
+
doc.append(k + ":")
|
|
136
|
+
doc.extend(v)
|
|
137
|
+
doc.append('\n')
|
|
138
|
+
|
|
139
|
+
return '\n'.join(doc)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class ElementBase(Generic[T_ApiClass], metaclass=_ElementMeta):
|
|
143
|
+
"""deepfos平台元素的基类"""
|
|
144
|
+
|
|
145
|
+
#: 元素绑定的api类
|
|
146
|
+
api_class: Type[T_ApiClass] = None
|
|
147
|
+
#: 元素类型
|
|
148
|
+
element_type: str = None
|
|
149
|
+
|
|
150
|
+
def __init__(
|
|
151
|
+
self,
|
|
152
|
+
element_name: str,
|
|
153
|
+
folder_id: str = None,
|
|
154
|
+
path: str = None,
|
|
155
|
+
server_name: str = None,
|
|
156
|
+
):
|
|
157
|
+
self.element_name = element_name
|
|
158
|
+
self._folder_id = folder_id
|
|
159
|
+
self._path = path
|
|
160
|
+
self._server_name = server_name
|
|
161
|
+
|
|
162
|
+
@cached_property
|
|
163
|
+
def api(self) -> T_ApiClass:
|
|
164
|
+
"""同步API对象"""
|
|
165
|
+
version = self._get_version(self.element_info)
|
|
166
|
+
api = self.api_class(version=version, sync=True, lazy=True)
|
|
167
|
+
api.set_url(self.element_info.serverName)
|
|
168
|
+
return api
|
|
169
|
+
|
|
170
|
+
async def _get_element_info(self) -> T_ElementInfoWithServer:
|
|
171
|
+
if (
|
|
172
|
+
OPTION.boost.skip_internal_existence_check
|
|
173
|
+
and self._folder_id is not None
|
|
174
|
+
and self._server_name is not None
|
|
175
|
+
and (match := RE_SERVER_NAME_PARSER.match(self._server_name))
|
|
176
|
+
):
|
|
177
|
+
return ModuleServerNameVO(
|
|
178
|
+
elementName=self.element_name,
|
|
179
|
+
elementType=self.element_type,
|
|
180
|
+
folderId=self._folder_id,
|
|
181
|
+
serverName=self._server_name,
|
|
182
|
+
moduleVersion=match.group('ver').replace('-', '.')
|
|
183
|
+
)
|
|
184
|
+
ele_info = await self.async_check_exist(
|
|
185
|
+
self.element_name,
|
|
186
|
+
folder=self._folder_id,
|
|
187
|
+
path=self._path,
|
|
188
|
+
silent=False
|
|
189
|
+
)
|
|
190
|
+
return ele_info
|
|
191
|
+
|
|
192
|
+
@staticmethod
|
|
193
|
+
def _get_version(ele_info):
|
|
194
|
+
"""
|
|
195
|
+
Returns: version_id with format as '1_0' or None if no version_id got
|
|
196
|
+
"""
|
|
197
|
+
if match := RE_SERVER_NAME_PARSER.match(ele_info.serverName):
|
|
198
|
+
return match.group('ver').replace('-', '_')
|
|
199
|
+
|
|
200
|
+
# Init with path or folderId provided
|
|
201
|
+
if isinstance(ele_info, ModuleServerNameVO):
|
|
202
|
+
# Format example: "2.0"
|
|
203
|
+
if ele_info.moduleVersion:
|
|
204
|
+
return ele_info.moduleVersion.replace('.', '_')
|
|
205
|
+
else:
|
|
206
|
+
# Format example: "MAINVIEW1_0"
|
|
207
|
+
if ele_info.moduleId and (match := RE_MODULEID_PARSER.match(ele_info.moduleId)):
|
|
208
|
+
return match.group('ver')
|
|
209
|
+
# Format example: "2.0"
|
|
210
|
+
if ele_info.moduleVersion:
|
|
211
|
+
return ele_info.moduleVersion.replace('.', '_')
|
|
212
|
+
|
|
213
|
+
async def _init_api(self) -> T_ApiClass:
|
|
214
|
+
ele_info = await self.wait_for('element_info')
|
|
215
|
+
version = self._get_version(ele_info)
|
|
216
|
+
api = await self.api_class(version=version, sync=False, lazy=True)
|
|
217
|
+
await api.set_url(ele_info.serverName)
|
|
218
|
+
if isinstance(ele_info, ElementRelationInfo):
|
|
219
|
+
# 更新api_class的缓存
|
|
220
|
+
api.__class__.server_cache[ele_info.moduleId] = \
|
|
221
|
+
ele_info.serverName
|
|
222
|
+
api.module_id = ele_info.moduleId
|
|
223
|
+
else:
|
|
224
|
+
api.module_id = f"{api.module_type}{version}"
|
|
225
|
+
return api
|
|
226
|
+
|
|
227
|
+
@future_property
|
|
228
|
+
async def element_info(self) -> T_ElementInfoWithServer:
|
|
229
|
+
"""元素信息"""
|
|
230
|
+
return await self._get_element_info()
|
|
231
|
+
|
|
232
|
+
@future_property
|
|
233
|
+
async def async_api(self) -> T_ApiClass:
|
|
234
|
+
"""异步API对象"""
|
|
235
|
+
return await self._init_api()
|
|
236
|
+
|
|
237
|
+
@classmethod
|
|
238
|
+
def check_exist(
|
|
239
|
+
cls,
|
|
240
|
+
ele_name: str,
|
|
241
|
+
ele_type: str = None,
|
|
242
|
+
folder: str = None,
|
|
243
|
+
path: str = None,
|
|
244
|
+
silent: bool = True,
|
|
245
|
+
) -> Union[T_ElementInfoWithServer, int]:
|
|
246
|
+
"""查询元素是否存在
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
ele_name: 元素名
|
|
250
|
+
ele_type: 元素类型
|
|
251
|
+
folder: 文件夹id
|
|
252
|
+
path: 文件夹路径
|
|
253
|
+
silent: 元素不唯一是是否报错
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
- 当指定 ``silent`` 为 ``True`` 时,返回查询到的元素个数( :obj:`int` 类型)。
|
|
257
|
+
- 当指定 ``silent`` 为 ``False`` 时,如果元素个数唯一,返回该元素
|
|
258
|
+
( :class:`ModuleServerNameVO` 或 :class:`ElementRelationInfo` 类型),否则将报错。
|
|
259
|
+
|
|
260
|
+
"""
|
|
261
|
+
return evloop.run(cls.async_check_exist(
|
|
262
|
+
ele_name=ele_name,
|
|
263
|
+
ele_type=ele_type,
|
|
264
|
+
folder=folder,
|
|
265
|
+
path=path,
|
|
266
|
+
silent=silent
|
|
267
|
+
))
|
|
268
|
+
|
|
269
|
+
@classmethod
|
|
270
|
+
async def async_check_exist(
|
|
271
|
+
cls,
|
|
272
|
+
ele_name: str,
|
|
273
|
+
ele_type: str = None,
|
|
274
|
+
folder: str = None,
|
|
275
|
+
path: str = None,
|
|
276
|
+
silent: bool = True,
|
|
277
|
+
) -> Union[T_ElementInfoWithServer, int]:
|
|
278
|
+
"""异步查询元素是否存在
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
ele_name: 元素名
|
|
282
|
+
ele_type: 元素类型
|
|
283
|
+
folder: 文件夹id
|
|
284
|
+
path: 文件夹路径
|
|
285
|
+
silent: 元素不唯一是是否报错
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
- 当指定 ``silent`` 为 ``True`` 时,返回查询到的元素个数( :obj:`int` 类型)。
|
|
289
|
+
- 当指定 ``silent`` 为 ``False`` 时,如果元素个数唯一,返回该元素
|
|
290
|
+
( :class:`ModuleServerNameVO` 或 :class:`ElementRelationInfo` 类型),否则将报错。
|
|
291
|
+
|
|
292
|
+
"""
|
|
293
|
+
if ele_type is None:
|
|
294
|
+
if cls.element_type is None and cls.api_class is None:
|
|
295
|
+
raise ElementTypeMissingError(
|
|
296
|
+
"Either api_class or module_type should be provided.")
|
|
297
|
+
|
|
298
|
+
ele_type = cls.element_type or cls.api_class.module_type
|
|
299
|
+
|
|
300
|
+
api = AppAPI(sync=False)
|
|
301
|
+
|
|
302
|
+
if path is None and folder is None:
|
|
303
|
+
ele_list = await api.elements.get_element_info_by_name(
|
|
304
|
+
ele_name, ele_type, use_cache=OPTION.api.cache_element_info
|
|
305
|
+
)
|
|
306
|
+
else:
|
|
307
|
+
ele_list = await api.element_info.get_server_names(
|
|
308
|
+
[
|
|
309
|
+
ElementModel(
|
|
310
|
+
elementName=ele_name, elementType=ele_type,
|
|
311
|
+
folderId=folder, path=path
|
|
312
|
+
)
|
|
313
|
+
],
|
|
314
|
+
use_cache=OPTION.api.cache_element_info
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
ele_no = len(ele_list or [])
|
|
318
|
+
if silent:
|
|
319
|
+
return ele_no
|
|
320
|
+
|
|
321
|
+
if ele_no == 0:
|
|
322
|
+
raise ElementNotFoundError(
|
|
323
|
+
f"element name: {ele_name}, element type: {ele_type}.")
|
|
324
|
+
elif ele_no > 1:
|
|
325
|
+
raise ElementAmbiguousError(
|
|
326
|
+
f"Found {ele_no} elements for element name: {ele_name}, "
|
|
327
|
+
f"element type: {ele_type}.")
|
|
328
|
+
|
|
329
|
+
return ele_list[0]
|
|
330
|
+
|
|
331
|
+
@eliminate_from_traceback
|
|
332
|
+
async def wait_for(self, attr):
|
|
333
|
+
"""异步等待成员变量"""
|
|
334
|
+
return await getattr(self.__class__, attr).wait_for(self)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
class SyncMeta(_ElementMeta):
|
|
338
|
+
__mangle_docs__ = False
|
|
339
|
+
|
|
340
|
+
def __new__(mcs, name, bases, namespace, **kwargs):
|
|
341
|
+
base = bases[0]
|
|
342
|
+
methods = None
|
|
343
|
+
|
|
344
|
+
if len(bases) > 1:
|
|
345
|
+
for parent in bases:
|
|
346
|
+
if hasattr(parent, "synchronize"):
|
|
347
|
+
methods = parent.synchronize
|
|
348
|
+
break
|
|
349
|
+
|
|
350
|
+
if methods is None:
|
|
351
|
+
methods = namespace.pop('synchronize', [])
|
|
352
|
+
|
|
353
|
+
for attr in methods:
|
|
354
|
+
namespace[attr] = synchronize(mcs._get_from_bases(base, attr))
|
|
355
|
+
|
|
356
|
+
cls = super().__new__(mcs, name, bases, namespace, **kwargs)
|
|
357
|
+
return cls
|
|
358
|
+
|
|
359
|
+
@staticmethod
|
|
360
|
+
def _get_from_bases(base, attr):
|
|
361
|
+
while issubclass(base, ElementBase):
|
|
362
|
+
if attr in base.__dict__:
|
|
363
|
+
return base.__dict__[attr]
|
|
364
|
+
base = base.__base__
|
|
365
|
+
raise AttributeError(attr)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
in_sync_ctx = contextvars.ContextVar('call_by_sync')
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
@eliminate_from_traceback
|
|
372
|
+
def synchronize(method):
|
|
373
|
+
assert inspect.iscoroutinefunction(method), \
|
|
374
|
+
"can only synchronize coroutine functions!"
|
|
375
|
+
|
|
376
|
+
@wraps(method)
|
|
377
|
+
def wrapper(self, *args, **kwargs):
|
|
378
|
+
coro = method(self, *args, **kwargs)
|
|
379
|
+
|
|
380
|
+
if not in_sync_ctx.get(False):
|
|
381
|
+
async def wrap_coro():
|
|
382
|
+
token = in_sync_ctx.set(True)
|
|
383
|
+
res = await coro
|
|
384
|
+
in_sync_ctx.reset(token)
|
|
385
|
+
return res
|
|
386
|
+
else:
|
|
387
|
+
wrap_coro = coro
|
|
388
|
+
|
|
389
|
+
if in_sync_ctx.get(False):
|
|
390
|
+
if OPTION.general.parallel_mode:
|
|
391
|
+
return ParallelProxy(coro)
|
|
392
|
+
return coro
|
|
393
|
+
else:
|
|
394
|
+
if OPTION.general.parallel_mode:
|
|
395
|
+
return ParallelProxy(evloop.apply(wrap_coro(), ensure_completed=True))
|
|
396
|
+
return evloop.run(wrap_coro())
|
|
397
|
+
|
|
398
|
+
return wrapper
|