ddeutil-workflow 0.0.42__py3-none-any.whl → 0.0.44__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 +5 -1
- ddeutil/workflow/exceptions.py +13 -3
- ddeutil/workflow/job.py +85 -80
- ddeutil/workflow/params.py +77 -18
- ddeutil/workflow/result.py +36 -8
- ddeutil/workflow/scheduler.py +6 -9
- ddeutil/workflow/stages.py +60 -86
- ddeutil/workflow/workflow.py +82 -104
- {ddeutil_workflow-0.0.42.dist-info → ddeutil_workflow-0.0.44.dist-info}/METADATA +29 -29
- {ddeutil_workflow-0.0.42.dist-info → ddeutil_workflow-0.0.44.dist-info}/RECORD +14 -14
- {ddeutil_workflow-0.0.42.dist-info → ddeutil_workflow-0.0.44.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.42.dist-info → ddeutil_workflow-0.0.44.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.42.dist-info → ddeutil_workflow-0.0.44.dist-info}/top_level.txt +0 -0
ddeutil/workflow/scheduler.py
CHANGED
@@ -56,14 +56,13 @@ from .conf import Loader, SimLoad, config, get_logger
|
|
56
56
|
from .cron import On
|
57
57
|
from .exceptions import ScheduleException, WorkflowException
|
58
58
|
from .logs import Audit, get_audit
|
59
|
-
from .result import
|
59
|
+
from .result import SUCCESS, Result
|
60
60
|
from .utils import batch, delay
|
61
61
|
from .workflow import Release, ReleaseQueue, Workflow, WorkflowTask
|
62
62
|
|
63
63
|
P = ParamSpec("P")
|
64
|
-
logger = get_logger("ddeutil.workflow")
|
65
64
|
|
66
|
-
|
65
|
+
logger = get_logger("ddeutil.workflow")
|
67
66
|
logging.getLogger("schedule").setLevel(logging.INFO)
|
68
67
|
|
69
68
|
|
@@ -393,7 +392,7 @@ class Schedule(BaseModel):
|
|
393
392
|
audit=audit,
|
394
393
|
)
|
395
394
|
|
396
|
-
return result.catch(status=
|
395
|
+
return result.catch(status=SUCCESS)
|
397
396
|
|
398
397
|
|
399
398
|
ResultOrCancel = Union[type[CancelJob], Result]
|
@@ -572,9 +571,7 @@ def schedule_task(
|
|
572
571
|
f"[SCHEDULE]: End schedule task that run since "
|
573
572
|
f"{current_date:%Y-%m-%d %H:%M:%S} {'=' * 30}"
|
574
573
|
)
|
575
|
-
return result.catch(
|
576
|
-
status=Status.SUCCESS, context={"task_date": current_date}
|
577
|
-
)
|
574
|
+
return result.catch(status=SUCCESS, context={"task_date": current_date})
|
578
575
|
|
579
576
|
|
580
577
|
def monitor(
|
@@ -690,7 +687,7 @@ def scheduler_pending(
|
|
690
687
|
f"[SCHEDULE]: Queue: {[list(queue[wf].queue) for wf in queue]}"
|
691
688
|
)
|
692
689
|
return result.catch(
|
693
|
-
status=
|
690
|
+
status=SUCCESS,
|
694
691
|
context={
|
695
692
|
"threads": [
|
696
693
|
{
|
@@ -759,7 +756,7 @@ def schedule_control(
|
|
759
756
|
audit=audit,
|
760
757
|
)
|
761
758
|
|
762
|
-
return result.catch(status=
|
759
|
+
return result.catch(status=SUCCESS, context={"schedules": schedules})
|
763
760
|
|
764
761
|
|
765
762
|
def schedule_runner(
|
ddeutil/workflow/stages.py
CHANGED
@@ -4,18 +4,18 @@
|
|
4
4
|
# license information.
|
5
5
|
# ------------------------------------------------------------------------------
|
6
6
|
# [x] Use dynamic config
|
7
|
-
"""Stage
|
8
|
-
The stage handle the minimize task that run in some thread
|
9
|
-
its job owner) that mean it is the lowest executor of a workflow
|
10
|
-
tracking logs.
|
7
|
+
"""Stage model. It stores all stage model that use for getting stage data template
|
8
|
+
from the Job Model. The stage handle the minimize task that run in some thread
|
9
|
+
(same thread at its job owner) that mean it is the lowest executor of a workflow
|
10
|
+
that can tracking logs.
|
11
11
|
|
12
12
|
The output of stage execution only return 0 status because I do not want to
|
13
13
|
handle stage error on this stage model. I think stage model should have a lot of
|
14
14
|
use-case, and it does not worry when I want to create a new one.
|
15
15
|
|
16
|
-
Execution --> Ok --> Result with
|
16
|
+
Execution --> Ok --> Result with SUCCESS
|
17
17
|
|
18
|
-
--> Error ┬-> Result with
|
18
|
+
--> Error ┬-> Result with FAILED (if env var was set)
|
19
19
|
╰-> Raise StageException(...)
|
20
20
|
|
21
21
|
On the context I/O that pass to a stage object at execute process. The
|
@@ -52,7 +52,7 @@ from typing_extensions import Self
|
|
52
52
|
from .__types import DictData, DictStr, TupleStr
|
53
53
|
from .conf import dynamic
|
54
54
|
from .exceptions import StageException, to_dict
|
55
|
-
from .result import Result, Status
|
55
|
+
from .result import FAILED, SUCCESS, Result, Status
|
56
56
|
from .reusables import TagFunc, extract_call, not_in_template, param2template
|
57
57
|
from .utils import (
|
58
58
|
gen_id,
|
@@ -80,6 +80,10 @@ class BaseStage(BaseModel, ABC):
|
|
80
80
|
This class is the abstraction class for any stage class.
|
81
81
|
"""
|
82
82
|
|
83
|
+
extras: DictData = Field(
|
84
|
+
default_factory=dict,
|
85
|
+
description="An extra override config values.",
|
86
|
+
)
|
83
87
|
id: Optional[str] = Field(
|
84
88
|
default=None,
|
85
89
|
description=(
|
@@ -95,10 +99,6 @@ class BaseStage(BaseModel, ABC):
|
|
95
99
|
description="A stage condition statement to allow stage executable.",
|
96
100
|
alias="if",
|
97
101
|
)
|
98
|
-
extras: DictData = Field(
|
99
|
-
default_factory=dict,
|
100
|
-
description="An extra override config values.",
|
101
|
-
)
|
102
102
|
|
103
103
|
@property
|
104
104
|
def iden(self) -> str:
|
@@ -170,12 +170,12 @@ class BaseStage(BaseModel, ABC):
|
|
170
170
|
specific environment variable,`WORKFLOW_CORE_STAGE_RAISE_ERROR`.
|
171
171
|
|
172
172
|
Execution --> Ok --> Result
|
173
|
-
|-status:
|
173
|
+
|-status: SUCCESS
|
174
174
|
╰-context:
|
175
175
|
╰-outputs: ...
|
176
176
|
|
177
177
|
--> Error --> Result (if env var was set)
|
178
|
-
|-status:
|
178
|
+
|-status: FAILED
|
179
179
|
╰-errors:
|
180
180
|
|-class: ...
|
181
181
|
|-name: ...
|
@@ -209,26 +209,25 @@ class BaseStage(BaseModel, ABC):
|
|
209
209
|
|
210
210
|
try:
|
211
211
|
rs: Result = self.execute(params, result=result, event=event)
|
212
|
-
if to is not None
|
213
|
-
|
214
|
-
|
215
|
-
except Exception as err:
|
216
|
-
result.trace.error(f"[STAGE]: {err.__class__.__name__}: {err}")
|
212
|
+
return self.set_outputs(rs.context, to=to) if to is not None else rs
|
213
|
+
except Exception as e:
|
214
|
+
result.trace.error(f"[STAGE]: {e.__class__.__name__}: {e}")
|
217
215
|
|
218
216
|
if dynamic("stage_raise_error", f=raise_error, extras=self.extras):
|
219
|
-
if isinstance(
|
217
|
+
if isinstance(e, StageException):
|
220
218
|
raise
|
221
219
|
|
222
220
|
raise StageException(
|
223
221
|
f"{self.__class__.__name__}: \n\t"
|
224
|
-
f"{
|
225
|
-
) from
|
226
|
-
|
227
|
-
errors: DictData = {"errors": to_dict(
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
222
|
+
f"{e.__class__.__name__}: {e}"
|
223
|
+
) from e
|
224
|
+
|
225
|
+
errors: DictData = {"errors": to_dict(e)}
|
226
|
+
return (
|
227
|
+
self.set_outputs(errors, to=to)
|
228
|
+
if to is not None
|
229
|
+
else result.catch(status=FAILED, context=errors)
|
230
|
+
)
|
232
231
|
|
233
232
|
def set_outputs(self, output: DictData, to: DictData) -> DictData:
|
234
233
|
"""Set an outputs from execution process to the received context. The
|
@@ -315,8 +314,8 @@ class BaseStage(BaseModel, ABC):
|
|
315
314
|
if not isinstance(rs, bool):
|
316
315
|
raise TypeError("Return type of condition does not be boolean")
|
317
316
|
return not rs
|
318
|
-
except Exception as
|
319
|
-
raise StageException(f"{
|
317
|
+
except Exception as e:
|
318
|
+
raise StageException(f"{e.__class__.__name__}: {e}") from e
|
320
319
|
|
321
320
|
|
322
321
|
class BaseAsyncStage(BaseStage):
|
@@ -328,7 +327,10 @@ class BaseAsyncStage(BaseStage):
|
|
328
327
|
*,
|
329
328
|
result: Result | None = None,
|
330
329
|
event: Event | None = None,
|
331
|
-
) -> Result:
|
330
|
+
) -> Result:
|
331
|
+
raise NotImplementedError(
|
332
|
+
"Async Stage should implement `execute` method."
|
333
|
+
)
|
332
334
|
|
333
335
|
@abstractmethod
|
334
336
|
async def axecute(
|
@@ -393,25 +395,23 @@ class BaseAsyncStage(BaseStage):
|
|
393
395
|
if to is not None:
|
394
396
|
return self.set_outputs(rs.context, to=to)
|
395
397
|
return rs
|
396
|
-
except Exception as
|
397
|
-
await result.trace.aerror(
|
398
|
-
f"[STAGE]: {err.__class__.__name__}: {err}"
|
399
|
-
)
|
398
|
+
except Exception as e:
|
399
|
+
await result.trace.aerror(f"[STAGE]: {e.__class__.__name__}: {e}")
|
400
400
|
|
401
401
|
if dynamic("stage_raise_error", f=raise_error, extras=self.extras):
|
402
|
-
if isinstance(
|
402
|
+
if isinstance(e, StageException):
|
403
403
|
raise
|
404
404
|
|
405
405
|
raise StageException(
|
406
406
|
f"{self.__class__.__name__}: \n\t"
|
407
|
-
f"{
|
407
|
+
f"{e.__class__.__name__}: {e}"
|
408
408
|
) from None
|
409
409
|
|
410
|
-
errors: DictData = {"errors": to_dict(
|
410
|
+
errors: DictData = {"errors": to_dict(e)}
|
411
411
|
if to is not None:
|
412
412
|
return self.set_outputs(errors, to=to)
|
413
413
|
|
414
|
-
return result.catch(status=
|
414
|
+
return result.catch(status=FAILED, context=errors)
|
415
415
|
|
416
416
|
|
417
417
|
class EmptyStage(BaseAsyncStage):
|
@@ -472,7 +472,7 @@ class EmptyStage(BaseAsyncStage):
|
|
472
472
|
result.trace.info(f"[STAGE]: ... sleep ({self.sleep} seconds)")
|
473
473
|
time.sleep(self.sleep)
|
474
474
|
|
475
|
-
return result.catch(status=
|
475
|
+
return result.catch(status=SUCCESS)
|
476
476
|
|
477
477
|
async def axecute(
|
478
478
|
self,
|
@@ -510,7 +510,7 @@ class EmptyStage(BaseAsyncStage):
|
|
510
510
|
)
|
511
511
|
await asyncio.sleep(self.sleep)
|
512
512
|
|
513
|
-
return result.catch(status=
|
513
|
+
return result.catch(status=SUCCESS)
|
514
514
|
|
515
515
|
|
516
516
|
class BashStage(BaseStage):
|
@@ -619,17 +619,17 @@ class BashStage(BaseStage):
|
|
619
619
|
|
620
620
|
if rs.returncode > 0:
|
621
621
|
# NOTE: Prepare stderr message that returning from subprocess.
|
622
|
-
|
622
|
+
e: str = (
|
623
623
|
rs.stderr.encode("utf-8").decode("utf-16")
|
624
624
|
if "\\x00" in rs.stderr
|
625
625
|
else rs.stderr
|
626
626
|
).removesuffix("\n")
|
627
627
|
raise StageException(
|
628
|
-
f"Subprocess: {
|
628
|
+
f"Subprocess: {e}\nRunning Statement:\n---\n"
|
629
629
|
f"```bash\n{bash}\n```"
|
630
630
|
)
|
631
631
|
return result.catch(
|
632
|
-
status=
|
632
|
+
status=SUCCESS,
|
633
633
|
context={
|
634
634
|
"return_code": rs.returncode,
|
635
635
|
"stdout": None if (out := rs.stdout.strip("\n")) == "" else out,
|
@@ -749,7 +749,7 @@ class PyStage(BaseStage):
|
|
749
749
|
)
|
750
750
|
|
751
751
|
return result.catch(
|
752
|
-
status=
|
752
|
+
status=SUCCESS, context={"locals": lc, "globals": gb}
|
753
753
|
)
|
754
754
|
|
755
755
|
|
@@ -871,7 +871,7 @@ class CallStage(BaseStage):
|
|
871
871
|
f"Return type: '{t_func.name}@{t_func.tag}' does not serialize "
|
872
872
|
f"to result model, you change return type to `dict`."
|
873
873
|
)
|
874
|
-
return result.catch(status=
|
874
|
+
return result.catch(status=SUCCESS, context=rs)
|
875
875
|
|
876
876
|
|
877
877
|
class TriggerStage(BaseStage):
|
@@ -999,19 +999,11 @@ class ParallelStage(BaseStage): # pragma: no cov
|
|
999
999
|
).context,
|
1000
1000
|
to=context,
|
1001
1001
|
)
|
1002
|
-
except StageException as
|
1002
|
+
except StageException as e: # pragma: no cov
|
1003
1003
|
result.trace.error(
|
1004
|
-
f"[STAGE]: Catch:\n\t{
|
1005
|
-
)
|
1006
|
-
context.update(
|
1007
|
-
{
|
1008
|
-
"errors": {
|
1009
|
-
"class": err,
|
1010
|
-
"name": err.__class__.__name__,
|
1011
|
-
"message": f"{err.__class__.__name__}: {err}",
|
1012
|
-
},
|
1013
|
-
},
|
1004
|
+
f"[STAGE]: Catch:\n\t{e.__class__.__name__}:" f"\n\t{e}"
|
1014
1005
|
)
|
1006
|
+
context.update({"errors": e.to_dict()})
|
1015
1007
|
return context
|
1016
1008
|
|
1017
1009
|
def execute(
|
@@ -1041,7 +1033,7 @@ class ParallelStage(BaseStage): # pragma: no cov
|
|
1041
1033
|
f"[STAGE]: Parallel-Execute with {self.max_parallel_core} cores."
|
1042
1034
|
)
|
1043
1035
|
rs: DictData = {"parallel": {}}
|
1044
|
-
status =
|
1036
|
+
status = SUCCESS
|
1045
1037
|
with ThreadPoolExecutor(
|
1046
1038
|
max_workers=self.max_parallel_core,
|
1047
1039
|
thread_name_prefix="parallel_stage_exec_",
|
@@ -1065,7 +1057,7 @@ class ParallelStage(BaseStage): # pragma: no cov
|
|
1065
1057
|
rs["parallel"][context.pop("branch")] = context
|
1066
1058
|
|
1067
1059
|
if "errors" in context:
|
1068
|
-
status =
|
1060
|
+
status = FAILED
|
1069
1061
|
|
1070
1062
|
return result.catch(status=status, context=rs)
|
1071
1063
|
|
@@ -1144,7 +1136,7 @@ class ForEachStage(BaseStage):
|
|
1144
1136
|
|
1145
1137
|
result.trace.info(f"[STAGE]: Foreach-Execute: {foreach!r}.")
|
1146
1138
|
rs: DictData = {"items": foreach, "foreach": {}}
|
1147
|
-
status =
|
1139
|
+
status: Status = SUCCESS
|
1148
1140
|
# TODO: Implement concurrent more than 1.
|
1149
1141
|
for item in foreach:
|
1150
1142
|
result.trace.debug(f"[STAGE]: Execute foreach item: {item!r}")
|
@@ -1161,21 +1153,12 @@ class ForEachStage(BaseStage):
|
|
1161
1153
|
).context,
|
1162
1154
|
to=context,
|
1163
1155
|
)
|
1164
|
-
except StageException as
|
1165
|
-
status =
|
1156
|
+
except StageException as e: # pragma: no cov
|
1157
|
+
status = FAILED
|
1166
1158
|
result.trace.error(
|
1167
|
-
f"[STAGE]: Catch:\n\t{
|
1168
|
-
f"\n\t{err}"
|
1169
|
-
)
|
1170
|
-
context.update(
|
1171
|
-
{
|
1172
|
-
"errors": {
|
1173
|
-
"class": err,
|
1174
|
-
"name": err.__class__.__name__,
|
1175
|
-
"message": f"{err.__class__.__name__}: {err}",
|
1176
|
-
},
|
1177
|
-
},
|
1159
|
+
f"[STAGE]: Catch:\n\t{e.__class__.__name__}:" f"\n\t{e}"
|
1178
1160
|
)
|
1161
|
+
context.update({"errors": e.to_dict()})
|
1179
1162
|
|
1180
1163
|
rs["foreach"][item] = context
|
1181
1164
|
|
@@ -1288,7 +1271,7 @@ class CaseStage(BaseStage): # pragma: no cov
|
|
1288
1271
|
result: Result = Result(
|
1289
1272
|
run_id=gen_id(self.name + (self.id or ""), unique=True)
|
1290
1273
|
)
|
1291
|
-
status =
|
1274
|
+
status = SUCCESS
|
1292
1275
|
_case = param2template(self.case, params, extras=self.extras)
|
1293
1276
|
_else = None
|
1294
1277
|
context = {}
|
@@ -1310,21 +1293,12 @@ class CaseStage(BaseStage): # pragma: no cov
|
|
1310
1293
|
).context,
|
1311
1294
|
to=context,
|
1312
1295
|
)
|
1313
|
-
except StageException as
|
1314
|
-
status =
|
1296
|
+
except StageException as e: # pragma: no cov
|
1297
|
+
status = FAILED
|
1315
1298
|
result.trace.error(
|
1316
|
-
f"[STAGE]: Catch:\n\t{
|
1317
|
-
f"\n\t{err}"
|
1318
|
-
)
|
1319
|
-
context.update(
|
1320
|
-
{
|
1321
|
-
"errors": {
|
1322
|
-
"class": err,
|
1323
|
-
"name": err.__class__.__name__,
|
1324
|
-
"message": f"{err.__class__.__name__}: {err}",
|
1325
|
-
},
|
1326
|
-
},
|
1299
|
+
f"[STAGE]: Catch:\n\t{e.__class__.__name__}:" f"\n\t{e}"
|
1327
1300
|
)
|
1301
|
+
context.update({"errors": e.to_dict()})
|
1328
1302
|
|
1329
1303
|
return result.catch(status=status, context=context)
|
1330
1304
|
|