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