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/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
|