ddeutil-workflow 0.0.2__py3-none-any.whl → 0.0.3__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.
@@ -1 +1 @@
1
- __version__: str = "0.0.2"
1
+ __version__: str = "0.0.3"
@@ -9,3 +9,4 @@ from typing import Any
9
9
 
10
10
  TupleStr = tuple[str, ...]
11
11
  DictData = dict[str, Any]
12
+ DictStr = dict[str, str]
ddeutil/workflow/conn.py CHANGED
@@ -43,8 +43,14 @@ class BaseConn(BaseModel):
43
43
  ]
44
44
 
45
45
  @classmethod
46
- def from_dict(cls, values: DictData):
47
- """Construct Connection with dict of data"""
46
+ def from_dict(cls, values: DictData) -> Self:
47
+ """Construct Connection Model from dict data. This construct is
48
+ different with ``.model_validate()`` because it will prepare the values
49
+ before using it if the data dose not have 'url'.
50
+
51
+ :param values: A dict data that use to construct this model.
52
+ """
53
+ # NOTE: filter out the fields of this model.
48
54
  filter_data: DictData = {
49
55
  k: values.pop(k)
50
56
  for k in values.copy()
@@ -73,15 +79,11 @@ class BaseConn(BaseModel):
73
79
  )
74
80
 
75
81
  @classmethod
76
- def from_loader(
77
- cls,
78
- name: str,
79
- externals: DictData,
80
- ) -> Self:
82
+ def from_loader(cls, name: str, externals: DictData) -> Self:
81
83
  """Construct Connection with Loader object with specific config name.
82
84
 
83
- :param name:
84
- :param externals:
85
+ :param name: A config name.
86
+ :param externals: A external data that want to adding to extras.
85
87
  """
86
88
  loader: Loader = Loader(name, externals=externals)
87
89
  # NOTE: Validate the config type match with current connection model
@@ -96,6 +98,7 @@ class BaseConn(BaseModel):
96
98
 
97
99
  @field_validator("endpoint")
98
100
  def __prepare_slash(cls, value: str) -> str:
101
+ """Prepare slash character that map double form URL model loading."""
99
102
  if value.startswith("//"):
100
103
  return value[1:]
101
104
  return value
@@ -148,7 +151,7 @@ class SFTP(Conn):
148
151
  dialect: Literal["sftp"] = "sftp"
149
152
 
150
153
  def __client(self):
151
- from .vendors.sftp_wrapped import WrapSFTP
154
+ from .vendors.sftp import WrapSFTP
152
155
 
