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