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,243 @@
1
+ from functools import partial
2
+ from typing import List, Union, Optional, TYPE_CHECKING
3
+
4
+ from deepfos.lib.asynchronous import future_property
5
+ from deepfos.element.base import ElementBase, SyncMeta
6
+ from deepfos.options import OPTION
7
+ from deepfos.api.role_strategy import RoleStrategyAPI
8
+ from deepfos.api.models.role_strategy import (
9
+ QueryRoleSchemeDTO, RoleSchemeInfoVO, UserDTO,
10
+ UserGroupListDTO, RoleDetailVO, RoleGroupDetailVO, ElementDetailVO
11
+ )
12
+ from deepfos.api.models.base import BaseModel
13
+ from deepfos.lib.utils import fetch_all_pages
14
+
15
+ __all__ = [
16
+ 'RoleStrategyRecord',
17
+ 'RoleStrategyInfo',
18
+ 'AsyncRoleStrategy',
19
+ 'RoleStrategy'
20
+ ]
21
+
22
+
23
+ # -----------------------------------------------------------------------------
24
+ # utils
25
+ class RoleStrategyRecord(BaseModel):
26
+ #: 用户
27
+ users: Optional[List[Union[UserDTO, str]]]
28
+ #: 用户组
29
+ user_groups: Optional[List[Union[UserGroupListDTO, str]]]
30
+ #: 角色
31
+ roles: Optional[List[Union[RoleDetailVO, str]]]
32
+ #: 角色组
33
+ role_groups: Optional[List[Union[RoleGroupDetailVO, str]]]
34
+ #: 维度表达式
35
+ dim_expr: List[Optional[str]]
36
+
37
+
38
+ class RoleStrategyInfo(BaseModel):
39
+ records: List[RoleStrategyRecord]
40
+ dimensions: List[ElementDetailVO]
41
+
42
+
43
+ def _get_record_count(response: RoleSchemeInfoVO):
44
+ return len(response.roleScheme['list'])
45
+
46
+
47
+ def ensure_list(maybe_list: Union[str, List[str]]):
48
+ if isinstance(maybe_list, str):
49
+ return [maybe_list]
50
+ else:
51
+ return maybe_list
52
+
53
+
54
+ class AsyncRoleStrategy(ElementBase[RoleStrategyAPI]):
55
+ """权限方案"""
56
+ def __init__(
57
+ self,
58
+ element_name: str,
59
+ folder_id: str = None,
60
+ path: str = None,
61
+ server_name: str = None,
62
+ ):
63
+ self.current_user = OPTION.api.header['user']
64
+ self.__meta = None
65
+ super().__init__(element_name, folder_id, path, server_name)
66
+
67
+ @future_property
68
+ async def meta(self):
69
+ """当前用户的权限方案元信息"""
70
+ if self.__meta is None:
71
+ await self.wait_for('async_api')
72
+ self.__meta = await self._query(user=self.current_user)
73
+ return self.__meta
74
+
75
+ async def _query(
76
+ self,
77
+ user: Union[str, List[str]] = None,
78
+ user_group: Union[str, List[str]] = None,
79
+ role: Union[str, List[str]] = None,
80
+ role_group: Union[str, List[str]] = None
81
+ ) -> RoleStrategyInfo:
82
+ """查询角色方案信息
83
+
84
+ 根据给定的筛选条件查询权限方案
85
+ 不同参数的查询条件间为 **或** 的关系。
86
+
87
+ Args:
88
+ user: 用户id
89
+ user_group: 用户组id
90
+ role: 角色
91
+ role_group: 角色组
92
+
93
+ .. admonition:: 示例
94
+
95
+ .. code-block:: python
96
+
97
+ rs = RoleStrategy("test")
98
+ r = rs.query(user='1234-5678')
99
+ # 维度列表
100
+ r.dimensions
101
+ # 权限方案记录列表
102
+ r.records
103
+
104
+ Note:
105
+ - 当不提供任何参数时,会使用当前用户作为查询条件。
106
+ - 如果要查询所有的权限方案,可以显式传入 ``user=[]``
107
+
108
+ Returns:
109
+ 权限方案信息
110
+
111
+ """
112
+ if all(item is None for item in (user, user_group, role, role_group)):
113
+ user = [self.current_user]
114
+ query_current_user = True
115
+ elif user is None:
116
+ query_current_user = False
117
+ else:
118
+ user = ensure_list(user)
119
+ query_current_user = (
120
+ not any((user_group, role, role_group)) and
121
+ len(user) == 1 and
122
+ user[0] == self.current_user
123
+ )
124
+
125
+ user_group = ensure_list(user_group)
126
+ role = ensure_list(role)
127
+ role_group = ensure_list(role_group)
128
+
129
+ if query_current_user and self.__meta is not None:
130
+ return self.__meta
131
+
132
+ fn = partial(
133
+ self._query_impl,
134
+ users=user,
135
+ user_groups=user_group,
136
+ roles=role,
137
+ role_groups=role_group
138
+ )
139
+
140
+ pages = await fetch_all_pages(
141
+ fn,
142
+ count_getter=_get_record_count,
143
+ page_size=200,
144
+ page_no_key='page_no',
145
+ page_size_key='page_size'
146
+ )
147
+ if not pages:
148
+ dimensions = []
149
+ else:
150
+ dimensions = [seg.elementDetail for seg in pages[0].segments]
151
+
152
+ rs_info = []
153
+
154
+ for page in pages:
155
+ for record in page.roleScheme['list']:
156
+ rs_info.append(RoleStrategyRecord(
157
+ users=record['users'],
158
+ user_groups=record['userGroups'],
159
+ roles=record['roles'],
160
+ role_groups=record['roleGroups'],
161
+ dim_expr=[
162
+ record[f'segment{i}']
163
+ for i in range(1, len(dimensions) + 1)
164
+ ]
165
+ ))
166
+
167
+ r = RoleStrategyInfo(records=rs_info, dimensions=dimensions)
168
+ return r
169
+
170
+ query = _query
171
+
172
+ async def _query_impl(
173
+ self,
174
+ page_size: int,
175
+ page_no: int,
176
+ users: List[str] = None,
177
+ user_groups: List[str] = None,
178
+ roles: List[str] = None,
179
+ role_groups: List[str] = None
180
+ ):
181
+ payload = QueryRoleSchemeDTO(
182
+ elementName=self.element_name,
183
+ elementType=self.api_class.module_type,
184
+ folderId=self.element_info.folderId,
185
+ pageSize=page_size,
186
+ pageNo=page_no,
187
+ userIds=users or [],
188
+ userGroupIds=user_groups or [],
189
+ roleNames=roles or [],
190
+ roleGroupNames=role_groups or [],
191
+ )
192
+
193
+ return await self.async_api.rolestrategy.info_list(payload)
194
+
195
+ async def _query_records(self, user):
196
+ if user is None or user == self.current_user:
197
+ meta = self.meta
198
+ records = meta.records
199
+ return records
200
+ else:
201
+ res = await self.query(user=user)
202
+ records = res.records
203
+ return records
204
+
205
+ async def query_roles(self, user: str = None) -> List[RoleDetailVO]:
206
+ """查询用户所属的角色
207
+
208
+ Args:
209
+ user: 用户id,默认使用当前用户
210
+
211
+ """
212
+ records = await self._query_records(user)
213
+ return sum((r.roles for r in records), [])
214
+
215
+ async def query_role_groups(self, user: str = None) -> List[RoleGroupDetailVO]:
216
+ """查询用户所属的角色组
217
+
218
+ Args:
219
+ user: 用户id,默认使用当前用户
220
+
221
+ """
222
+ records = await self._query_records(user)
223
+ return sum((r.role_groups for r in records), [])
224
+
225
+
226
+ class RoleStrategy(AsyncRoleStrategy, metaclass=SyncMeta):
227
+ synchronize = ('query', 'query_roles', 'query_role_groups')
228
+
229
+ if TYPE_CHECKING:
230
+ def query(
231
+ self,
232
+ user: Union[str, List[str]] = None,
233
+ user_group: Union[str, List[str]] = None,
234
+ role: Union[str, List[str]] = None,
235
+ role_group: Union[str, List[str]] = None
236
+ ) -> RoleStrategyInfo: # pragma: no cover
237
+ ...
238
+
239
+ def query_roles(self, user: str = None) -> List[RoleGroupDetailVO]: # pragma: no cover
240
+ ...
241
+
242
+ def query_role_groups(self, user: str = None) -> List[RoleGroupDetailVO]: # pragma: no cover
243
+ ...
@@ -0,0 +1,457 @@
1
+ from typing import List, Dict, Optional, Union, Tuple, Any, Iterable, TYPE_CHECKING, TypeVar # noqa
2
+
3
+ import pandas as pd
4
+
5
+ from .base import ElementBase, SyncMeta
6
+ from .dimension import Strategy # noqa
7
+ from deepfos.lib.utils import CIEnum, get_language_key_map
8
+ from deepfos.lib.asynchronous import future_property
9
+ from deepfos.lib.constant import ACCEPT_LANS
10
+ from deepfos.api.smartlist import SmartListAPI
11
+ from deepfos.api.models.smartlist import (
12
+ SmartListDTO, SmartListUd,
13
+ SmartList as SmartListItem
14
+ )
15
+ from deepfos.lib.decorator import cached_property
16
+
17
+ __all__ = [
18
+ 'SmartList',
19
+ 'AsyncSmartList',
20
+ 'SmartListItem'
21
+ ]
22
+
23
+
24
+ # -----------------------------------------------------------------------------
25
+ # utils
26
+ class OnDupAction(CIEnum):
27
+ ignore = 'ignore'
28
+ error = 'error'
29
+ replace = 'replace'
30
+
31
+
32
+ class OnAbsAction(CIEnum):
33
+ ignore = 'ignore'
34
+ error = 'error'
35
+ create = 'create'
36
+
37
+
38
+ SelfType = TypeVar('SelfType', bound='AsyncSmartList')
39
+
40
+
41
+ # -----------------------------------------------------------------------------
42
+ # core classes
43
+ class AsyncSmartList(ElementBase[SmartListAPI]):
44
+ """值列表"""
45
+ @cached_property
46
+ def _item_memo(self) -> Dict[str, SmartListItem]:
47
+ return {
48
+ mbr.subjectValue: mbr
49
+ for mbr in self.meta.smartList
50
+ }
51
+
52
+ @cached_property
53
+ def _ud_memo(self) -> Dict[str, SmartListUd]:
54
+ return {
55
+ ud.udName: ud
56
+ for ud in self.meta.smartListUd
57
+ }
58
+
59
+ @future_property
60
+ async def meta(self) -> SmartListDTO:
61
+ """值列表的元数据信息"""
62
+ api = await self.wait_for('async_api')
63
+ return await api.sml.data(
64
+ folderId=self.element_info.folderId,
65
+ name=self.element_name
66
+ )
67
+
68
+ @staticmethod
69
+ def _get_desc(
70
+ fallback: str,
71
+ desc_zh: str = None,
72
+ desc_en: str = None,
73
+ ):
74
+ if desc_zh is None:
75
+ desc_zh = fallback
76
+ if desc_en is None:
77
+ desc_en = fallback
78
+
79
+ return {
80
+ 'en': desc_en,
81
+ 'zh-cn': desc_zh
82
+ }
83
+
84
+ def add(
85
+ self: SelfType,
86
+ value: str,
87
+ desc_zh: str = None,
88
+ desc_en: str = None,
89
+ on_duplicate: Union[OnDupAction, str] = OnDupAction.error,
90
+ **extra: Any,
91
+ ) -> SelfType:
92
+ """增加成员
93
+
94
+ 调用 :meth:`save` 之后生效。
95
+
96
+ Args:
97
+ value: 成员值
98
+ desc_zh: 成员中文描述
99
+ desc_en: 成员英文描述
100
+ on_duplicate: 成员值重复时的行为
101
+ extra: 成员的其他属性,如ud等,可设置的参数参考 :class:`SmartListItem`
102
+
103
+ Hint:
104
+ 参数 ``on_duplicate`` 的可选值及其说明如下:
105
+
106
+ +---------+----------------+
107
+ | 值 | 说明 |
108
+ +=========+================+
109
+ | ignore | 静默处理,忽略 |
110
+ +---------+----------------+
111
+ | error | 抛出异常 |
112
+ +---------+----------------+
113
+ | replace | 替换成员 |
114
+ +---------+----------------+
115
+
116
+ Returns:
117
+ self
118
+
119
+ See Also:
120
+ :meth:`add_item`
121
+
122
+ """
123
+ if value in self._item_memo:
124
+ action = OnDupAction[on_duplicate]
125
+ if action is OnDupAction.error:
126
+ raise ValueError(f"Item {value} already exists.")
127
+ if action is OnDupAction.ignore:
128
+ return self
129
+
130
+ desc = self._get_desc(value, desc_zh=desc_zh, desc_en=desc_en)
131
+ item = SmartListItem.construct_from(subjectValue=value, desc=desc, **extra)
132
+ self._item_memo[item.subjectValue] = item
133
+ return self
134
+
135
+ def add_item(
136
+ self: SelfType,
137
+ *items: SmartListItem,
138
+ on_duplicate: Union[OnDupAction, str] = OnDupAction.error,
139
+ ) -> SelfType:
140
+ """增加成员对象
141
+
142
+ 调用 :meth:`save` 之后生效。
143
+
144
+ Args:
145
+ *items: 成员对象
146
+ on_duplicate: 成员值重复时的行为,参考 :meth:`add`
147
+
148
+ Returns:
149
+ self
150
+
151
+ See Also:
152
+ 如果不希望自己构造成员对象,可以使用 :meth:`add`
153
+
154
+ """
155
+ to_add = []
156
+ action = OnDupAction[on_duplicate]
157
+
158
+ for item in items:
159
+ value = item.subjectValue
160
+ if value in self._item_memo:
161
+ if action is OnDupAction.error:
162
+ raise ValueError(f"Item {value} already exists.")
163
+ if action is OnDupAction.ignore:
164
+ continue
165
+ to_add.append(item)
166
+
167
+ # 其他情况(新增,已存在但替换)都可以直接赋值
168
+ for item in to_add:
169
+ self._item_memo[item.subjectValue] = item
170
+ return self
171
+
172
+ def delete(
173
+ self: SelfType,
174
+ *item_values: str,
175
+ silent: bool = True
176
+ ) -> SelfType:
177
+ """删除成员
178
+
179
+ 调用 :meth:`save` 之后生效。
180
+
181
+ Args:
182
+ *item_values: 成员值
183
+ silent: 当成员不存在时,是报错还是静默处理。默认True, 即静默处理。
184
+
185
+ Returns:
186
+ self
187
+
188
+ """
189
+ item_not_found = []
190
+ for item in item_values:
191
+ if item in self._item_memo:
192
+ del self._item_memo[item]
193
+ else:
194
+ item_not_found.append(item)
195
+
196
+ if not silent and item_not_found:
197
+ raise KeyError(f"Item {item_not_found} does not exist.")
198
+ return self
199
+
200
+ def update(
201
+ self: SelfType,
202
+ value: str,
203
+ on_absent: Union[OnAbsAction, str] = OnAbsAction.error,
204
+ **upd_attrs: Any,
205
+ ) -> SelfType:
206
+ """更新成员
207
+
208
+ 调用 :meth:`save` 之后生效。
209
+
210
+ Args:
211
+ value: 成员值
212
+ on_absent: 更新字段不存在时的行为
213
+ **upd_attrs: 要更新的成员的属性,如ud等,可设置的参数参考 :class:`SmartListItem`
214
+
215
+ Hint:
216
+ 参数 ``on_absent`` 的可选值及其说明如下:
217
+
218
+ +--------+----------------+
219
+ | 值 | 说明 |
220
+ +========+================+
221
+ | ignore | 静默处理,忽略 |
222
+ +--------+----------------+
223
+ | error | 抛出异常 |
224
+ +--------+----------------+
225
+ | create | 新建成员 |
226
+ +--------+----------------+
227
+
228
+ Returns:
229
+ self
230
+
231
+ See Also:
232
+ :meth:`update_item`
233
+
234
+ """
235
+ if value not in self._item_memo:
236
+ action = OnAbsAction[on_absent]
237
+ if action is OnAbsAction.error:
238
+ raise ValueError(f"Item {value} does not exist.")
239
+ if action is OnAbsAction.ignore:
240
+ return self
241
+ item = SmartListItem()
242
+ else:
243
+ item = self._item_memo[value]
244
+
245
+ # 重命名成员值
246
+ if 'subjectValue' in upd_attrs:
247
+ new_value = upd_attrs['subjectValue']
248
+ if new_value in self._item_memo:
249
+ raise ValueError(
250
+ f"Cannot rename '{value}' to '{new_value}'. "
251
+ f"Item '{new_value}' already exists.")
252
+
253
+ self._item_memo.pop(value, None)
254
+ value = new_value # todo:test_case_bug bug fix 2022.9.5
255
+
256
+ new_item = SmartListItem.construct_from(item, **upd_attrs)
257
+ self._item_memo[value] = new_item
258
+ return self
259
+
260
+ def update_item(
261
+ self: SelfType,
262
+ *items: SmartListItem,
263
+ on_absent: Union[OnAbsAction, str] = OnAbsAction.error
264
+ ) -> SelfType:
265
+ """使用成员对象更新
266
+
267
+ Args:
268
+ *items: 更新的成员对象
269
+ on_absent: 更新字段不存在时的行为,参考 :meth:`update`
270
+
271
+ Returns:
272
+ self
273
+
274
+ See Also:
275
+ 如果不希望自己构造成员对象,可以使用 :meth:`update`
276
+ """
277
+ to_update = []
278
+ action = OnAbsAction[on_absent]
279
+
280
+ for item in items:
281
+ value = item.subjectValue
282
+ if value not in self._item_memo:
283
+ if action is OnAbsAction.error:
284
+ raise ValueError(f"Item {value} does not exist.")
285
+ if action is OnAbsAction.ignore:
286
+ continue
287
+ to_update.append(item)
288
+
289
+ for item in to_update:
290
+ self._item_memo[item.subjectValue] = item
291
+ return self
292
+
293
+ async def save(self):
294
+ """保存值列表
295
+
296
+ 将对当前值列表的修改保存至系统。
297
+ """
298
+ payload = SmartListDTO.construct_from(
299
+ self.meta,
300
+ smartList=list(self._item_memo.values()),
301
+ smartListUd=list(self._ud_memo.values())
302
+ )
303
+ return await self._save_impl(payload)
304
+
305
+ async def _save_impl(self, payload: SmartListDTO):
306
+ await self.async_api.sml.update(payload)
307
+ # 使memo的缓存失效
308
+ self.__class__.meta.reset(self)
309
+ self.__dict__.pop('_item_memo', None)
310
+ self.__dict__.pop('_ud_memo', None)
311
+
312
+ def set_ud(
313
+ self: SelfType,
314
+ ud_num: Union[str, int],
315
+ desc_zh: str = None,
316
+ desc_en: str = None,
317
+ active: bool = True
318
+ ) -> SelfType:
319
+ """设置ud
320
+
321
+ 对于未设置的ud,会默认新增,如果已经设置过,则更新。
322
+ 新增维度默认为激活状态。
323
+
324
+ 调用 :meth:`save` 之后生效。
325
+
326
+ Args:
327
+ ud_num: ud编号
328
+ desc_zh: 中文描述
329
+ desc_en: 英文描述
330
+ active: 是否激活
331
+
332
+ Returns:
333
+ self
334
+
335
+ """
336
+ ud_name = f"ud{ud_num}"
337
+ ud = self._ud_memo.get(ud_name, SmartListUd(udName=ud_name))
338
+ ud.desc = ud.desc or {}
339
+
340
+ if desc_zh is not None:
341
+ ud.desc['zh-cn'] = desc_zh
342
+ if desc_en is not None:
343
+ ud.desc['en'] = desc_en
344
+
345
+ if ud.active is not False:
346
+ ud.active = active
347
+
348
+ self._ud_memo[ud_name] = ud
349
+ return self
350
+
351
+ @property
352
+ def items(self) -> List[SmartListItem]:
353
+ """值列表成员"""
354
+ return list(self._item_memo.values())
355
+
356
+ async def load_dataframe(
357
+ self,
358
+ dataframe: pd.DataFrame,
359
+ strategy: Union[Strategy, str] = Strategy.incr_replace,
360
+ **langugage_keys: str
361
+ ):
362
+ """保存 ``DataFrame`` 数据至值列表
363
+
364
+ 此方法不同于 :meth:`add`,:meth:`delete` 等方法,
365
+ 保存结果将直接反映至系统,不需要再调用 :meth:`save` 。
366
+
367
+ Args:
368
+ dataframe: 包含值列表数据的 ``DataFrame``
369
+ strategy: 数据保存策略
370
+ **langugage_keys: 值列表成员描述(多语言)对应的列名
371
+
372
+ Note:
373
+ 1. 数据保存策略可选参数如下:
374
+
375
+ +--------------+--------------------------------------------+
376
+ | 参数 | 说明 |
377
+ +==============+============================================+
378
+ | full_replace | 完全替换所有值列表成员。 |
379
+ | | 此策略将会删除所有已有值列表成员, |
380
+ | | 以dataframe为数据源新建值列表成员。 |
381
+ +--------------+--------------------------------------------+
382
+ | incr_replace | 增量替换值列表成员。 |
383
+ | | 此策略不会删除已有值列表成员。 |
384
+ | | 在保存过程中,如果遇到成员名重复的情况, |
385
+ | | 会以dataframe数据为准,覆盖已有成员。 |
386
+ +--------------+--------------------------------------------+
387
+ | keep_old | 保留已有值列表成员。 |
388
+ | | 此策略在保存过程中,遇到成员名重复的情况, |
389
+ | | 会保留已有成员。其他与incr_replace相同。 |
390
+ +--------------+--------------------------------------------+
391
+
392
+ 2. 目前描述支持两种语言: ``zh-cn, en``,此方法默认会在dataframe中寻找
393
+ 名为 ``'language_zh-cn', 'language_en'`` 的列,将其数据作为对应
394
+ 语言的描述。如果想改变这种默认行为,比如希望用'name'列作为中文语言描述,
395
+ 可以传入关键字参数: ``language_zh_cn='name'``。
396
+
397
+ """
398
+ strategy = Strategy[strategy]
399
+ df = dataframe.copy()
400
+ # create language columns
401
+ language_map = get_language_key_map(langugage_keys)
402
+ for lan, key in language_map.items():
403
+ if key in df.columns:
404
+ df[lan] = df[key]
405
+
406
+ # 合并描述列
407
+ lan_columns = list(set(df.columns).intersection(ACCEPT_LANS))
408
+ if lan_columns:
409
+ df['desc'] = df[lan_columns].to_dict(orient='records')
410
+ # pick up valid columns
411
+ valid_columns = list(set(SmartListItem.__fields__).intersection(df.columns))
412
+ # check exist data
413
+ if strategy is Strategy.full_replace:
414
+ existed_items: List[SmartListItem] = []
415
+ else:
416
+ if strategy is Strategy.incr_replace:
417
+ self.delete(*df['subjectValue'], silent=True)
418
+ elif strategy is Strategy.keep_old:
419
+ drop_index = df[df['subjectValue'].isin(self._item_memo)].index
420
+ df = df.drop(drop_index, errors='ignore')
421
+
422
+ existed_items: List[SmartListItem] = list(self._item_memo.values())
423
+
424
+ df_records = df[valid_columns].to_dict(orient='records')
425
+
426
+ items = [mbr.dict(by_alias=True) for mbr in existed_items] + df_records
427
+ payload = SmartListDTO.construct_from(
428
+ self.meta,
429
+ smartList=items,
430
+ smartListUd=list(self._ud_memo.values())
431
+ )
432
+ return await self._save_impl(payload)
433
+
434
+ def __getitem__(self, item) -> SmartListItem: # pragma: no cover
435
+ return self._item_memo[item]
436
+
437
+ def __len__(self) -> int: # pragma: no cover
438
+ return len(self._item_memo)
439
+
440
+ def __iter__(self) -> Iterable[SmartListItem]: # pragma: no cover
441
+ yield from self._item_memo.values()
442
+
443
+
444
+ class SmartList(AsyncSmartList, metaclass=SyncMeta):
445
+ synchronize = ('save', 'load_dataframe')
446
+
447
+ if TYPE_CHECKING:
448
+ def save(self): # pragma: no cover
449
+ ...
450
+
451
+ def load_dataframe(
452
+ self,
453
+ dataframe: pd.DataFrame,
454
+ strategy: Union[Strategy, str] = Strategy.incr_replace,
455
+ **langugage_keys: str
456
+ ): # pragma: no cover
457
+ ...