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
deepfos/lib/httpcli.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""HTTP客户端"""
|
|
2
|
+
|
|
3
|
+
import atexit
|
|
4
|
+
import threading
|
|
5
|
+
import asyncio
|
|
6
|
+
import aiohttp
|
|
7
|
+
import requests
|
|
8
|
+
from .utils import auto_setup
|
|
9
|
+
from .asynchronous import register_on_loop_shutdown
|
|
10
|
+
from deepfos.options import OPTION
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AioHttpCli:
|
|
14
|
+
session = {}
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
async def get_session(cls):
|
|
18
|
+
tid = threading.get_ident()
|
|
19
|
+
if (
|
|
20
|
+
(session := cls.session.get(tid)) is None
|
|
21
|
+
or session.closed
|
|
22
|
+
or session._loop.is_closed() # noqa
|
|
23
|
+
):
|
|
24
|
+
cls.session[tid] = aiohttp.ClientSession(connector=aiohttp.TCPConnector(verify_ssl=False))
|
|
25
|
+
register_on_loop_shutdown(cls.close_current)
|
|
26
|
+
return cls.session[tid]
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
async def get(cls, url, *, allow_redirects=True, **kwargs):
|
|
30
|
+
session = await cls.get_session()
|
|
31
|
+
return await session.get(
|
|
32
|
+
url,
|
|
33
|
+
allow_redirects=allow_redirects,
|
|
34
|
+
verify_ssl=OPTION.api.verify_ssl,
|
|
35
|
+
timeout=OPTION.api.timeout,
|
|
36
|
+
**kwargs
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
async def post(cls, url, *, data=None, **kwargs):
|
|
41
|
+
session = await cls.get_session()
|
|
42
|
+
return await session.post(
|
|
43
|
+
url,
|
|
44
|
+
data=data,
|
|
45
|
+
verify_ssl=OPTION.api.verify_ssl,
|
|
46
|
+
timeout=OPTION.api.timeout,
|
|
47
|
+
**kwargs
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
async def close(cls):
|
|
52
|
+
for session in cls.session.values():
|
|
53
|
+
await session.close()
|
|
54
|
+
await asyncio.sleep(0)
|
|
55
|
+
del session
|
|
56
|
+
cls.session = {}
|
|
57
|
+
|
|
58
|
+
@classmethod
|
|
59
|
+
async def close_current(cls):
|
|
60
|
+
tid = threading.get_ident()
|
|
61
|
+
session = cls.session.pop(tid, None)
|
|
62
|
+
if session is not None:
|
|
63
|
+
await session.close()
|
|
64
|
+
await asyncio.sleep(0)
|
|
65
|
+
del session
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class SyncHttpCli:
|
|
69
|
+
"""同步HTTP客户端"""
|
|
70
|
+
session: requests.Session = None
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def setup(cls):
|
|
74
|
+
if cls.session is None:
|
|
75
|
+
session = requests.Session()
|
|
76
|
+
cls.session = session
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
@auto_setup
|
|
80
|
+
def post(cls, url, headers=None, **kwargs):
|
|
81
|
+
return cls.session.post(
|
|
82
|
+
url,
|
|
83
|
+
headers=headers,
|
|
84
|
+
timeout=(4, 180),
|
|
85
|
+
verify=OPTION.api.verify_ssl,
|
|
86
|
+
**kwargs
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
@classmethod
|
|
90
|
+
@auto_setup
|
|
91
|
+
def get(cls, url, params=None, headers=None):
|
|
92
|
+
return cls.session.get(
|
|
93
|
+
url,
|
|
94
|
+
params=params,
|
|
95
|
+
headers=headers,
|
|
96
|
+
timeout=(4, 180),
|
|
97
|
+
verify=OPTION.api.verify_ssl,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _close_session(): # pragma: no cover
|
|
102
|
+
if SyncHttpCli.session is not None:
|
|
103
|
+
SyncHttpCli.session.close()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
atexit.register(_close_session)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from deepfos.boost import jstream
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class JsonStreamer:
|
|
7
|
+
def __init__(self, file, key: str):
|
|
8
|
+
self.key = key.encode('utf-8')
|
|
9
|
+
# 是否在引号内
|
|
10
|
+
self.in_double_quotation = False
|
|
11
|
+
# 是否在转义符内
|
|
12
|
+
self.in_escape = False
|
|
13
|
+
# 花括号个数
|
|
14
|
+
self.lbrace = 0
|
|
15
|
+
self.rbrace = 0
|
|
16
|
+
# 匹配的键是否已开始
|
|
17
|
+
self.is_started = False
|
|
18
|
+
# 当前已匹配的键段
|
|
19
|
+
self.matched_index = 0
|
|
20
|
+
# 是否已找到完整的值
|
|
21
|
+
self.finished = False
|
|
22
|
+
|
|
23
|
+
if isinstance(file, str):
|
|
24
|
+
if os.path.exists(file):
|
|
25
|
+
self.file = open(file, 'rb')
|
|
26
|
+
self.should_close = True
|
|
27
|
+
else:
|
|
28
|
+
raise ValueError('Filename or IO value expected.')
|
|
29
|
+
elif isinstance(file, JsonStreamer):
|
|
30
|
+
self.should_close = True
|
|
31
|
+
self.file = file
|
|
32
|
+
else:
|
|
33
|
+
self.should_close = False
|
|
34
|
+
self.file = file
|
|
35
|
+
|
|
36
|
+
def read(self, length: int = 1024):
|
|
37
|
+
result = b''
|
|
38
|
+
read_size = length
|
|
39
|
+
while len(result) < length:
|
|
40
|
+
if self.finished:
|
|
41
|
+
break
|
|
42
|
+
temp = self.file.read(read_size)
|
|
43
|
+
if len(temp) == 0:
|
|
44
|
+
break
|
|
45
|
+
result = result + jstream.read(temp, self)
|
|
46
|
+
read_size = length - len(result)
|
|
47
|
+
return result
|
|
48
|
+
|
|
49
|
+
def __del__(self):
|
|
50
|
+
self.close()
|
|
51
|
+
|
|
52
|
+
def close(self):
|
|
53
|
+
if getattr(self, 'should_close', False):
|
|
54
|
+
self.file.close()
|
|
55
|
+
self.should_close = False
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class JsonStreamMultiKeyPath:
|
|
59
|
+
def __init__(self, file, key_path: str):
|
|
60
|
+
all_key = key_path.split('.')
|
|
61
|
+
streamer = JsonStreamer(file, all_key[0])
|
|
62
|
+
for key in all_key[1::]:
|
|
63
|
+
streamer = JsonStreamer(streamer, key)
|
|
64
|
+
self.streamer = streamer
|
|
65
|
+
|
|
66
|
+
def __enter__(self):
|
|
67
|
+
return self.streamer
|
|
68
|
+
|
|
69
|
+
def __exit__(self, *args):
|
|
70
|
+
self.streamer.close()
|
|
71
|
+
|
|
72
|
+
def read(self, length: int = 1024):
|
|
73
|
+
return self.streamer.read(length)
|
|
74
|
+
|
|
75
|
+
def close(self):
|
|
76
|
+
self.streamer.close()
|
|
77
|
+
|
|
78
|
+
def __del__(self):
|
|
79
|
+
if getattr(self, 'streamer', None):
|
|
80
|
+
self.streamer.close()
|
deepfos/lib/msg.py
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from functools import partial
|
|
4
|
+
from typing import Dict, List, Union, Literal, TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from deepfos import OPTION
|
|
7
|
+
from deepfos.api.models.system import (
|
|
8
|
+
MessageTemplateListVOResp, MessageTemplateListQueryDto,
|
|
9
|
+
MessageTemplateListVO, MessageTemplateParamResp,
|
|
10
|
+
MessageTemplateParamListQueryDto
|
|
11
|
+
)
|
|
12
|
+
from deepfos.api.platform import PlatformAPI
|
|
13
|
+
from deepfos.api.system import SystemAPI
|
|
14
|
+
from deepfos.db.dbkits import SyncMeta
|
|
15
|
+
from deepfos.exceptions import MsgCenterError
|
|
16
|
+
from deepfos.lib.asynchronous import future_property
|
|
17
|
+
from deepfos.lib.decorator import cached_property
|
|
18
|
+
from deepfos.lib.utils import fetch_all_pages
|
|
19
|
+
|
|
20
|
+
__all__ = ['MsgCenter', 'AsyncMsgCenter']
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TemplateType(Enum):
|
|
24
|
+
# 平台公告
|
|
25
|
+
plat = 1
|
|
26
|
+
# 站内消息
|
|
27
|
+
station = 2
|
|
28
|
+
# 短信
|
|
29
|
+
sms = 3
|
|
30
|
+
# 邮箱
|
|
31
|
+
email = 4
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _tpl_list_count(resp: Union[MessageTemplateListVOResp, MessageTemplateParamResp]):
|
|
35
|
+
return len(resp.list)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AsyncMsgCenter:
|
|
39
|
+
"""消息中心"""
|
|
40
|
+
|
|
41
|
+
def __init__(self):
|
|
42
|
+
self.__class__.templates.submit(self)
|
|
43
|
+
self.system = SystemAPI(sync=False)
|
|
44
|
+
self.platform = PlatformAPI(sync=False)
|
|
45
|
+
|
|
46
|
+
@cached_property
|
|
47
|
+
def type_api_map(self):
|
|
48
|
+
svc = self.system.msg_service
|
|
49
|
+
return {
|
|
50
|
+
TemplateType.plat: (svc.push_plat_notice, svc.push_plat_notice),
|
|
51
|
+
TemplateType.station: (svc.push_station_message, svc.push_station_message),
|
|
52
|
+
TemplateType.email: (svc.push_email_message, svc.send_email),
|
|
53
|
+
TemplateType.sms: (svc.push_sms_message, svc.send_sms),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@future_property(on_demand=True)
|
|
57
|
+
async def templates(self) -> Dict[str, MessageTemplateListVO]:
|
|
58
|
+
"""当前有效的空间消息模板"""
|
|
59
|
+
|
|
60
|
+
def impl(api, num, size):
|
|
61
|
+
return api(MessageTemplateListQueryDto(pageNum=num, pageSize=size))
|
|
62
|
+
|
|
63
|
+
pages = await fetch_all_pages(
|
|
64
|
+
partial(impl, api=self.system.msg_template.page),
|
|
65
|
+
count_getter=_tpl_list_count,
|
|
66
|
+
page_size=100,
|
|
67
|
+
page_no_key='num',
|
|
68
|
+
page_size_key='size'
|
|
69
|
+
)
|
|
70
|
+
return {vo.templateCode: vo for p in pages if p.list
|
|
71
|
+
for vo in p.list if vo.status == 1}
|
|
72
|
+
|
|
73
|
+
async def _params(self, template_id: int) -> List[Dict]:
|
|
74
|
+
def impl(api, num, size):
|
|
75
|
+
return api(MessageTemplateParamListQueryDto(
|
|
76
|
+
pageNum=num, pageSize=size, templateId=template_id
|
|
77
|
+
))
|
|
78
|
+
|
|
79
|
+
pages = await fetch_all_pages(
|
|
80
|
+
partial(impl, api=self.system.msg_template.get_template_param),
|
|
81
|
+
count_getter=_tpl_list_count,
|
|
82
|
+
page_size=100,
|
|
83
|
+
page_no_key='num',
|
|
84
|
+
page_size_key='size'
|
|
85
|
+
)
|
|
86
|
+
return [
|
|
87
|
+
{'name': vo.paramName, 'scope': vo.scope}
|
|
88
|
+
for p in pages if p.list for vo in p.list
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
async def _fit_param(self, tpl_id, scope: Literal[1, 2], param=None):
|
|
92
|
+
if not param:
|
|
93
|
+
param = {}
|
|
94
|
+
|
|
95
|
+
valid = {p['name'] for p in (await self._params(tpl_id)) if p['scope'] == scope}
|
|
96
|
+
scope_name = {1: '标题', 2: '内容'}
|
|
97
|
+
if lacked := (valid - param.keys()):
|
|
98
|
+
raise ValueError(f'{scope_name[scope]}参数: {lacked} 缺失')
|
|
99
|
+
|
|
100
|
+
return [
|
|
101
|
+
{'paramName': k, 'paramValue': str(param[k]), 'scope': scope}
|
|
102
|
+
for k in valid
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
async def _publish(
|
|
106
|
+
self,
|
|
107
|
+
template: MessageTemplateListVO,
|
|
108
|
+
receivers: List,
|
|
109
|
+
sender: str = None,
|
|
110
|
+
title_param: Dict[str, str] = None,
|
|
111
|
+
content_param: Dict[str, str] = None,
|
|
112
|
+
attachments: Dict[str, Union[str, bytes]] = None,
|
|
113
|
+
cc_email: List[str] = None,
|
|
114
|
+
api_idx: Literal[0, 1] = 0
|
|
115
|
+
) -> List:
|
|
116
|
+
payload = {
|
|
117
|
+
'receiver': receivers,
|
|
118
|
+
'sender': sender or OPTION.api.header.get('user'),
|
|
119
|
+
'templateCode': template.templateCode,
|
|
120
|
+
'params': [
|
|
121
|
+
*(await self._fit_param(template.id, 1, title_param)),
|
|
122
|
+
*(await self._fit_param(template.id, 2, content_param)),
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
tpl_type = TemplateType(template.type)
|
|
126
|
+
if cc_email:
|
|
127
|
+
payload['ccEmail'] = cc_email
|
|
128
|
+
|
|
129
|
+
if tpl_type in [TemplateType.email, TemplateType.station] and attachments:
|
|
130
|
+
async def _gen_attach(name, file):
|
|
131
|
+
return {
|
|
132
|
+
'id': (await self.platform.file.upload('DL', name, file)).id,
|
|
133
|
+
'space': OPTION.api.header.get('space')
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
payload['attachment'] = await asyncio.gather(*[
|
|
137
|
+
_gen_attach(name, file) for name, file in attachments.items()
|
|
138
|
+
])
|
|
139
|
+
|
|
140
|
+
api = self.type_api_map[tpl_type][api_idx]
|
|
141
|
+
resp = await api(payload)
|
|
142
|
+
if resp.failure:
|
|
143
|
+
raise MsgCenterError(*resp.failure)
|
|
144
|
+
|
|
145
|
+
return resp.success
|
|
146
|
+
|
|
147
|
+
async def publish(
|
|
148
|
+
self,
|
|
149
|
+
template_code: str,
|
|
150
|
+
receiver_users: List[str] = None,
|
|
151
|
+
receiver_groups: List[str] = None,
|
|
152
|
+
sender: str = None,
|
|
153
|
+
title_param: Dict[str, str] = None,
|
|
154
|
+
content_param: Dict[str, str] = None,
|
|
155
|
+
attachments: Dict[str, Union[str, bytes]] = None,
|
|
156
|
+
) -> List:
|
|
157
|
+
"""推送指定消息模版的消息
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
template_code: 模板编码
|
|
161
|
+
sender: 可选,发送人userid,默认为当前用户id
|
|
162
|
+
receiver_users: 可选,收件人userid列表
|
|
163
|
+
receiver_groups: 可选,收件人groupid列表
|
|
164
|
+
title_param: 可选,标题变量
|
|
165
|
+
content_param: 可选,内容变量
|
|
166
|
+
attachments: 可选,站内消息或邮箱的消息附件,以 文件名: 文件(字符串/bytes) 的字典形式提供
|
|
167
|
+
|
|
168
|
+
.. admonition:: 示例
|
|
169
|
+
|
|
170
|
+
.. code-block:: python
|
|
171
|
+
|
|
172
|
+
from deepfos.lib.msg import MsgCenter
|
|
173
|
+
msg = MsgCenter()
|
|
174
|
+
|
|
175
|
+
#. 推送带附件的站内消息
|
|
176
|
+
|
|
177
|
+
.. code-block:: python
|
|
178
|
+
|
|
179
|
+
# demo_station标题如下:
|
|
180
|
+
# 标题【{var1}】
|
|
181
|
+
# 内容如下:
|
|
182
|
+
# 内容【{var1}】
|
|
183
|
+
#
|
|
184
|
+
# 发送至1个用户和1个用户组, 并附上2个文件附件
|
|
185
|
+
|
|
186
|
+
msg.publish(
|
|
187
|
+
'demo_station',
|
|
188
|
+
receiver_users=['00000000-0000-0000-0000-000000000000'],
|
|
189
|
+
receiver_groups=['00000000-0000-0000-0000-000000000001'],
|
|
190
|
+
title_param={'var1': 'a'},
|
|
191
|
+
content_param={'var1': 'b'},
|
|
192
|
+
attachments={
|
|
193
|
+
'file1.txt': 'Some text...',
|
|
194
|
+
'file2.txt': 'More text...',
|
|
195
|
+
}
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
See Also:
|
|
199
|
+
|
|
200
|
+
:meth:`send_mail`
|
|
201
|
+
:meth:`send_sms`
|
|
202
|
+
|
|
203
|
+
"""
|
|
204
|
+
template = self.templates.get(template_code)
|
|
205
|
+
if not template:
|
|
206
|
+
raise ValueError('模板编码对应的模板不存在或未启用')
|
|
207
|
+
|
|
208
|
+
receivers = []
|
|
209
|
+
if receiver_users:
|
|
210
|
+
receivers.extend([{'id': r, 'type': 'USER'} for r in receiver_users])
|
|
211
|
+
if receiver_groups:
|
|
212
|
+
receivers.extend([{'id': r, 'type': 'GROUP'} for r in receiver_groups])
|
|
213
|
+
|
|
214
|
+
if not receivers:
|
|
215
|
+
raise ValueError('需提供receiver_users和receiver_groups中的至少一项')
|
|
216
|
+
|
|
217
|
+
return await self._publish(
|
|
218
|
+
template, sender=sender, receivers=receivers,
|
|
219
|
+
title_param=title_param, content_param=content_param,
|
|
220
|
+
attachments=attachments,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
async def send_mail(
|
|
224
|
+
self,
|
|
225
|
+
template_code: str,
|
|
226
|
+
receivers: List[str],
|
|
227
|
+
sender: str = None,
|
|
228
|
+
title_param: Dict[str, str] = None,
|
|
229
|
+
content_param: Dict[str, str] = None,
|
|
230
|
+
attachments: Dict[str, Union[str, bytes]] = None,
|
|
231
|
+
cc_email: List[str] = None,
|
|
232
|
+
) -> List:
|
|
233
|
+
"""发送指定消息模版的邮件
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
template_code: 模板编码
|
|
237
|
+
receivers: 收件人邮箱列表
|
|
238
|
+
sender: 可选,发送人userid,默认为当前用户id
|
|
239
|
+
title_param: 可选,标题变量
|
|
240
|
+
content_param: 可选,内容变量
|
|
241
|
+
attachments: 可选,附件,以 文件名: 文件(字符串/bytes) 的字典形式提供
|
|
242
|
+
cc_email: 可选,抄送人列表
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
.. admonition:: 示例
|
|
246
|
+
|
|
247
|
+
.. code-block:: python
|
|
248
|
+
|
|
249
|
+
from deepfos.lib.msg import MsgCenter
|
|
250
|
+
msg = MsgCenter()
|
|
251
|
+
|
|
252
|
+
#. 发送邮件
|
|
253
|
+
|
|
254
|
+
.. code-block:: python
|
|
255
|
+
|
|
256
|
+
# demo_mail标题如下:
|
|
257
|
+
# 邮件标题【{var2}】
|
|
258
|
+
# 内容如下:
|
|
259
|
+
# 邮件内容【{var2}】
|
|
260
|
+
#
|
|
261
|
+
# 发送至一个邮箱并抄送另一个邮箱, 并附上2个文件附件
|
|
262
|
+
|
|
263
|
+
msg.send_mail(
|
|
264
|
+
'demo_mail',
|
|
265
|
+
receivers=['xxx@a.com'],
|
|
266
|
+
cc_email=['yyy@b.com'],
|
|
267
|
+
title_param={'var2': '42'},
|
|
268
|
+
content_param={'var2': '24'},
|
|
269
|
+
attachments={
|
|
270
|
+
'file1.txt': 'Some text...',
|
|
271
|
+
'file2.txt': 'More text...',
|
|
272
|
+
}
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
See Also:
|
|
276
|
+
|
|
277
|
+
:meth:`publish`
|
|
278
|
+
:meth:`send_sms`
|
|
279
|
+
|
|
280
|
+
"""
|
|
281
|
+
template = self.templates.get(template_code)
|
|
282
|
+
if not template:
|
|
283
|
+
raise ValueError('模板编码对应的模板不存在或未启用')
|
|
284
|
+
|
|
285
|
+
if (tpl_type := TemplateType(template.type)) is not TemplateType.email:
|
|
286
|
+
raise ValueError(f'模板类型[{tpl_type}]非邮箱类型')
|
|
287
|
+
|
|
288
|
+
return await self._publish(
|
|
289
|
+
template, sender=sender, receivers=receivers,
|
|
290
|
+
title_param=title_param, content_param=content_param,
|
|
291
|
+
attachments=attachments, cc_email=cc_email,
|
|
292
|
+
api_idx=1
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
async def send_sms(
|
|
296
|
+
self,
|
|
297
|
+
template_code: str,
|
|
298
|
+
receivers: List[str],
|
|
299
|
+
sender: str = None,
|
|
300
|
+
title_param: Dict[str, str] = None,
|
|
301
|
+
content_param: Dict[str, str] = None
|
|
302
|
+
) -> List:
|
|
303
|
+
"""发送指定消息模版的短信
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
template_code: 模板编码
|
|
307
|
+
receivers: 收件人手机号列表
|
|
308
|
+
sender: 可选,发送人userid,默认为当前用户id
|
|
309
|
+
title_param: 可选,标题变量
|
|
310
|
+
content_param: 可选,内容变量
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
.. admonition:: 示例
|
|
314
|
+
|
|
315
|
+
.. code-block:: python
|
|
316
|
+
|
|
317
|
+
from deepfos.lib.msg import MsgCenter
|
|
318
|
+
msg = MsgCenter()
|
|
319
|
+
|
|
320
|
+
#. 发送短信
|
|
321
|
+
|
|
322
|
+
.. code-block:: python
|
|
323
|
+
|
|
324
|
+
# demo_sms标题如下:
|
|
325
|
+
# 短信标题【{var3}】
|
|
326
|
+
# 内容如下:
|
|
327
|
+
# 短信内容【{var3}】
|
|
328
|
+
#
|
|
329
|
+
# 发送至2个手机号
|
|
330
|
+
|
|
331
|
+
msg.send_mail(
|
|
332
|
+
'demo_sms',
|
|
333
|
+
receivers=['15000000000', '13000000000'],
|
|
334
|
+
title_param={'var3': 'Hello'},
|
|
335
|
+
content_param={'var3': 'World'}
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
See Also:
|
|
339
|
+
|
|
340
|
+
:meth:`publish`
|
|
341
|
+
:meth:`send_mail`
|
|
342
|
+
|
|
343
|
+
"""
|
|
344
|
+
template = self.templates.get(template_code)
|
|
345
|
+
if not template:
|
|
346
|
+
raise ValueError('模板编码对应的模板不存在或未启用')
|
|
347
|
+
|
|
348
|
+
if (tpl_type := TemplateType(template.type)) is not TemplateType.sms:
|
|
349
|
+
raise ValueError(f'模板类型[{tpl_type}]非短信类型')
|
|
350
|
+
|
|
351
|
+
return await self._publish(
|
|
352
|
+
template, sender=sender, receivers=receivers,
|
|
353
|
+
title_param=title_param, content_param=content_param,
|
|
354
|
+
api_idx=1
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
class MsgCenter(AsyncMsgCenter, metaclass=SyncMeta):
|
|
359
|
+
synchronize = ('publish', 'send_mail', 'send_sms',)
|
|
360
|
+
|
|
361
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
362
|
+
def publish(
|
|
363
|
+
self,
|
|
364
|
+
template_code: str,
|
|
365
|
+
receiver_users: List[str] = None,
|
|
366
|
+
receiver_groups: List[str] = None,
|
|
367
|
+
sender: str = None,
|
|
368
|
+
title_param: Dict[str, str] = None,
|
|
369
|
+
content_param: Dict[str, str] = None,
|
|
370
|
+
attachments: Dict[str, Union[str, bytes]] = None,
|
|
371
|
+
) -> List:
|
|
372
|
+
...
|
|
373
|
+
|
|
374
|
+
def send_mail(
|
|
375
|
+
self,
|
|
376
|
+
template_code: str,
|
|
377
|
+
receivers: List[str],
|
|
378
|
+
sender: str = None,
|
|
379
|
+
title_param: Dict[str, str] = None,
|
|
380
|
+
content_param: Dict[str, str] = None,
|
|
381
|
+
attachments: Dict[str, Union[str, bytes]] = None,
|
|
382
|
+
cc_email: List[str] = None,
|
|
383
|
+
) -> List:
|
|
384
|
+
...
|
|
385
|
+
|
|
386
|
+
def send_sms(
|
|
387
|
+
self,
|
|
388
|
+
template_code: str,
|
|
389
|
+
receivers: List[str],
|
|
390
|
+
sender: str = None,
|
|
391
|
+
title_param: Dict[str, str] = None,
|
|
392
|
+
content_param: Dict[str, str] = None
|
|
393
|
+
) -> List:
|
|
394
|
+
...
|