ddeutil-workflow 0.0.38__py3-none-any.whl → 0.0.40__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/__cron.py +89 -25
- ddeutil/workflow/__types.py +2 -0
- ddeutil/workflow/audit.py +7 -5
- ddeutil/workflow/caller.py +11 -10
- ddeutil/workflow/conf.py +8 -23
- ddeutil/workflow/context.py +2 -0
- ddeutil/workflow/job.py +127 -17
- ddeutil/workflow/scheduler.py +40 -1
- ddeutil/workflow/stages.py +55 -38
- ddeutil/workflow/templates.py +39 -20
- ddeutil/workflow/workflow.py +81 -23
- {ddeutil_workflow-0.0.38.dist-info → ddeutil_workflow-0.0.40.dist-info}/METADATA +5 -5
- {ddeutil_workflow-0.0.38.dist-info → ddeutil_workflow-0.0.40.dist-info}/RECORD +17 -17
- {ddeutil_workflow-0.0.38.dist-info → ddeutil_workflow-0.0.40.dist-info}/WHEEL +1 -1
- {ddeutil_workflow-0.0.38.dist-info → ddeutil_workflow-0.0.40.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.38.dist-info → ddeutil_workflow-0.0.40.dist-info}/top_level.txt +0 -0
ddeutil/workflow/workflow.py
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
# Licensed under the MIT License. See LICENSE in the project root for
|
4
4
|
# license information.
|
5
5
|
# ------------------------------------------------------------------------------
|
6
|
-
"""A Workflow module."""
|
6
|
+
"""A Workflow module that is the core model of this package."""
|
7
7
|
from __future__ import annotations
|
8
8
|
|
9
9
|
import copy
|
@@ -18,6 +18,7 @@ from datetime import datetime, timedelta
|
|
18
18
|
from enum import Enum
|
19
19
|
from functools import partial, total_ordering
|
20
20
|
from heapq import heappop, heappush
|
21
|
+
from pathlib import Path
|
21
22
|
from queue import Queue
|
22
23
|
from textwrap import dedent
|
23
24
|
from threading import Event
|
@@ -31,10 +32,10 @@ from typing_extensions import Self
|
|
31
32
|
from .__cron import CronJob, CronRunner
|
32
33
|
from .__types import DictData, TupleStr
|
33
34
|
from .audit import Audit, get_audit
|
34
|
-
from .conf import Loader, config, get_logger
|
35
|
+
from .conf import Loader, SimLoad, config, get_logger
|
35
36
|
from .cron import On
|
36
37
|
from .exceptions import JobException, WorkflowException
|
37
|
-
from .job import Job
|
38
|
+
from .job import Job, TriggerState
|
38
39
|
from .params import Param
|
39
40
|
from .result import Result, Status
|
40
41
|
from .templates import has_template, param2template
|
@@ -299,33 +300,71 @@ class Workflow(BaseModel):
|
|
299
300
|
|
300
301
|
# NOTE: Add name to loader data
|
301
302
|
loader_data["name"] = name.replace(" ", "_")
|
303
|
+
cls.__bypass_on__(
|
304
|
+
loader_data, path=loader.conf_path, externals=externals
|
305
|
+
)
|
306
|
+
return cls.model_validate(obj=loader_data)
|
307
|
+
|
308
|
+
@classmethod
|
309
|
+
def from_path(
|
310
|
+
cls,
|
311
|
+
name: str,
|
312
|
+
path: Path,
|
313
|
+
externals: DictData | None = None,
|
314
|
+
) -> Self:
|
315
|
+
"""Create Workflow instance from the specific path. The loader object
|
316
|
+
will use this workflow name and path to searching configuration data of
|
317
|
+
this workflow model.
|
302
318
|
|
303
|
-
|
304
|
-
|
319
|
+
:param name: (str) A workflow name that want to pass to Loader object.
|
320
|
+
:param path: (Path) A config path that want to search.
|
321
|
+
:param externals: An external parameters that want to pass to Loader
|
322
|
+
object.
|
323
|
+
|
324
|
+
:raise ValueError: If the type does not match with current object.
|
325
|
+
|
326
|
+
:rtype: Self
|
327
|
+
"""
|
328
|
+
loader: SimLoad = SimLoad(
|
329
|
+
name, conf_path=path, externals=(externals or {})
|
330
|
+
)
|
331
|
+
# NOTE: Validate the config type match with current connection model
|
332
|
+
if loader.type != cls.__name__:
|
333
|
+
raise ValueError(f"Type {loader.type} does not match with {cls}")
|
334
|
+
|
335
|
+
loader_data: DictData = copy.deepcopy(loader.data)
|
336
|
+
|
337
|
+
# NOTE: Add name to loader data
|
338
|
+
loader_data["name"] = name.replace(" ", "_")
|
339
|
+
cls.__bypass_on__(loader_data, path=path, externals=externals)
|
305
340
|
return cls.model_validate(obj=loader_data)
|
306
341
|
|
307
342
|
@classmethod
|
308
343
|
def __bypass_on__(
|
309
344
|
cls,
|
310
345
|
data: DictData,
|
346
|
+
path: Path,
|
311
347
|
externals: DictData | None = None,
|
312
348
|
) -> DictData:
|
313
349
|
"""Bypass the on data to loaded config data.
|
314
350
|
|
315
|
-
:param data:
|
316
|
-
:param
|
351
|
+
:param data: A data to construct to this Workflow model.
|
352
|
+
:param path: A config path.
|
353
|
+
:param externals: An external parameters that want to pass to SimLoad
|
354
|
+
object.
|
317
355
|
:rtype: DictData
|
318
356
|
"""
|
319
357
|
if on := data.pop("on", []):
|
320
358
|
if isinstance(on, str):
|
321
|
-
on = [on]
|
359
|
+
on: list[str] = [on]
|
322
360
|
if any(not isinstance(i, (dict, str)) for i in on):
|
323
361
|
raise TypeError("The ``on`` key should be list of str or dict")
|
324
362
|
|
325
|
-
# NOTE: Pass on value to
|
363
|
+
# NOTE: Pass on value to SimLoad and keep on model object to the on
|
364
|
+
# field.
|
326
365
|
data["on"] = [
|
327
366
|
(
|
328
|
-
|
367
|
+
SimLoad(n, conf_path=path, externals=(externals or {})).data
|
329
368
|
if isinstance(n, str)
|
330
369
|
else n
|
331
370
|
)
|
@@ -882,8 +921,6 @@ class Workflow(BaseModel):
|
|
882
921
|
f"workflow."
|
883
922
|
)
|
884
923
|
|
885
|
-
result.trace.info(f"[WORKFLOW]: Start execute job: {job_id!r}")
|
886
|
-
|
887
924
|
if event and event.is_set(): # pragma: no cov
|
888
925
|
raise WorkflowException(
|
889
926
|
"Workflow job was canceled from event that had set before "
|
@@ -898,15 +935,20 @@ class Workflow(BaseModel):
|
|
898
935
|
#
|
899
936
|
try:
|
900
937
|
job: Job = self.jobs[job_id]
|
901
|
-
job.
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
938
|
+
if job.is_skipped(params=params):
|
939
|
+
result.trace.info(f"[JOB]: Skip job: {job_id!r}")
|
940
|
+
job.set_outputs(output={"SKIP": {"skipped": True}}, to=params)
|
941
|
+
else:
|
942
|
+
result.trace.info(f"[JOB]: Start execute job: {job_id!r}")
|
943
|
+
job.set_outputs(
|
944
|
+
job.execute(
|
945
|
+
params=params,
|
946
|
+
run_id=result.run_id,
|
947
|
+
parent_run_id=result.parent_run_id,
|
948
|
+
event=event,
|
949
|
+
).context,
|
950
|
+
to=params,
|
951
|
+
)
|
910
952
|
except JobException as err:
|
911
953
|
result.trace.error(f"[WORKFLOW]: {err.__class__.__name__}: {err}")
|
912
954
|
if raise_error:
|
@@ -1054,11 +1096,20 @@ class Workflow(BaseModel):
|
|
1054
1096
|
job_id: str = job_queue.get()
|
1055
1097
|
job: Job = self.jobs[job_id]
|
1056
1098
|
|
1057
|
-
if
|
1099
|
+
if (check := job.check_needs(context["jobs"])).is_waiting():
|
1058
1100
|
job_queue.task_done()
|
1059
1101
|
job_queue.put(job_id)
|
1060
1102
|
time.sleep(0.15)
|
1061
1103
|
continue
|
1104
|
+
elif check == TriggerState.failed: # pragma: no cov
|
1105
|
+
raise WorkflowException(
|
1106
|
+
"Check job trigger rule was failed."
|
1107
|
+
)
|
1108
|
+
elif check == TriggerState.skipped: # pragma: no cov
|
1109
|
+
result.trace.info(f"[JOB]: Skip job: {job_id!r}")
|
1110
|
+
job.set_outputs({"SKIP": {"skipped": True}}, to=context)
|
1111
|
+
job_queue.task_done()
|
1112
|
+
continue
|
1062
1113
|
|
1063
1114
|
# NOTE: Start workflow job execution with deep copy context data
|
1064
1115
|
# before release.
|
@@ -1149,11 +1200,18 @@ class Workflow(BaseModel):
|
|
1149
1200
|
job: Job = self.jobs[job_id]
|
1150
1201
|
|
1151
1202
|
# NOTE: Waiting dependency job run successful before release.
|
1152
|
-
if
|
1203
|
+
if (check := job.check_needs(context["jobs"])).is_waiting():
|
1153
1204
|
job_queue.task_done()
|
1154
1205
|
job_queue.put(job_id)
|
1155
1206
|
time.sleep(0.075)
|
1156
1207
|
continue
|
1208
|
+
elif check == TriggerState.failed: # pragma: no cov
|
1209
|
+
raise WorkflowException("Check job trigger rule was failed.")
|
1210
|
+
elif check == TriggerState.skipped: # pragma: no cov
|
1211
|
+
result.trace.info(f"[JOB]: Skip job: {job_id!r}")
|
1212
|
+
job.set_outputs({"SKIP": {"skipped": True}}, to=context)
|
1213
|
+
job_queue.task_done()
|
1214
|
+
continue
|
1157
1215
|
|
1158
1216
|
# NOTE: Start workflow job execution with deep copy context data
|
1159
1217
|
# before release. This job execution process will run until
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ddeutil-workflow
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.40
|
4
4
|
Summary: Lightweight workflow orchestration
|
5
5
|
Author-email: ddeutils <korawich.anu@gmail.com>
|
6
6
|
License: MIT
|
@@ -23,9 +23,9 @@ Requires-Python: >=3.9.13
|
|
23
23
|
Description-Content-Type: text/markdown
|
24
24
|
License-File: LICENSE
|
25
25
|
Requires-Dist: ddeutil>=0.4.6
|
26
|
-
Requires-Dist: ddeutil-io[toml,yaml]>=0.2.
|
27
|
-
Requires-Dist: pydantic==2.
|
28
|
-
Requires-Dist: python-dotenv==1.0
|
26
|
+
Requires-Dist: ddeutil-io[toml,yaml]>=0.2.10
|
27
|
+
Requires-Dist: pydantic==2.11.1
|
28
|
+
Requires-Dist: python-dotenv==1.1.0
|
29
29
|
Requires-Dist: schedule<2.0.0,==1.2.2
|
30
30
|
Provides-Extra: api
|
31
31
|
Requires-Dist: fastapi<1.0.0,>=0.115.0; extra == "api"
|
@@ -130,7 +130,7 @@ flowchart LR
|
|
130
130
|
> - [Google **Workflows**](https://cloud.google.com/workflows)
|
131
131
|
> - [AWS **Step Functions**](https://aws.amazon.com/step-functions/)
|
132
132
|
|
133
|
-
##
|
133
|
+
## 📦 Installation
|
134
134
|
|
135
135
|
This project need `ddeutil` and `ddeutil-io` extension namespace packages.
|
136
136
|
If you want to install this package with application add-ons, you should add
|
@@ -1,22 +1,22 @@
|
|
1
|
-
ddeutil/workflow/__about__.py,sha256=
|
2
|
-
ddeutil/workflow/__cron.py,sha256=
|
1
|
+
ddeutil/workflow/__about__.py,sha256=4_L4aAteV6ecIgxXQuBmMHF6LoFXtYgCVakxe2GlHjk,28
|
2
|
+
ddeutil/workflow/__cron.py,sha256=h8rLeIUAAEB2SdZ4Jhch7LU1Yl3bbJ-iNNJ3tQ0eYVM,28095
|
3
3
|
ddeutil/workflow/__init__.py,sha256=hIM2Ha-7F3YF3aLsu4IY3N-UvD_EE1ai6DO6WL2mILg,1908
|
4
|
-
ddeutil/workflow/__types.py,sha256=
|
5
|
-
ddeutil/workflow/audit.py,sha256=
|
6
|
-
ddeutil/workflow/caller.py,sha256=
|
7
|
-
ddeutil/workflow/conf.py,sha256=
|
8
|
-
ddeutil/workflow/context.py,sha256=
|
4
|
+
ddeutil/workflow/__types.py,sha256=8jBdbfb3aZSetjz0mvNrpGHwwxJff7mK8_4v41cLqlc,4316
|
5
|
+
ddeutil/workflow/audit.py,sha256=BpzLI6ZKXi6xQO8C2sAHxSi8RAfF31dMPdjn3DbYlw8,8364
|
6
|
+
ddeutil/workflow/caller.py,sha256=M7_nan1DbRUAHEIQoQc1qIX4AHgI5fKFNx0KDbzhDMk,5736
|
7
|
+
ddeutil/workflow/conf.py,sha256=USYzITPwBS5Q_pl3DTTdFK8OxeL9GQu4GUXqi8nKpJo,11819
|
8
|
+
ddeutil/workflow/context.py,sha256=vsk4JQL7t3KsnKPfshw3O7YrPFo2h4rnnNd3B-G9Kj4,1700
|
9
9
|
ddeutil/workflow/cron.py,sha256=j8EeoHst70toRfnD_frix41vrI-eLYVJkZ9yeJtpfnI,8871
|
10
10
|
ddeutil/workflow/exceptions.py,sha256=fO37f9p7lOjIJgVOpKE_1X44yJTwBepyukZV9a7NNm4,1241
|
11
|
-
ddeutil/workflow/job.py,sha256=
|
11
|
+
ddeutil/workflow/job.py,sha256=LCQf_3gyTmnj6aAQ0ksz4lJxU10-F5MfVFHczkWt_VE,28669
|
12
12
|
ddeutil/workflow/logs.py,sha256=mAKQjNri-oourd3dBLLG3Fqoo4QG27U9IO2h6IqCOU0,10196
|
13
13
|
ddeutil/workflow/params.py,sha256=qw9XJyjh2ocf9pf6h_XiYHLOvQN4R5TMqPElmItKnRM,8019
|
14
14
|
ddeutil/workflow/result.py,sha256=cDkItrhpzZfMS1Oj8IZX8O-KBD4KZYDi43XJZvvC3Gc,4318
|
15
|
-
ddeutil/workflow/scheduler.py,sha256=
|
16
|
-
ddeutil/workflow/stages.py,sha256=
|
17
|
-
ddeutil/workflow/templates.py,sha256=
|
15
|
+
ddeutil/workflow/scheduler.py,sha256=daNdcTp8dQN0gXuK3cg7K7idspOcqKNmfOLTWirNEUM,27652
|
16
|
+
ddeutil/workflow/stages.py,sha256=ONBR7GjgLEbA21wNioBrhOucwqj6M1v2ht5JhipoWSg,36994
|
17
|
+
ddeutil/workflow/templates.py,sha256=yA2xgrSXcxfBxNT2hc6v06HkVY_0RKsc1UwdJRip9EE,11554
|
18
18
|
ddeutil/workflow/utils.py,sha256=Fz5y-LK_JfikDfvMKcFaxad_VvCnr7UC2C9KFCbzPNA,7105
|
19
|
-
ddeutil/workflow/workflow.py,sha256=
|
19
|
+
ddeutil/workflow/workflow.py,sha256=8ForojeLboLL6dHmV5-NjtU9o4mIL7NtY2F2NFxdqZY,49400
|
20
20
|
ddeutil/workflow/api/__init__.py,sha256=F53NMBWtb9IKaDWkPU5KvybGGfKAcbehgn6TLBwHuuM,21
|
21
21
|
ddeutil/workflow/api/api.py,sha256=gGQtqkzyJNaJIfka_w2M1lrCS3Ep46re2Dznsk9RxYQ,5191
|
22
22
|
ddeutil/workflow/api/log.py,sha256=NMTnOnsBrDB5129329xF2myLdrb-z9k1MQrmrP7qXJw,1818
|
@@ -26,8 +26,8 @@ ddeutil/workflow/api/routes/job.py,sha256=YVta083i8vU8-o4WdKFwDpfdC9vN1dZ6goZSmN
|
|
26
26
|
ddeutil/workflow/api/routes/logs.py,sha256=uEQ6k5PwRg-k0eSiSFArXfyeDq5xzepxILrRnwgAe1I,5373
|
27
27
|
ddeutil/workflow/api/routes/schedules.py,sha256=uWYDOwlV8w56hKQmfkQFwdZ6t2gZSJeCdBIzMmJenAQ,4824
|
28
28
|
ddeutil/workflow/api/routes/workflows.py,sha256=KVywA7vD9b4QrfmWBdSFF5chj34yJe1zNCzl6iBMeGI,4538
|
29
|
-
ddeutil_workflow-0.0.
|
30
|
-
ddeutil_workflow-0.0.
|
31
|
-
ddeutil_workflow-0.0.
|
32
|
-
ddeutil_workflow-0.0.
|
33
|
-
ddeutil_workflow-0.0.
|
29
|
+
ddeutil_workflow-0.0.40.dist-info/licenses/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
|
30
|
+
ddeutil_workflow-0.0.40.dist-info/METADATA,sha256=AdIiZTWsPhfcVba7x2OrWw5CN3y0OQB-dRdBb9RQh2o,19501
|
31
|
+
ddeutil_workflow-0.0.40.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
32
|
+
ddeutil_workflow-0.0.40.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
|
33
|
+
ddeutil_workflow-0.0.40.dist-info/RECORD,,
|
File without changes
|
File without changes
|