ddeutil-workflow 0.0.53__py3-none-any.whl → 0.0.55__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/api/__init__.py +170 -1
- ddeutil/workflow/api/routes/job.py +23 -22
- ddeutil/workflow/api/routes/schedules.py +0 -2
- ddeutil/workflow/api/routes/workflows.py +3 -4
- ddeutil/workflow/job.py +125 -170
- ddeutil/workflow/result.py +1 -0
- ddeutil/workflow/scheduler.py +1 -3
- ddeutil/workflow/stages.py +641 -399
- ddeutil/workflow/utils.py +5 -4
- ddeutil/workflow/workflow.py +118 -258
- {ddeutil_workflow-0.0.53.dist-info → ddeutil_workflow-0.0.55.dist-info}/METADATA +5 -13
- ddeutil_workflow-0.0.55.dist-info/RECORD +30 -0
- ddeutil/workflow/api/api.py +0 -170
- ddeutil_workflow-0.0.53.dist-info/RECORD +0 -31
- /ddeutil/workflow/api/{log.py → logs.py} +0 -0
- /ddeutil/workflow/api/{repeat.py → utils.py} +0 -0
- {ddeutil_workflow-0.0.53.dist-info → ddeutil_workflow-0.0.55.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.53.dist-info → ddeutil_workflow-0.0.55.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.53.dist-info → ddeutil_workflow-0.0.55.dist-info}/top_level.txt +0 -0
ddeutil/workflow/job.py
CHANGED
@@ -3,12 +3,17 @@
|
|
3
3
|
# Licensed under the MIT License. See LICENSE in the project root for
|
4
4
|
# license information.
|
5
5
|
# ------------------------------------------------------------------------------
|
6
|
-
"""Job
|
7
|
-
The job handle the lineage of stages and location of
|
8
|
-
|
9
|
-
|
6
|
+
"""Job model that use for store Stage models and node parameter that use for
|
7
|
+
running these stages. The job model handle the lineage of stages and location of
|
8
|
+
execution that mean you can define `runs-on` field with the Self-Hosted mode
|
9
|
+
for execute on target machine instead of the current local machine.
|
10
10
|
|
11
|
-
This module include Strategy
|
11
|
+
This module include Strategy model that use on the job `strategy` field for
|
12
|
+
making matrix values before execution parallelism stage execution.
|
13
|
+
|
14
|
+
The Job model does not implement `handler_execute` same as Stage model
|
15
|
+
because the job should raise only `JobException` class from the execution
|
16
|
+
method.
|
12
17
|
"""
|
13
18
|
from __future__ import annotations
|
14
19
|
|
@@ -41,7 +46,7 @@ from .exceptions import (
|
|
41
46
|
from .result import CANCEL, FAILED, SKIP, SUCCESS, WAIT, Result, Status
|
42
47
|
from .reusables import has_template, param2template
|
43
48
|
from .stages import Stage
|
44
|
-
from .utils import cross_product, filter_func, gen_id
|
49
|
+
from .utils import NEWLINE, cross_product, filter_func, gen_id
|
45
50
|
|
46
51
|
MatrixFilter = list[dict[str, Union[str, int]]]
|
47
52
|
|
@@ -59,10 +64,10 @@ def make(
|
|
59
64
|
This function use the `lru_cache` decorator function increase
|
60
65
|
performance for duplicate matrix value scenario.
|
61
66
|
|
62
|
-
:param matrix: A matrix values that want to cross product to
|
63
|
-
parallelism values.
|
64
|
-
:param include: A list of additional matrix that want to adds-in.
|
65
|
-
:param exclude: A list of exclude matrix that want to filter-out.
|
67
|
+
:param matrix: (Matrix) A matrix values that want to cross product to
|
68
|
+
possible parallelism values.
|
69
|
+
:param include: (A list of additional matrix that want to adds-in.
|
70
|
+
:param exclude: (A list of exclude matrix that want to filter-out.
|
66
71
|
|
67
72
|
:rtype: list[DictStr]
|
68
73
|
"""
|
@@ -320,12 +325,13 @@ class Job(BaseModel):
|
|
320
325
|
id: Optional[str] = Field(
|
321
326
|
default=None,
|
322
327
|
description=(
|
323
|
-
"A job ID that
|
328
|
+
"A job ID that was set from Workflow model after initialize step. "
|
329
|
+
"If this model create standalone, it will be None."
|
324
330
|
),
|
325
331
|
)
|
326
332
|
desc: Optional[str] = Field(
|
327
333
|
default=None,
|
328
|
-
description="A job description that can be
|
334
|
+
description="A job description that can be markdown syntax.",
|
329
335
|
)
|
330
336
|
runs_on: RunsOnModel = Field(
|
331
337
|
default_factory=OnLocal,
|
@@ -339,7 +345,7 @@ class Job(BaseModel):
|
|
339
345
|
)
|
340
346
|
stages: list[Stage] = Field(
|
341
347
|
default_factory=list,
|
342
|
-
description="A list of Stage of this job.",
|
348
|
+
description="A list of Stage model of this job.",
|
343
349
|
)
|
344
350
|
trigger_rule: Rule = Field(
|
345
351
|
default=Rule.ALL_SUCCESS,
|
@@ -373,7 +379,7 @@ class Job(BaseModel):
|
|
373
379
|
|
374
380
|
@field_validator("stages", mode="after")
|
375
381
|
def __validate_stage_id__(cls, value: list[Stage]) -> list[Stage]:
|
376
|
-
"""Validate
|
382
|
+
"""Validate stage ID of each stage in the `stages` field should not be
|
377
383
|
duplicate.
|
378
384
|
|
379
385
|
:rtype: list[Stage]
|
@@ -391,11 +397,10 @@ class Job(BaseModel):
|
|
391
397
|
|
392
398
|
@model_validator(mode="after")
|
393
399
|
def __validate_job_id__(self) -> Self:
|
394
|
-
"""Validate job id should not
|
400
|
+
"""Validate job id should not dynamic with params template.
|
395
401
|
|
396
402
|
:rtype: Self
|
397
403
|
"""
|
398
|
-
# VALIDATE: Validate job id should not dynamic with params template.
|
399
404
|
if has_template(self.id):
|
400
405
|
raise ValueError(
|
401
406
|
f"Job ID, {self.id!r}, should not has any template."
|
@@ -419,14 +424,11 @@ class Job(BaseModel):
|
|
419
424
|
return stage
|
420
425
|
raise ValueError(f"Stage {stage_id!r} does not exists in this job.")
|
421
426
|
|
422
|
-
def check_needs(
|
423
|
-
|
424
|
-
|
425
|
-
) -> Status: # pragma: no cov
|
426
|
-
"""Return Status enum for checking job's need trigger logic in an
|
427
|
-
input list of job's ID.
|
427
|
+
def check_needs(self, jobs: dict[str, Any]) -> Status: # pragma: no cov
|
428
|
+
"""Return trigger status from checking job's need trigger rule logic was
|
429
|
+
valid. The return status should be SUCCESS, FAILED, WAIT, or SKIP.
|
428
430
|
|
429
|
-
:param jobs: A mapping of job ID and
|
431
|
+
:param jobs: A mapping of job ID and its context data.
|
430
432
|
|
431
433
|
:raise NotImplementedError: If the job trigger rule out of scope.
|
432
434
|
|
@@ -441,7 +443,6 @@ class Job(BaseModel):
|
|
441
443
|
need_exist: dict[str, Any] = {
|
442
444
|
need: jobs[need] for need in self.needs if need in jobs
|
443
445
|
}
|
444
|
-
|
445
446
|
if len(need_exist) != len(self.needs):
|
446
447
|
return WAIT
|
447
448
|
elif all("skipped" in need_exist[job] for job in need_exist):
|
@@ -469,30 +470,26 @@ class Job(BaseModel):
|
|
469
470
|
elif self.trigger_rule == Rule.NONE_FAILED:
|
470
471
|
rs = all("errors" not in need_exist[job] for job in need_exist)
|
471
472
|
else: # pragma: no cov
|
472
|
-
|
473
|
-
f"Trigger rule: {self.trigger_rule} does not support yet."
|
474
|
-
)
|
473
|
+
return FAILED
|
475
474
|
return make_return(rs)
|
476
475
|
|
477
|
-
def is_skipped(self, params: DictData
|
476
|
+
def is_skipped(self, params: DictData) -> bool:
|
478
477
|
"""Return true if condition of this job do not correct. This process
|
479
478
|
use build-in eval function to execute the if-condition.
|
480
479
|
|
480
|
+
:param params: (DictData) A parameter value that want to pass to condition
|
481
|
+
template.
|
482
|
+
|
481
483
|
:raise JobException: When it has any error raise from the eval
|
482
484
|
condition statement.
|
483
485
|
:raise JobException: When return type of the eval condition statement
|
484
486
|
does not return with boolean type.
|
485
487
|
|
486
|
-
:param params: (DictData) A parameters that want to pass to condition
|
487
|
-
template.
|
488
|
-
|
489
488
|
:rtype: bool
|
490
489
|
"""
|
491
490
|
if self.condition is None:
|
492
491
|
return False
|
493
492
|
|
494
|
-
params: DictData = {} if params is None else params
|
495
|
-
|
496
493
|
try:
|
497
494
|
# WARNING: The eval build-in function is very dangerous. So, it
|
498
495
|
# should use the `re` module to validate eval-string before
|
@@ -513,15 +510,20 @@ class Job(BaseModel):
|
|
513
510
|
output: DictData,
|
514
511
|
to: DictData,
|
515
512
|
*,
|
516
|
-
job_id: Optional[
|
513
|
+
job_id: Optional[str] = None,
|
517
514
|
) -> DictData:
|
518
|
-
"""Set an outputs from execution
|
519
|
-
|
515
|
+
"""Set an outputs from execution result context to the received context
|
516
|
+
with a `to` input parameter. The result context from job strategy
|
517
|
+
execution will be set with `strategies` key in this job ID key.
|
520
518
|
|
521
519
|
For example of setting output method, If you receive execute output
|
522
520
|
and want to set on the `to` like;
|
523
521
|
|
524
|
-
... (i) output: {
|
522
|
+
... (i) output: {
|
523
|
+
'strategy-01': 'foo',
|
524
|
+
'strategy-02': 'bar',
|
525
|
+
'skipped': True,
|
526
|
+
}
|
525
527
|
... (ii) to: {'jobs': {}}
|
526
528
|
|
527
529
|
The result of the `to` argument will be;
|
@@ -530,19 +532,26 @@ class Job(BaseModel):
|
|
530
532
|
'jobs': {
|
531
533
|
'<job-id>': {
|
532
534
|
'strategies': {
|
533
|
-
'strategy-01':
|
534
|
-
'strategy-02': bar,
|
535
|
-
}
|
535
|
+
'strategy-01': 'foo',
|
536
|
+
'strategy-02': 'bar',
|
537
|
+
},
|
538
|
+
'skipped': True,
|
536
539
|
}
|
537
540
|
}
|
538
541
|
}
|
539
542
|
|
543
|
+
The keys that will set to the received context is `strategies`,
|
544
|
+
`errors`, and `skipped` keys. The `errors` and `skipped` keys will
|
545
|
+
extract from the result context if it exists. If it does not found, it
|
546
|
+
will not set on the received context.
|
547
|
+
|
540
548
|
:raise JobException: If the job's ID does not set and the setting
|
541
549
|
default job ID flag does not set.
|
542
550
|
|
543
|
-
:param output:
|
544
|
-
|
545
|
-
:param
|
551
|
+
:param output: (DictData) A result data context that want to extract
|
552
|
+
and transfer to the `strategies` key in receive context.
|
553
|
+
:param to: (DictData) A received context data.
|
554
|
+
:param job_id: (str | None) A job ID if the `id` field does not set.
|
546
555
|
|
547
556
|
:rtype: DictData
|
548
557
|
"""
|
@@ -555,7 +564,7 @@ class Job(BaseModel):
|
|
555
564
|
)
|
556
565
|
|
557
566
|
_id: str = self.id or job_id
|
558
|
-
output: DictData = copy
|
567
|
+
output: DictData = output.copy()
|
559
568
|
errors: DictData = (
|
560
569
|
{"errors": output.pop("errors", {})} if "errors" in output else {}
|
561
570
|
)
|
@@ -584,9 +593,7 @@ class Job(BaseModel):
|
|
584
593
|
*,
|
585
594
|
run_id: str | None = None,
|
586
595
|
parent_run_id: str | None = None,
|
587
|
-
result: Result | None = None,
|
588
596
|
event: Event | None = None,
|
589
|
-
raise_error: bool = True,
|
590
597
|
) -> Result:
|
591
598
|
"""Job execution with passing dynamic parameters from the workflow
|
592
599
|
execution. It will generate matrix values at the first step and run
|
@@ -595,22 +602,18 @@ class Job(BaseModel):
|
|
595
602
|
This method be execution routing for call dynamic execution function
|
596
603
|
with specific target `runs-on` value.
|
597
604
|
|
598
|
-
:param params:
|
605
|
+
:param params: (DictData) A parameter data.
|
599
606
|
:param run_id: (str) A job running ID.
|
600
|
-
:param parent_run_id: (str) A parent
|
601
|
-
:param
|
602
|
-
|
603
|
-
:param event: (Event) An event manager that pass to the
|
604
|
-
PoolThreadExecutor.
|
605
|
-
:param raise_error: (bool) A flag that all this method raise error to
|
606
|
-
the strategy execution. Default is `True`.
|
607
|
+
:param parent_run_id: (str) A parent running ID.
|
608
|
+
:param event: (Event) An Event manager instance that use to cancel this
|
609
|
+
execution if it forces stopped by parent execution.
|
607
610
|
|
608
|
-
:raise NotImplementedError: If the `runs-on` value does not implement
|
611
|
+
:raise NotImplementedError: If the `runs-on` value does not implement on
|
612
|
+
this execution.
|
609
613
|
|
610
614
|
:rtype: Result
|
611
615
|
"""
|
612
616
|
result: Result = Result.construct_with_rs_or_id(
|
613
|
-
result,
|
614
617
|
run_id=run_id,
|
615
618
|
parent_run_id=parent_run_id,
|
616
619
|
id_logic=(self.id or "not-set"),
|
@@ -627,7 +630,6 @@ class Job(BaseModel):
|
|
627
630
|
run_id=run_id,
|
628
631
|
parent_run_id=parent_run_id,
|
629
632
|
event=event,
|
630
|
-
raise_error=raise_error,
|
631
633
|
)
|
632
634
|
elif self.runs_on.type == RunsOn.SELF_HOSTED: # pragma: no cov
|
633
635
|
pass
|
@@ -638,15 +640,14 @@ class Job(BaseModel):
|
|
638
640
|
run_id=run_id,
|
639
641
|
parent_run_id=parent_run_id,
|
640
642
|
event=event,
|
641
|
-
raise_error=raise_error,
|
642
643
|
)
|
643
644
|
|
644
645
|
# pragma: no cov
|
645
646
|
result.trace.error(
|
646
|
-
f"[JOB]:
|
647
|
+
f"[JOB]: Execute not support runs-on: {self.runs_on.type!r} yet."
|
647
648
|
)
|
648
649
|
raise NotImplementedError(
|
649
|
-
f"
|
650
|
+
f"Execute runs-on type: {self.runs_on.type} does not support yet."
|
650
651
|
)
|
651
652
|
|
652
653
|
|
@@ -657,7 +658,6 @@ def local_execute_strategy(
|
|
657
658
|
*,
|
658
659
|
result: Result | None = None,
|
659
660
|
event: Event | None = None,
|
660
|
-
raise_error: bool = True,
|
661
661
|
) -> Result:
|
662
662
|
"""Local job strategy execution with passing dynamic parameters from the
|
663
663
|
workflow execution to strategy matrix.
|
@@ -671,33 +671,33 @@ def local_execute_strategy(
|
|
671
671
|
For each stage that execution with this strategy metrix, it will use the
|
672
672
|
`set_outputs` method for reconstruct result context data.
|
673
673
|
|
674
|
-
:raise JobException: If it has any error from `StageException` or
|
675
|
-
`UtilException`.
|
676
|
-
|
677
674
|
:param job: (Job) A job model that want to execute.
|
678
|
-
:param strategy: A strategy metrix value
|
679
|
-
|
680
|
-
:param params: A
|
681
|
-
:param result: (Result) A
|
682
|
-
|
683
|
-
|
684
|
-
|
675
|
+
:param strategy: (DictData) A strategy metrix value. This value will pass
|
676
|
+
to the `matrix` key for templating in context data.
|
677
|
+
:param params: (DictData) A parameter data.
|
678
|
+
:param result: (Result) A Result instance for return context and status.
|
679
|
+
:param event: (Event) An Event manager instance that use to cancel this
|
680
|
+
execution if it forces stopped by parent execution.
|
681
|
+
|
682
|
+
:raise JobException: If stage execution raise any error as `StageException`
|
683
|
+
or `UtilException`.
|
685
684
|
|
686
685
|
:rtype: Result
|
687
686
|
"""
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
context: DictData = copy.deepcopy(params)
|
693
|
-
context.update({"matrix": strategy, "stages": {}})
|
694
|
-
|
687
|
+
result: Result = result or Result(
|
688
|
+
run_id=gen_id(job.id or "not-set", unique=True),
|
689
|
+
extras=job.extras,
|
690
|
+
)
|
695
691
|
if strategy:
|
696
|
-
|
692
|
+
strategy_id: str = gen_id(strategy)
|
693
|
+
result.trace.info(f"[JOB]: Start Strategy: {strategy_id!r}")
|
697
694
|
result.trace.info(f"[JOB]: ... matrix: {strategy!r}")
|
698
695
|
else:
|
699
|
-
|
696
|
+
strategy_id: str = "EMPTY"
|
697
|
+
result.trace.info("[JOB]: Start Strategy: 'EMPTY'")
|
700
698
|
|
699
|
+
context: DictData = copy.deepcopy(params)
|
700
|
+
context.update({"matrix": strategy, "stages": {}})
|
701
701
|
for stage in job.stages:
|
702
702
|
|
703
703
|
if job.extras:
|
@@ -711,7 +711,7 @@ def local_execute_strategy(
|
|
711
711
|
if event and event.is_set():
|
712
712
|
error_msg: str = (
|
713
713
|
"Job strategy was canceled from event that had set before "
|
714
|
-
"strategy execution."
|
714
|
+
"job strategy execution."
|
715
715
|
)
|
716
716
|
return result.catch(
|
717
717
|
status=CANCEL,
|
@@ -735,12 +735,7 @@ def local_execute_strategy(
|
|
735
735
|
stage.set_outputs(rs.context, to=context)
|
736
736
|
except (StageException, UtilException) as e:
|
737
737
|
result.trace.error(f"[JOB]: {e.__class__.__name__}: {e}")
|
738
|
-
|
739
|
-
raise JobException(
|
740
|
-
f"Stage execution error: {e.__class__.__name__}: {e}"
|
741
|
-
) from None
|
742
|
-
|
743
|
-
return result.catch(
|
738
|
+
result.catch(
|
744
739
|
status=FAILED,
|
745
740
|
context={
|
746
741
|
strategy_id: {
|
@@ -750,13 +745,17 @@ def local_execute_strategy(
|
|
750
745
|
},
|
751
746
|
},
|
752
747
|
)
|
748
|
+
raise JobException(
|
749
|
+
f"Stage raise: {e.__class__.__name__}: {e}"
|
750
|
+
) from e
|
753
751
|
|
754
752
|
if rs.status == FAILED:
|
755
753
|
error_msg: str = (
|
756
|
-
f"
|
757
|
-
f"
|
754
|
+
f"Strategy break because stage, {stage.iden!r}, return FAILED "
|
755
|
+
f"status."
|
758
756
|
)
|
759
|
-
|
757
|
+
result.trace.warning(f"[JOB]: {error_msg}")
|
758
|
+
result.catch(
|
760
759
|
status=FAILED,
|
761
760
|
context={
|
762
761
|
strategy_id: {
|
@@ -766,6 +765,7 @@ def local_execute_strategy(
|
|
766
765
|
},
|
767
766
|
},
|
768
767
|
)
|
768
|
+
raise JobException(error_msg)
|
769
769
|
|
770
770
|
return result.catch(
|
771
771
|
status=SUCCESS,
|
@@ -785,22 +785,20 @@ def local_execute(
|
|
785
785
|
run_id: str | None = None,
|
786
786
|
parent_run_id: str | None = None,
|
787
787
|
event: Event | None = None,
|
788
|
-
raise_error: bool = True,
|
789
788
|
) -> Result:
|
790
789
|
"""Local job execution with passing dynamic parameters from the workflow
|
791
790
|
execution or itself execution. It will generate matrix values at the first
|
792
791
|
step and run multithread on this metrics to the `stages` field of this job.
|
793
792
|
|
794
|
-
This method does not raise any JobException if it runs with
|
793
|
+
This method does not raise any `JobException` if it runs with
|
795
794
|
multi-threading strategy.
|
796
795
|
|
797
|
-
:param job: (Job) A job model
|
798
|
-
:param params: (DictData)
|
799
|
-
:param run_id: (str) A job running ID
|
800
|
-
:param parent_run_id: (str) A parent workflow running ID
|
801
|
-
:param event: (Event) An
|
802
|
-
|
803
|
-
strategy execution. Default is `True`.
|
796
|
+
:param job: (Job) A job model.
|
797
|
+
:param params: (DictData) A parameter data.
|
798
|
+
:param run_id: (str) A job running ID.
|
799
|
+
:param parent_run_id: (str) A parent workflow running ID.
|
800
|
+
:param event: (Event) An Event manager instance that use to cancel this
|
801
|
+
execution if it forces stopped by parent execution.
|
804
802
|
|
805
803
|
:rtype: Result
|
806
804
|
"""
|
@@ -812,40 +810,12 @@ def local_execute(
|
|
812
810
|
)
|
813
811
|
|
814
812
|
event: Event = Event() if event is None else event
|
815
|
-
|
816
|
-
# NOTE: Normal Job execution without parallel strategy matrix. It uses
|
817
|
-
# for-loop to control strategy execution sequentially.
|
818
|
-
if (not job.strategy.is_set()) or job.strategy.max_parallel == 1:
|
819
|
-
|
820
|
-
for strategy in job.strategy.make():
|
821
|
-
|
822
|
-
if event and event.is_set(): # pragma: no cov
|
823
|
-
return result.catch(
|
824
|
-
status=CANCEL,
|
825
|
-
context={
|
826
|
-
"errors": JobException(
|
827
|
-
"Job strategy was canceled from event that had set "
|
828
|
-
"before strategy execution."
|
829
|
-
).to_dict()
|
830
|
-
},
|
831
|
-
)
|
832
|
-
|
833
|
-
local_execute_strategy(
|
834
|
-
job,
|
835
|
-
strategy,
|
836
|
-
params,
|
837
|
-
result=result,
|
838
|
-
event=event,
|
839
|
-
raise_error=raise_error,
|
840
|
-
)
|
841
|
-
|
842
|
-
return result
|
843
|
-
|
844
813
|
fail_fast_flag: bool = job.strategy.fail_fast
|
845
814
|
ls: str = "Fail-Fast" if fail_fast_flag else "All-Completed"
|
815
|
+
workers: int = job.strategy.max_parallel
|
846
816
|
result.trace.info(
|
847
|
-
f"[JOB]:
|
848
|
-
f"
|
817
|
+
f"[JOB]: {ls}-Execute: {job.id} with {workers} "
|
818
|
+
f"worker{'s' if workers > 1 else ''}."
|
849
819
|
)
|
850
820
|
|
851
821
|
if event and event.is_set(): # pragma: no cov
|
@@ -853,15 +823,14 @@ def local_execute(
|
|
853
823
|
status=CANCEL,
|
854
824
|
context={
|
855
825
|
"errors": JobException(
|
856
|
-
"Job
|
857
|
-
"
|
826
|
+
"Job was canceled from event that had set before "
|
827
|
+
"local job execution."
|
858
828
|
).to_dict()
|
859
829
|
},
|
860
830
|
)
|
861
831
|
|
862
832
|
with ThreadPoolExecutor(
|
863
|
-
max_workers=
|
864
|
-
thread_name_prefix="job_strategy_exec_",
|
833
|
+
max_workers=workers, thread_name_prefix="job_strategy_exec_"
|
865
834
|
) as executor:
|
866
835
|
|
867
836
|
futures: list[Future] = [
|
@@ -872,7 +841,6 @@ def local_execute(
|
|
872
841
|
params=params,
|
873
842
|
result=result,
|
874
843
|
event=event,
|
875
|
-
raise_error=raise_error,
|
876
844
|
)
|
877
845
|
for strategy in job.strategy.make()
|
878
846
|
]
|
@@ -881,12 +849,9 @@ def local_execute(
|
|
881
849
|
status: Status = SUCCESS
|
882
850
|
|
883
851
|
if not fail_fast_flag:
|
884
|
-
done = as_completed(futures
|
852
|
+
done: list[Future] = as_completed(futures)
|
885
853
|
else:
|
886
|
-
done, not_done = wait(
|
887
|
-
futures, timeout=1800, return_when=FIRST_EXCEPTION
|
888
|
-
)
|
889
|
-
|
854
|
+
done, not_done = wait(futures, return_when=FIRST_EXCEPTION)
|
890
855
|
if len(done) != len(futures):
|
891
856
|
result.trace.warning(
|
892
857
|
"[JOB]: Set event for stop pending stage future."
|
@@ -895,10 +860,8 @@ def local_execute(
|
|
895
860
|
for future in not_done:
|
896
861
|
future.cancel()
|
897
862
|
|
898
|
-
nd: str =
|
899
|
-
|
900
|
-
)
|
901
|
-
result.trace.debug(f"[JOB]: Strategy set Fail-Fast{nd}")
|
863
|
+
nd: str = f", strategies not run: {not_done}" if not_done else ""
|
864
|
+
result.trace.debug(f"... Strategy set Fail-Fast{nd}")
|
902
865
|
|
903
866
|
for future in done:
|
904
867
|
try:
|
@@ -906,10 +869,12 @@ def local_execute(
|
|
906
869
|
except JobException as e:
|
907
870
|
status = FAILED
|
908
871
|
result.trace.error(
|
909
|
-
f"[JOB]: {ls}
|
872
|
+
f"[JOB]: {ls}: {e.__class__.__name__}:{NEWLINE}{e}"
|
910
873
|
)
|
911
|
-
|
912
|
-
|
874
|
+
if "errors" in context:
|
875
|
+
context["errors"].append(e.to_dict())
|
876
|
+
else:
|
877
|
+
context["errors"] = [e.to_dict()]
|
913
878
|
return result.catch(status=status, context=context)
|
914
879
|
|
915
880
|
|
@@ -920,19 +885,17 @@ def self_hosted_execute(
|
|
920
885
|
run_id: str | None = None,
|
921
886
|
parent_run_id: str | None = None,
|
922
887
|
event: Event | None = None,
|
923
|
-
raise_error: bool = True,
|
924
888
|
) -> Result: # pragma: no cov
|
925
889
|
"""Self-Hosted job execution with passing dynamic parameters from the
|
926
890
|
workflow execution or itself execution. It will make request to the
|
927
891
|
self-hosted host url.
|
928
892
|
|
929
893
|
:param job: (Job) A job model that want to execute.
|
930
|
-
:param params: (DictData)
|
931
|
-
:param run_id: (str) A job running ID
|
932
|
-
:param parent_run_id: (str) A parent workflow running ID
|
933
|
-
:param event: (Event) An
|
934
|
-
|
935
|
-
strategy execution.
|
894
|
+
:param params: (DictData) A parameter data.
|
895
|
+
:param run_id: (str) A job running ID.
|
896
|
+
:param parent_run_id: (str) A parent workflow running ID.
|
897
|
+
:param event: (Event) An Event manager instance that use to cancel this
|
898
|
+
execution if it forces stopped by parent execution.
|
936
899
|
|
937
900
|
:rtype: Result
|
938
901
|
"""
|
@@ -948,8 +911,8 @@ def self_hosted_execute(
|
|
948
911
|
status=CANCEL,
|
949
912
|
context={
|
950
913
|
"errors": JobException(
|
951
|
-
"Job
|
952
|
-
"
|
914
|
+
"Job was canceled from event that had set before start "
|
915
|
+
"self-hosted execution."
|
953
916
|
).to_dict()
|
954
917
|
},
|
955
918
|
)
|
@@ -964,20 +927,17 @@ def self_hosted_execute(
|
|
964
927
|
"job": job.model_dump(),
|
965
928
|
"params": params,
|
966
929
|
"result": result.__dict__,
|
967
|
-
"raise_error": raise_error,
|
968
930
|
},
|
969
931
|
)
|
970
932
|
except requests.exceptions.RequestException as e:
|
971
933
|
return result.catch(status=FAILED, context={"errors": to_dict(e)})
|
972
934
|
|
973
935
|
if resp.status_code != 200:
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
)
|
936
|
+
raise JobException(
|
937
|
+
f"Job execution error from request to self-hosted: "
|
938
|
+
f"{job.runs_on.args.host!r}"
|
939
|
+
)
|
979
940
|
|
980
|
-
return result.catch(status=FAILED)
|
981
941
|
return result.catch(status=SUCCESS)
|
982
942
|
|
983
943
|
|
@@ -988,7 +948,6 @@ def azure_batch_execute(
|
|
988
948
|
run_id: str | None = None,
|
989
949
|
parent_run_id: str | None = None,
|
990
950
|
event: Event | None = None,
|
991
|
-
raise_error: bool | None = None,
|
992
951
|
) -> Result: # pragma no cov
|
993
952
|
"""Azure Batch job execution that will run all job's stages on the Azure
|
994
953
|
Batch Node and extract the result file to be returning context result.
|
@@ -1013,7 +972,6 @@ def azure_batch_execute(
|
|
1013
972
|
:param run_id:
|
1014
973
|
:param parent_run_id:
|
1015
974
|
:param event:
|
1016
|
-
:param raise_error:
|
1017
975
|
|
1018
976
|
:rtype: Result
|
1019
977
|
"""
|
@@ -1028,13 +986,12 @@ def azure_batch_execute(
|
|
1028
986
|
status=CANCEL,
|
1029
987
|
context={
|
1030
988
|
"errors": JobException(
|
1031
|
-
"Job
|
1032
|
-
"
|
989
|
+
"Job was canceled from event that had set before start "
|
990
|
+
"azure-batch execution."
|
1033
991
|
).to_dict()
|
1034
992
|
},
|
1035
993
|
)
|
1036
994
|
print(params)
|
1037
|
-
print(raise_error)
|
1038
995
|
return result.catch(status=SUCCESS)
|
1039
996
|
|
1040
997
|
|
@@ -1045,7 +1002,6 @@ def docker_execution(
|
|
1045
1002
|
run_id: str | None = None,
|
1046
1003
|
parent_run_id: str | None = None,
|
1047
1004
|
event: Event | None = None,
|
1048
|
-
raise_error: bool | None = None,
|
1049
1005
|
):
|
1050
1006
|
"""Docker job execution.
|
1051
1007
|
|
@@ -1071,5 +1027,4 @@ def docker_execution(
|
|
1071
1027
|
},
|
1072
1028
|
)
|
1073
1029
|
print(params)
|
1074
|
-
print(raise_error)
|
1075
1030
|
return result.catch(status=SUCCESS)
|
ddeutil/workflow/result.py
CHANGED
ddeutil/workflow/scheduler.py
CHANGED
@@ -535,9 +535,7 @@ def schedule_task(
|
|
535
535
|
current_release: datetime = current_date.replace(
|
536
536
|
second=0, microsecond=0
|
537
537
|
)
|
538
|
-
if (
|
539
|
-
first_date := q.first_queue.date
|
540
|
-
) > current_release: # pragma: no cov
|
538
|
+
if (first_date := q.queue[0].date) > current_release: # pragma: no cov
|
541
539
|
result.trace.debug(
|
542
540
|
f"[WORKFLOW]: Skip schedule "
|
543
541
|
f"{first_date:%Y-%m-%d %H:%M:%S} for : {task.alias!r}"
|