ddeutil-workflow 0.0.54__py3-none-any.whl → 0.0.56__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 +4 -2
- ddeutil/workflow/__main__.py +30 -0
- ddeutil/workflow/api/__init__.py +170 -1
- ddeutil/workflow/api/routes/job.py +22 -21
- ddeutil/workflow/api/routes/schedules.py +0 -2
- ddeutil/workflow/api/routes/workflows.py +3 -4
- ddeutil/workflow/conf.py +144 -94
- ddeutil/workflow/{cron.py → event.py} +36 -20
- ddeutil/workflow/exceptions.py +10 -1
- ddeutil/workflow/job.py +23 -14
- ddeutil/workflow/result.py +1 -0
- ddeutil/workflow/scheduler.py +33 -74
- ddeutil/workflow/stages.py +169 -116
- ddeutil/workflow/workflow.py +57 -106
- {ddeutil_workflow-0.0.54.dist-info → ddeutil_workflow-0.0.56.dist-info}/METADATA +5 -7
- ddeutil_workflow-0.0.56.dist-info/RECORD +31 -0
- ddeutil_workflow-0.0.56.dist-info/entry_points.txt +2 -0
- ddeutil/workflow/api/api.py +0 -170
- ddeutil_workflow-0.0.54.dist-info/RECORD +0 -31
- {ddeutil_workflow-0.0.54.dist-info → ddeutil_workflow-0.0.56.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.54.dist-info → ddeutil_workflow-0.0.56.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.54.dist-info → ddeutil_workflow-0.0.56.dist-info}/top_level.txt +0 -0
ddeutil/workflow/scheduler.py
CHANGED
@@ -34,7 +34,7 @@ from heapq import heappop, heappush
|
|
34
34
|
from pathlib import Path
|
35
35
|
from textwrap import dedent
|
36
36
|
from threading import Thread
|
37
|
-
from typing import Callable, Optional, TypedDict, Union
|
37
|
+
from typing import Any, Callable, Optional, TypedDict, Union
|
38
38
|
|
39
39
|
from pydantic import BaseModel, Field, ValidationInfo
|
40
40
|
from pydantic.functional_validators import field_validator, model_validator
|
@@ -52,8 +52,8 @@ except ImportError: # pragma: no cov
|
|
52
52
|
|
53
53
|
from .__cron import CronRunner
|
54
54
|
from .__types import DictData, TupleStr
|
55
|
-
from .conf import
|
56
|
-
from .
|
55
|
+
from .conf import FileLoad, Loader, dynamic
|
56
|
+
from .event import On
|
57
57
|
from .exceptions import ScheduleException, WorkflowException
|
58
58
|
from .logs import Audit, get_audit
|
59
59
|
from .result import SUCCESS, Result
|
@@ -113,21 +113,15 @@ class ScheduleWorkflow(BaseModel):
|
|
113
113
|
)
|
114
114
|
|
115
115
|
@model_validator(mode="before")
|
116
|
-
def __prepare_before__(cls,
|
117
|
-
"""Prepare incoming values before validating with model fields.
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
# VALIDATE: Add default the alias field with the name.
|
126
|
-
if not values.get("alias"):
|
127
|
-
values["alias"] = values.get("name")
|
128
|
-
|
129
|
-
cls.__bypass_on(values, extras=values.get("extras"))
|
130
|
-
return values
|
116
|
+
def __prepare_before__(cls, data: Any) -> Any:
|
117
|
+
"""Prepare incoming values before validating with model fields."""
|
118
|
+
if isinstance(data, dict):
|
119
|
+
# VALIDATE: Add default the alias field with the name.
|
120
|
+
if "alias" not in data:
|
121
|
+
data["alias"] = data.get("name")
|
122
|
+
|
123
|
+
cls.__bypass_on(data, extras=data.get("extras"))
|
124
|
+
return data
|
131
125
|
|
132
126
|
@classmethod
|
133
127
|
def __bypass_on(
|
@@ -135,8 +129,10 @@ class ScheduleWorkflow(BaseModel):
|
|
135
129
|
) -> DictData:
|
136
130
|
"""Bypass and prepare the on data to loaded config data.
|
137
131
|
|
138
|
-
:param data: A data that want to validate for model
|
139
|
-
|
132
|
+
:param data: (DictData) A data that want to validate for the model
|
133
|
+
initialization.
|
134
|
+
:param extras: (DictData) An extra parameter that want to override core
|
135
|
+
config values.
|
140
136
|
|
141
137
|
:rtype: DictData
|
142
138
|
"""
|
@@ -151,7 +147,7 @@ class ScheduleWorkflow(BaseModel):
|
|
151
147
|
# NOTE: Pass on value to Loader and keep on model object to on
|
152
148
|
# field.
|
153
149
|
data["on"] = [
|
154
|
-
|
150
|
+
FileLoad(n, externals=extras).data if isinstance(n, str) else n
|
155
151
|
for n in on
|
156
152
|
]
|
157
153
|
|
@@ -162,6 +158,10 @@ class ScheduleWorkflow(BaseModel):
|
|
162
158
|
"""Validate the on fields should not contain duplicate values and if it
|
163
159
|
contains every minute value, it should have only one on value.
|
164
160
|
|
161
|
+
:param value: (list[On]) A list of `On` object.
|
162
|
+
:param info: (ValidationInfo) An validation info object for getting an
|
163
|
+
extra parameter.
|
164
|
+
|
165
165
|
:rtype: list[On]
|
166
166
|
"""
|
167
167
|
set_ons: set[str] = {str(on.cronjob) for on in value}
|
@@ -191,23 +191,22 @@ class ScheduleWorkflow(BaseModel):
|
|
191
191
|
This task creation need queue to tracking release date already
|
192
192
|
mapped or not.
|
193
193
|
|
194
|
-
:param start_date: A start
|
195
|
-
|
194
|
+
:param start_date: (datetime) A start datetime that get from the
|
195
|
+
workflow schedule.
|
196
|
+
:param queue: (dict[str, ReleaseQueue]) A mapping of name and list of
|
197
|
+
datetime for queue.
|
196
198
|
|
197
199
|
:rtype: list[WorkflowTask]
|
198
200
|
:return: Return the list of WorkflowTask object from the specific
|
199
201
|
input datetime that mapping with the on field.
|
200
202
|
"""
|
201
|
-
workflow_tasks: list[WorkflowTask] = []
|
202
|
-
|
203
|
-
# NOTE: Loading workflow model from the name of workflow.
|
204
203
|
wf: Workflow = Workflow.from_conf(self.name, extras=self.extras)
|
205
204
|
wf_queue: ReleaseQueue = queue[self.alias]
|
206
205
|
|
207
206
|
# IMPORTANT: Create the default 'on' value if it does not pass the `on`
|
208
207
|
# field to the Schedule object.
|
209
208
|
ons: list[On] = self.on or wf.on.copy()
|
210
|
-
|
209
|
+
workflow_tasks: list[WorkflowTask] = []
|
211
210
|
for on in ons:
|
212
211
|
|
213
212
|
# NOTE: Create CronRunner instance from the start_date param.
|
@@ -250,7 +249,7 @@ class Schedule(BaseModel):
|
|
250
249
|
)
|
251
250
|
workflows: list[ScheduleWorkflow] = Field(
|
252
251
|
default_factory=list,
|
253
|
-
description="A list of ScheduleWorkflow
|
252
|
+
description="A list of ScheduleWorkflow model.",
|
254
253
|
)
|
255
254
|
|
256
255
|
@field_validator("desc", mode="after")
|
@@ -267,6 +266,8 @@ class Schedule(BaseModel):
|
|
267
266
|
def from_conf(
|
268
267
|
cls,
|
269
268
|
name: str,
|
269
|
+
*,
|
270
|
+
path: Optional[Path] = None,
|
270
271
|
extras: DictData | None = None,
|
271
272
|
) -> Self:
|
272
273
|
"""Create Schedule instance from the Loader object that only receive
|
@@ -274,6 +275,7 @@ class Schedule(BaseModel):
|
|
274
275
|
searching configuration data of this schedule model in conf path.
|
275
276
|
|
276
277
|
:param name: (str) A schedule name that want to pass to Loader object.
|
278
|
+
:param path: (Path) An override config path.
|
277
279
|
:param extras: An extra parameters that want to pass to Loader
|
278
280
|
object.
|
279
281
|
|
@@ -281,55 +283,14 @@ class Schedule(BaseModel):
|
|
281
283
|
|
282
284
|
:rtype: Self
|
283
285
|
"""
|
284
|
-
loader: Loader =
|
286
|
+
loader: Loader = FileLoad(name, path=path, extras=extras)
|
285
287
|
|
286
288
|
# NOTE: Validate the config type match with current connection model
|
287
289
|
if loader.type != cls.__name__:
|
288
290
|
raise ValueError(f"Type {loader.type} does not match with {cls}")
|
289
291
|
|
290
292
|
loader_data: DictData = copy.deepcopy(loader.data)
|
291
|
-
|
292
|
-
# NOTE: Add name to loader data
|
293
|
-
loader_data["name"] = name.replace(" ", "_")
|
294
|
-
|
295
|
-
if extras:
|
296
|
-
loader_data["extras"] = extras
|
297
|
-
|
298
|
-
return cls.model_validate(obj=loader_data)
|
299
|
-
|
300
|
-
@classmethod
|
301
|
-
def from_path(
|
302
|
-
cls,
|
303
|
-
name: str,
|
304
|
-
path: Path,
|
305
|
-
extras: DictData | None = None,
|
306
|
-
) -> Self:
|
307
|
-
"""Create Schedule instance from the SimLoad object that receive an
|
308
|
-
input schedule name and conf path. The loader object will use this
|
309
|
-
schedule name to searching configuration data of this schedule model
|
310
|
-
in conf path.
|
311
|
-
|
312
|
-
:param name: (str) A schedule name that want to pass to Loader object.
|
313
|
-
:param path: (Path) A config path that want to search.
|
314
|
-
:param extras: An external parameters that want to pass to Loader
|
315
|
-
object.
|
316
|
-
|
317
|
-
:raise ValueError: If the type does not match with current object.
|
318
|
-
|
319
|
-
:rtype: Self
|
320
|
-
"""
|
321
|
-
loader: SimLoad = SimLoad(
|
322
|
-
name, conf_path=path, externals=(extras or {})
|
323
|
-
)
|
324
|
-
|
325
|
-
# NOTE: Validate the config type match with current connection model
|
326
|
-
if loader.type != cls.__name__:
|
327
|
-
raise ValueError(f"Type {loader.type} does not match with {cls}")
|
328
|
-
|
329
|
-
loader_data: DictData = copy.deepcopy(loader.data)
|
330
|
-
|
331
|
-
# NOTE: Add name to loader data
|
332
|
-
loader_data["name"] = name.replace(" ", "_")
|
293
|
+
loader_data["name"] = name
|
333
294
|
|
334
295
|
if extras:
|
335
296
|
loader_data["extras"] = extras
|
@@ -535,9 +496,7 @@ def schedule_task(
|
|
535
496
|
current_release: datetime = current_date.replace(
|
536
497
|
second=0, microsecond=0
|
537
498
|
)
|
538
|
-
if (
|
539
|
-
first_date := q.first_queue.date
|
540
|
-
) > current_release: # pragma: no cov
|
499
|
+
if (first_date := q.queue[0].date) > current_release: # pragma: no cov
|
541
500
|
result.trace.debug(
|
542
501
|
f"[WORKFLOW]: Skip schedule "
|
543
502
|
f"{first_date:%Y-%m-%d %H:%M:%S} for : {task.alias!r}"
|