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
@@ -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
+ ...
@@ -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