ddeutil-workflow 0.0.19__tar.gz → 0.0.21__tar.gz
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-0.0.19/src/ddeutil_workflow.egg-info → ddeutil_workflow-0.0.21}/PKG-INFO +4 -3
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/README.md +1 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/pyproject.toml +2 -2
- ddeutil_workflow-0.0.21/src/ddeutil/workflow/__about__.py +1 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/src/ddeutil/workflow/__cron.py +28 -2
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/src/ddeutil/workflow/__init__.py +9 -4
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/src/ddeutil/workflow/__types.py +1 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/src/ddeutil/workflow/conf.py +34 -25
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/src/ddeutil/workflow/exceptions.py +4 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/src/ddeutil/workflow/job.py +96 -101
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/src/ddeutil/workflow/on.py +4 -15
- ddeutil_workflow-0.0.21/src/ddeutil/workflow/scheduler.py +574 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/src/ddeutil/workflow/stage.py +94 -68
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/src/ddeutil/workflow/utils.py +29 -24
- ddeutil_workflow-0.0.21/src/ddeutil/workflow/workflow.py +1132 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21/src/ddeutil_workflow.egg-info}/PKG-INFO +4 -3
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/src/ddeutil_workflow.egg-info/SOURCES.txt +12 -10
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/src/ddeutil_workflow.egg-info/requires.txt +2 -2
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/tests/test__cron.py +1 -2
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/tests/test_conf_log.py +3 -3
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/tests/test_job.py +21 -21
- ddeutil_workflow-0.0.19/tests/test_job_strategy_run.py → ddeutil_workflow-0.0.21/tests/test_job_exec_strategy.py +49 -12
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/tests/test_on.py +6 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/tests/test_scheduler.py +26 -18
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/tests/test_scheduler_tasks.py +20 -32
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/tests/test_stage.py +12 -10
- ddeutil_workflow-0.0.19/tests/test_stage_bash.py → ddeutil_workflow-0.0.21/tests/test_stage_exec_bash.py +3 -3
- ddeutil_workflow-0.0.19/tests/test_stage_hook.py → ddeutil_workflow-0.0.21/tests/test_stage_exec_hook.py +5 -5
- ddeutil_workflow-0.0.19/tests/test_stage_py.py → ddeutil_workflow-0.0.21/tests/test_stage_exec_py.py +4 -4
- ddeutil_workflow-0.0.19/tests/test_stage_trigger.py → ddeutil_workflow-0.0.21/tests/test_stage_exec_trigger.py +2 -2
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/tests/test_utils_result.py +1 -1
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/tests/test_workflow.py +21 -12
- ddeutil_workflow-0.0.19/tests/test_workflow_run.py → ddeutil_workflow-0.0.21/tests/test_workflow_exec.py +57 -5
- ddeutil_workflow-0.0.19/tests/test_workflow_task.py → ddeutil_workflow-0.0.21/tests/test_workflow_exec_hook.py +4 -4
- ddeutil_workflow-0.0.19/tests/test_workflow_depends.py → ddeutil_workflow-0.0.21/tests/test_workflow_exec_needs.py +2 -2
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/tests/test_workflow_poke.py +17 -23
- ddeutil_workflow-0.0.21/tests/test_workflow_release.py +44 -0
- ddeutil_workflow-0.0.19/src/ddeutil/workflow/__about__.py +0 -1
- ddeutil_workflow-0.0.19/src/ddeutil/workflow/scheduler.py +0 -1477
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/LICENSE +0 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/setup.cfg +0 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/src/ddeutil/workflow/api.py +0 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/src/ddeutil/workflow/cli.py +0 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/src/ddeutil/workflow/repeat.py +0 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/src/ddeutil/workflow/route.py +0 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/src/ddeutil_workflow.egg-info/dependency_links.txt +0 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/src/ddeutil_workflow.egg-info/entry_points.txt +0 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/src/ddeutil_workflow.egg-info/top_level.txt +0 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/tests/test__regex.py +0 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/tests/test_conf.py +0 -0
- /ddeutil_workflow-0.0.19/tests/test_job_py.py → /ddeutil_workflow-0.0.21/tests/test_job_exec_py.py +0 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/tests/test_job_strategy.py +0 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/tests/test_params.py +0 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/tests/test_utils.py +0 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/tests/test_utils_filter.py +0 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/tests/test_utils_params.py +0 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/tests/test_utils_tag.py +0 -0
- {ddeutil_workflow-0.0.19 → ddeutil_workflow-0.0.21}/tests/test_utils_template.py +0 -0
- /ddeutil_workflow-0.0.19/tests/test_workflow_job_run.py → /ddeutil_workflow-0.0.21/tests/test_workflow_job_exec.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ddeutil-workflow
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.21
|
4
4
|
Summary: Lightweight workflow orchestration with less dependencies
|
5
5
|
Author-email: ddeutils <korawich.anu@gmail.com>
|
6
6
|
License: MIT
|
@@ -24,9 +24,9 @@ Description-Content-Type: text/markdown
|
|
24
24
|
License-File: LICENSE
|
25
25
|
Requires-Dist: ddeutil>=0.4.3
|
26
26
|
Requires-Dist: ddeutil-io[toml,yaml]>=0.2.3
|
27
|
-
Requires-Dist: pydantic==2.
|
27
|
+
Requires-Dist: pydantic==2.10.2
|
28
28
|
Requires-Dist: python-dotenv==1.0.1
|
29
|
-
Requires-Dist: typer
|
29
|
+
Requires-Dist: typer==0.15.1
|
30
30
|
Requires-Dist: schedule<2.0.0,==1.2.2
|
31
31
|
Provides-Extra: api
|
32
32
|
Requires-Dist: fastapi<1.0.0,>=0.115.0; extra == "api"
|
@@ -195,6 +195,7 @@ and do not raise any error to you.
|
|
195
195
|
| `WORKFLOW_CORE_JOB_RAISE_ERROR` | Core | true | A flag that all job raise JobException from job strategy execution. | |
|
196
196
|
| `WORKFLOW_CORE_MAX_NUM_POKING` | Core | 4 | . | |
|
197
197
|
| `WORKFLOW_CORE_MAX_JOB_PARALLEL` | Core | 2 | The maximum job number that able to run parallel in workflow executor. | |
|
198
|
+
| `WORKFLOW_CORE_MAX_JOB_EXEC_TIMEOUT` | Core | 600 | | |
|
198
199
|
| `WORKFLOW_CORE_GENERATE_ID_SIMPLE_MODE` | Core | true | A flog that enable generating ID with `md5` algorithm. | |
|
199
200
|
| `WORKFLOW_LOG_DEBUG_MODE` | Log | true | A flag that enable logging with debug level mode. | |
|
200
201
|
| `WORKFLOW_LOG_ENABLE_WRITE` | Log | true | A flag that enable logging object saving log to its destination. | |
|
@@ -162,6 +162,7 @@ and do not raise any error to you.
|
|
162
162
|
| `WORKFLOW_CORE_JOB_RAISE_ERROR` | Core | true | A flag that all job raise JobException from job strategy execution. | |
|
163
163
|
| `WORKFLOW_CORE_MAX_NUM_POKING` | Core | 4 | . | |
|
164
164
|
| `WORKFLOW_CORE_MAX_JOB_PARALLEL` | Core | 2 | The maximum job number that able to run parallel in workflow executor. | |
|
165
|
+
| `WORKFLOW_CORE_MAX_JOB_EXEC_TIMEOUT` | Core | 600 | | |
|
165
166
|
| `WORKFLOW_CORE_GENERATE_ID_SIMPLE_MODE` | Core | true | A flog that enable generating ID with `md5` algorithm. | |
|
166
167
|
| `WORKFLOW_LOG_DEBUG_MODE` | Log | true | A flag that enable logging with debug level mode. | |
|
167
168
|
| `WORKFLOW_LOG_ENABLE_WRITE` | Log | true | A flag that enable logging object saving log to its destination. | |
|
@@ -28,9 +28,9 @@ requires-python = ">=3.9.13"
|
|
28
28
|
dependencies = [
|
29
29
|
"ddeutil>=0.4.3",
|
30
30
|
"ddeutil-io[yaml,toml]>=0.2.3",
|
31
|
-
"pydantic==2.
|
31
|
+
"pydantic==2.10.2",
|
32
32
|
"python-dotenv==1.0.1",
|
33
|
-
"typer==0.
|
33
|
+
"typer==0.15.1",
|
34
34
|
"schedule==1.2.2,<2.0.0",
|
35
35
|
]
|
36
36
|
dynamic = ["version"]
|
@@ -0,0 +1 @@
|
|
1
|
+
__version__: str = "0.0.21"
|
@@ -653,6 +653,20 @@ class CronJob:
|
|
653
653
|
|
654
654
|
|
655
655
|
class CronJobYear(CronJob):
|
656
|
+
"""The Cron Job Converter with Year extension object that generate datetime
|
657
|
+
dimension of cron job schedule format,
|
658
|
+
|
659
|
+
* * * * * * <command to execute>
|
660
|
+
|
661
|
+
(i) minute (0 - 59)
|
662
|
+
(ii) hour (0 - 23)
|
663
|
+
(iii) day of the month (1 - 31)
|
664
|
+
(iv) month (1 - 12)
|
665
|
+
(v) day of the week (0 - 6) (Sunday to Saturday; 7 is also Sunday
|
666
|
+
on some systems)
|
667
|
+
(vi) year (1990 - 2100)
|
668
|
+
"""
|
669
|
+
|
656
670
|
cron_length = 6
|
657
671
|
cron_units = CRON_UNITS_YEAR
|
658
672
|
|
@@ -705,9 +719,17 @@ class CronRunner:
|
|
705
719
|
else:
|
706
720
|
self.date: datetime = datetime.now(tz=self.tz)
|
707
721
|
|
722
|
+
# NOTE: Add one second if the microsecond value more than 0.
|
723
|
+
if self.date.microsecond > 0:
|
724
|
+
self.date: datetime = self.date.replace(microsecond=0) + timedelta(
|
725
|
+
seconds=1
|
726
|
+
)
|
727
|
+
|
708
728
|
# NOTE: Add one minute if the second value more than 0.
|
709
729
|
if self.date.second > 0:
|
710
|
-
self.date: datetime = self.date + timedelta(
|
730
|
+
self.date: datetime = self.date.replace(second=0) + timedelta(
|
731
|
+
minutes=1
|
732
|
+
)
|
711
733
|
|
712
734
|
self.__start_date: datetime = self.date
|
713
735
|
self.cron: CronJob | CronJobYear = cron
|
@@ -753,7 +775,7 @@ class CronRunner:
|
|
753
775
|
not self.__shift_date(mode, reverse)
|
754
776
|
for mode in ("year", "month", "day", "hour", "minute")
|
755
777
|
):
|
756
|
-
return copy.deepcopy(self.date
|
778
|
+
return copy.deepcopy(self.date)
|
757
779
|
|
758
780
|
raise RecursionError("Unable to find execution time for schedule")
|
759
781
|
|
@@ -802,6 +824,10 @@ class CronRunner:
|
|
802
824
|
# NOTE: Replace date that less than it mode to zero.
|
803
825
|
self.date: datetime = replace_date(self.date, mode, reverse=reverse)
|
804
826
|
|
827
|
+
# NOTE: Replace second and microsecond values that change from
|
828
|
+
# the replace_date func with reverse flag.
|
829
|
+
self.date: datetime = self.date.replace(second=0, microsecond=0)
|
830
|
+
|
805
831
|
if current_value != getattr(self.date, switch[mode]):
|
806
832
|
return mode != "month"
|
807
833
|
|
@@ -15,7 +15,10 @@ from .exceptions import (
|
|
15
15
|
UtilException,
|
16
16
|
WorkflowException,
|
17
17
|
)
|
18
|
-
from .job import
|
18
|
+
from .job import (
|
19
|
+
Job,
|
20
|
+
Strategy,
|
21
|
+
)
|
19
22
|
from .on import (
|
20
23
|
On,
|
21
24
|
YearOn,
|
@@ -24,8 +27,6 @@ from .on import (
|
|
24
27
|
from .scheduler import (
|
25
28
|
Schedule,
|
26
29
|
ScheduleWorkflow,
|
27
|
-
Workflow,
|
28
|
-
WorkflowTaskData,
|
29
30
|
)
|
30
31
|
from .stage import (
|
31
32
|
BashStage,
|
@@ -34,7 +35,7 @@ from .stage import (
|
|
34
35
|
PyStage,
|
35
36
|
Stage,
|
36
37
|
TriggerStage,
|
37
|
-
|
38
|
+
extract_hook,
|
38
39
|
)
|
39
40
|
from .utils import (
|
40
41
|
FILTERS,
|
@@ -70,3 +71,7 @@ from .utils import (
|
|
70
71
|
str2template,
|
71
72
|
tag,
|
72
73
|
)
|
74
|
+
from .workflow import (
|
75
|
+
Workflow,
|
76
|
+
WorkflowTaskData,
|
77
|
+
)
|
@@ -33,6 +33,29 @@ load_dotenv()
|
|
33
33
|
env = os.getenv
|
34
34
|
|
35
35
|
|
36
|
+
@lru_cache
|
37
|
+
def get_logger(name: str):
|
38
|
+
"""Return logger object with an input module name.
|
39
|
+
|
40
|
+
:param name: A module name that want to log.
|
41
|
+
"""
|
42
|
+
lg = logging.getLogger(name)
|
43
|
+
formatter = logging.Formatter(
|
44
|
+
fmt=(
|
45
|
+
"%(asctime)s.%(msecs)03d (%(name)-10s, %(process)-5d, "
|
46
|
+
"%(thread)-5d) [%(levelname)-7s] %(message)-120s "
|
47
|
+
"(%(filename)s:%(lineno)s)"
|
48
|
+
),
|
49
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
50
|
+
)
|
51
|
+
stream = logging.StreamHandler()
|
52
|
+
stream.setFormatter(formatter)
|
53
|
+
lg.addHandler(stream)
|
54
|
+
|
55
|
+
lg.setLevel(logging.DEBUG if config.debug else logging.INFO)
|
56
|
+
return lg
|
57
|
+
|
58
|
+
|
36
59
|
class Config:
|
37
60
|
"""Config object for keeping application configuration on current session
|
38
61
|
without changing when if the application still running.
|
@@ -77,6 +100,9 @@ class Config:
|
|
77
100
|
|
78
101
|
# NOTE: Workflow
|
79
102
|
max_job_parallel: int = int(env("WORKFLOW_CORE_MAX_JOB_PARALLEL", "2"))
|
103
|
+
max_job_exec_timeout: int = int(
|
104
|
+
env("WORKFLOW_CORE_MAX_JOB_EXEC_TIMEOUT", "600")
|
105
|
+
)
|
80
106
|
max_poking_pool_worker: int = int(
|
81
107
|
os.getenv("WORKFLOW_CORE_MAX_NUM_POKING", "4")
|
82
108
|
)
|
@@ -98,12 +124,14 @@ class Config:
|
|
98
124
|
os.getenv("WORKFLOW_API_ENABLE_ROUTE_SCHEDULE", "true")
|
99
125
|
)
|
100
126
|
|
101
|
-
def __init__(self):
|
127
|
+
def __init__(self) -> None:
|
128
|
+
# VALIDATE: the MAX_JOB_PARALLEL value should not less than 0.
|
102
129
|
if self.max_job_parallel < 0:
|
103
130
|
raise ValueError(
|
104
131
|
f"``MAX_JOB_PARALLEL`` should more than 0 but got "
|
105
132
|
f"{self.max_job_parallel}."
|
106
133
|
)
|
134
|
+
|
107
135
|
try:
|
108
136
|
self.stop_boundary_delta: timedelta = timedelta(
|
109
137
|
**json.loads(self.stop_boundary_delta_str)
|
@@ -287,29 +315,7 @@ def get_type(t: str, params: Config) -> AnyModelType:
|
|
287
315
|
|
288
316
|
|
289
317
|
config = Config()
|
290
|
-
|
291
|
-
|
292
|
-
@lru_cache
|
293
|
-
def get_logger(name: str):
|
294
|
-
"""Return logger object with an input module name.
|
295
|
-
|
296
|
-
:param name: A module name that want to log.
|
297
|
-
"""
|
298
|
-
logger = logging.getLogger(name)
|
299
|
-
formatter = logging.Formatter(
|
300
|
-
fmt=(
|
301
|
-
"%(asctime)s.%(msecs)03d (%(name)-10s, %(process)-5d, "
|
302
|
-
"%(thread)-5d) [%(levelname)-7s] %(message)-120s "
|
303
|
-
"(%(filename)s:%(lineno)s)"
|
304
|
-
),
|
305
|
-
datefmt="%Y-%m-%d %H:%M:%S",
|
306
|
-
)
|
307
|
-
stream = logging.StreamHandler()
|
308
|
-
stream.setFormatter(formatter)
|
309
|
-
logger.addHandler(stream)
|
310
|
-
|
311
|
-
logger.setLevel(logging.DEBUG if config.debug else logging.INFO)
|
312
|
-
return logger
|
318
|
+
logger = get_logger("ddeutil.workflow")
|
313
319
|
|
314
320
|
|
315
321
|
class BaseLog(BaseModel, ABC):
|
@@ -319,8 +325,8 @@ class BaseLog(BaseModel, ABC):
|
|
319
325
|
"""
|
320
326
|
|
321
327
|
name: str = Field(description="A workflow name.")
|
322
|
-
on: str = Field(description="A cronjob string of this piepline schedule.")
|
323
328
|
release: datetime = Field(description="A release datetime.")
|
329
|
+
type: str = Field(description="A running type before logging.")
|
324
330
|
context: DictData = Field(
|
325
331
|
default_factory=dict,
|
326
332
|
description=(
|
@@ -462,6 +468,9 @@ class FileLog(BaseLog):
|
|
462
468
|
if not config.enable_write_log:
|
463
469
|
return self
|
464
470
|
|
471
|
+
logger.debug(
|
472
|
+
f"({self.run_id}) [LOG]: Start writing log: {self.name!r}."
|
473
|
+
)
|
465
474
|
log_file: Path = self.pointer() / f"{self.run_id}.log"
|
466
475
|
log_file.write_text(
|
467
476
|
json.dumps(
|
@@ -3,6 +3,10 @@
|
|
3
3
|
# Licensed under the MIT License. See LICENSE in the project root for
|
4
4
|
# license information.
|
5
5
|
# ------------------------------------------------------------------------------
|
6
|
+
"""Exception objects for this package do not do anything because I want to
|
7
|
+
create the lightweight workflow package. So, this module do just a exception
|
8
|
+
annotate for handle error only.
|
9
|
+
"""
|
6
10
|
from __future__ import annotations
|
7
11
|
|
8
12
|
|