deepfos 1.1.60__py3-none-any.whl → 1.1.78__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/_version.py +3 -3
- deepfos/api/V1_1/models/business_model.py +322 -322
- deepfos/api/V1_1/models/dimension.py +1075 -1075
- deepfos/api/V1_2/models/dimension.py +1119 -1116
- deepfos/api/account.py +1 -0
- deepfos/api/app.py +1 -0
- deepfos/api/base.py +70 -71
- deepfos/api/deep_pipeline.py +1 -1
- deepfos/api/deepconnector.py +3 -3
- deepfos/api/financial_model.py +12 -0
- deepfos/api/models/account.py +130 -130
- deepfos/api/models/accounting_engines.py +250 -250
- deepfos/api/models/app.py +355 -355
- deepfos/api/models/approval_process.py +231 -231
- deepfos/api/models/base.py +49 -209
- deepfos/api/models/business_model.py +239 -239
- deepfos/api/models/consolidation.py +196 -196
- deepfos/api/models/consolidation_process.py +31 -31
- deepfos/api/models/datatable_mysql.py +78 -78
- deepfos/api/models/deep_pipeline.py +20 -9
- deepfos/api/models/deepconnector.py +9 -8
- deepfos/api/models/deepfos_task.py +118 -118
- deepfos/api/models/deepmodel.py +120 -120
- deepfos/api/models/dimension.py +613 -610
- deepfos/api/models/financial_model.py +691 -663
- deepfos/api/models/journal_model.py +120 -120
- deepfos/api/models/journal_template.py +185 -185
- deepfos/api/models/memory_financial_model.py +131 -131
- deepfos/api/models/platform.py +16 -16
- deepfos/api/models/python.py +32 -32
- deepfos/api/models/reconciliation_engine.py +104 -104
- deepfos/api/models/reconciliation_report.py +29 -29
- deepfos/api/models/role_strategy.py +213 -213
- deepfos/api/models/smartlist.py +86 -86
- deepfos/api/models/space.py +312 -312
- deepfos/api/models/system.py +299 -297
- deepfos/api/models/variable.py +131 -131
- deepfos/api/models/workflow.py +290 -270
- deepfos/api/platform.py +3 -1
- deepfos/api/space.py +1 -0
- deepfos/api/system.py +1 -0
- deepfos/api/workflow.py +8 -0
- deepfos/cache.py +50 -4
- deepfos/element/bizmodel.py +2 -2
- deepfos/element/deep_pipeline.py +29 -16
- deepfos/element/deepconnector.py +36 -1
- deepfos/element/deepmodel.py +591 -332
- deepfos/element/dimension.py +30 -17
- deepfos/element/finmodel.py +542 -101
- deepfos/element/journal.py +20 -10
- deepfos/element/rolestrategy.py +4 -4
- deepfos/element/variable.py +23 -17
- deepfos/element/workflow.py +60 -3
- deepfos/exceptions/__init__.py +1 -1
- deepfos/lib/deepchart.py +14 -13
- deepfos/lib/deepux.py +11 -11
- deepfos/lib/discovery.py +3 -0
- deepfos/lib/filterparser.py +2 -2
- deepfos/lib/k8s.py +101 -0
- deepfos/lib/msg.py +34 -8
- deepfos/lib/serutils.py +34 -9
- deepfos/lib/sysutils.py +37 -18
- deepfos/lib/utils.py +62 -2
- deepfos/options.py +39 -8
- {deepfos-1.1.60.dist-info → deepfos-1.1.78.dist-info}/METADATA +7 -7
- {deepfos-1.1.60.dist-info → deepfos-1.1.78.dist-info}/RECORD +68 -67
- {deepfos-1.1.60.dist-info → deepfos-1.1.78.dist-info}/WHEEL +0 -0
- {deepfos-1.1.60.dist-info → deepfos-1.1.78.dist-info}/top_level.txt +0 -0
deepfos/element/journal.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import uuid
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
|
|
3
|
+
from deepfos.api.models import compat_parse_obj_as as parse_obj_as
|
|
5
4
|
from deepfos.element.datatable import get_table_class
|
|
6
5
|
from deepfos.exceptions import (
|
|
7
6
|
JournalModelSaveError, JournalModelCheckError, JournalModelPostingError
|
|
@@ -123,8 +122,7 @@ class AsyncJournalModel(ElementBase[JournalModelAPI]):
|
|
|
123
122
|
line_df: pd.DataFrame,
|
|
124
123
|
callback: Union[Dict, CallbackInfo] = None,
|
|
125
124
|
relation_field: str = 'journal_id',
|
|
126
|
-
id_col: str =
|
|
127
|
-
operate_type: Literal['EDIT', 'ADD'] = 'ADD',
|
|
125
|
+
id_col: str = MAIN_ID,
|
|
128
126
|
enable_create: bool = True,
|
|
129
127
|
enable_default_value: bool = True,
|
|
130
128
|
enable_repeat_check: bool = True,
|
|
@@ -132,9 +130,10 @@ class AsyncJournalModel(ElementBase[JournalModelAPI]):
|
|
|
132
130
|
enable_valid_range: bool = True,
|
|
133
131
|
enable_all_errors: bool = True,
|
|
134
132
|
enable_need_one_line: bool = True,
|
|
133
|
+
header_operate: Literal['EDIT', 'ADD', 'DELETE_ADD'] = 'ADD',
|
|
134
|
+
line_operate: Literal['EDIT', 'ADD', 'DELETE', 'DELETE_ADD'] = 'ADD',
|
|
135
135
|
):
|
|
136
136
|
errors = set()
|
|
137
|
-
is_editing = operate_type == 'EDIT'
|
|
138
137
|
|
|
139
138
|
if head_df.empty:
|
|
140
139
|
errors.add('凭证头表数据DataFrame不能为空')
|
|
@@ -144,6 +143,7 @@ class AsyncJournalModel(ElementBase[JournalModelAPI]):
|
|
|
144
143
|
errors.add('凭证头行表的关联字段relation_field不能为空')
|
|
145
144
|
|
|
146
145
|
self._maybe_raise_errors(errors)
|
|
146
|
+
is_editing = not (header_operate == 'ADD' and line_operate == 'ADD')
|
|
147
147
|
|
|
148
148
|
if is_editing:
|
|
149
149
|
head_required_fields = [relation_field, '_type', id_col]
|
|
@@ -202,7 +202,7 @@ class AsyncJournalModel(ElementBase[JournalModelAPI]):
|
|
|
202
202
|
index=head_data.index
|
|
203
203
|
)
|
|
204
204
|
)
|
|
205
|
-
head_data = head_data.assign(operateType=
|
|
205
|
+
head_data = head_data.assign(operateType=header_operate)
|
|
206
206
|
|
|
207
207
|
# NB: replace twice in case of infer None to nan happened
|
|
208
208
|
head_df = head_df.replace({None: np.nan})
|
|
@@ -228,7 +228,7 @@ class AsyncJournalModel(ElementBase[JournalModelAPI]):
|
|
|
228
228
|
self._maybe_raise_errors(errors)
|
|
229
229
|
|
|
230
230
|
line_data = line_df[line_required_fields]
|
|
231
|
-
line_data = line_data.assign(operateType=
|
|
231
|
+
line_data = line_data.assign(operateType=line_operate)
|
|
232
232
|
|
|
233
233
|
# generate lineMainId
|
|
234
234
|
if is_editing:
|
|
@@ -302,7 +302,7 @@ class AsyncJournalModel(ElementBase[JournalModelAPI]):
|
|
|
302
302
|
enable_need_one_line: bool = True,
|
|
303
303
|
sync: bool = True
|
|
304
304
|
) -> CommonResultDTO:
|
|
305
|
-
"""
|
|
305
|
+
"""凭证模型数据新增
|
|
306
306
|
|
|
307
307
|
Args:
|
|
308
308
|
head_df: 凭证头表的数据(字段名与凭证模型上头表的字段名对应)
|
|
@@ -472,6 +472,8 @@ class AsyncJournalModel(ElementBase[JournalModelAPI]):
|
|
|
472
472
|
enable_valid_range: bool = True,
|
|
473
473
|
enable_all_errors: bool = True,
|
|
474
474
|
enable_need_one_line: bool = True,
|
|
475
|
+
header_operate: Literal['EDIT', 'DELETE_ADD'] = 'EDIT',
|
|
476
|
+
line_operate: Literal['EDIT', 'ADD', 'DELETE', 'DELETE_ADD'] = 'EDIT',
|
|
475
477
|
) -> CommonResultDTO:
|
|
476
478
|
"""凭证模型数据更新
|
|
477
479
|
|
|
@@ -492,6 +494,10 @@ class AsyncJournalModel(ElementBase[JournalModelAPI]):
|
|
|
492
494
|
enable_valid_range: 是否启用有效性范围的校验,默认为True
|
|
493
495
|
enable_all_errors: 是否启用一次性校验所有规则和数据,默认为True
|
|
494
496
|
enable_need_one_line: 是否启用凭证行表至少需要一条数据的校验,默认为True
|
|
497
|
+
header_operate: 凭证头表的操作类型,默认为EDIT
|
|
498
|
+
line_operate: 凭证行表的操作类型,默认为EDIT
|
|
499
|
+
头表EDIT, 行表可以ADD/EDIT/DELETE
|
|
500
|
+
头表DELETE_ADD, 头表update,行表全删全增,行表只能为DELETE_ADD
|
|
495
501
|
|
|
496
502
|
Returns:
|
|
497
503
|
接口返回信息(CommonResultDTO的success为true 表示成功,如false 则错误在errors集合里)
|
|
@@ -501,14 +507,16 @@ class AsyncJournalModel(ElementBase[JournalModelAPI]):
|
|
|
501
507
|
batch = self._gen_batch_payload(
|
|
502
508
|
head_df=head_df, line_df=line_df,
|
|
503
509
|
callback=callback, relation_field=relation_field,
|
|
504
|
-
id_col=MAIN_ID,
|
|
510
|
+
id_col=MAIN_ID,
|
|
505
511
|
enable_create=enable_create,
|
|
506
512
|
enable_default_value=enable_default_value,
|
|
507
513
|
enable_repeat_check=enable_repeat_check,
|
|
508
514
|
enable_required=enable_required,
|
|
509
515
|
enable_valid_range=enable_valid_range,
|
|
510
516
|
enable_all_errors=enable_all_errors,
|
|
511
|
-
enable_need_one_line=enable_need_one_line
|
|
517
|
+
enable_need_one_line=enable_need_one_line,
|
|
518
|
+
header_operate=header_operate,
|
|
519
|
+
line_operate=line_operate,
|
|
512
520
|
)
|
|
513
521
|
resp = await self.async_api.journal_model_data.update(batch)
|
|
514
522
|
|
|
@@ -815,6 +823,8 @@ class JournalModel(AsyncJournalModel, metaclass=SyncMeta):
|
|
|
815
823
|
enable_valid_range: bool = True,
|
|
816
824
|
enable_all_errors: bool = True,
|
|
817
825
|
enable_need_one_line: bool = True,
|
|
826
|
+
header_operate: Literal['EDIT', 'DELETE_ADD'] = 'EDIT',
|
|
827
|
+
line_operate: Literal['EDIT', 'ADD', 'DELETE', 'DELETE_ADD'] = 'EDIT',
|
|
818
828
|
) -> CommonResultDTO:
|
|
819
829
|
...
|
|
820
830
|
|
deepfos/element/rolestrategy.py
CHANGED
|
@@ -24,13 +24,13 @@ __all__ = [
|
|
|
24
24
|
# utils
|
|
25
25
|
class RoleStrategyRecord(BaseModel):
|
|
26
26
|
#: 用户
|
|
27
|
-
users: Optional[List[Union[UserDTO, str]]]
|
|
27
|
+
users: Optional[List[Union[UserDTO, str]]] = None
|
|
28
28
|
#: 用户组
|
|
29
|
-
user_groups: Optional[List[Union[UserGroupListDTO, str]]]
|
|
29
|
+
user_groups: Optional[List[Union[UserGroupListDTO, str]]] = None
|
|
30
30
|
#: 角色
|
|
31
|
-
roles: Optional[List[Union[RoleDetailVO, str]]]
|
|
31
|
+
roles: Optional[List[Union[RoleDetailVO, str]]] = None
|
|
32
32
|
#: 角色组
|
|
33
|
-
role_groups: Optional[List[Union[RoleGroupDetailVO, str]]]
|
|
33
|
+
role_groups: Optional[List[Union[RoleGroupDetailVO, str]]] = None
|
|
34
34
|
#: 维度表达式
|
|
35
35
|
dim_expr: List[Optional[str]]
|
|
36
36
|
|
deepfos/element/variable.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
|
-
from typing import List, Dict, Union, Any, Callable, TYPE_CHECKING
|
|
2
|
+
from typing import List, Dict, Union, Any, Callable, TYPE_CHECKING, Optional
|
|
3
3
|
import pandas as pd
|
|
4
4
|
import datetime
|
|
5
5
|
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
|
|
6
8
|
from deepfos.api.models.variable import VariableValueDTO, VariableVO, UpdateVariavlesDTO, ValueTypeMapDTO, \
|
|
7
9
|
BaseElementDetail
|
|
8
10
|
from deepfos.api.variable import VariableAPI
|
|
@@ -23,13 +25,13 @@ __all__ = [
|
|
|
23
25
|
|
|
24
26
|
|
|
25
27
|
class GlobalVariable(VariableValueDTO):
|
|
26
|
-
description:
|
|
28
|
+
description: Optional[Dict] = {'zh-cn': None, 'en': None}
|
|
27
29
|
scope: int = 1
|
|
28
30
|
status: bool = True
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
class UserVariable(VariableValueDTO):
|
|
32
|
-
description:
|
|
34
|
+
description: Optional[Dict] = {'zh-cn': None, 'en': None}
|
|
33
35
|
scope: int = 2
|
|
34
36
|
status: bool = True
|
|
35
37
|
|
|
@@ -72,17 +74,17 @@ class NumberValueMap(ValueTypeMapDTO):
|
|
|
72
74
|
#: 变量类型代码
|
|
73
75
|
valueType: int = VariableTypeId.NUMBER
|
|
74
76
|
#: 整数长度
|
|
75
|
-
maxLen: int = None
|
|
77
|
+
maxLen: Optional[int] = None
|
|
76
78
|
#: 小数长度
|
|
77
|
-
digitLen: int = None
|
|
79
|
+
digitLen: Optional[int] = None
|
|
78
80
|
#: 允许等于最小值
|
|
79
81
|
minEqual: bool = True
|
|
80
82
|
#: 最小值
|
|
81
|
-
minimun: int = None
|
|
83
|
+
minimun: Optional[int] = None
|
|
82
84
|
#: 允许等于最大值
|
|
83
85
|
maxEqual: bool = True
|
|
84
86
|
#: 最大值
|
|
85
|
-
maximun: int = None
|
|
87
|
+
maximun: Optional[int] = None
|
|
86
88
|
#: 是否显示为百分比
|
|
87
89
|
percentage: bool = False
|
|
88
90
|
|
|
@@ -91,30 +93,30 @@ class SmartListValueMap(ValueTypeMapDTO):
|
|
|
91
93
|
#: 变量类型代码
|
|
92
94
|
valueType: int = VariableTypeId.SML
|
|
93
95
|
#: 元素详情(由smartlist提取后的元素信息)
|
|
94
|
-
elementDetail: BaseElementDetail = None
|
|
96
|
+
elementDetail: Optional[BaseElementDetail] = None
|
|
95
97
|
#: 值列表元素名称
|
|
96
|
-
valueKey: str = None
|
|
98
|
+
valueKey: Optional[str] = None
|
|
97
99
|
#: 是否选择多个值列表成员
|
|
98
100
|
selectedMulti: bool = False
|
|
99
101
|
#: 多选值列表的成员值列表
|
|
100
|
-
valueField: List[str] =
|
|
102
|
+
valueField: List[str] = Field(default_factory=list)
|
|
101
103
|
#: 多选成员数上限
|
|
102
|
-
multipleChoiceLimit: int = None
|
|
104
|
+
multipleChoiceLimit: Optional[int] = None
|
|
103
105
|
|
|
104
106
|
|
|
105
107
|
class DimensionValueMap(ValueTypeMapDTO):
|
|
106
108
|
#: 变量类型代码
|
|
107
109
|
valueType: int = VariableTypeId.DIM
|
|
108
110
|
#: 元素详情(由Dimension提取后的元素信息)
|
|
109
|
-
elementDetail: BaseElementDetail = None
|
|
111
|
+
elementDetail: Optional[BaseElementDetail] = None
|
|
110
112
|
#: 维度元素名称
|
|
111
|
-
dimensionName: str = None
|
|
113
|
+
dimensionName: Optional[str] = None
|
|
112
114
|
#: 维度表达式
|
|
113
|
-
valueKey: str = None
|
|
115
|
+
valueKey: Optional[str] = None
|
|
114
116
|
#: 是否选择多个值列表成员
|
|
115
117
|
selectedMulti: bool = False
|
|
116
118
|
#: 多选成员数上限
|
|
117
|
-
multipleChoiceLimit: int = None
|
|
119
|
+
multipleChoiceLimit: Optional[int] = None
|
|
118
120
|
|
|
119
121
|
|
|
120
122
|
class DateValueMap(ValueTypeMapDTO):
|
|
@@ -128,11 +130,11 @@ class CustomListValueMap(ValueTypeMapDTO):
|
|
|
128
130
|
#: 变量类型代码
|
|
129
131
|
valueType: int = VariableTypeId.CUSTOM_LIST
|
|
130
132
|
#: 自定义列表表达式(多个值请用“,”分开。示例: 1,2,3)
|
|
131
|
-
valueKey: str = None
|
|
133
|
+
valueKey: Optional[str] = None
|
|
132
134
|
#: 是否选择多个值列表成员
|
|
133
135
|
selectedMulti: bool = False
|
|
134
136
|
#: 多选成员数上限
|
|
135
|
-
multipleChoiceLimit: int = None
|
|
137
|
+
multipleChoiceLimit: Optional[int] = None
|
|
136
138
|
|
|
137
139
|
|
|
138
140
|
# -----------------------------------------------------------------------------
|
|
@@ -332,6 +334,10 @@ class AsyncVariable(ElementBase[VariableAPI]):
|
|
|
332
334
|
if obj_hook is not None:
|
|
333
335
|
return obj_hook(value)
|
|
334
336
|
|
|
337
|
+
# 启用维度表达式的维度变量应直接取原值
|
|
338
|
+
if var.valueType == VariableTypeId.DIM and not var.valueTypeMap.selectedMulti:
|
|
339
|
+
return value
|
|
340
|
+
|
|
335
341
|
if not auto_cast:
|
|
336
342
|
cast = None
|
|
337
343
|
elif var.valueType == VariableTypeId.NUMBER:
|
deepfos/element/workflow.py
CHANGED
|
@@ -2,8 +2,9 @@ from functools import partial
|
|
|
2
2
|
from typing import *
|
|
3
3
|
|
|
4
4
|
import pandas as pd
|
|
5
|
-
from pydantic import parse_obj_as
|
|
6
5
|
|
|
6
|
+
from deepfos.api.models import compat_parse_obj_as as parse_obj_as
|
|
7
|
+
from deepfos.api.models.platform import FileUplodRes
|
|
7
8
|
from deepfos.api.models.workflow import *
|
|
8
9
|
from deepfos.api.workflow import WorkFlowAPI
|
|
9
10
|
from deepfos.element.base import ElementBase, SyncMeta
|
|
@@ -295,22 +296,76 @@ class AsyncWorkFlow(ElementBase[WorkFlowAPI]):
|
|
|
295
296
|
comment: str = None,
|
|
296
297
|
file_path: str = None,
|
|
297
298
|
outcome: str = None,
|
|
299
|
+
extra_res_params: Dict[str, Any] = None,
|
|
300
|
+
attachments: List[Union[Dict[str, str], FileUplodRes]] = None,
|
|
298
301
|
) -> bool:
|
|
299
302
|
"""完成任务实例
|
|
300
303
|
|
|
301
304
|
Args:
|
|
302
305
|
task_id: 任务实例id
|
|
303
306
|
comment: 备注
|
|
304
|
-
file_path: 附件路径
|
|
307
|
+
file_path: 附件路径(工作流版本在V3.0.4.0后弃用,后续版本请使用attachments参数)
|
|
305
308
|
outcome: 结果选项,在任务可选结果不唯一时,必须提供
|
|
309
|
+
extra_res_params: 完成参数
|
|
310
|
+
attachments: 附件信息列表,
|
|
311
|
+
内容一般来自文件管理的/files/upload或/files/upload/content响应值
|
|
312
|
+
|
|
306
313
|
|
|
307
314
|
Returns:
|
|
308
315
|
True: 成功
|
|
309
316
|
False: 失败
|
|
310
317
|
|
|
318
|
+
.. admonition:: 示例
|
|
319
|
+
|
|
320
|
+
.. code-block:: python
|
|
321
|
+
|
|
322
|
+
from deepfos.api.platform import PlatformAPI
|
|
323
|
+
from deepfos.element.workflow import WorkFlow
|
|
324
|
+
|
|
325
|
+
# 上传附件't.txt'
|
|
326
|
+
upload_resp = PlatformAPI().file.upload(
|
|
327
|
+
file_type='DL', file_name='t.txt', file='some text'
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
# 以动作approve,参数{"a": 1, "b": "42"}完成任务实例,
|
|
331
|
+
# 并提供附件信息为上传的't.txt'文件,备注为"Completed by SDK"
|
|
332
|
+
test_task = WorkFlow('test_task')
|
|
333
|
+
test_task.complete_task_by_id(
|
|
334
|
+
task_id='fd94f6a7-3467-47f9-8a3c-ff626e68dcf5',
|
|
335
|
+
outcome='approve',
|
|
336
|
+
extra_res_params={'a': 1, 'b': '42'},
|
|
337
|
+
comment='Completed by SDK',
|
|
338
|
+
attachments=[upload_resp]
|
|
339
|
+
)
|
|
340
|
+
|
|
311
341
|
"""
|
|
342
|
+
action_id = None
|
|
343
|
+
if outcome is not None:
|
|
344
|
+
outcomes = await self.async_api.task.outcomes(task_id)
|
|
345
|
+
for o in outcomes:
|
|
346
|
+
if o.code == outcome:
|
|
347
|
+
action_id = o.id
|
|
348
|
+
break
|
|
349
|
+
|
|
350
|
+
if extra_res_params is None:
|
|
351
|
+
extra_res_params = {}
|
|
352
|
+
if not isinstance(extra_res_params, dict):
|
|
353
|
+
raise TypeError('extra_res_params参数应为字典类型')
|
|
354
|
+
|
|
355
|
+
if attachments is not None:
|
|
356
|
+
attachments = parse_obj_as(List[FileUplodRes], attachments)
|
|
357
|
+
else:
|
|
358
|
+
attachments = []
|
|
312
359
|
return await self.async_api.task.express_complete(
|
|
313
|
-
TaskCompleteInstance(
|
|
360
|
+
TaskCompleteInstance(
|
|
361
|
+
comment=comment, filePath=file_path, outcome=outcome, taskId=task_id,
|
|
362
|
+
extraResParams=[
|
|
363
|
+
TaskCompleteParam(name=k, value=v)
|
|
364
|
+
for k, v in extra_res_params.items()
|
|
365
|
+
],
|
|
366
|
+
actionId=action_id,
|
|
367
|
+
attachments=attachments
|
|
368
|
+
)
|
|
314
369
|
)
|
|
315
370
|
|
|
316
371
|
async def list_process(
|
|
@@ -536,6 +591,8 @@ class WorkFlow(AsyncWorkFlow, metaclass=SyncMeta):
|
|
|
536
591
|
comment: str = None,
|
|
537
592
|
file_path: str = None,
|
|
538
593
|
outcome: str = None,
|
|
594
|
+
extra_res_params: Dict[str, Any] = None,
|
|
595
|
+
attachments: List[Union[Dict[str, str], FileUplodRes]] = None,
|
|
539
596
|
) -> bool:
|
|
540
597
|
...
|
|
541
598
|
|
deepfos/exceptions/__init__.py
CHANGED
|
@@ -200,7 +200,7 @@ class MsgCenterError(DeepfosBaseException):
|
|
|
200
200
|
self.reasons = reasons
|
|
201
201
|
|
|
202
202
|
def __str__(self):
|
|
203
|
-
return '
|
|
203
|
+
return '推送失败,响应错误详情:\n' + '\n'.join([str(r.dict()) for r in self.reasons])
|
|
204
204
|
|
|
205
205
|
|
|
206
206
|
# -----------------------------------------------------------------------------
|
deepfos/lib/deepchart.py
CHANGED
|
@@ -6,8 +6,9 @@ from inspect import isfunction
|
|
|
6
6
|
from pandas import DataFrame
|
|
7
7
|
from typing import *
|
|
8
8
|
import pandas as pd
|
|
9
|
-
from pydantic import Field
|
|
9
|
+
from pydantic import Field
|
|
10
10
|
|
|
11
|
+
from deepfos.api.models import compat_parse_obj_as as parse_obj_as
|
|
11
12
|
from deepfos.lib.asynchronous import evloop
|
|
12
13
|
from deepfos.api.models import BaseModel
|
|
13
14
|
from deepfos.api.dimension import DimensionAPI
|
|
@@ -94,10 +95,10 @@ class LogicInfo(NamedTuple):
|
|
|
94
95
|
|
|
95
96
|
|
|
96
97
|
class BaseField(BaseModel):
|
|
97
|
-
code: int = None
|
|
98
|
-
columnName: str = None
|
|
99
|
-
description: str = None
|
|
100
|
-
value_key: str = None
|
|
98
|
+
code: Optional[int] = None
|
|
99
|
+
columnName: Optional[str] = None
|
|
100
|
+
description: Optional[str] = None
|
|
101
|
+
value_key: Optional[str] = None
|
|
101
102
|
|
|
102
103
|
def __init__(self, name: str = None, description: str = None, **data):
|
|
103
104
|
super().__init__(columnName=name, description=description, **data)
|
|
@@ -125,14 +126,14 @@ class BaseField(BaseModel):
|
|
|
125
126
|
|
|
126
127
|
|
|
127
128
|
class Text(BaseField):
|
|
128
|
-
code = 1
|
|
129
|
+
code: int = 1
|
|
129
130
|
|
|
130
131
|
|
|
131
132
|
class Date(BaseField):
|
|
132
|
-
code = 11
|
|
133
|
+
code: int = 11
|
|
133
134
|
# 前端时间筛选器颗粒度字段;4:最宽泛的颗粒度
|
|
134
|
-
value_key = '4'
|
|
135
|
-
precision = TimeType.day
|
|
135
|
+
value_key: str = '4'
|
|
136
|
+
precision: TimeType = TimeType.day
|
|
136
137
|
|
|
137
138
|
@property
|
|
138
139
|
def logic_info(self) -> LogicInfo:
|
|
@@ -140,11 +141,11 @@ class Date(BaseField):
|
|
|
140
141
|
|
|
141
142
|
|
|
142
143
|
class Number(BaseField):
|
|
143
|
-
code = 15
|
|
144
|
+
code: int = 15
|
|
144
145
|
|
|
145
146
|
|
|
146
147
|
class Person(BaseField):
|
|
147
|
-
code = 12
|
|
148
|
+
code: int = 12
|
|
148
149
|
|
|
149
150
|
|
|
150
151
|
class BoundField(BaseField):
|
|
@@ -195,7 +196,7 @@ class BoundField(BaseField):
|
|
|
195
196
|
|
|
196
197
|
|
|
197
198
|
class SmartList(BoundField):
|
|
198
|
-
code = 3
|
|
199
|
+
code: int = 3
|
|
199
200
|
bound_class: Type[ElementBase] = SmlElement
|
|
200
201
|
|
|
201
202
|
async def async_to_dict(self) -> Dict:
|
|
@@ -209,7 +210,7 @@ class SmartList(BoundField):
|
|
|
209
210
|
|
|
210
211
|
|
|
211
212
|
class Dimension(BoundField):
|
|
212
|
-
code = 8
|
|
213
|
+
code: int = 8
|
|
213
214
|
bound_class: Type[ElementBase] = DimElement
|
|
214
215
|
|
|
215
216
|
|
deepfos/lib/deepux.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""DeepUX组件数据源"""
|
|
2
2
|
import asyncio
|
|
3
3
|
import functools
|
|
4
|
-
from typing import Dict, Type, List, Union, Tuple
|
|
4
|
+
from typing import Dict, Type, List, Union, Tuple, Optional
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
import pandas as pd
|
|
@@ -40,8 +40,8 @@ OBJECT_TYPE = "object[]"
|
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
class BaseField(BaseModel):
|
|
43
|
-
name: str = None
|
|
44
|
-
type: str = None
|
|
43
|
+
name: Optional[str] = None
|
|
44
|
+
type: Optional[str] = None
|
|
45
45
|
|
|
46
46
|
def __init__(self, name: str = None, **data):
|
|
47
47
|
super().__init__(name=name, **data)
|
|
@@ -57,7 +57,7 @@ class ScalarField(BaseField):
|
|
|
57
57
|
|
|
58
58
|
class String(ScalarField):
|
|
59
59
|
"""文本"""
|
|
60
|
-
type = 'str'
|
|
60
|
+
type: str = 'str'
|
|
61
61
|
|
|
62
62
|
def fit(self, df: pd.DataFrame, field_name: str):
|
|
63
63
|
df[field_name] = np.where(
|
|
@@ -69,7 +69,7 @@ class String(ScalarField):
|
|
|
69
69
|
|
|
70
70
|
class Integer(ScalarField):
|
|
71
71
|
"""整数"""
|
|
72
|
-
type = 'int'
|
|
72
|
+
type: str = 'int'
|
|
73
73
|
|
|
74
74
|
def fit(self, df: pd.DataFrame, field_name: str):
|
|
75
75
|
df[field_name] = np.where(
|
|
@@ -81,7 +81,7 @@ class Integer(ScalarField):
|
|
|
81
81
|
|
|
82
82
|
class Boolean(ScalarField):
|
|
83
83
|
"""布尔"""
|
|
84
|
-
type = 'bool'
|
|
84
|
+
type: str = 'bool'
|
|
85
85
|
|
|
86
86
|
def fit(self, df: pd.DataFrame, field_name: str):
|
|
87
87
|
df[field_name] = np.where(
|
|
@@ -93,12 +93,12 @@ class Boolean(ScalarField):
|
|
|
93
93
|
|
|
94
94
|
class Json(ScalarField):
|
|
95
95
|
"""多语言文本(json)"""
|
|
96
|
-
type = 'json'
|
|
96
|
+
type: str = 'json'
|
|
97
97
|
|
|
98
98
|
|
|
99
99
|
class Float(ScalarField):
|
|
100
100
|
"""小数"""
|
|
101
|
-
type = 'float'
|
|
101
|
+
type: str = 'float'
|
|
102
102
|
|
|
103
103
|
def fit(self, df: pd.DataFrame, field_name: str):
|
|
104
104
|
df[field_name] = np.where(
|
|
@@ -110,17 +110,17 @@ class Float(ScalarField):
|
|
|
110
110
|
|
|
111
111
|
class DateTime(ScalarField):
|
|
112
112
|
"""日期时间"""
|
|
113
|
-
type = 'datetime'
|
|
113
|
+
type: str = 'datetime'
|
|
114
114
|
|
|
115
115
|
|
|
116
116
|
class UUID(ScalarField):
|
|
117
117
|
"""uuid"""
|
|
118
|
-
type = 'uuid'
|
|
118
|
+
type: str = 'uuid'
|
|
119
119
|
|
|
120
120
|
|
|
121
121
|
class ObjectField(BaseField):
|
|
122
122
|
"""表示对象类型的结构信息, 图格式数据源专用"""
|
|
123
|
-
type = OBJECT_TYPE
|
|
123
|
+
type: str = OBJECT_TYPE
|
|
124
124
|
fields: List[ScalarField] = Field(default_factory=list)
|
|
125
125
|
|
|
126
126
|
|
deepfos/lib/discovery.py
CHANGED
deepfos/lib/filterparser.py
CHANGED
|
@@ -2,9 +2,9 @@ import collections
|
|
|
2
2
|
import re
|
|
3
3
|
from enum import Enum, IntEnum
|
|
4
4
|
from typing import *
|
|
5
|
-
from pydantic import Field
|
|
5
|
+
from pydantic import Field
|
|
6
6
|
|
|
7
|
-
from deepfos.api.models import BaseModel
|
|
7
|
+
from deepfos.api.models import BaseModel, compat_parse_obj_as as parse_obj_as
|
|
8
8
|
from deepfos.core.logictable.nodemixin import NodeMixin, TreeRenderer
|
|
9
9
|
from deepfos.lib.utils import CIEnum, CIEnumMeta
|
|
10
10
|
|
deepfos/lib/k8s.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from functools import cached_property
|
|
4
|
+
from typing import TypedDict, Literal, Type, Dict, FrozenSet
|
|
5
|
+
|
|
6
|
+
from loguru import logger
|
|
7
|
+
from kubernetes_asyncio import client, config
|
|
8
|
+
from kubernetes_asyncio.client import (
|
|
9
|
+
V1ServiceList,
|
|
10
|
+
V1Endpoints,
|
|
11
|
+
V1EndpointSubset,
|
|
12
|
+
V1EndpointAddress,
|
|
13
|
+
CoreV1EndpointPort,
|
|
14
|
+
)
|
|
15
|
+
from deepfos.lib.discovery import ServiceDiscovery
|
|
16
|
+
from deepfos import OPTION
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class K8s(ServiceDiscovery):
|
|
20
|
+
def __init__(self):
|
|
21
|
+
super().__init__()
|
|
22
|
+
self._instance_lock: Dict[str, asyncio.Lock] = defaultdict(asyncio.Lock)
|
|
23
|
+
self._server_lock = asyncio.Lock()
|
|
24
|
+
self._cares = set()
|
|
25
|
+
self._ns = OPTION.k8s.namespace
|
|
26
|
+
|
|
27
|
+
async def on_close(self):
|
|
28
|
+
self._cares.clear()
|
|
29
|
+
self.server_cache.clear()
|
|
30
|
+
self._instance_lock.clear()
|
|
31
|
+
|
|
32
|
+
async def on_startup(self):
|
|
33
|
+
config.load_incluster_config()
|
|
34
|
+
|
|
35
|
+
@cached_property
|
|
36
|
+
def _api(self):
|
|
37
|
+
return client.CoreV1Api()
|
|
38
|
+
|
|
39
|
+
async def on_interval(self):
|
|
40
|
+
await self._update_cache()
|
|
41
|
+
|
|
42
|
+
async def _update_cache(self):
|
|
43
|
+
logger.opt(lazy=True).debug(f"Update cache for instance: {self._cares}")
|
|
44
|
+
await asyncio.gather(*(
|
|
45
|
+
self.update_instance_for_service(sn)
|
|
46
|
+
for sn in self._cares
|
|
47
|
+
))
|
|
48
|
+
|
|
49
|
+
async def update_service_cache(self, server_name: str):
|
|
50
|
+
async with self._server_lock:
|
|
51
|
+
if server_name not in self.server_cache:
|
|
52
|
+
await self.update_services()
|
|
53
|
+
|
|
54
|
+
async def update_instance_cache(self, server_name):
|
|
55
|
+
self._cares.add(server_name)
|
|
56
|
+
await self.update_instance_for_service(server_name)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
async def _list_all_service_names(self) -> FrozenSet[str]:
|
|
60
|
+
svcs: V1ServiceList = await self._api.list_namespaced_service(self._ns)
|
|
61
|
+
return frozenset(svc.metadata.name for svc in svcs.items)
|
|
62
|
+
|
|
63
|
+
async def update_services(self):
|
|
64
|
+
new_services = await self._list_all_service_names()
|
|
65
|
+
cur_services = frozenset(self.server_cache.keys())
|
|
66
|
+
|
|
67
|
+
if added := new_services - cur_services:
|
|
68
|
+
for srv in added:
|
|
69
|
+
self.server_cache.__getitem__(srv)
|
|
70
|
+
logger.debug(f"Added services: {added}")
|
|
71
|
+
|
|
72
|
+
if removed := cur_services - new_services:
|
|
73
|
+
for srv in removed:
|
|
74
|
+
self.server_cache.pop(srv)
|
|
75
|
+
self._instance_lock.pop(srv, None) # noqa
|
|
76
|
+
logger.debug(f"Removed servieces: {removed}")
|
|
77
|
+
|
|
78
|
+
async def update_instance_for_service(self, server_name: str):
|
|
79
|
+
endpoints: V1Endpoints = await self._api.read_namespaced_endpoints(
|
|
80
|
+
server_name,
|
|
81
|
+
self._ns,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
no_active_nodes = True
|
|
85
|
+
async with self._instance_lock[server_name]:
|
|
86
|
+
cache = self.server_cache[server_name]
|
|
87
|
+
|
|
88
|
+
for subset in endpoints.subsets or []:
|
|
89
|
+
subset: V1EndpointSubset
|
|
90
|
+
for addr in subset.addresses or []:
|
|
91
|
+
addr: V1EndpointAddress
|
|
92
|
+
no_active_nodes = False
|
|
93
|
+
for port in subset.ports:
|
|
94
|
+
port: CoreV1EndpointPort
|
|
95
|
+
url = f"{port.name}://{addr.ip}:{port.port}"
|
|
96
|
+
cache.add(url)
|
|
97
|
+
|
|
98
|
+
if no_active_nodes:
|
|
99
|
+
logger.debug(f"{server_name} has no active endpoint, clear invalid...")
|
|
100
|
+
for item in list(cache):
|
|
101
|
+
cache.delete(item)
|