ddeutil-workflow 0.0.35__py3-none-any.whl → 0.0.37__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.
- ddeutil/workflow/__about__.py +1 -1
- ddeutil/workflow/__init__.py +2 -0
- ddeutil/workflow/api/api.py +50 -9
- ddeutil/workflow/api/log.py +59 -0
- ddeutil/workflow/api/repeat.py +22 -12
- ddeutil/workflow/api/routes/__init__.py +1 -0
- ddeutil/workflow/api/routes/job.py +73 -0
- ddeutil/workflow/api/routes/logs.py +142 -13
- ddeutil/workflow/api/routes/schedules.py +7 -6
- ddeutil/workflow/api/routes/workflows.py +9 -7
- ddeutil/workflow/audit.py +6 -3
- ddeutil/workflow/caller.py +3 -1
- ddeutil/workflow/conf.py +0 -60
- ddeutil/workflow/job.py +293 -249
- ddeutil/workflow/logs.py +164 -52
- ddeutil/workflow/params.py +52 -15
- ddeutil/workflow/result.py +3 -5
- ddeutil/workflow/scheduler.py +42 -14
- ddeutil/workflow/stages.py +65 -4
- ddeutil/workflow/utils.py +40 -13
- ddeutil/workflow/workflow.py +1 -15
- {ddeutil_workflow-0.0.35.dist-info → ddeutil_workflow-0.0.37.dist-info}/METADATA +15 -13
- ddeutil_workflow-0.0.37.dist-info/RECORD +32 -0
- {ddeutil_workflow-0.0.35.dist-info → ddeutil_workflow-0.0.37.dist-info}/WHEEL +1 -1
- ddeutil_workflow-0.0.35.dist-info/RECORD +0 -30
- {ddeutil_workflow-0.0.35.dist-info → ddeutil_workflow-0.0.37.dist-info}/LICENSE +0 -0
- {ddeutil_workflow-0.0.35.dist-info → ddeutil_workflow-0.0.37.dist-info}/top_level.txt +0 -0
ddeutil/workflow/job.py
CHANGED
@@ -32,7 +32,7 @@ from pydantic.functional_validators import field_validator, model_validator
|
|
32
32
|
from typing_extensions import Self
|
33
33
|
|
34
34
|
from .__types import DictData, DictStr, Matrix, TupleStr
|
35
|
-
from .conf import config
|
35
|
+
from .conf import config
|
36
36
|
from .exceptions import (
|
37
37
|
JobException,
|
38
38
|
StageException,
|
@@ -48,7 +48,6 @@ from .utils import (
|
|
48
48
|
gen_id,
|
49
49
|
)
|
50
50
|
|
51
|
-
logger = get_logger("ddeutil.workflow")
|
52
51
|
MatrixFilter = list[dict[str, Union[str, int]]]
|
53
52
|
|
54
53
|
|
@@ -59,9 +58,10 @@ __all__: TupleStr = (
|
|
59
58
|
"RunsOn",
|
60
59
|
"RunsOnLocal",
|
61
60
|
"RunsOnSelfHosted",
|
62
|
-
"RunsOnDocker",
|
63
61
|
"RunsOnK8s",
|
64
62
|
"make",
|
63
|
+
"local_execute_strategy",
|
64
|
+
"local_execute",
|
65
65
|
)
|
66
66
|
|
67
67
|
|
@@ -225,12 +225,15 @@ class RunsOnType(str, Enum):
|
|
225
225
|
"""Runs-On enum object."""
|
226
226
|
|
227
227
|
LOCAL: str = "local"
|
228
|
-
DOCKER: str = "docker"
|
229
228
|
SELF_HOSTED: str = "self_hosted"
|
230
229
|
K8S: str = "k8s"
|
231
230
|
|
232
231
|
|
233
|
-
class BaseRunsOn(BaseModel):
|
232
|
+
class BaseRunsOn(BaseModel): # pragma: no cov
|
233
|
+
"""Base Runs-On Model for generate runs-on types via inherit this model
|
234
|
+
object and override execute method.
|
235
|
+
"""
|
236
|
+
|
234
237
|
model_config = ConfigDict(use_enum_values=True)
|
235
238
|
|
236
239
|
type: Literal[RunsOnType.LOCAL]
|
@@ -240,27 +243,26 @@ class BaseRunsOn(BaseModel):
|
|
240
243
|
)
|
241
244
|
|
242
245
|
|
243
|
-
class RunsOnLocal(BaseRunsOn):
|
246
|
+
class RunsOnLocal(BaseRunsOn): # pragma: no cov
|
244
247
|
"""Runs-on local."""
|
245
248
|
|
246
249
|
type: Literal[RunsOnType.LOCAL] = Field(default=RunsOnType.LOCAL)
|
247
250
|
|
248
251
|
|
249
|
-
class
|
252
|
+
class SelfHostedArgs(BaseModel):
|
253
|
+
host: str
|
254
|
+
|
255
|
+
|
256
|
+
class RunsOnSelfHosted(BaseRunsOn): # pragma: no cov
|
250
257
|
"""Runs-on self-hosted."""
|
251
258
|
|
252
259
|
type: Literal[RunsOnType.SELF_HOSTED] = Field(
|
253
260
|
default=RunsOnType.SELF_HOSTED
|
254
261
|
)
|
262
|
+
args: SelfHostedArgs = Field(alias="with")
|
255
263
|
|
256
264
|
|
257
|
-
class
|
258
|
-
"""Runs-on local Docker."""
|
259
|
-
|
260
|
-
type: Literal[RunsOnType.DOCKER] = Field(default=RunsOnType.DOCKER)
|
261
|
-
|
262
|
-
|
263
|
-
class RunsOnK8s(BaseRunsOn):
|
265
|
+
class RunsOnK8s(BaseRunsOn): # pragma: no cov
|
264
266
|
"""Runs-on Kubernetes."""
|
265
267
|
|
266
268
|
type: Literal[RunsOnType.K8S] = Field(default=RunsOnType.K8S)
|
@@ -270,7 +272,6 @@ RunsOn = Annotated[
|
|
270
272
|
Union[
|
271
273
|
RunsOnLocal,
|
272
274
|
RunsOnSelfHosted,
|
273
|
-
RunsOnDocker,
|
274
275
|
RunsOnK8s,
|
275
276
|
],
|
276
277
|
Field(discriminator="type"),
|
@@ -286,7 +287,7 @@ class Job(BaseModel):
|
|
286
287
|
|
287
288
|
Data Validate:
|
288
289
|
>>> job = {
|
289
|
-
... "runs-on":
|
290
|
+
... "runs-on": {"type": "local"},
|
290
291
|
... "strategy": {
|
291
292
|
... "max-parallel": 1,
|
292
293
|
... "matrix": {
|
@@ -464,271 +465,314 @@ class Job(BaseModel):
|
|
464
465
|
)
|
465
466
|
return to
|
466
467
|
|
467
|
-
def
|
468
|
+
def execute(
|
468
469
|
self,
|
469
|
-
strategy: DictData,
|
470
470
|
params: DictData,
|
471
471
|
*,
|
472
|
+
run_id: str | None = None,
|
473
|
+
parent_run_id: str | None = None,
|
472
474
|
result: Result | None = None,
|
473
|
-
event: Event | None = None,
|
474
475
|
) -> Result:
|
475
|
-
"""Job
|
476
|
-
|
477
|
-
|
478
|
-
This execution is the minimum level of execution of this job model.
|
479
|
-
It different with `self.execute` because this method run only one
|
480
|
-
strategy and return with context of this strategy data.
|
481
|
-
|
482
|
-
The result of this execution will return result with strategy ID
|
483
|
-
that generated from the `gen_id` function with an input strategy value.
|
484
|
-
|
485
|
-
:raise JobException: If it has any error from `StageException` or
|
486
|
-
`UtilException`.
|
476
|
+
"""Job execution with passing dynamic parameters from the workflow
|
477
|
+
execution. It will generate matrix values at the first step and run
|
478
|
+
multithread on this metrics to the `stages` field of this job.
|
487
479
|
|
488
|
-
:param
|
489
|
-
|
490
|
-
:param
|
480
|
+
:param params: An input parameters that use on job execution.
|
481
|
+
:param run_id: A job running ID for this execution.
|
482
|
+
:param parent_run_id: A parent workflow running ID for this release.
|
491
483
|
:param result: (Result) A result object for keeping context and status
|
492
484
|
data.
|
493
|
-
:param event: An event manager that pass to the PoolThreadExecutor.
|
494
485
|
|
495
486
|
:rtype: Result
|
496
487
|
"""
|
497
488
|
if result is None: # pragma: no cov
|
498
|
-
result: Result = Result(
|
499
|
-
|
500
|
-
|
489
|
+
result: Result = Result(
|
490
|
+
run_id=(run_id or gen_id(self.id or "", unique=True)),
|
491
|
+
parent_run_id=parent_run_id,
|
492
|
+
)
|
493
|
+
elif parent_run_id: # pragma: no cov
|
494
|
+
result.set_parent_run_id(parent_run_id)
|
501
495
|
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
# }
|
513
|
-
#
|
514
|
-
context: DictData = copy.deepcopy(params)
|
515
|
-
context.update({"matrix": strategy, "stages": {}})
|
496
|
+
if self.runs_on.type == RunsOnType.LOCAL:
|
497
|
+
return local_execute(
|
498
|
+
job=self,
|
499
|
+
params=params,
|
500
|
+
result=result,
|
501
|
+
)
|
502
|
+
raise NotImplementedError(
|
503
|
+
f"The job runs-on other type: {self.runs_on.type} does not "
|
504
|
+
f"support yet."
|
505
|
+
)
|
516
506
|
|
517
|
-
# IMPORTANT: The stage execution only run sequentially one-by-one.
|
518
|
-
for stage in self.stages:
|
519
507
|
|
520
|
-
|
521
|
-
|
522
|
-
|
508
|
+
def local_execute_strategy(
|
509
|
+
job: Job,
|
510
|
+
strategy: DictData,
|
511
|
+
params: DictData,
|
512
|
+
*,
|
513
|
+
result: Result | None = None,
|
514
|
+
event: Event | None = None,
|
515
|
+
) -> Result:
|
516
|
+
"""Local job strategy execution with passing dynamic parameters from the
|
517
|
+
workflow execution to strategy matrix.
|
518
|
+
|
519
|
+
This execution is the minimum level of execution of this job model.
|
520
|
+
It different with `self.execute` because this method run only one
|
521
|
+
strategy and return with context of this strategy data.
|
522
|
+
|
523
|
+
The result of this execution will return result with strategy ID
|
524
|
+
that generated from the `gen_id` function with an input strategy value.
|
525
|
+
|
526
|
+
:raise JobException: If it has any error from `StageException` or
|
527
|
+
`UtilException`.
|
528
|
+
|
529
|
+
:param job: (Job) A job model that want to execute.
|
530
|
+
:param strategy: A strategy metrix value that use on this execution.
|
531
|
+
This value will pass to the `matrix` key for templating.
|
532
|
+
:param params: A dynamic parameters that will deepcopy to the context.
|
533
|
+
:param result: (Result) A result object for keeping context and status
|
534
|
+
data.
|
535
|
+
:param event: (Event) An event manager that pass to the PoolThreadExecutor.
|
536
|
+
|
537
|
+
:rtype: Result
|
538
|
+
"""
|
539
|
+
if result is None: # pragma: no cov
|
540
|
+
result: Result = Result(run_id=gen_id(job.id or "", unique=True))
|
541
|
+
|
542
|
+
strategy_id: str = gen_id(strategy)
|
543
|
+
|
544
|
+
# PARAGRAPH:
|
545
|
+
#
|
546
|
+
# Create strategy execution context and update a matrix and copied
|
547
|
+
# of params. So, the context value will have structure like;
|
548
|
+
#
|
549
|
+
# {
|
550
|
+
# "params": { ... }, <== Current input params
|
551
|
+
# "jobs": { ... }, <== Current input params
|
552
|
+
# "matrix": { ... } <== Current strategy value
|
553
|
+
# "stages": { ... } <== Catching stage outputs
|
554
|
+
# }
|
555
|
+
#
|
556
|
+
context: DictData = copy.deepcopy(params)
|
557
|
+
context.update({"matrix": strategy, "stages": {}})
|
558
|
+
|
559
|
+
# IMPORTANT: The stage execution only run sequentially one-by-one.
|
560
|
+
for stage in job.stages:
|
561
|
+
|
562
|
+
if stage.is_skipped(params=context):
|
563
|
+
result.trace.info(f"[JOB]: Skip stage: {stage.iden!r}")
|
564
|
+
continue
|
523
565
|
|
524
|
-
|
566
|
+
result.trace.info(f"[JOB]: Execute stage: {stage.iden!r}")
|
525
567
|
|
526
|
-
|
527
|
-
|
528
|
-
|
568
|
+
# NOTE: Logging a matrix that pass on this stage execution.
|
569
|
+
if strategy:
|
570
|
+
result.trace.info(f"[JOB]: ... Matrix: {strategy}")
|
529
571
|
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
},
|
572
|
+
# NOTE: Force stop this execution if event was set from main
|
573
|
+
# execution.
|
574
|
+
if event and event.is_set():
|
575
|
+
error_msg: str = (
|
576
|
+
"Job strategy was canceled from event that had set before "
|
577
|
+
"strategy execution."
|
578
|
+
)
|
579
|
+
return result.catch(
|
580
|
+
status=1,
|
581
|
+
context={
|
582
|
+
strategy_id: {
|
583
|
+
"matrix": strategy,
|
584
|
+
# NOTE: If job strategy executor use multithreading,
|
585
|
+
# it will not filter function object from context.
|
586
|
+
# ---
|
587
|
+
# "stages": filter_func(context.pop("stages", {})),
|
588
|
+
#
|
589
|
+
"stages": context.pop("stages", {}),
|
590
|
+
"errors": {
|
591
|
+
"class": JobException(error_msg),
|
592
|
+
"name": "JobException",
|
593
|
+
"message": error_msg,
|
553
594
|
},
|
554
595
|
},
|
555
|
-
|
596
|
+
},
|
597
|
+
)
|
556
598
|
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
},
|
599
|
+
# PARAGRAPH:
|
600
|
+
#
|
601
|
+
# I do not use below syntax because `params` dict be the
|
602
|
+
# reference memory pointer, and it was changed when I action
|
603
|
+
# anything like update or re-construct this.
|
604
|
+
#
|
605
|
+
# ... params |= stage.execute(params=params)
|
606
|
+
#
|
607
|
+
# This step will add the stage result to `stages` key in
|
608
|
+
# that stage id. It will have structure like;
|
609
|
+
#
|
610
|
+
# {
|
611
|
+
# "params": { ... },
|
612
|
+
# "jobs": { ... },
|
613
|
+
# "matrix": { ... },
|
614
|
+
# "stages": { { "stage-id-1": ... }, ... }
|
615
|
+
# }
|
616
|
+
#
|
617
|
+
# IMPORTANT:
|
618
|
+
# This execution change all stage running IDs to the current job
|
619
|
+
# running ID, but it still trac log to the same parent running ID
|
620
|
+
# (with passing `run_id` and `parent_run_id` to the stage
|
621
|
+
# execution arguments).
|
622
|
+
#
|
623
|
+
try:
|
624
|
+
stage.set_outputs(
|
625
|
+
stage.handler_execute(
|
626
|
+
params=context,
|
627
|
+
run_id=result.run_id,
|
628
|
+
parent_run_id=result.parent_run_id,
|
629
|
+
).context,
|
630
|
+
to=context,
|
631
|
+
)
|
632
|
+
except (StageException, UtilException) as err:
|
633
|
+
result.trace.error(f"[JOB]: {err.__class__.__name__}: {err}")
|
634
|
+
if config.job_raise_error:
|
635
|
+
raise JobException(
|
636
|
+
f"Stage execution error: {err.__class__.__name__}: "
|
637
|
+
f"{err}"
|
638
|
+
) from None
|
639
|
+
|
640
|
+
return result.catch(
|
641
|
+
status=1,
|
642
|
+
context={
|
643
|
+
strategy_id: {
|
644
|
+
"matrix": strategy,
|
645
|
+
"stages": context.pop("stages", {}),
|
646
|
+
"errors": {
|
647
|
+
"class": err,
|
648
|
+
"name": err.__class__.__name__,
|
649
|
+
"message": f"{err.__class__.__name__}: {err}",
|
609
650
|
},
|
610
651
|
},
|
611
|
-
|
652
|
+
},
|
653
|
+
)
|
612
654
|
|
613
|
-
|
614
|
-
|
655
|
+
# NOTE: Remove the current stage object for saving memory.
|
656
|
+
del stage
|
615
657
|
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
},
|
658
|
+
return result.catch(
|
659
|
+
status=Status.SUCCESS,
|
660
|
+
context={
|
661
|
+
strategy_id: {
|
662
|
+
"matrix": strategy,
|
663
|
+
"stages": filter_func(context.pop("stages", {})),
|
623
664
|
},
|
624
|
-
|
625
|
-
|
626
|
-
def execute(
|
627
|
-
self,
|
628
|
-
params: DictData,
|
629
|
-
*,
|
630
|
-
run_id: str | None = None,
|
631
|
-
parent_run_id: str | None = None,
|
632
|
-
result: Result | None = None,
|
633
|
-
) -> Result:
|
634
|
-
"""Job execution with passing dynamic parameters from the workflow
|
635
|
-
execution. It will generate matrix values at the first step and run
|
636
|
-
multithread on this metrics to the `stages` field of this job.
|
665
|
+
},
|
666
|
+
)
|
637
667
|
|
638
|
-
:param params: An input parameters that use on job execution.
|
639
|
-
:param run_id: A job running ID for this execution.
|
640
|
-
:param parent_run_id: A parent workflow running ID for this release.
|
641
|
-
:param result: (Result) A result object for keeping context and status
|
642
|
-
data.
|
643
668
|
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
669
|
+
def local_execute(
|
670
|
+
job: Job,
|
671
|
+
params: DictData,
|
672
|
+
*,
|
673
|
+
run_id: str | None = None,
|
674
|
+
parent_run_id: str | None = None,
|
675
|
+
result: Result | None = None,
|
676
|
+
) -> Result:
|
677
|
+
"""Local job execution with passing dynamic parameters from the workflow
|
678
|
+
execution. It will generate matrix values at the first step and run
|
679
|
+
multithread on this metrics to the `stages` field of this job.
|
680
|
+
|
681
|
+
:param job: A job model that want to execute.
|
682
|
+
:param params: An input parameters that use on job execution.
|
683
|
+
:param run_id: A job running ID for this execution.
|
684
|
+
:param parent_run_id: A parent workflow running ID for this release.
|
685
|
+
:param result: (Result) A result object for keeping context and status
|
686
|
+
data.
|
687
|
+
|
688
|
+
:rtype: Result
|
689
|
+
"""
|
690
|
+
if result is None: # pragma: no cov
|
691
|
+
result: Result = Result(
|
692
|
+
run_id=(run_id or gen_id(job.id or "", unique=True)),
|
693
|
+
parent_run_id=parent_run_id,
|
694
|
+
)
|
695
|
+
elif parent_run_id: # pragma: no cov
|
696
|
+
result.set_parent_run_id(parent_run_id)
|
697
|
+
|
698
|
+
# NOTE: Normal Job execution without parallel strategy matrix. It uses
|
699
|
+
# for-loop to control strategy execution sequentially.
|
700
|
+
if (not job.strategy.is_set()) or job.strategy.max_parallel == 1:
|
701
|
+
|
702
|
+
for strategy in job.strategy.make():
|
703
|
+
result: Result = local_execute_strategy(
|
704
|
+
job=job,
|
705
|
+
strategy=strategy,
|
706
|
+
params=params,
|
707
|
+
result=result,
|
650
708
|
)
|
651
|
-
elif parent_run_id: # pragma: no cov
|
652
|
-
result.set_parent_run_id(parent_run_id)
|
653
709
|
|
654
|
-
|
655
|
-
|
656
|
-
|
710
|
+
return result.catch(status=Status.SUCCESS)
|
711
|
+
|
712
|
+
# NOTE: Create event for cancel executor by trigger stop running event.
|
713
|
+
event: Event = Event()
|
714
|
+
|
715
|
+
# IMPORTANT: Start running strategy execution by multithreading because
|
716
|
+
# it will run by strategy values without waiting previous execution.
|
717
|
+
with ThreadPoolExecutor(
|
718
|
+
max_workers=job.strategy.max_parallel,
|
719
|
+
thread_name_prefix="job_strategy_exec_",
|
720
|
+
) as executor:
|
721
|
+
|
722
|
+
futures: list[Future] = [
|
723
|
+
executor.submit(
|
724
|
+
local_execute_strategy,
|
725
|
+
job=job,
|
726
|
+
strategy=strategy,
|
727
|
+
params=params,
|
728
|
+
result=result,
|
729
|
+
event=event,
|
730
|
+
)
|
731
|
+
for strategy in job.strategy.make()
|
732
|
+
]
|
733
|
+
|
734
|
+
context: DictData = {}
|
735
|
+
status: Status = Status.SUCCESS
|
736
|
+
fail_fast_flag: bool = job.strategy.fail_fast
|
737
|
+
|
738
|
+
if not fail_fast_flag:
|
739
|
+
done = as_completed(futures, timeout=1800)
|
740
|
+
else:
|
741
|
+
# NOTE: Get results from a collection of tasks with a timeout
|
742
|
+
# that has the first exception.
|
743
|
+
done, not_done = wait(
|
744
|
+
futures, timeout=1800, return_when=FIRST_EXCEPTION
|
745
|
+
)
|
746
|
+
nd: str = (
|
747
|
+
f", the strategies do not run is {not_done}" if not_done else ""
|
748
|
+
)
|
749
|
+
result.trace.debug(f"[JOB]: Strategy is set Fail Fast{nd}")
|
657
750
|
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
751
|
+
# NOTE: Stop all running tasks with setting the event manager
|
752
|
+
# and cancel any scheduled tasks.
|
753
|
+
if len(done) != len(futures):
|
754
|
+
event.set()
|
755
|
+
for future in not_done:
|
756
|
+
future.cancel()
|
664
757
|
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
thread_name_prefix="job_strategy_exec_",
|
675
|
-
) as executor:
|
676
|
-
|
677
|
-
futures: list[Future] = [
|
678
|
-
executor.submit(
|
679
|
-
self.execute_strategy,
|
680
|
-
strategy=strategy,
|
681
|
-
params=params,
|
682
|
-
result=result,
|
683
|
-
event=event,
|
684
|
-
)
|
685
|
-
for strategy in self.strategy.make()
|
686
|
-
]
|
687
|
-
|
688
|
-
context: DictData = {}
|
689
|
-
status: Status = Status.SUCCESS
|
690
|
-
fail_fast_flag: bool = self.strategy.fail_fast
|
691
|
-
|
692
|
-
if fail_fast_flag:
|
693
|
-
# NOTE: Get results from a collection of tasks with a timeout
|
694
|
-
# that has the first exception.
|
695
|
-
done, not_done = wait(
|
696
|
-
futures, timeout=1800, return_when=FIRST_EXCEPTION
|
697
|
-
)
|
698
|
-
nd: str = (
|
699
|
-
f", the strategies do not run is {not_done}"
|
700
|
-
if not_done
|
701
|
-
else ""
|
758
|
+
for future in done:
|
759
|
+
try:
|
760
|
+
future.result()
|
761
|
+
except JobException as err:
|
762
|
+
status = Status.FAILED
|
763
|
+
ls: str = "Fail-Fast" if fail_fast_flag else "All-Completed"
|
764
|
+
result.trace.error(
|
765
|
+
f"[JOB]: {ls} Catch:\n\t{err.__class__.__name__}:"
|
766
|
+
f"\n\t{err}"
|
702
767
|
)
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
for future in not_done:
|
710
|
-
future.cancel()
|
711
|
-
else:
|
712
|
-
done = as_completed(futures, timeout=1800)
|
713
|
-
|
714
|
-
for future in done:
|
715
|
-
try:
|
716
|
-
future.result()
|
717
|
-
except JobException as err:
|
718
|
-
status = Status.FAILED
|
719
|
-
ls: str = "Fail-Fast" if fail_fast_flag else "All-Completed"
|
720
|
-
result.trace.error(
|
721
|
-
f"[JOB]: {ls} Catch:\n\t{err.__class__.__name__}:"
|
722
|
-
f"\n\t{err}"
|
723
|
-
)
|
724
|
-
context.update(
|
725
|
-
{
|
726
|
-
"errors": {
|
727
|
-
"class": err,
|
728
|
-
"name": err.__class__.__name__,
|
729
|
-
"message": f"{err.__class__.__name__}: {err}",
|
730
|
-
},
|
768
|
+
context.update(
|
769
|
+
{
|
770
|
+
"errors": {
|
771
|
+
"class": err,
|
772
|
+
"name": err.__class__.__name__,
|
773
|
+
"message": f"{err.__class__.__name__}: {err}",
|
731
774
|
},
|
732
|
-
|
775
|
+
},
|
776
|
+
)
|
733
777
|
|
734
|
-
|
778
|
+
return result.catch(status=status, context=context)
|