ddeutil-workflow 0.0.29__py3-none-any.whl → 0.0.31__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 +1 -0
- ddeutil/workflow/__init__.py +5 -1
- ddeutil/workflow/api/api.py +2 -2
- ddeutil/workflow/conf.py +4 -0
- ddeutil/workflow/cron.py +77 -21
- ddeutil/workflow/exceptions.py +3 -0
- ddeutil/workflow/hook.py +3 -1
- ddeutil/workflow/params.py +18 -1
- ddeutil/workflow/result.py +1 -0
- ddeutil/workflow/scheduler.py +101 -67
- ddeutil/workflow/stage.py +13 -1
- ddeutil/workflow/utils.py +36 -10
- ddeutil/workflow/workflow.py +179 -141
- {ddeutil_workflow-0.0.29.dist-info → ddeutil_workflow-0.0.31.dist-info}/METADATA +17 -12
- ddeutil_workflow-0.0.31.dist-info/RECORD +25 -0
- ddeutil_workflow-0.0.29.dist-info/RECORD +0 -25
- {ddeutil_workflow-0.0.29.dist-info → ddeutil_workflow-0.0.31.dist-info}/LICENSE +0 -0
- {ddeutil_workflow-0.0.29.dist-info → ddeutil_workflow-0.0.31.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.29.dist-info → ddeutil_workflow-0.0.31.dist-info}/top_level.txt +0 -0
ddeutil/workflow/__about__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__: str = "0.0.
|
1
|
+
__version__: str = "0.0.31"
|
ddeutil/workflow/__cron.py
CHANGED
ddeutil/workflow/__init__.py
CHANGED
@@ -47,8 +47,10 @@ from .params import (
|
|
47
47
|
from .result import Result
|
48
48
|
from .scheduler import (
|
49
49
|
Schedule,
|
50
|
-
|
50
|
+
ScheduleWorkflow,
|
51
|
+
schedule_control,
|
51
52
|
schedule_runner,
|
53
|
+
schedule_task,
|
52
54
|
)
|
53
55
|
from .stage import (
|
54
56
|
BashStage,
|
@@ -83,6 +85,8 @@ from .utils import (
|
|
83
85
|
make_exec,
|
84
86
|
)
|
85
87
|
from .workflow import (
|
88
|
+
Release,
|
89
|
+
ReleaseQueue,
|
86
90
|
Workflow,
|
87
91
|
WorkflowTask,
|
88
92
|
)
|
ddeutil/workflow/api/api.py
CHANGED
@@ -18,7 +18,7 @@ from fastapi.responses import UJSONResponse
|
|
18
18
|
from ..__about__ import __version__
|
19
19
|
from ..conf import config, get_logger
|
20
20
|
from ..scheduler import ReleaseThread, ReleaseThreads
|
21
|
-
from ..workflow import
|
21
|
+
from ..workflow import ReleaseQueue, WorkflowTask
|
22
22
|
from .repeat import repeat_at
|
23
23
|
|
24
24
|
load_dotenv()
|
@@ -31,7 +31,7 @@ class State(TypedDict):
|
|
31
31
|
scheduler: list[str]
|
32
32
|
workflow_threads: ReleaseThreads
|
33
33
|
workflow_tasks: list[WorkflowTask]
|
34
|
-
workflow_queue: dict[str,
|
34
|
+
workflow_queue: dict[str, ReleaseQueue]
|
35
35
|
|
36
36
|
|
37
37
|
@contextlib.asynccontextmanager
|
ddeutil/workflow/conf.py
CHANGED
@@ -582,6 +582,10 @@ Log = Union[
|
|
582
582
|
|
583
583
|
|
584
584
|
def get_log() -> type[Log]: # pragma: no cov
|
585
|
+
"""Get logging class that dynamic base on the config log path value.
|
586
|
+
|
587
|
+
:rtype: type[Log]
|
588
|
+
"""
|
585
589
|
if config.log_path.is_file():
|
586
590
|
return SQLiteLog
|
587
591
|
return FileLog
|
ddeutil/workflow/cron.py
CHANGED
@@ -5,16 +5,17 @@
|
|
5
5
|
# ------------------------------------------------------------------------------
|
6
6
|
from __future__ import annotations
|
7
7
|
|
8
|
+
from dataclasses import fields
|
8
9
|
from datetime import datetime
|
9
|
-
from typing import Annotated, Literal
|
10
|
+
from typing import Annotated, Literal, Union
|
10
11
|
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
11
12
|
|
12
|
-
from pydantic import BaseModel, ConfigDict, Field
|
13
|
+
from pydantic import BaseModel, ConfigDict, Field, ValidationInfo
|
13
14
|
from pydantic.functional_serializers import field_serializer
|
14
15
|
from pydantic.functional_validators import field_validator, model_validator
|
15
16
|
from typing_extensions import Self
|
16
17
|
|
17
|
-
from .__cron import WEEKDAYS, CronJob, CronJobYear, CronRunner
|
18
|
+
from .__cron import WEEKDAYS, CronJob, CronJobYear, CronRunner, Options
|
18
19
|
from .__types import DictData, DictStr, TupleStr
|
19
20
|
from .conf import Loader
|
20
21
|
|
@@ -47,6 +48,8 @@ def interval2crontab(
|
|
47
48
|
'0 0 1 * *'
|
48
49
|
>>> interval2crontab(interval='monthly', day='tuesday', time='12:00')
|
49
50
|
'12 0 1 * 2'
|
51
|
+
|
52
|
+
:rtype: str
|
50
53
|
"""
|
51
54
|
d: str = "*"
|
52
55
|
if interval == "weekly":
|
@@ -64,12 +67,19 @@ class On(BaseModel):
|
|
64
67
|
"""On Pydantic model (Warped crontab object by model).
|
65
68
|
|
66
69
|
See Also:
|
67
|
-
* ``generate()`` is the main
|
70
|
+
* ``generate()`` is the main use-case of this schedule object.
|
68
71
|
"""
|
69
72
|
|
70
73
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
71
74
|
|
72
75
|
# NOTE: This is fields of the base schedule.
|
76
|
+
extras: Annotated[
|
77
|
+
DictData,
|
78
|
+
Field(
|
79
|
+
default_factory=dict,
|
80
|
+
description="An extras mapping parameters",
|
81
|
+
),
|
82
|
+
]
|
73
83
|
cronjob: Annotated[
|
74
84
|
CronJob,
|
75
85
|
Field(description="Cron job of this schedule"),
|
@@ -81,13 +91,6 @@ class On(BaseModel):
|
|
81
91
|
alias="timezone",
|
82
92
|
),
|
83
93
|
] = "Etc/UTC"
|
84
|
-
extras: Annotated[
|
85
|
-
DictData,
|
86
|
-
Field(
|
87
|
-
default_factory=dict,
|
88
|
-
description="An extras mapping parameters",
|
89
|
-
),
|
90
|
-
]
|
91
94
|
|
92
95
|
@classmethod
|
93
96
|
def from_value(cls, value: DictStr, externals: DictData) -> Self:
|
@@ -153,6 +156,7 @@ class On(BaseModel):
|
|
153
156
|
|
154
157
|
@model_validator(mode="before")
|
155
158
|
def __prepare_values(cls, values: DictData) -> DictData:
|
159
|
+
"""Extract tz key from value and change name to timezone key."""
|
156
160
|
if tz := values.pop("tz", None):
|
157
161
|
values["timezone"] = tz
|
158
162
|
return values
|
@@ -160,24 +164,55 @@ class On(BaseModel):
|
|
160
164
|
@field_validator("tz")
|
161
165
|
def __validate_tz(cls, value: str) -> str:
|
162
166
|
"""Validate timezone value that able to initialize with ZoneInfo after
|
163
|
-
it passing to this model in before mode.
|
167
|
+
it passing to this model in before mode.
|
168
|
+
|
169
|
+
:rtype: str
|
170
|
+
"""
|
164
171
|
try:
|
165
172
|
_ = ZoneInfo(value)
|
166
173
|
return value
|
167
174
|
except ZoneInfoNotFoundError as err:
|
168
175
|
raise ValueError(f"Invalid timezone: {value}") from err
|
169
176
|
|
170
|
-
@field_validator(
|
171
|
-
|
172
|
-
|
173
|
-
|
177
|
+
@field_validator(
|
178
|
+
"cronjob", mode="before", json_schema_input_type=Union[CronJob, str]
|
179
|
+
)
|
180
|
+
def __prepare_cronjob(
|
181
|
+
cls, value: str | CronJob, info: ValidationInfo
|
182
|
+
) -> CronJob:
|
183
|
+
"""Prepare crontab value that able to receive with string type.
|
184
|
+
This step will get options kwargs from extras and pass to the
|
185
|
+
CronJob object.
|
186
|
+
|
187
|
+
:rtype: CronJob
|
188
|
+
"""
|
189
|
+
extras: DictData = info.data.get("extras", {})
|
190
|
+
return (
|
191
|
+
CronJob(
|
192
|
+
value,
|
193
|
+
option={
|
194
|
+
name: extras[name]
|
195
|
+
for name in (f.name for f in fields(Options))
|
196
|
+
if name in extras
|
197
|
+
},
|
198
|
+
)
|
199
|
+
if isinstance(value, str)
|
200
|
+
else value
|
201
|
+
)
|
174
202
|
|
175
203
|
@field_serializer("cronjob")
|
176
204
|
def __serialize_cronjob(self, value: CronJob) -> str:
|
205
|
+
"""Serialize the cronjob field that store with CronJob object.
|
206
|
+
|
207
|
+
:rtype: str
|
208
|
+
"""
|
177
209
|
return str(value)
|
178
210
|
|
179
211
|
def generate(self, start: str | datetime) -> CronRunner:
|
180
|
-
"""Return Cron runner object.
|
212
|
+
"""Return Cron runner object.
|
213
|
+
|
214
|
+
:rtype: CronRunner
|
215
|
+
"""
|
181
216
|
if isinstance(start, str):
|
182
217
|
start: datetime = datetime.fromisoformat(start)
|
183
218
|
elif not isinstance(start, datetime):
|
@@ -187,6 +222,8 @@ class On(BaseModel):
|
|
187
222
|
def next(self, start: str | datetime) -> CronRunner:
|
188
223
|
"""Return a next datetime from Cron runner object that start with any
|
189
224
|
date that given from input.
|
225
|
+
|
226
|
+
:rtype: CronRunner
|
190
227
|
"""
|
191
228
|
runner: CronRunner = self.generate(start=start)
|
192
229
|
|
@@ -209,7 +246,26 @@ class YearOn(On):
|
|
209
246
|
Field(description="Cron job of this schedule"),
|
210
247
|
]
|
211
248
|
|
212
|
-
@field_validator(
|
213
|
-
|
214
|
-
|
215
|
-
|
249
|
+
@field_validator(
|
250
|
+
"cronjob", mode="before", json_schema_input_type=Union[CronJob, str]
|
251
|
+
)
|
252
|
+
def __prepare_cronjob(
|
253
|
+
cls, value: str | CronJobYear, info: ValidationInfo
|
254
|
+
) -> CronJobYear:
|
255
|
+
"""Prepare crontab value that able to receive with string type.
|
256
|
+
|
257
|
+
:rtype: CronJobYear
|
258
|
+
"""
|
259
|
+
extras: DictData = info.data.get("extras", {})
|
260
|
+
return (
|
261
|
+
CronJobYear(
|
262
|
+
value,
|
263
|
+
option={
|
264
|
+
name: extras[name]
|
265
|
+
for name in (f.name for f in fields(Options))
|
266
|
+
if name in extras
|
267
|
+
},
|
268
|
+
)
|
269
|
+
if isinstance(value, str)
|
270
|
+
else value
|
271
|
+
)
|
ddeutil/workflow/exceptions.py
CHANGED
ddeutil/workflow/hook.py
CHANGED
@@ -79,7 +79,9 @@ def make_registry(submodule: str) -> dict[str, Registry]:
|
|
79
79
|
:rtype: dict[str, Registry]
|
80
80
|
"""
|
81
81
|
rs: dict[str, Registry] = {}
|
82
|
-
|
82
|
+
regis_hooks: list[str] = config.regis_hook
|
83
|
+
regis_hooks.extend(["ddeutil.vendors"])
|
84
|
+
for module in regis_hooks:
|
83
85
|
# NOTE: try to sequential import task functions
|
84
86
|
try:
|
85
87
|
importer = import_module(f"{module}.{submodule}")
|
ddeutil/workflow/params.py
CHANGED
@@ -5,6 +5,7 @@
|
|
5
5
|
# ------------------------------------------------------------------------------
|
6
6
|
from __future__ import annotations
|
7
7
|
|
8
|
+
import decimal
|
8
9
|
import logging
|
9
10
|
from abc import ABC, abstractmethod
|
10
11
|
from datetime import date, datetime
|
@@ -49,7 +50,7 @@ class BaseParam(BaseModel, ABC):
|
|
49
50
|
|
50
51
|
class DefaultParam(BaseParam):
|
51
52
|
"""Default Parameter that will check default if it required. This model do
|
52
|
-
not implement the receive method.
|
53
|
+
not implement the `receive` method.
|
53
54
|
"""
|
54
55
|
|
55
56
|
required: bool = Field(
|
@@ -68,6 +69,15 @@ class DefaultParam(BaseParam):
|
|
68
69
|
)
|
69
70
|
|
70
71
|
|
72
|
+
# TODO: Not implement this parameter yet
|
73
|
+
class DateParam(DefaultParam):
|
74
|
+
"""Date parameter."""
|
75
|
+
|
76
|
+
type: Literal["date"] = "date"
|
77
|
+
|
78
|
+
def receive(self, value: Optional[str | date] = None) -> date: ...
|
79
|
+
|
80
|
+
|
71
81
|
class DatetimeParam(DefaultParam):
|
72
82
|
"""Datetime parameter."""
|
73
83
|
|
@@ -145,6 +155,13 @@ class IntParam(DefaultParam):
|
|
145
155
|
return value
|
146
156
|
|
147
157
|
|
158
|
+
# TODO: Not implement this parameter yet
|
159
|
+
class DecimalParam(DefaultParam):
|
160
|
+
type: Literal["decimal"] = "decimal"
|
161
|
+
|
162
|
+
def receive(self, value: float | None = None) -> decimal.Decimal: ...
|
163
|
+
|
164
|
+
|
148
165
|
class ChoiceParam(BaseParam):
|
149
166
|
"""Choice parameter."""
|
150
167
|
|
ddeutil/workflow/result.py
CHANGED
@@ -94,6 +94,7 @@ class Result:
|
|
94
94
|
# NOTE: Check the context has jobs key.
|
95
95
|
if "jobs" not in self.__dict__["context"]:
|
96
96
|
self.__dict__["context"]["jobs"] = {}
|
97
|
+
|
97
98
|
self.__dict__["context"]["jobs"].update(result.context)
|
98
99
|
|
99
100
|
# NOTE: Update running ID from an incoming result.
|