153
156
  return WrapSFTP(
154
157
  host=self.host,
@@ -9,24 +9,4 @@ Define Errors Object for Node package
9
9
  from __future__ import annotations
10
10
 
11
11
 
12
- class BaseError(Exception):
13
- """Base Error Object that use for catch any errors statement of
14
- all step in this src
15
- """
16
-
17
-
18
- class WorkflowBaseError(BaseError):
19
- """Core Base Error object"""
20
-
21
-
22
- class ConfigNotFound(WorkflowBaseError):
23
- """Error raise for a method not found the config file or data."""
24
-
25
-
26
- class PyException(Exception): ...
27
-
28
-
29
- class ShellException(Exception): ...
30
-
31
-
32
12
  class TaskException(Exception): ...
@@ -6,7 +6,7 @@
6
6
  from __future__ import annotations
7
7
 
8
8
  from functools import cached_property
9
- from typing import Any, TypeVar
9
+ from typing import Any, ClassVar, TypeVar
10
10
 
11
11
  from ddeutil.core import (
12
12
  getdot,
@@ -14,12 +14,12 @@ from ddeutil.core import (
14
14
  import_string,
15
15
  )
16
16
  from ddeutil.io import (
17
- ConfigNotFound,
18
- Params,
17
+ PathData,
19
18
  PathSearch,
20
19
  YamlEnvFl,
21
20
  )
22
- from pydantic import BaseModel
21
+ from pydantic import BaseModel, Field
22
+ from pydantic.functional_validators import model_validator
23
23
 
24
24
  from .__regex import RegexConf
25
25
  from .__types import DictData
@@ -29,6 +29,25 @@ BaseModelType = type[BaseModel]
29
29
  AnyModel = TypeVar("AnyModel", bound=BaseModel)
30
30
 
31
31
 
32
+ class Engine(BaseModel):
33
+ """Engine Model"""
34
+
35
+ paths: PathData = Field(default_factory=PathData)
36
+ registry: list[str] = Field(default_factory=lambda: ["ddeutil.workflow"])
37
+
38
+ @model_validator(mode="before")
39
+ def __prepare_registry(cls, values: DictData) -> DictData:
40
+ if (_regis := values.get("registry")) and isinstance(_regis, str):
41
+ values["registry"] = [_regis]
42
+ return values
43
+
44
+
45
+ class Params(BaseModel):
46
+ """Params Model"""
47
+
48
+ engine: Engine = Field(default_factory=Engine)
49
+
50
+
32
51
  class SimLoad:
33
52
  """Simple Load Object that will search config data by name.
34
53
 
@@ -36,13 +55,11 @@ class SimLoad:
36
55
  :param params: A Params model object.
37
56
  :param externals: An external parameters
38
57
 
39
- Note:
58
+ Noted:
40
59
  The config data should have ``type`` key for engine can know what is
41
60
  config should to do next.
42
61
  """
43
62
 
44
- import_prefix: str = "ddeutil.workflow"
45
-
46
63
  def __init__(
47
64
  self,
48
65
  name: str,
@@ -56,7 +73,7 @@ class SimLoad:
56
73
  ):
57
74
  self.data = data
58
75
  if not self.data:
59
- raise ConfigNotFound(f"Config {name!r} does not found on conf path")
76
+ raise ValueError(f"Config {name!r} does not found on conf path")
60
77
  self.__conf_params: Params = params
61
78
  self.externals: DictData = externals
62
79
 
@@ -75,6 +92,11 @@ class SimLoad:
75
92
  # NOTE: Auto adding module prefix if it does not set
76
93
  return import_string(f"ddeutil.workflow.{_typ}")
77
94
  except ModuleNotFoundError:
95
+ for registry in self.conf_params.engine.registry:
96
+ try:
97
+ return import_string(f"{registry}.{_typ}")
98
+ except ModuleNotFoundError:
99
+ continue
78
100
  return import_string(f"{_typ}")
79
101
 
80
102
  def load(self) -> AnyModel:
@@ -82,12 +104,14 @@ class SimLoad:
82
104
 
83
105
 
84
106
  class Loader(SimLoad):
85
- """Main Loader Object.
107
+ """Main Loader Object that get the config `yaml` file from current path.
86
108
 
87
109
  :param name: A name of config data that will read by Yaml Loader object.
88
110
  :param externals: An external parameters
89
111
  """
90
112
 
113
+ conf_name: ClassVar[str] = "workflows-conf"
114
+
91
115
  def __init__(
92
116
  self,
93
117
  name: str,
@@ -106,12 +130,16 @@ class Loader(SimLoad):
106
130
  def config(cls, path: str | None = None) -> Params:
107
131
  """Load Config data from ``workflows-conf.yaml`` file."""
108
132
  return Params.model_validate(
109
- YamlEnvFl(path or "./workflows-conf.yaml").read()
133
+ YamlEnvFl(path or f"./{cls.conf_name}.yaml").read()
110
134
  )
111
135
 
112
136
 
113
137
  def map_params(value: Any, params: dict[str, Any]) -> Any:
114
- """Map caller value that found from ``RE_CALLER`` regex.
138
+ """Map caller value that found from ``RE_CALLER`` regular expression.
139
+
140
+ :param value: A value that want to mapped with an params
141
+ :param params: A parameter value that getting with matched regular
142
+ expression.
115
143
 
116
144
  :rtype: Any
117
145
  :returns: An any getter value from the params input.
@@ -6,38 +6,52 @@
6
6
  from __future__ import annotations
7
7
 
8
8
  import inspect
9
+ import itertools
9
10
  import logging
10
11
  import subprocess
12
+ import time
11
13
  from abc import ABC, abstractmethod
12
- from datetime import date, datetime
13
14
  from inspect import Parameter
15
+ from queue import Queue
14
16
  from subprocess import CompletedProcess
15
- from typing import Any, Callable, Literal, Optional, Union
17
+ from typing import Any, Callable, Optional, Union
16
18
 
17
- from ddeutil.io.models.lineage import dt_now
19
+ import msgspec as spec
18
20
  from pydantic import BaseModel, Field
19
21
  from pydantic.functional_validators import model_validator
20
22
  from typing_extensions import Self
21
23
 
22
24
  from .__regex import RegexConf
23
- from .__types import DictData
24
- from .exceptions import PyException, TaskException
25
+ from .__types import DictData, DictStr
26
+ from .exceptions import TaskException
25
27
  from .loader import Loader, map_params
26
- from .utils import make_registry
28
+ from .utils import Params, make_registry
27
29
 
28
30
 
29
31
  class BaseStage(BaseModel, ABC):
30
- """Base Stage Model."""
31
-
32
- id: Optional[str] = None
33
- name: str
32
+ """Base Stage Model that keep only id and name fields."""
33
+
34
+ id: Optional[str] = Field(
35
+ default=None,
36
+ description=(
37
+ "The stage ID that use to keep execution output or getting by job "
38
+ "owner."
39
+ ),
40
+ )
41
+ name: str = Field(
42
+ description="The stage name that want to logging when start execution."
43
+ )
34
44
 
35
45
  @abstractmethod
36
46
  def execute(self, params: DictData) -> DictData:
47
+ """Execute abstraction method that action something by sub-model class.
48
+
49
+ :param params: A parameter data that want to use in this execution.
50
+ """
37
51
  raise NotImplementedError("Stage should implement ``execute`` method.")
38
52
 
39
53
  def set_outputs(self, rs: DictData, params: DictData) -> DictData:
40
- """Set outputs to params"""
54
+ """Set an outputs from execution process to an input params."""
41
55
  if self.id is None:
42
56
  return params
43
57
 
@@ -61,7 +75,7 @@ class ShellStage(BaseStage):
61
75
  """Shell statement stage."""
62
76
 
63
77
  shell: str
64
- env: dict[str, str] = Field(default_factory=dict)
78
+ env: DictStr = Field(default_factory=dict)
65
79
 
66
80
  @staticmethod
67
81
  def __prepare_shell(shell: str):
@@ -100,7 +114,7 @@ class ShellStage(BaseStage):
100
114
  if rs.returncode > 0:
101
115
  print(f"{rs.stderr}\nRunning Statement:\n---\n{self.shell}")
102
116
  # FIXME: raise err for this execution.
103
- # raise ShellException(
117
+ # raise TaskException(
104
118
  # f"{rs.stderr}\nRunning Statement:\n---\n"
105
119
  # f"{self.shell}"
106
120
  # )
@@ -116,7 +130,7 @@ class PyStage(BaseStage):
116
130
  run: str
117
131
  vars: DictData = Field(default_factory=dict)
118
132
 
119
- def get_var(self, params: DictData) -> DictData:
133
+ def get_vars(self, params: DictData) -> DictData:
120
134
  """Return variables"""
121
135
  rs = self.vars.copy()
122
136
  for p, v in self.vars.items():
@@ -149,12 +163,12 @@ class PyStage(BaseStage):
149
163
  :returns: A parameters from an input that was mapped output if the stage
150
164
  ID was set.
151
165
  """
152
- _globals: DictData = globals() | params | self.get_var(params)
166
+ _globals: DictData = globals() | params | self.get_vars(params)
153
167
  _locals: DictData = {}
154
168
  try:
155
169
  exec(map_params(self.run, params), _globals, _locals)
156
170
  except Exception as err:
157
- raise PyException(
171
+ raise TaskException(
158
172
  f"{err.__class__.__name__}: {err}\nRunning Statement:\n---\n"
159
173
  f"{self.run}"
160
174
  ) from None
@@ -164,13 +178,17 @@ class PyStage(BaseStage):
164
178
  return params | {k: _globals[k] for k in params if k in _globals}
165
179
 
166
180
 
167
- class TaskSearch(BaseModel):
168
- """Task Search Model"""
181
+ class TaskSearch(spec.Struct, kw_only=True, tag="task"):
182
+ """Task Search Struct that use the `msgspec` for the best performance."""
169
183
 
170
184
  path: str
171
185
  func: str
172
186
  tag: str
173
187
 
188
+ def to_dict(self) -> DictData:
189
+ """Return dict data from struct fields."""
190
+ return {f: getattr(self, f) for f in self.__struct_fields__}
191
+
174
192
 
175
193
  class TaskStage(BaseStage):
176
194
  """Task executor stage that running the Python function."""
@@ -183,7 +201,7 @@ class TaskStage(BaseStage):
183
201
  """Extract Task string value to task function."""
184
202
  if not (found := RegexConf.RE_TASK_FMT.search(task)):
185
203
  raise ValueError("Task does not match with task format regex.")
186
- tasks = TaskSearch(**found.groupdict())
204
+ tasks: TaskSearch = TaskSearch(**found.groupdict())
187
205
 
188
206
  # NOTE: Registry object should implement on this package only.
189
207
  # TODO: This prefix value to search registry should dynamic with
@@ -238,153 +256,131 @@ Stage = Union[
238
256
 
239
257
 
240
258
  class Strategy(BaseModel):
241
- """Strategy Model"""
259
+ """Strategy Model that will combine a matrix together for running the
260
+ special job.
261
+
262
+ Examples:
263
+ >>> strategy = {
264
+ ... 'matrix': {
265
+ ... 'first': [1, 2, 3],
266
+ ... 'second': ['foo', 'bar']
267
+ ... },
268
+ ... 'include': [{'first': 4, 'second': 'foo'}],
269
+ ... 'exclude': [{'first': 1, 'second': 'bar'}],
270
+ ... }
271
+ """
242
272
 
243
- matrix: list[str] = Field(default_factory=list)
244
- include: list[str] = Field(default_factory=list)
245
- exclude: list[str] = Field(default_factory=list)
273
+ fail_fast: bool = Field(default=False)
274
+ max_parallel: int = Field(default=-1)
275
+ matrix: dict[str, Union[list[str], list[int]]] = Field(default_factory=dict)
276
+ include: list[dict[str, Union[str, int]]] = Field(default_factory=list)
277
+ exclude: list[dict[str, Union[str, int]]] = Field(default_factory=list)
278
+
279
+ @model_validator(mode="before")
280
+ def __prepare_keys(cls, values: DictData) -> DictData:
281
+ if "max-parallel" in values:
282
+ values["max_parallel"] = values.pop("max-parallel")
283
+ if "fail-fast" in values:
284
+ values["fail_fast"] = values.pop("fail-fast")
285
+ return values
246
286
 
247
287
 
248
288
  class Job(BaseModel):
249
289
  """Job Model"""
250
290
 
291
+ runs_on: Optional[str] = Field(default=None)
251
292
  stages: list[Stage] = Field(default_factory=list)
252
293
  needs: list[str] = Field(default_factory=list)
253
294
  strategy: Strategy = Field(default_factory=Strategy)
254
295
 
296
+ @model_validator(mode="before")
297
+ def __prepare_keys(cls, values: DictData) -> DictData:
298
+ if "runs-on" in values:
299
+ values["runs_on"] = values.pop("runs-on")
300
+ return values
301
+
255
302
  def stage(self, stage_id: str) -> Stage:
303
+ """Return stage model that match with an input stage ID."""
256
304
  for stage in self.stages:
257
305
  if stage_id == (stage.id or ""):
258
306
  return stage
259
307
  raise ValueError(f"Stage ID {stage_id} does not exists")
260
308
 
309
+ def make_strategy(self) -> list[DictStr]:
310
+ """Return List of combination of matrix values that already filter with
311
+ exclude and add include values.
312
+ """
313
+ if not (mt := self.strategy.matrix):
314
+ return [{}]
315
+ final: list[DictStr] = []
316
+ for r in [
317
+ {_k: _v for e in mapped for _k, _v in e.items()}
318
+ for mapped in itertools.product(
319
+ *[[{k: v} for v in vs] for k, vs in mt.items()]
320
+ )
321
+ ]:
322
+ if any(
323
+ all(r[k] == v for k, v in exclude.items())
324
+ for exclude in self.strategy.exclude
325
+ ):
326
+ continue
327
+ final.append(r)
328
+
329
+ if not final:
330
+ return [{}]
331
+
332
+ for include in self.strategy.include:
333
+ if include.keys() != final[0].keys():
334
+ raise ValueError("Include should have the keys equal to matrix")
335
+ if any(all(include[k] == v for k, v in f.items()) for f in final):
336
+ continue
337
+ final.append(include)
338
+ return final
339
+
261
340
  def execute(self, params: DictData | None = None) -> DictData:
262
341
  """Execute job with passing dynamic parameters from the pipeline."""
263
- for stage in self.stages:
264
- # NOTE:
265
- # I do not use below syntax because `params` dict be the
266
- # reference memory pointer and it was changed when I action
267
- # anything like update or re-construct this.
268
- # ... params |= stage.execute(params=params)
269
- stage.execute(params=params)
342
+ for strategy in self.make_strategy():
343
+ params.update({"matrix": strategy})
344
+
345
+ # IMPORTANT: The stage execution only run sequentially one-by-one.
346
+ for stage in self.stages:
347
+ logging.info(
348
+ f"[JOB]: Start execute the stage: "
349
+ f"{(stage.id if stage.id else stage.name)!r}"
350
+ )
351
+
352
+ # NOTE:
353
+ # I do not use below syntax because `params` dict be the
354
+ # reference memory pointer and it was changed when I action
355
+ # anything like update or re-construct this.
356
+ # ... params |= stage.execute(params=params)
357
+ stage.execute(params=params)
358
+ # TODO: We should not return matrix key to outside
270
359
  return params
271
360
 
272
361
 
273
- class BaseParams(BaseModel, ABC):
274
- """Base Parameter that use to make Params Model."""
275
-
276
- desc: Optional[str] = None
277
- required: bool = True
278
- type: str
279
-
280
- @abstractmethod
281
- def receive(self, value: Optional[Any] = None) -> Any:
282
- raise ValueError(
283
- "Receive value and validate typing before return valid value."
284
- )
285
-
286
-
287
- class DefaultParams(BaseParams):
288
- """Default Parameter that will check default if it required"""
289
-
290
- default: Optional[str] = None
291
-
292
- @abstractmethod
293
- def receive(self, value: Optional[Any] = None) -> Any:
294
- raise ValueError(
295
- "Receive value and validate typing before return valid value."
296
- )
297
-
298
- @model_validator(mode="after")
299
- def check_default(self) -> Self:
300
- if not self.required and self.default is None:
301
- raise ValueError(
302
- "Default should set when this parameter does not required."
303
- )
304
- return self
305
-
306
-
307
- class DatetimeParams(DefaultParams):
308
- """Datetime parameter."""
309
-
310
- type: Literal["datetime"] = "datetime"
311
- required: bool = False
312
- default: datetime = Field(default_factory=dt_now)
313
-
314
- def receive(self, value: str | datetime | date | None = None) -> datetime:
315
- if value is None:
316
- return self.default
317
-
318
- if isinstance(value, datetime):
319
- return value
320
- elif isinstance(value, date):
321
- return datetime(value.year, value.month, value.day)
322
- elif not isinstance(value, str):
323
- raise ValueError(
324
- f"Value that want to convert to datetime does not support for "
325
- f"type: {type(value)}"
326
- )
327
- return datetime.fromisoformat(value)
328
-
329
-
330
- class StrParams(DefaultParams):
331
- """String parameter."""
332
-
333
- type: Literal["str"] = "str"
334
-
335
- def receive(self, value: Optional[str] = None) -> str | None:
336
- if value is None:
337
- return self.default
338
- return str(value)
339
-
340
-
341
- class IntParams(DefaultParams):
342
- """Integer parameter."""
343
-
344
- type: Literal["int"] = "int"
345
-
346
- def receive(self, value: Optional[int] = None) -> int | None:
347
- if value is None:
348
- return self.default
349
- if not isinstance(value, int):
350
- try:
351
- return int(str(value))
352
- except TypeError as err:
353
- raise ValueError(
354
- f"Value that want to convert to integer does not support "
355
- f"for type: {type(value)}"
356
- ) from err
357
- return value
358
-
359
-
360
- class ChoiceParams(BaseParams):
361
- type: Literal["choice"] = "choice"
362
- options: list[str]
363
-
364
- def receive(self, value: Optional[str] = None) -> str:
365
- """Receive value that match with options."""
366
- # NOTE:
367
- # Return the first value in options if does not pass any input value
368
- if value is None:
369
- return self.options[0]
370
- if any(value not in self.options):
371
- raise ValueError(f"{value} does not match any value in options")
372
- return value
373
-
374
-
375
- Params = Union[
376
- ChoiceParams,
377
- DatetimeParams,
378
- StrParams,
379
- ]
380
-
381
-
382
362
  class Pipeline(BaseModel):
383
- """Pipeline Model"""
363
+ """Pipeline Model this is the main feature of this project because it use to
364
+ be workflow data for running everywhere that you want. It use lightweight
365
+ coding line to execute it.
366
+ """
384
367
 
385
368
  params: dict[str, Params] = Field(default_factory=dict)
386
369
  jobs: dict[str, Job]
387
370
 
371
+ @model_validator(mode="before")
372
+ def __prepare_params(cls, values: DictData) -> DictData:
373
+ if params := values.pop("params", {}):
374
+ values["params"] = {
375
+ p: (
376
+ {"type": params[p]}
377
+ if isinstance(params[p], str)
378
+ else params[p]
379
+ )
380
+ for p in params
381
+ }
382
+ return values
383
+
388
384
  @classmethod
389
385
  def from_loader(
390
386
  cls,
@@ -399,6 +395,10 @@ class Pipeline(BaseModel):
399
395
  params=loader.data["params"],
400
396
  )
401
397
 
398
+ @model_validator(mode="after")
399
+ def job_checking_needs(self):
400
+ return self
401
+
402
402
  def job(self, name: str) -> Job:
403
403
  """Return Job model that exists on this pipeline.
404
404
 
@@ -406,13 +406,23 @@ class Pipeline(BaseModel):
406
406
  :type name: str
407
407
 
408
408
  :rtype: Job
409
+ :returns: A job model that exists on this pipeline by input name.
409
410
  """
410
411
  if name not in self.jobs:
411
- raise ValueError(f"Job {name} does not exists")
412
+ raise ValueError(f"Job {name!r} does not exists")
412
413
  return self.jobs[name]
413
414
 
414
- def execute(self, params: DictData | None = None) -> DictData:
415
- """Execute pipeline with passing dynamic parameters.
415
+ def execute(
416
+ self,
417
+ params: DictData | None = None,
418
+ time_out: int = 60,
419
+ ) -> DictData:
420
+ """Execute pipeline with passing dynamic parameters to any jobs that
421
+ included in the pipeline.
422
+
423
+ :param params: An input parameters that use on pipeline execution.
424
+ :param time_out: A time out second value for limit time of this
425
+ execution.
416
426
 
417
427
  See Also:
418
428
 
@@ -427,8 +437,7 @@ class Pipeline(BaseModel):
427
437
 
428
438
  """
429
439
  params: DictData = params or {}
430
- check_key = tuple(f"{k!r}" for k in self.params if k not in params)
431
- if check_key:
440
+ if check_key := tuple(f"{k!r}" for k in self.params if k not in params):
432
441
  raise ValueError(
433
442
  f"Parameters that needed on pipeline does not pass: "
434
443
  f"{', '.join(check_key)}."
@@ -445,12 +454,39 @@ class Pipeline(BaseModel):
445
454
  for k in params
446
455
  if k in self.params
447
456
  }
448
- )
457
+ ),
458
+ "jobs": {},
449
459
  }
460
+
461
+ jq = Queue()
450
462
  for job_id in self.jobs:
451
- print(f"[PIPELINE]: Start execute the job: {job_id!r}")
463
+ jq.put(job_id)
464
+
465
+ ts: float = time.monotonic()
466
+ not_time_out_flag = True
467
+
468
+ # IMPORTANT: The job execution can run parallel and waiting by needed.
469
+ while not jq.empty() and (
470
+ not_time_out_flag := ((time.monotonic() - ts) < time_out)
471
+ ):
472
+ job_id: str = jq.get()
473
+ logging.info(f"[PIPELINE]: Start execute the job: {job_id!r}")
452
474
  job: Job = self.jobs[job_id]
453
475
  # TODO: Condition on ``needs`` of this job was set. It should create
454
476
  # multithreading process on this step.
477
+ # But, I don't know how to handle changes params between each job
478
+ # execution while its use them together.
479
+ # ---
480
+ # >>> import multiprocessing
481
+ # >>> with multiprocessing.Pool(processes=3) as pool:
482
+ # ... results = pool.starmap(merge_names, ('', '', ...))
483
+ if any(params["jobs"].get(need) for need in job.needs):
484
+ jq.put(job_id)
455
485
  job.execute(params=params)
486
+ params["jobs"][job_id] = {
487
+ "stages": params.pop("stages", {}),
488
+ "matrix": params.pop("matrix", {}),
489
+ }
490
+ if not not_time_out_flag:
491
+ raise RuntimeError("Execution of pipeline was time out")
456
492
  return params