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
@@ -0,0 +1,412 @@
1
+ import asyncio.exceptions
2
+ import json
3
+ import threading
4
+ import time
5
+ from abc import ABC
6
+ from contextlib import contextmanager
7
+ from importlib import import_module
8
+ from math import ceil
9
+ from typing import Any, Dict, Tuple
10
+ from urllib.parse import quote_plus
11
+
12
+ from loguru import logger
13
+
14
+ from deepfos import OPTION
15
+ from deepfos.api.app import AppAPI
16
+ from deepfos.api.models.python import PyRunInfo
17
+ from deepfos.api.python import PythonAPI
18
+ from deepfos.element.base import ElementBase
19
+ from deepfos.exceptions import (
20
+ ResultTimeOutError,
21
+ PyTaskRevokedError,
22
+ PyTaskRunTimeError,
23
+ PyTaskInvalidError,
24
+ PyTaskConcurrencyExceed,
25
+ PyTaskTimeLimitExceed,
26
+ APIResponseError,
27
+ )
28
+ from deepfos.lib.asynchronous import future_property
29
+ from deepfos.lib.constant import UNSET
30
+ from deepfos.lib.redis import RedisCli
31
+
32
+ __all__ = [
33
+ 'PythonScript',
34
+ 'OnlineTask',
35
+ 'LocalTask',
36
+ ]
37
+
38
+ ONLINE_MODE = (
39
+ OPTION.module.src_task is not None
40
+ and OPTION.module.src_celeryapp is not None
41
+ and OPTION.module.src_options is not None
42
+ and OPTION.module.src_errors_classes is not None
43
+ )
44
+
45
+ if ONLINE_MODE: # pragma: no cover
46
+ try:
47
+ import celery.states as cstates # noqa
48
+ from celery.exceptions import TaskRevokedError, TimeoutError # noqa
49
+ from celery.result import allow_join_result # noqa
50
+ except ImportError:
51
+ ONLINE_MODE = False
52
+
53
+ if ONLINE_MODE: # pragma: no cover
54
+ try:
55
+ pyrunner = import_module(OPTION.module.src_task).run # noqa
56
+ AsyncResult = import_module(OPTION.module.src_celeryapp).celery_app.AsyncResult # noqa
57
+ SERVER_OPTION = import_module(OPTION.module.src_options).OPTION # noqa
58
+ PyExecutionError = import_module(OPTION.module.src_errors_classes).PyExecutionError # noqa
59
+
60
+ assert SERVER_OPTION.redis.mode == 'single', 'Only supported in redis single mode'
61
+
62
+ redis_addr = SERVER_OPTION.redis.addr
63
+ redis_password = quote_plus(SERVER_OPTION.redis.password)
64
+
65
+ CONCURRENCY_KEY_PREFIX = f'celery_concurrency_task:{OPTION.general.task_info.get("worker_name")}'
66
+ redis_cli = RedisCli(f"redis://:{redis_password}@{redis_addr}/13")
67
+ CONCURRENCY_KEY = None
68
+ GLOBAL_LOCK = None
69
+
70
+ except (ImportError, AssertionError):
71
+ ONLINE_MODE = False
72
+
73
+ LOCAL_LOCK = threading.Lock()
74
+
75
+ WAITING_TASKS = 0
76
+
77
+ _on_const = {
78
+ 'NaN': None
79
+ }.__getitem__
80
+
81
+
82
+ def maybe_prepare_redis_lock():
83
+ global CONCURRENCY_KEY
84
+ global GLOBAL_LOCK
85
+ if CONCURRENCY_KEY is None:
86
+ CONCURRENCY_KEY = redis_cli.lock(
87
+ f'{CONCURRENCY_KEY_PREFIX}:{OPTION.general.task_info["task_id"]}',
88
+ renew_interval=2, expire_sec=4, blocking_timeout=None
89
+ )
90
+ if GLOBAL_LOCK is None:
91
+ GLOBAL_LOCK = redis_cli.lock(
92
+ CONCURRENCY_KEY_PREFIX,
93
+ renew_interval=1, expire_sec=2, blocking_timeout=None
94
+ )
95
+
96
+
97
+ class AbstractPythonTask(ABC):
98
+ task_id = UNSET
99
+
100
+ def get_result(self, timeout: int = None): # pragma: no cover
101
+ """获取当前脚本执行结果
102
+
103
+ Args:
104
+ timeout: 获取等待超时时间,默认为None,意味着会等待直到有结果
105
+
106
+ """
107
+ raise NotImplementedError
108
+
109
+ def terminate(self): # pragma: no cover
110
+ """取消当前脚本
111
+
112
+ Important:
113
+ 只有非结束状态的脚本可以被取消
114
+
115
+ """
116
+ raise NotImplementedError
117
+
118
+
119
+ errcode_map = {
120
+ 28030001: PyTaskRunTimeError,
121
+ 28030003: PyTaskRevokedError,
122
+ 28030005: ResultTimeOutError,
123
+ 28030006: PyTaskTimeLimitExceed,
124
+ 28040001: PyTaskInvalidError,
125
+ }
126
+
127
+
128
+ class LocalTask(AbstractPythonTask):
129
+ """本地任务实例
130
+
131
+ Args:
132
+ parameter: 任务入参
133
+ manager: PythonScript实例,用于提供元素信息
134
+
135
+
136
+ """
137
+
138
+ def __init__(self, parameter, manager: 'PythonScript'):
139
+ self.manager = manager
140
+ self.task_id = manager.api.script.run(
141
+ PyRunInfo.construct_from(manager.element_info, parameter=parameter)
142
+ )
143
+ logger.debug(f'python脚本[id:{self.task_id}]任务信息已发送')
144
+
145
+ def get_result(self, timeout: int = None):
146
+ start_time = time.time()
147
+ remaining_time = interval = min(max(OPTION.api.timeout - 1, 1), 5)
148
+ if timeout is not None:
149
+ remaining_time = min(interval, timeout)
150
+
151
+ while timeout is None or remaining_time > 0:
152
+ try:
153
+ return self.manager.api.script.result(
154
+ self.task_id,
155
+ timeout=remaining_time
156
+ )
157
+ except APIResponseError as e:
158
+ if e.code not in errcode_map:
159
+ raise
160
+
161
+ err_cls = errcode_map[e.code]
162
+ if err_cls is not ResultTimeOutError:
163
+ raise errcode_map[e.code](str(e)) from None
164
+
165
+ delta_time = time.time() - start_time
166
+ remaining_time = (
167
+ ceil(min(interval, timeout - delta_time))
168
+ if timeout is not None else interval
169
+ )
170
+
171
+ raise ResultTimeOutError() from None
172
+
173
+ def terminate(self):
174
+ return self.manager.api.script.terminate(self.task_id)
175
+
176
+
177
+ class OnlineTask(AbstractPythonTask):
178
+ """线上任务实例
179
+
180
+ Args:
181
+ parameter: 任务入参
182
+ manager: PythonScript实例,用于提供元素信息
183
+
184
+
185
+ """
186
+
187
+ def __init__(self, parameter, manager: 'PythonScript'):
188
+ if not ONLINE_MODE:
189
+ raise NotImplementedError('OnlineTask实例只能在线上环境使用')
190
+
191
+ task = pyrunner.apply_async(
192
+ args=(parameter,
193
+ *manager.env)
194
+ )
195
+ self.task_id = task.id
196
+
197
+ logger.debug(f'python脚本[id:{self.task_id}]任务信息已发送')
198
+
199
+ def _get_valid_task(self):
200
+ task = AsyncResult(self.task_id)
201
+ if task.status is cstates.PENDING: # pragma: no cover
202
+ raise PyTaskInvalidError()
203
+
204
+ return task
205
+
206
+ @contextmanager
207
+ def _ensure_valid_concurrency(self):
208
+ global WAITING_TASKS
209
+ maybe_prepare_redis_lock()
210
+ global CONCURRENCY_KEY
211
+ global GLOBAL_LOCK
212
+
213
+ with LOCAL_LOCK:
214
+ if not CONCURRENCY_KEY.locked() and WAITING_TASKS == 0: # noqa
215
+ with GLOBAL_LOCK:
216
+ con_num = len(list(redis_cli.client.scan_iter(f'{CONCURRENCY_KEY_PREFIX}:*')))
217
+
218
+ if (SERVER_OPTION.celery.autoscale_max_concurreny - con_num) <= OPTION.general.preserve_concurrency:
219
+ raise PyTaskConcurrencyExceed(
220
+ OPTION.general.task_info.get("worker_name"),
221
+ con_num
222
+ )
223
+
224
+ with GLOBAL_LOCK:
225
+ CONCURRENCY_KEY.do_hold() # noqa
226
+
227
+ WAITING_TASKS = WAITING_TASKS + 1
228
+ try:
229
+ yield
230
+ finally:
231
+ with LOCAL_LOCK:
232
+ WAITING_TASKS = WAITING_TASKS - 1
233
+ if WAITING_TASKS == 0:
234
+ with GLOBAL_LOCK:
235
+ CONCURRENCY_KEY.release() # noqa
236
+
237
+ def get_result(self, timeout: int = None):
238
+ task = self._get_valid_task()
239
+
240
+ with self._ensure_valid_concurrency():
241
+ with allow_join_result():
242
+ try:
243
+ ret, *std = task.get(timeout=timeout)
244
+ return json.loads(ret, parse_constant=_on_const)
245
+ except TimeoutError:
246
+ raise ResultTimeOutError() from None
247
+ except TaskRevokedError:
248
+ raise PyTaskRevokedError() from None
249
+ except PyExecutionError as e:
250
+ raise PyTaskRunTimeError(e.stderr) from None
251
+ except Exception as e:
252
+ raise PyTaskRunTimeError(e) from None
253
+
254
+ def terminate(self):
255
+ task = self._get_valid_task()
256
+
257
+ if task.status in cstates.READY_STATES:
258
+ logger.warning('python脚本已结束')
259
+ return
260
+
261
+ task.revoke(terminate=True)
262
+ logger.info(f'python脚本[id:{self.task_id}]已取消')
263
+
264
+ def status(self):
265
+ return self._get_valid_task().status
266
+
267
+
268
+ class PythonScript(ElementBase[PythonAPI]):
269
+ """Python脚本对象
270
+
271
+ Args:
272
+ task_name: 任务名称
273
+ should_log: 仅在线上执行时有效,脚本是否记录执行日志,
274
+ 线下执行时,该值与脚本元素配置项“记录执行日志”保持一致
275
+
276
+
277
+ """
278
+ def __init__(
279
+ self,
280
+ element_name: str,
281
+ folder_id: str = None,
282
+ path: str = None,
283
+ task_name: str = None,
284
+ should_log: bool = False
285
+ ):
286
+ self.should_log = should_log
287
+ self.task_name = task_name
288
+ super().__init__(
289
+ element_name=element_name, folder_id=folder_id, path=path
290
+ )
291
+
292
+ @future_property
293
+ async def env(self) -> Tuple[str, str, Dict]:
294
+ if not ONLINE_MODE:
295
+ raise NotImplementedError('只在线上环境中可获得env值')
296
+
297
+ ele_info = await self.wait_for('element_info')
298
+ python_path = '/'.join([
299
+ SERVER_OPTION.general.py_root,
300
+ OPTION.api.header['space'],
301
+ OPTION.api.header['app']]
302
+ )
303
+ if self._path is not None:
304
+ folder = self._path.strip('\\/') \
305
+ .replace('/', '.').replace('\\', '.')
306
+ else:
307
+ path = await AppAPI(sync=False).folder.get_folder_full(ele_info.folderId)
308
+ folder = path.strip('\\/').replace('/', '.').replace('\\', '.')
309
+
310
+ if not folder:
311
+ module = self.element_name
312
+ else:
313
+ module = '.'.join([folder, self.element_name])
314
+
315
+ return (
316
+ module,
317
+ python_path,
318
+ {
319
+ 'header': OPTION.api.header,
320
+ 'server': SERVER_OPTION.server.to_dict(),
321
+ 'element_desc': {},
322
+ 'should_log': self.should_log,
323
+ 'compressed_flag': False,
324
+ 'task_type': "NATIVE",
325
+ 'return_structure_data': None,
326
+ 'return_structure_type': None,
327
+ 'enable_return_structure': False,
328
+ 'task_name': self.task_name,
329
+ 'use_eureka': OPTION.discovery.enabled
330
+ }
331
+ )
332
+
333
+ def run(self, parameter: Any = None, timeout: int = None):
334
+ """发送python脚本任务信息并获取执行结果
335
+
336
+ Args:
337
+ parameter: 脚本入参
338
+ timeout: 获取等待超时时间,默认为None,意味着会等待直到有结果
339
+
340
+ .. admonition:: 示例
341
+
342
+ .. code-block:: python
343
+
344
+ from deepfos.element.pyscript import PythonScript
345
+
346
+ script = PythonScript(element_name='test_task',
347
+ path='/',
348
+ should_log=True)
349
+
350
+ script.run(parameter={'a': 1})
351
+
352
+ 线上执行时,将执行发送当前空间内元素路径为'/',元素名为test_task的python脚本的任务,
353
+ 并等待结果,且由于初始化时should_log为True,其在作业管理中将更新作业信息
354
+
355
+
356
+ See Also:
357
+ :meth: `run_async`
358
+ :class:`LocalTask`
359
+ :class:`OnlineTask`
360
+
361
+ """
362
+ task = self.run_async(parameter)
363
+ return task.get_result(timeout)
364
+
365
+ def run_async(self, parameter: Any = None) -> AbstractPythonTask:
366
+ """发送python脚本任务信息并返回Task实例
367
+
368
+ 该方法不会等待任务执行结果,任务信息将被提交至celery任务队列,
369
+ 待有可用并发数时执行,执行结果可通过Task实例的 `get_result` 方法得到
370
+
371
+ Args:
372
+ parameter: 脚本入参
373
+
374
+
375
+ Returns: python脚本任务id
376
+
377
+ .. admonition:: 示例
378
+
379
+ .. code-block:: python
380
+
381
+ from deepfos.element.pyscript import PythonScript
382
+
383
+ script = PythonScript(element_name='test_task',
384
+ path='/',
385
+ should_log=True)
386
+
387
+ script.run_async(parameter={'a': 1})
388
+
389
+ 线上执行时,将执行发送当前空间内元素路径为'/',
390
+ 元素名为test_task的python脚本的任务,且由于初始化时should_log为True,
391
+ 其在作业管理中将更新作业信息
392
+
393
+ 可通过如下代码等待并获得结果,如不使用,任务亦会照常执行,如记录了任务id,
394
+ 可通过python组件的/script/result/{任务id}接口得到结果
395
+
396
+ .. code-block:: python
397
+
398
+ res = script.get_result()
399
+
400
+
401
+ See Also:
402
+ :meth:`run`
403
+ :class:`LocalTask`
404
+ :class:`OnlineTask`
405
+
406
+ """
407
+ if not ONLINE_MODE:
408
+ task = LocalTask(parameter, self)
409
+ else:
410
+ task = OnlineTask(parameter, self)
411
+
412
+ return task