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/subtask.py ADDED
@@ -0,0 +1,572 @@
1
+ """子任务相关"""
2
+ import asyncio
3
+ import itertools
4
+ import json
5
+ import re
6
+ from collections import deque
7
+ from contextlib import AbstractContextManager
8
+ from datetime import datetime
9
+ from typing import Optional, Any, Iterable, Iterator, Dict, Sequence
10
+ from threading import Lock
11
+ import warnings
12
+
13
+ from loguru import logger
14
+
15
+ from deepfos.options import OPTION
16
+ from deepfos.lib.decorator import cached_class_property
17
+ from deepfos.lib.utils import retry
18
+ from deepfos.lib.asynchronous import evloop
19
+ from deepfos.api.models.system import JobContentDto, UpdateJobCurrentDto
20
+ from deepfos.api.system import SystemAPI
21
+
22
+ from dip.client import Client as WorkerClient
23
+
24
+ __all__ = [
25
+ 'TaskContainer', 'Task', 'create_tasks'
26
+ ]
27
+
28
+ # -----------------------------------------------------------------------------
29
+ # constants
30
+ _RE_HAS_COUNTER = re.compile('.*{counter:?[0-9]*}')
31
+ WAIT = 'WAIT'
32
+ START = 'GO'
33
+ SUCCESS = 'SUCCESS'
34
+ FAIL = 'FAIL'
35
+ FINISHED = (SUCCESS, FAIL)
36
+ COUNTER = itertools.count().__next__
37
+
38
+
39
+ def _now():
40
+ return datetime.now().strftime('%Y-%m-%d %H:%M:%S')
41
+
42
+
43
+ class TaskManager:
44
+ #: 子任务id -> 子任务状态
45
+ _task_info: Dict[str, JobContentDto] = {}
46
+ _lock = Lock()
47
+ #: 实例缓存,用于实现基于子任务id的单例
48
+ _ins_cache: dict = {}
49
+ #: 更新任务的协程
50
+ _schedule = None
51
+ #: 更新任务的sleep间隔
52
+ _interval: int = 2
53
+ #: 当前还未结束的任务数量
54
+ _pending_tasks: int = 0
55
+
56
+ def __new__(cls, task_id: str):
57
+ try:
58
+ with cls._lock:
59
+ return cls._ins_cache[task_id]
60
+ except KeyError:
61
+ pass # key not found
62
+ ins = super().__new__(cls)
63
+ ins._id = task_id
64
+ ins._status = JobContentDto(key=task_id)
65
+ cls._pending_tasks += 1
66
+ cls.setup()
67
+ # in case of a race, prefer the item already in the cache
68
+ try:
69
+ with cls._lock:
70
+ return cls._ins_cache.setdefault(task_id, ins)
71
+ except ValueError: # pragma: no cover
72
+ return ins # value too large
73
+
74
+ @classmethod
75
+ def all_done(cls):
76
+ cls._pending_tasks = 0
77
+
78
+ def set_status(self, status: str) -> 'TaskManager':
79
+ """更新任务状态
80
+
81
+ Args:
82
+ status: 任务状态
83
+ """
84
+ self._status.status = status
85
+ if status in FINISHED:
86
+ self._status.endTime = _now()
87
+ self.__class__._pending_tasks -= 1
88
+ self._ins_cache.pop(self._id, None) # remov self from cache
89
+
90
+ # add self to class's task_info
91
+ self.__class__._task_info[self._id] = self._status
92
+ return self
93
+
94
+ def set_name(self, name):
95
+ self._status.jobContentNameEn = \
96
+ self._status.jobContentNameZhcn = \
97
+ self._status.jobContentNameZhtw = name
98
+
99
+ def init(self, name) -> 'TaskManager':
100
+ self.set_name(name)
101
+ return self.set_status(WAIT)
102
+
103
+ @classmethod
104
+ def setup(cls):
105
+ if cls._schedule is None:
106
+ cls._schedule = evloop.create_task(cls._periodic_submit_status())
107
+
108
+ @classmethod
109
+ @retry(retries=5, wait=3)
110
+ async def _update_status(cls):
111
+ if task_info := cls._task_info:
112
+ try:
113
+ # 清空_task_info因为await时_task_info可能被其他协程更新
114
+ cls._task_info = {}
115
+ cls._meta_task.jobContents = list(task_info.values())
116
+ await cls._api.extra.job_update(cls._meta_task.dict()) # noqa
117
+ except Exception: # pragma: no cover
118
+ # 把没有提交成功的_task_info重新放入
119
+ cls._task_info = {**task_info, **cls._task_info}
120
+ raise
121
+
122
+ @classmethod
123
+ async def _periodic_submit_status(cls):
124
+ """定时提交子任务状态"""
125
+ logger.debug("Scheduled a background coroutine to update task status.")
126
+ while cls._pending_tasks > 0:
127
+ await cls._update_status()
128
+ await asyncio.sleep(cls._interval)
129
+
130
+ # we need double check here because task_info may increase in last loop
131
+ if cls._task_info:
132
+ await cls._update_status()
133
+
134
+ cls._schedule = None
135
+ logger.debug("Background coroutine finished.")
136
+
137
+ @cached_class_property
138
+ def _api(self):
139
+ return SystemAPI(sync=False)
140
+
141
+ @cached_class_property
142
+ def _meta_task(self):
143
+ meta_task_id = OPTION.general.task_info['task_id']
144
+ return UpdateJobCurrentDto(id=meta_task_id)
145
+
146
+ @classmethod
147
+ def set_task_status(cls, task_id, status, arg=None) -> 'TaskManager':
148
+ return cls(task_id).set_status(status)
149
+
150
+ @classmethod
151
+ def init_tasks(cls, tasks: Iterable['Task']):
152
+ for task in tasks:
153
+ task_mgr = cls(task.task_id)
154
+ task_mgr.init(task.task_name)
155
+
156
+ @classmethod
157
+ def wait(cls, timeout=5):
158
+ if cls._schedule is None:
159
+ return
160
+
161
+ try:
162
+ cls._schedule.result(timeout)
163
+ except Exception: # noqa # pragma: no cover
164
+ pass
165
+
166
+
167
+ class TaskManagerSocket:
168
+ #: 子任务id -> 子任务状态
169
+ _task_info: Dict[str, Dict[str, str]] = {}
170
+ _lock = Lock()
171
+ #: 实例缓存,用于实现基于子任务id的单例
172
+ _ins_cache: dict = {}
173
+ #: 更新任务的协程
174
+ _schedule = None
175
+ #: 更新任务的sleep间隔
176
+ _interval: int = 2
177
+ #: 当前还未结束的任务数量
178
+ _pending_tasks: int = 0
179
+
180
+ # NB: set port to avoid init args err on win system
181
+ _client = WorkerClient(
182
+ port=None,
183
+ sockname=OPTION.general.socket_name,
184
+ loop=evloop.loop,
185
+ id=OPTION.general.task_info.get('task_id'),
186
+ timeout=2
187
+ )
188
+
189
+ def __new__(cls, task_id: str, arg: str):
190
+ try:
191
+ with cls._lock:
192
+ return cls._ins_cache[task_id]
193
+ except KeyError:
194
+ pass # key not found
195
+ ins = super().__new__(cls)
196
+ ins._id = task_id
197
+ ins._status = {'key': task_id, 'arg': arg}
198
+ cls._pending_tasks += 1
199
+ cls.setup()
200
+ # in case of a race, prefer the item already in the cache
201
+ try:
202
+ with cls._lock:
203
+ return cls._ins_cache.setdefault(task_id, ins)
204
+ except ValueError: # pragma: no cover
205
+ return ins # value too large
206
+
207
+ @classmethod
208
+ def all_done(cls):
209
+ cls._pending_tasks = 0
210
+
211
+ def set_status(self, status: str) -> 'TaskManagerSocket':
212
+ """更新任务状态
213
+
214
+ Args:
215
+ status: 任务状态
216
+ """
217
+ self._status.update({'status': status, 'endTime': ''})
218
+ if status in FINISHED:
219
+ self._status.update({'endTime': _now()})
220
+ self.__class__._pending_tasks -= 1
221
+ self._ins_cache.pop(self._id, None) # remove self from cache
222
+
223
+ # add self to class's task_info
224
+ self.__class__._task_info[self._id] = self._status
225
+ return self
226
+
227
+ def set_name(self, name):
228
+ self._status.update({'name': name})
229
+
230
+ def init(self, name) -> 'TaskManagerSocket':
231
+ self.set_name(name)
232
+ return self.set_status(WAIT)
233
+
234
+ @classmethod
235
+ def setup(cls):
236
+ if cls._schedule is None:
237
+ cls._schedule = evloop.create_task(cls._periodic_submit_status())
238
+
239
+ @classmethod
240
+ @retry(retries=5, wait=1)
241
+ async def _update_status(cls):
242
+ if task_info := cls._task_info:
243
+ try:
244
+ # 清空_task_info因为await时_task_info可能被其他协程更新
245
+ cls._task_info = {}
246
+ logger.debug(f'Send msg to Master with task info:\n{list(task_info.values())}...')
247
+ await cls._client.send_msg(
248
+ "U",
249
+ list(task_info.values())
250
+ )
251
+ logger.debug(f'Send msg to Master with task info:\n{list(task_info.values())} done.')
252
+ except Exception: # pragma: no cover
253
+ # 把没有提交成功的_task_info重新放入
254
+ cls._task_info = {**task_info, **cls._task_info}
255
+ raise
256
+
257
+ @classmethod
258
+ async def _periodic_submit_status(cls):
259
+ """定时提交子任务状态"""
260
+ logger.debug("Scheduled a background coroutine to update task status.")
261
+ while cls._pending_tasks > 0:
262
+ await cls._update_status()
263
+ await asyncio.sleep(cls._interval)
264
+ # we need double-check here because task_info may increase in last loop
265
+ if cls._task_info:
266
+ await cls._update_status()
267
+ cls._schedule = None
268
+ logger.debug("Background coroutine finished.")
269
+
270
+ @classmethod
271
+ def set_task_status(cls, task_id, status, arg=None) -> 'TaskManagerSocket':
272
+ return cls(task_id, json.dumps(arg, default=str)).set_status(status)
273
+
274
+ @classmethod
275
+ def init_tasks(cls, tasks: Iterable['Task']):
276
+ for task in tasks:
277
+ task_mgr = cls(task.task_id, json.dumps(task.arg, default=str))
278
+ task_mgr.init(task.task_name)
279
+
280
+ @classmethod
281
+ def wait(cls, timeout=5):
282
+ logger.debug('Wait called.')
283
+ if cls._schedule is None:
284
+ cls._client.close()
285
+ return
286
+
287
+ try:
288
+ cls._schedule.result(timeout)
289
+ except Exception: # noqa # pragma: no cover
290
+ logger.exception('Exception occurs while wait.')
291
+ finally:
292
+ cls._client.close()
293
+
294
+
295
+ if OPTION.general.socket_communication:
296
+ TaskRealManager = TaskManagerSocket
297
+ else:
298
+ TaskRealManager = TaskManager
299
+
300
+
301
+ class TaskIdGenerator:
302
+ __cache__ = {}
303
+
304
+ def __new__(cls,):
305
+ base_id = OPTION.general.task_info.get("task_id")
306
+
307
+ if base_id not in cls.__cache__:
308
+ ins = super().__new__(cls)
309
+ ins._id = base_id
310
+ ins._counter = itertools.count().__next__
311
+ cls.__cache__[base_id] = ins
312
+
313
+ return cls.__cache__[base_id]
314
+
315
+ def next(self):
316
+ return f'{self._id}-{self._counter()}'
317
+
318
+
319
+ class _AbsTask:
320
+ def __init__(
321
+ self,
322
+ arg: Optional[Any] = None,
323
+ task_name_tmpl: Optional[str] = 'Task - {counter:03}',
324
+ swallow_exc: Optional[bool] = False
325
+ ):
326
+ self.arg = arg
327
+ self.swallow_exc = swallow_exc
328
+ self.task_name_format = task_name_tmpl.format(counter=COUNTER(), arg=arg)
329
+ self.is_called = False
330
+
331
+ def get_arg(self):
332
+ return self
333
+
334
+ def __enter__(self):
335
+ self.is_called = True
336
+ return self.arg
337
+
338
+ def __exit__(self, exc_type, exc_value, traceback):
339
+ if exc_type:
340
+ return self.swallow_exc
341
+ else:
342
+ return True
343
+
344
+
345
+ class _AbsTaskContainer:
346
+ def __init__(
347
+ self, args: Iterable[Any],
348
+ task_name_tmpl: Optional[str] = 'Task - {counter:03}',
349
+ swallow_exc: Optional[bool] = False
350
+ ):
351
+ self.arg: Iterator = iter(args)
352
+ self.task_name_format = task_name_tmpl
353
+ self.swallow_exc = swallow_exc
354
+ self.tasks = deque([])
355
+ self._init_tasks(task_name_tmpl)
356
+
357
+ def _init_tasks(self, task_name_format):
358
+ for arg in self.arg:
359
+ self.tasks.append(_AbsTask(arg, task_name_format, self.swallow_exc))
360
+
361
+ def __iter__(self):
362
+ return self
363
+
364
+ def __next__(self) -> _AbsTask:
365
+ if self.tasks:
366
+ return self.tasks.popleft()
367
+ raise StopIteration
368
+
369
+
370
+ class Task(AbstractContextManager):
371
+ """子任务对象
372
+
373
+ Args:
374
+ arg: 执行参数
375
+ task_name_tmpl: 任务名模板,支持替换的字段为counter(自增任务编号)
376
+ 和 arg(任务使用的参数),默认以 Task-{counter:03} 格式产生
377
+ swallow_exc: 在任务出现异常时是否忽略(不中断程序执行)
378
+ check_started: 是否检查任务启动过
379
+
380
+ .. admonition:: 示例
381
+
382
+ .. code-block:: python
383
+
384
+ with Task():
385
+ time.sleep(1)
386
+
387
+ with Task(swallow_exc=True):
388
+ raise ValueError('an error occurs')
389
+
390
+ with Task(task_name_tmpl="Your task name"):
391
+ time.sleep(1)
392
+
393
+ Note:
394
+ - 若不通过 :class:`TaskContainer` 或 :function:`create_tasks`,
395
+ 而是直接实例化Task对象,则只有其中任务被执行时,
396
+ 记录才会创建更新,作业界面进度条不能反映实际运行进度
397
+ - 如需看到稳定进度条的情况,建议从 :class:`TaskContainer` 调用或使用
398
+ :function:`create_tasks` 方法
399
+
400
+ See Also:
401
+ :class:`TaskContainer`
402
+ :func:`create_tasks`
403
+
404
+ """
405
+ __slots__ = (
406
+ '_check_started', 'arg', 'task_id', 'swallow_exc',
407
+ 'task_name', '_started', 'initialized'
408
+ )
409
+
410
+ def __init__(
411
+ self,
412
+ arg: Optional[Any] = None,
413
+ task_name_tmpl: Optional[str] = 'Task - {counter:03}',
414
+ swallow_exc: Optional[bool] = False,
415
+ check_started: Optional[bool] = True,
416
+ ):
417
+ self._check_started = check_started
418
+ self.arg = arg
419
+ self.task_id = TaskIdGenerator().next()
420
+ self.swallow_exc = swallow_exc
421
+ if _RE_HAS_COUNTER.match(task_name_tmpl):
422
+ self.task_name = task_name_tmpl.format(counter=COUNTER(), arg=arg)
423
+ else:
424
+ self.task_name = task_name_tmpl.format(arg=arg)
425
+ self._started = False
426
+ self.initialized = False
427
+
428
+ def get_arg(self):
429
+ return self
430
+
431
+ def __enter__(self):
432
+ self._started = True
433
+ mgr = TaskRealManager.set_task_status(self.task_id, START, self.arg)
434
+ if not self.initialized:
435
+ mgr.set_name(self.task_name)
436
+ return self.arg
437
+
438
+ def __exit__(self, exc_type, exc_value, traceback):
439
+ if exc_type:
440
+ TaskRealManager.set_task_status(self.task_id, FAIL, self.arg)
441
+ if self.swallow_exc:
442
+ logger.exception('')
443
+ else:
444
+ # 后续子任务不会再执行,因此提前通知TaskManger结束任务更新
445
+ TaskRealManager.all_done()
446
+ return self.swallow_exc
447
+ else:
448
+ TaskRealManager.set_task_status(self.task_id, SUCCESS, self.arg)
449
+ return True
450
+
451
+ def __del__(self):
452
+ if not self._started and self._check_started:
453
+ warnings.warn(f'Task: {self.task_name} never started!')
454
+
455
+
456
+ class TaskContainer:
457
+ """创建子任务容器
458
+
459
+ 将提供的执行参数包装为当前脚本的子任务,在循环的不同时刻更新状态
460
+
461
+ Args:
462
+ args: 执行参数
463
+ task_name_tmpl: 任务名模板,支持替换的字段为counter(自增任务编号)
464
+ 和arg(任务使用的参数),默认以 Task-{counter:03} 格式产生
465
+ swallow_exc: 在任务出现异常时是否忽略(不中断程序执行)
466
+
467
+ Note:
468
+ - 在循环开始前,基于此迭代方法创建并初始化所有子任务,状态为“等待”
469
+ - 在每次循环开始时,更新当前子任务状态为“运行中”
470
+ - 结束时,视结果及是否忽略异常(swallow_exc)更新状态为“成功”或“失败”
471
+
472
+ .. admonition:: 示例
473
+
474
+ 例如原先有如下代码结构:
475
+
476
+ .. code-block:: python
477
+
478
+ for arg in range(10):
479
+ do_something(arg)
480
+
481
+ 如果希望每次循环能作为子任务在作业管理中查看,
482
+ 可以对代码作如下修改:
483
+
484
+ .. code-block:: python
485
+ :emphasize-lines: 1,2
486
+
487
+ for task in TaskContainer(range(10)):
488
+ with task.get_arg() as arg:
489
+ do_something(arg)
490
+
491
+ See Also:
492
+ - :class:`Task`
493
+ - :func:`create_tasks`
494
+
495
+ """
496
+
497
+ def __init__(
498
+ self,
499
+ args: Iterable[Any],
500
+ task_name_tmpl: Optional[str] = 'Task - {counter:03}',
501
+ swallow_exc: Optional[bool] = True
502
+ ):
503
+ self.arg: Iterator = iter(args)
504
+ self.swallow_exc = swallow_exc
505
+ self.tasks = deque()
506
+ self._init_tasks(task_name_tmpl)
507
+
508
+ def _init_tasks(self, task_name_tmpl):
509
+ for arg in self.arg:
510
+ task = Task(
511
+ arg, task_name_tmpl, self.swallow_exc,
512
+ check_started=self.swallow_exc
513
+ )
514
+ task.initialized = True
515
+ self.tasks.append(task)
516
+ TaskRealManager.init_tasks(self.tasks)
517
+
518
+ def __iter__(self):
519
+ return self
520
+
521
+ def __next__(self) -> Task:
522
+ if self.tasks:
523
+ return self.tasks.popleft()
524
+ raise StopIteration
525
+
526
+
527
+ def create_tasks(
528
+ task_count: int,
529
+ task_name_tmpl: Optional[str] = 'Task - {counter:03}',
530
+ swallow_exc: Optional[bool] = False
531
+ ) -> Sequence[Task]:
532
+ """创建多个任务,获取子任务对象
533
+
534
+ Args:
535
+ task_count: 生成任务数
536
+ task_name_tmpl: 任务名模板,支持替换的字段为counter(自增任务编号)
537
+ 和arg(任务使用的参数),默认以 Task-{counter:03} 格式产生
538
+ swallow_exc: 在任务出现异常时是否忽略(不中断程序执行)
539
+
540
+ .. admonition:: 示例
541
+
542
+ 为当前脚本创建2个子任务:
543
+
544
+ .. code-block:: python
545
+
546
+ task_a, task_b = create_tasks(2)
547
+
548
+ with task_a:
549
+ do_something()
550
+
551
+ with task_b:
552
+ do_something()
553
+
554
+ See Also:
555
+ :class:`TaskContainer`
556
+ :class:`Task`
557
+
558
+ """
559
+ if task_count <= 0:
560
+ raise ValueError("The task_count should be a positive number!")
561
+ task_container = TaskContainer(
562
+ args=range(task_count),
563
+ task_name_tmpl=task_name_tmpl,
564
+ swallow_exc=swallow_exc
565
+ )
566
+
567
+ return task_container.tasks
568
+
569
+
570
+ if OPTION.general.dev_mode or not OPTION.general.task_info.get('should_log', True):
571
+ TaskContainer = _AbsTaskContainer
572
+ Task = _AbsTask