ddeutil-workflow 0.0.49__py3-none-any.whl → 0.0.51__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 +8 -26
- ddeutil/workflow/conf.py +11 -11
- ddeutil/workflow/cron.py +46 -20
- ddeutil/workflow/exceptions.py +3 -3
- ddeutil/workflow/job.py +269 -145
- ddeutil/workflow/logs.py +23 -19
- ddeutil/workflow/params.py +56 -16
- ddeutil/workflow/result.py +12 -4
- ddeutil/workflow/reusables.py +4 -2
- ddeutil/workflow/scheduler.py +5 -1
- ddeutil/workflow/stages.py +580 -217
- ddeutil/workflow/utils.py +42 -38
- ddeutil/workflow/workflow.py +92 -95
- {ddeutil_workflow-0.0.49.dist-info → ddeutil_workflow-0.0.51.dist-info}/METADATA +71 -14
- ddeutil_workflow-0.0.51.dist-info/RECORD +31 -0
- ddeutil_workflow-0.0.49.dist-info/RECORD +0 -31
- {ddeutil_workflow-0.0.49.dist-info → ddeutil_workflow-0.0.51.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.49.dist-info → ddeutil_workflow-0.0.51.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.49.dist-info → ddeutil_workflow-0.0.51.dist-info}/top_level.txt +0 -0
ddeutil/workflow/utils.py
CHANGED
@@ -26,9 +26,11 @@ T = TypeVar("T")
|
|
26
26
|
UTC: Final[ZoneInfo] = ZoneInfo("UTC")
|
27
27
|
|
28
28
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
29
|
+
def replace_sec(dt: datetime) -> datetime:
|
30
|
+
return dt.replace(second=0, microsecond=0)
|
31
|
+
|
32
|
+
|
33
|
+
def get_dt_now(tz: ZoneInfo | None = None, offset: float = 0.0) -> datetime:
|
32
34
|
"""Return the current datetime object.
|
33
35
|
|
34
36
|
:param tz: A ZoneInfo object for replace timezone of return datetime object.
|
@@ -54,42 +56,31 @@ def get_d_now(
|
|
54
56
|
return (datetime.now(tz=(tz or UTC)) - timedelta(seconds=offset)).date()
|
55
57
|
|
56
58
|
|
57
|
-
def get_diff_sec(
|
58
|
-
dt: datetime, tz: ZoneInfo | None = None, offset: float = 0.0
|
59
|
-
) -> int: # pragma: no cov
|
59
|
+
def get_diff_sec(dt: datetime, offset: float = 0.0) -> int:
|
60
60
|
"""Return second value that come from diff of an input datetime and the
|
61
61
|
current datetime with specific timezone.
|
62
62
|
|
63
|
-
:param dt:
|
64
|
-
:param
|
65
|
-
:param offset: An offset second value.
|
63
|
+
:param dt: (datetime) A datetime object that want to get different second value.
|
64
|
+
:param offset: (float) An offset second value.
|
66
65
|
|
67
66
|
:rtype: int
|
68
67
|
"""
|
69
68
|
return round(
|
70
69
|
(
|
71
|
-
dt - datetime.now(tz=
|
70
|
+
dt - datetime.now(tz=dt.tzinfo) - timedelta(seconds=offset)
|
72
71
|
).total_seconds()
|
73
72
|
)
|
74
73
|
|
75
74
|
|
76
|
-
def reach_next_minute(
|
77
|
-
dt: datetime, tz: ZoneInfo | None = None, offset: float = 0.0
|
78
|
-
) -> bool:
|
75
|
+
def reach_next_minute(dt: datetime, offset: float = 0.0) -> bool:
|
79
76
|
"""Check this datetime object is not in range of minute level on the current
|
80
77
|
datetime.
|
81
78
|
|
82
|
-
:param dt:
|
83
|
-
:param
|
84
|
-
:param offset: An offset second value.
|
79
|
+
:param dt: (datetime) A datetime object that want to check.
|
80
|
+
:param offset: (float) An offset second value.
|
85
81
|
"""
|
86
82
|
diff: float = (
|
87
|
-
dt
|
88
|
-
- (
|
89
|
-
get_dt_now(tz=(tz or UTC), offset=offset).replace(
|
90
|
-
second=0, microsecond=0
|
91
|
-
)
|
92
|
-
)
|
83
|
+
replace_sec(dt) - replace_sec(get_dt_now(tz=dt.tzinfo, offset=offset))
|
93
84
|
).total_seconds()
|
94
85
|
if diff >= 60:
|
95
86
|
return True
|
@@ -106,7 +97,7 @@ def wait_to_next_minute(
|
|
106
97
|
dt: datetime, second: float = 0
|
107
98
|
) -> None: # pragma: no cov
|
108
99
|
"""Wait with sleep to the next minute with an offset second value."""
|
109
|
-
future = dt
|
100
|
+
future: datetime = replace_sec(dt) + timedelta(minutes=1)
|
110
101
|
time.sleep((future - dt).total_seconds() + second)
|
111
102
|
|
112
103
|
|
@@ -114,7 +105,7 @@ def delay(second: float = 0) -> None: # pragma: no cov
|
|
114
105
|
"""Delay time that use time.sleep with random second value between
|
115
106
|
0.00 - 0.99 seconds.
|
116
107
|
|
117
|
-
:param second: A second number that want to adds-on random value.
|
108
|
+
:param second: (float) A second number that want to adds-on random value.
|
118
109
|
"""
|
119
110
|
time.sleep(second + randrange(0, 99, step=10) / 100)
|
120
111
|
|
@@ -124,32 +115,42 @@ def gen_id(
|
|
124
115
|
*,
|
125
116
|
sensitive: bool = True,
|
126
117
|
unique: bool = False,
|
118
|
+
simple_mode: bool | None = None,
|
119
|
+
extras: DictData | None = None,
|
127
120
|
) -> str:
|
128
|
-
"""Generate running ID for able to tracking. This generates process use
|
129
|
-
algorithm function if
|
130
|
-
false. But it will cut this hashing value length to 10 it the setting
|
131
|
-
set to true.
|
121
|
+
"""Generate running ID for able to tracking. This generates process use
|
122
|
+
`md5` algorithm function if `WORKFLOW_CORE_WORKFLOW_ID_SIMPLE_MODE` set
|
123
|
+
to false. But it will cut this hashing value length to 10 it the setting
|
124
|
+
value set to true.
|
125
|
+
|
126
|
+
Simple Mode:
|
127
|
+
|
128
|
+
... 0000 00 00 00 00 00 000000 T 0000000000
|
129
|
+
... year month day hour minute second microsecond sep simple-id
|
132
130
|
|
133
131
|
:param value: A value that want to add to prefix before hashing with md5.
|
134
132
|
:param sensitive: A flag that convert the value to lower case before hashing
|
135
133
|
:param unique: A flag that add timestamp at microsecond level to value
|
136
134
|
before hashing.
|
135
|
+
:param simple_mode: A flag for generate ID by simple mode.
|
136
|
+
:param extras: An extra parameter that use for override config value.
|
137
137
|
|
138
138
|
:rtype: str
|
139
139
|
"""
|
140
|
-
from .conf import
|
140
|
+
from .conf import dynamic
|
141
141
|
|
142
142
|
if not isinstance(value, str):
|
143
143
|
value: str = str(value)
|
144
144
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
145
|
+
dt: datetime = datetime.now(tz=dynamic("tz", extras=extras))
|
146
|
+
if dynamic("generate_id_simple_mode", f=simple_mode, extras=extras):
|
147
|
+
return (f"{dt:%Y%m%d%H%M%S%f}T" if unique else "") + hash_str(
|
148
|
+
f"{(value if sensitive else value.lower())}", n=10
|
149
|
+
)
|
149
150
|
|
150
151
|
return md5(
|
151
152
|
(
|
152
|
-
(f"{
|
153
|
+
(f"{dt}T" if unique else "")
|
153
154
|
+ f"{(value if sensitive else value.lower())}"
|
154
155
|
).encode()
|
155
156
|
).hexdigest()
|
@@ -243,12 +244,15 @@ def cut_id(run_id: str, *, num: int = 6) -> str:
|
|
243
244
|
"""Cutting running ID with length.
|
244
245
|
|
245
246
|
Example:
|
246
|
-
>>> cut_id(run_id='
|
247
|
-
'
|
247
|
+
>>> cut_id(run_id='20240101081330000000T1354680202')
|
248
|
+
'202401010813680202'
|
248
249
|
|
249
|
-
:param run_id:
|
250
|
+
:param run_id: A running ID That want to cut
|
250
251
|
:param num:
|
251
252
|
|
252
253
|
:rtype: str
|
253
254
|
"""
|
254
|
-
|
255
|
+
if "T" in run_id:
|
256
|
+
dt, simple = run_id.split("T", maxsplit=1)
|
257
|
+
return dt[:12] + simple[-num:]
|
258
|
+
return run_id[:12] + run_id[-num:]
|
ddeutil/workflow/workflow.py
CHANGED
@@ -6,6 +6,9 @@
|
|
6
6
|
# [x] Use dynamic config
|
7
7
|
"""A Workflow module that is the core module of this package. It keeps Release
|
8
8
|
and Workflow Pydantic models.
|
9
|
+
|
10
|
+
I will implement timeout on the workflow execution layer only because the
|
11
|
+
main propose of this package in Workflow model.
|
9
12
|
"""
|
10
13
|
from __future__ import annotations
|
11
14
|
|
@@ -36,7 +39,7 @@ from .__cron import CronJob, CronRunner
|
|
36
39
|
from .__types import DictData, TupleStr
|
37
40
|
from .conf import Loader, SimLoad, dynamic
|
38
41
|
from .cron import On
|
39
|
-
from .exceptions import JobException, WorkflowException
|
42
|
+
from .exceptions import JobException, UtilException, WorkflowException
|
40
43
|
from .job import Job
|
41
44
|
from .logs import Audit, get_audit
|
42
45
|
from .params import Param
|
@@ -636,20 +639,20 @@ class Workflow(BaseModel):
|
|
636
639
|
run_id: str | None = None,
|
637
640
|
parent_run_id: str | None = None,
|
638
641
|
audit: type[Audit] = None,
|
639
|
-
queue: ReleaseQueue
|
642
|
+
queue: Optional[ReleaseQueue] = None,
|
640
643
|
override_log_name: str | None = None,
|
641
|
-
result: Result
|
644
|
+
result: Optional[Result] = None,
|
645
|
+
timeout: int = 600,
|
642
646
|
) -> Result:
|
643
647
|
"""Release the workflow execution with overriding parameter with the
|
644
648
|
release templating that include logical date (release date), execution
|
645
649
|
date, or running id to the params.
|
646
650
|
|
647
651
|
This method allow workflow use audit object to save the execution
|
648
|
-
result to audit destination like file audit to the local
|
649
|
-
directory.
|
652
|
+
result to audit destination like file audit to the local `./logs` path.
|
650
653
|
|
651
654
|
Steps:
|
652
|
-
- Initialize
|
655
|
+
- Initialize Release and validate ReleaseQueue.
|
653
656
|
- Create release data for pass to parameter templating function.
|
654
657
|
- Execute this workflow with mapping release data to its parameters.
|
655
658
|
- Writing result audit
|
@@ -658,15 +661,15 @@ class Workflow(BaseModel):
|
|
658
661
|
|
659
662
|
:param release: A release datetime or Release object.
|
660
663
|
:param params: A workflow parameter that pass to execute method.
|
661
|
-
:param
|
662
|
-
:param
|
663
|
-
:param parent_run_id: A parent workflow running ID for this release.
|
664
|
+
:param run_id: (str) A workflow running ID.
|
665
|
+
:param parent_run_id: (str) A parent workflow running ID.
|
664
666
|
:param audit: An audit class that want to save the execution result.
|
665
|
-
:param queue: A ReleaseQueue object.
|
666
|
-
:param override_log_name: An override logging name that use
|
667
|
-
the workflow name.
|
667
|
+
:param queue: (ReleaseQueue) A ReleaseQueue object.
|
668
|
+
:param override_log_name: (str) An override logging name that use
|
669
|
+
instead the workflow name.
|
668
670
|
:param result: (Result) A result object for keeping context and status
|
669
671
|
data.
|
672
|
+
:param timeout: (int) A workflow execution time out in second unit.
|
670
673
|
|
671
674
|
:raise TypeError: If a queue parameter does not match with ReleaseQueue
|
672
675
|
type.
|
@@ -683,7 +686,8 @@ class Workflow(BaseModel):
|
|
683
686
|
extras=self.extras,
|
684
687
|
)
|
685
688
|
|
686
|
-
|
689
|
+
# VALIDATE: check type of queue that valid with ReleaseQueue.
|
690
|
+
if queue and not isinstance(queue, ReleaseQueue):
|
687
691
|
raise TypeError(
|
688
692
|
"The queue argument should be ReleaseQueue object only."
|
689
693
|
)
|
@@ -693,36 +697,29 @@ class Workflow(BaseModel):
|
|
693
697
|
release: Release = Release.from_dt(release, extras=self.extras)
|
694
698
|
|
695
699
|
result.trace.debug(
|
696
|
-
f"[RELEASE]: Start
|
697
|
-
f"{release.date:%Y-%m-%d %H:%M:%S}"
|
700
|
+
f"[RELEASE]: Start {name!r} : {release.date:%Y-%m-%d %H:%M:%S}"
|
698
701
|
)
|
699
|
-
|
700
|
-
# NOTE: Release parameters that use to templating on the schedule
|
701
|
-
# config data.
|
702
|
-
release_params: DictData = {
|
703
|
-
"release": {
|
704
|
-
"logical_date": release.date,
|
705
|
-
"execute_date": datetime.now(
|
706
|
-
tz=dynamic("tz", extras=self.extras)
|
707
|
-
),
|
708
|
-
"run_id": result.run_id,
|
709
|
-
"timezone": dynamic("tz", extras=self.extras),
|
710
|
-
}
|
711
|
-
}
|
712
|
-
|
713
|
-
# NOTE: Execute workflow with templating params from release mapping.
|
714
|
-
# The result context that return from execution method is:
|
715
|
-
#
|
716
|
-
# ... {"params": ..., "jobs": ...}
|
717
|
-
#
|
718
702
|
self.execute(
|
719
|
-
params=param2template(
|
703
|
+
params=param2template(
|
704
|
+
params,
|
705
|
+
params={
|
706
|
+
"release": {
|
707
|
+
"logical_date": release.date,
|
708
|
+
"execute_date": datetime.now(
|
709
|
+
tz=dynamic("tz", extras=self.extras)
|
710
|
+
),
|
711
|
+
"run_id": result.run_id,
|
712
|
+
"timezone": dynamic("tz", extras=self.extras),
|
713
|
+
}
|
714
|
+
},
|
715
|
+
extras=self.extras,
|
716
|
+
),
|
720
717
|
result=result,
|
721
718
|
parent_run_id=result.parent_run_id,
|
719
|
+
timeout=timeout,
|
722
720
|
)
|
723
721
|
result.trace.debug(
|
724
|
-
f"[RELEASE]: End
|
725
|
-
f"{release.date:%Y-%m-%d %H:%M:%S}"
|
722
|
+
f"[RELEASE]: End {name!r} : {release.date:%Y-%m-%d %H:%M:%S}"
|
726
723
|
)
|
727
724
|
|
728
725
|
# NOTE: Saving execution result to destination of the input audit
|
@@ -741,19 +738,10 @@ class Workflow(BaseModel):
|
|
741
738
|
).save(excluded=None)
|
742
739
|
)
|
743
740
|
|
744
|
-
|
745
|
-
if queue is not None:
|
741
|
+
if queue:
|
746
742
|
queue.remove_running(release)
|
747
743
|
queue.mark_complete(release)
|
748
744
|
|
749
|
-
# NOTE: Remove the params key from the result context for deduplicate.
|
750
|
-
# This step is prepare result context for this release method.
|
751
|
-
context: DictData = result.context
|
752
|
-
jobs: DictData = context.pop("jobs", {})
|
753
|
-
errors: DictData = (
|
754
|
-
{"errors": context.pop("errors", {})} if "errors" in context else {}
|
755
|
-
)
|
756
|
-
|
757
745
|
return result.catch(
|
758
746
|
status=SUCCESS,
|
759
747
|
context={
|
@@ -763,8 +751,7 @@ class Workflow(BaseModel):
|
|
763
751
|
"logical_date": release.date,
|
764
752
|
"release": release,
|
765
753
|
},
|
766
|
-
"outputs": {"jobs": jobs},
|
767
|
-
**errors,
|
754
|
+
"outputs": {"jobs": result.context.pop("jobs", {})},
|
768
755
|
},
|
769
756
|
)
|
770
757
|
|
@@ -923,15 +910,11 @@ class Workflow(BaseModel):
|
|
923
910
|
# NOTE: Pop the latest Release object from the release queue.
|
924
911
|
release: Release = heappop(q.queue)
|
925
912
|
|
926
|
-
if reach_next_minute(
|
927
|
-
release.date,
|
928
|
-
tz=dynamic("tz", extras=self.extras),
|
929
|
-
offset=offset,
|
930
|
-
):
|
913
|
+
if reach_next_minute(release.date, offset=offset):
|
931
914
|
result.trace.debug(
|
932
|
-
f"[POKING]:
|
933
|
-
f"{release.date:%Y-%m-%d %H:%M:%S},
|
934
|
-
f"
|
915
|
+
f"[POKING]: Latest Release, "
|
916
|
+
f"{release.date:%Y-%m-%d %H:%M:%S}, can not run on "
|
917
|
+
f"this time"
|
935
918
|
)
|
936
919
|
heappush(q.queue, release)
|
937
920
|
wait_to_next_minute(
|
@@ -976,7 +959,6 @@ class Workflow(BaseModel):
|
|
976
959
|
*,
|
977
960
|
result: Result | None = None,
|
978
961
|
event: Event | None = None,
|
979
|
-
raise_error: bool = True,
|
980
962
|
) -> Result:
|
981
963
|
"""Job execution with passing dynamic parameters from the main workflow
|
982
964
|
execution to the target job object via job's ID.
|
@@ -987,7 +969,6 @@ class Workflow(BaseModel):
|
|
987
969
|
|
988
970
|
:raise WorkflowException: If execute with not exist job's ID.
|
989
971
|
:raise WorkflowException: If the job execution raise JobException.
|
990
|
-
:raise NotImplementedError: If set raise_error argument to False.
|
991
972
|
|
992
973
|
:param job_id: A job ID that want to execute.
|
993
974
|
:param params: A params that was parameterized from workflow execution.
|
@@ -995,8 +976,6 @@ class Workflow(BaseModel):
|
|
995
976
|
data.
|
996
977
|
:param event: (Event) An event manager that pass to the
|
997
978
|
PoolThreadExecutor.
|
998
|
-
:param raise_error: A flag that raise error instead catching to result
|
999
|
-
if it gets exception from job execution.
|
1000
979
|
|
1001
980
|
:rtype: Result
|
1002
981
|
:return: Return the result object that receive the job execution result
|
@@ -1012,6 +991,12 @@ class Workflow(BaseModel):
|
|
1012
991
|
f"workflow."
|
1013
992
|
)
|
1014
993
|
|
994
|
+
job: Job = self.job(name=job_id)
|
995
|
+
if job.is_skipped(params=params):
|
996
|
+
result.trace.info(f"[WORKFLOW]: Skip job: {job_id!r}")
|
997
|
+
job.set_outputs(output={"skipped": True}, to=params)
|
998
|
+
return result.catch(status=SKIP, context=params)
|
999
|
+
|
1015
1000
|
if event and event.is_set(): # pragma: no cov
|
1016
1001
|
raise WorkflowException(
|
1017
1002
|
"Workflow job was canceled from event that had set before "
|
@@ -1019,31 +1004,31 @@ class Workflow(BaseModel):
|
|
1019
1004
|
)
|
1020
1005
|
|
1021
1006
|
try:
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
run_id=result.run_id,
|
1032
|
-
parent_run_id=result.parent_run_id,
|
1033
|
-
event=event,
|
1034
|
-
).context,
|
1035
|
-
to=params,
|
1036
|
-
)
|
1037
|
-
except JobException as e:
|
1007
|
+
result.trace.info(f"[WORKFLOW]: Execute Job: {job_id!r}")
|
1008
|
+
rs: Result = job.execute(
|
1009
|
+
params=params,
|
1010
|
+
run_id=result.run_id,
|
1011
|
+
parent_run_id=result.parent_run_id,
|
1012
|
+
event=event,
|
1013
|
+
)
|
1014
|
+
job.set_outputs(rs.context, to=params)
|
1015
|
+
except (JobException, UtilException) as e:
|
1038
1016
|
result.trace.error(f"[WORKFLOW]: {e.__class__.__name__}: {e}")
|
1039
|
-
|
1040
|
-
|
1041
|
-
f"Get job execution error {job_id}: JobException: {e}"
|
1042
|
-
) from None
|
1043
|
-
raise NotImplementedError(
|
1044
|
-
"Handle error from the job execution does not support yet."
|
1017
|
+
raise WorkflowException(
|
1018
|
+
f"Get job execution error {job_id}: JobException: {e}"
|
1045
1019
|
) from None
|
1046
1020
|
|
1021
|
+
if rs.status == FAILED:
|
1022
|
+
error_msg: str = (
|
1023
|
+
f"Workflow job, {job.id}, failed without raise error."
|
1024
|
+
)
|
1025
|
+
return result.catch(
|
1026
|
+
status=FAILED,
|
1027
|
+
context={
|
1028
|
+
"errors": WorkflowException(error_msg).to_dict(),
|
1029
|
+
**params,
|
1030
|
+
},
|
1031
|
+
)
|
1047
1032
|
return result.catch(status=SUCCESS, context=params)
|
1048
1033
|
|
1049
1034
|
def execute(
|
@@ -1095,7 +1080,7 @@ class Workflow(BaseModel):
|
|
1095
1080
|
extras=self.extras,
|
1096
1081
|
)
|
1097
1082
|
|
1098
|
-
result.trace.info(f"[WORKFLOW]:
|
1083
|
+
result.trace.info(f"[WORKFLOW]: Execute: {self.name!r} ...")
|
1099
1084
|
if not self.jobs:
|
1100
1085
|
result.trace.warning(
|
1101
1086
|
f"[WORKFLOW]: {self.name!r} does not have any jobs"
|
@@ -1140,7 +1125,7 @@ class Workflow(BaseModel):
|
|
1140
1125
|
timeout=timeout,
|
1141
1126
|
event=event,
|
1142
1127
|
)
|
1143
|
-
except WorkflowException as e:
|
1128
|
+
except (WorkflowException, JobException) as e:
|
1144
1129
|
status: Status = FAILED
|
1145
1130
|
context.update({"errors": e.to_dict()})
|
1146
1131
|
|
@@ -1179,7 +1164,7 @@ class Workflow(BaseModel):
|
|
1179
1164
|
"max_job_exec_timeout", f=timeout, extras=self.extras
|
1180
1165
|
)
|
1181
1166
|
event: Event = event or Event()
|
1182
|
-
result.trace.debug(f"
|
1167
|
+
result.trace.debug(f"... Run {self.name!r} with threading.")
|
1183
1168
|
with ThreadPoolExecutor(
|
1184
1169
|
max_workers=dynamic("max_job_parallel", extras=self.extras),
|
1185
1170
|
thread_name_prefix="wf_exec_threading_",
|
@@ -1204,7 +1189,7 @@ class Workflow(BaseModel):
|
|
1204
1189
|
)
|
1205
1190
|
elif check == SKIP: # pragma: no cov
|
1206
1191
|
result.trace.info(f"[JOB]: Skip job: {job_id!r}")
|
1207
|
-
job.set_outputs({"
|
1192
|
+
job.set_outputs(output={"skipped": True}, to=context)
|
1208
1193
|
job_queue.task_done()
|
1209
1194
|
continue
|
1210
1195
|
|
@@ -1271,12 +1256,12 @@ class Workflow(BaseModel):
|
|
1271
1256
|
"max_job_exec_timeout", f=timeout, extras=self.extras
|
1272
1257
|
)
|
1273
1258
|
event: Event = event or Event()
|
1274
|
-
result.trace.debug(f"
|
1259
|
+
result.trace.debug(f"... Run {self.name!r} with non-threading.")
|
1275
1260
|
with ThreadPoolExecutor(
|
1276
1261
|
max_workers=1,
|
1277
1262
|
thread_name_prefix="wf_exec_non_threading_",
|
1278
1263
|
) as executor:
|
1279
|
-
future: Future
|
1264
|
+
future: Optional[Future] = None
|
1280
1265
|
|
1281
1266
|
while not job_queue.empty() and (
|
1282
1267
|
not_timeout_flag := ((time.monotonic() - ts) < timeout)
|
@@ -1296,7 +1281,7 @@ class Workflow(BaseModel):
|
|
1296
1281
|
)
|
1297
1282
|
elif check == SKIP: # pragma: no cov
|
1298
1283
|
result.trace.info(f"[JOB]: Skip job: {job_id!r}")
|
1299
|
-
job.set_outputs({"
|
1284
|
+
job.set_outputs(output={"skipped": True}, to=context)
|
1300
1285
|
job_queue.task_done()
|
1301
1286
|
continue
|
1302
1287
|
|
@@ -1309,27 +1294,33 @@ class Workflow(BaseModel):
|
|
1309
1294
|
event=event,
|
1310
1295
|
)
|
1311
1296
|
time.sleep(0.025)
|
1312
|
-
elif future.done():
|
1297
|
+
elif future.done() or future.cancelled():
|
1313
1298
|
if e := future.exception():
|
1314
1299
|
result.trace.error(f"[WORKFLOW]: {e}")
|
1315
1300
|
raise WorkflowException(str(e))
|
1316
1301
|
|
1317
1302
|
future = None
|
1318
1303
|
job_queue.put(job_id)
|
1319
|
-
elif future.running():
|
1304
|
+
elif future.running() or "state=pending" in str(future):
|
1320
1305
|
time.sleep(0.075)
|
1321
1306
|
job_queue.put(job_id)
|
1322
1307
|
else: # pragma: no cov
|
1323
1308
|
job_queue.put(job_id)
|
1324
|
-
result.trace.
|
1325
|
-
f"Execution non-threading
|
1326
|
-
f"that not running."
|
1309
|
+
result.trace.warning(
|
1310
|
+
f"... Execution non-threading not handle: {future}."
|
1327
1311
|
)
|
1328
1312
|
|
1329
1313
|
job_queue.task_done()
|
1330
1314
|
|
1331
1315
|
if not_timeout_flag:
|
1332
1316
|
job_queue.join()
|
1317
|
+
if future: # pragma: no cov
|
1318
|
+
if e := future.exception():
|
1319
|
+
result.trace.error(f"[WORKFLOW]: {e}")
|
1320
|
+
raise WorkflowException(str(e))
|
1321
|
+
|
1322
|
+
future.result()
|
1323
|
+
|
1333
1324
|
return context
|
1334
1325
|
|
1335
1326
|
result.trace.error(
|
@@ -1352,6 +1343,12 @@ class WorkflowTask:
|
|
1352
1343
|
|
1353
1344
|
This dataclass has the release method for itself that prepare necessary
|
1354
1345
|
arguments before passing to the parent release method.
|
1346
|
+
|
1347
|
+
:param alias: (str) An alias name of Workflow model.
|
1348
|
+
:param workflow: (Workflow) A Workflow model instance.
|
1349
|
+
:param runner: (CronRunner)
|
1350
|
+
:param values:
|
1351
|
+
:param extras:
|
1355
1352
|
"""
|
1356
1353
|
|
1357
1354
|
alias: str
|