ddeutil-workflow 0.0.48__py3-none-any.whl → 0.0.49__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 -1
- ddeutil/workflow/api/routes/logs.py +6 -5
- ddeutil/workflow/conf.py +31 -31
- ddeutil/workflow/job.py +10 -5
- ddeutil/workflow/logs.py +144 -80
- ddeutil/workflow/result.py +8 -6
- ddeutil/workflow/reusables.py +3 -3
- ddeutil/workflow/scheduler.py +54 -44
- ddeutil/workflow/stages.py +278 -78
- ddeutil/workflow/utils.py +3 -3
- ddeutil/workflow/workflow.py +107 -87
- {ddeutil_workflow-0.0.48.dist-info → ddeutil_workflow-0.0.49.dist-info}/METADATA +3 -5
- ddeutil_workflow-0.0.49.dist-info/RECORD +31 -0
- ddeutil_workflow-0.0.48.dist-info/RECORD +0 -31
- {ddeutil_workflow-0.0.48.dist-info → ddeutil_workflow-0.0.49.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.48.dist-info → ddeutil_workflow-0.0.49.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.48.dist-info → ddeutil_workflow-0.0.49.dist-info}/top_level.txt +0 -0
ddeutil/workflow/reusables.py
CHANGED
@@ -91,7 +91,7 @@ def make_filter_registry(
|
|
91
91
|
:rtype: dict[str, FilterRegistry]
|
92
92
|
"""
|
93
93
|
rs: dict[str, FilterRegistry] = {}
|
94
|
-
for module in dynamic("
|
94
|
+
for module in dynamic("registry_filter", f=registers):
|
95
95
|
# NOTE: try to sequential import task functions
|
96
96
|
try:
|
97
97
|
importer = import_module(module)
|
@@ -343,7 +343,7 @@ def param2template(
|
|
343
343
|
:returns: An any getter value from the params input.
|
344
344
|
"""
|
345
345
|
registers: Optional[list[str]] = (
|
346
|
-
extras.get("
|
346
|
+
extras.get("registry_filter") if extras else None
|
347
347
|
)
|
348
348
|
filters: dict[str, FilterRegistry] = filters or make_filter_registry(
|
349
349
|
registers=registers
|
@@ -449,7 +449,7 @@ def make_registry(
|
|
449
449
|
"""
|
450
450
|
rs: dict[str, Registry] = {}
|
451
451
|
regis_calls: list[str] = dynamic(
|
452
|
-
"
|
452
|
+
"registry_caller", f=registries
|
453
453
|
) # pragma: no cov
|
454
454
|
regis_calls.extend(["ddeutil.vendors"])
|
455
455
|
|
ddeutil/workflow/scheduler.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
|
-
# [x] Use
|
6
|
+
# [x] Use dynamic config
|
7
7
|
"""The main schedule running is `schedule_runner` function that trigger the
|
8
8
|
multiprocess of `schedule_control` function for listing schedules on the
|
9
9
|
config by `Loader.finds(Schedule)`.
|
@@ -36,7 +36,7 @@ from textwrap import dedent
|
|
36
36
|
from threading import Thread
|
37
37
|
from typing import Callable, Optional, TypedDict, Union
|
38
38
|
|
39
|
-
from pydantic import BaseModel, Field
|
39
|
+
from pydantic import BaseModel, Field, ValidationInfo
|
40
40
|
from pydantic.functional_validators import field_validator, model_validator
|
41
41
|
from typing_extensions import Self
|
42
42
|
|
@@ -52,7 +52,7 @@ except ImportError: # pragma: no cov
|
|
52
52
|
|
53
53
|
from .__cron import CronRunner
|
54
54
|
from .__types import DictData, TupleStr
|
55
|
-
from .conf import Loader, SimLoad,
|
55
|
+
from .conf import Loader, SimLoad, dynamic
|
56
56
|
from .cron import On
|
57
57
|
from .exceptions import ScheduleException, WorkflowException
|
58
58
|
from .logs import Audit, get_audit
|
@@ -89,6 +89,11 @@ class ScheduleWorkflow(BaseModel):
|
|
89
89
|
not want to change on the workflow model.
|
90
90
|
"""
|
91
91
|
|
92
|
+
extras: DictData = Field(
|
93
|
+
default_factory=dict,
|
94
|
+
description="An extra parameters that want to override config values.",
|
95
|
+
)
|
96
|
+
|
92
97
|
alias: Optional[str] = Field(
|
93
98
|
default=None,
|
94
99
|
description="An alias name of workflow that use for schedule model.",
|
@@ -121,14 +126,17 @@ class ScheduleWorkflow(BaseModel):
|
|
121
126
|
if not values.get("alias"):
|
122
127
|
values["alias"] = values.get("name")
|
123
128
|
|
124
|
-
cls.__bypass_on(values)
|
129
|
+
cls.__bypass_on(values, extras=values.get("extras"))
|
125
130
|
return values
|
126
131
|
|
127
132
|
@classmethod
|
128
|
-
def __bypass_on(
|
133
|
+
def __bypass_on(
|
134
|
+
cls, data: DictData, *, extras: Optional[DictData] = None
|
135
|
+
) -> DictData:
|
129
136
|
"""Bypass and prepare the on data to loaded config data.
|
130
137
|
|
131
138
|
:param data: A data that want to validate for model initialization.
|
139
|
+
:param extras: An extra parameter that want to override core config.
|
132
140
|
|
133
141
|
:rtype: DictData
|
134
142
|
"""
|
@@ -143,14 +151,14 @@ class ScheduleWorkflow(BaseModel):
|
|
143
151
|
# NOTE: Pass on value to Loader and keep on model object to on
|
144
152
|
# field.
|
145
153
|
data["on"] = [
|
146
|
-
Loader(n, externals=
|
154
|
+
Loader(n, externals=extras).data if isinstance(n, str) else n
|
147
155
|
for n in on
|
148
156
|
]
|
149
157
|
|
150
158
|
return data
|
151
159
|
|
152
160
|
@field_validator("on", mode="after")
|
153
|
-
def __on_no_dup__(cls, value: list[On]) -> list[On]:
|
161
|
+
def __on_no_dup__(cls, value: list[On], info: ValidationInfo) -> list[On]:
|
154
162
|
"""Validate the on fields should not contain duplicate values and if it
|
155
163
|
contains every minute value, it should have only one on value.
|
156
164
|
|
@@ -162,10 +170,12 @@ class ScheduleWorkflow(BaseModel):
|
|
162
170
|
"The on fields should not contain duplicate on value."
|
163
171
|
)
|
164
172
|
|
165
|
-
|
173
|
+
extras: Optional[DictData] = info.data.get("extras")
|
174
|
+
if len(set_ons) > (
|
175
|
+
conf := dynamic("max_cron_per_workflow", extras=extras)
|
176
|
+
):
|
166
177
|
raise ValueError(
|
167
|
-
f"The number of the on should not more than "
|
168
|
-
f"{config.max_on_per_workflow} crontab."
|
178
|
+
f"The number of the on should not more than {conf} crontabs."
|
169
179
|
)
|
170
180
|
|
171
181
|
return value
|
@@ -174,8 +184,6 @@ class ScheduleWorkflow(BaseModel):
|
|
174
184
|
self,
|
175
185
|
start_date: datetime,
|
176
186
|
queue: dict[str, ReleaseQueue],
|
177
|
-
*,
|
178
|
-
extras: DictData | None = None,
|
179
187
|
) -> list[WorkflowTask]:
|
180
188
|
"""Return the list of WorkflowTask object from the specific input
|
181
189
|
datetime that mapping with the on field.
|
@@ -185,17 +193,15 @@ class ScheduleWorkflow(BaseModel):
|
|
185
193
|
|
186
194
|
:param start_date: A start date that get from the workflow schedule.
|
187
195
|
:param queue: A mapping of name and list of datetime for queue.
|
188
|
-
:param extras: An extra parameters that pass to the Loader object.
|
189
196
|
|
190
197
|
:rtype: list[WorkflowTask]
|
191
198
|
:return: Return the list of WorkflowTask object from the specific
|
192
199
|
input datetime that mapping with the on field.
|
193
200
|
"""
|
194
201
|
workflow_tasks: list[WorkflowTask] = []
|
195
|
-
extras: DictData = extras or {}
|
196
202
|
|
197
203
|
# NOTE: Loading workflow model from the name of workflow.
|
198
|
-
wf: Workflow = Workflow.from_conf(self.name, extras=extras)
|
204
|
+
wf: Workflow = Workflow.from_conf(self.name, extras=self.extras)
|
199
205
|
wf_queue: ReleaseQueue = queue[self.alias]
|
200
206
|
|
201
207
|
# IMPORTANT: Create the default 'on' value if it does not pass the `on`
|
@@ -217,6 +223,7 @@ class ScheduleWorkflow(BaseModel):
|
|
217
223
|
workflow=wf,
|
218
224
|
runner=runner,
|
219
225
|
values=self.values,
|
226
|
+
extras=self.extras,
|
220
227
|
),
|
221
228
|
)
|
222
229
|
|
@@ -232,7 +239,7 @@ class Schedule(BaseModel):
|
|
232
239
|
|
233
240
|
extras: DictData = Field(
|
234
241
|
default_factory=dict,
|
235
|
-
description="An extra override config values.",
|
242
|
+
description="An extra parameters that want to override config values.",
|
236
243
|
)
|
237
244
|
|
238
245
|
desc: Optional[str] = Field(
|
@@ -333,8 +340,6 @@ class Schedule(BaseModel):
|
|
333
340
|
self,
|
334
341
|
start_date: datetime,
|
335
342
|
queue: dict[str, ReleaseQueue],
|
336
|
-
*,
|
337
|
-
extras: DictData | None = None,
|
338
343
|
) -> list[WorkflowTask]:
|
339
344
|
"""Return the list of WorkflowTask object from the specific input
|
340
345
|
datetime that mapping with the on field from workflow schedule model.
|
@@ -342,8 +347,6 @@ class Schedule(BaseModel):
|
|
342
347
|
:param start_date: A start date that get from the workflow schedule.
|
343
348
|
:param queue: (dict[str, ReleaseQueue]) A mapping of name and list of
|
344
349
|
datetime for queue.
|
345
|
-
:param extras: (DictData) An extra parameters that pass to the Loader
|
346
|
-
object.
|
347
350
|
|
348
351
|
:rtype: list[WorkflowTask]
|
349
352
|
:return: Return the list of WorkflowTask object from the specific
|
@@ -352,13 +355,13 @@ class Schedule(BaseModel):
|
|
352
355
|
workflow_tasks: list[WorkflowTask] = []
|
353
356
|
|
354
357
|
for workflow in self.workflows:
|
358
|
+
if self.extras:
|
359
|
+
workflow.extras = self.extras
|
355
360
|
|
356
361
|
if workflow.alias not in queue:
|
357
362
|
queue[workflow.alias] = ReleaseQueue()
|
358
363
|
|
359
|
-
workflow_tasks.extend(
|
360
|
-
workflow.tasks(start_date, queue=queue, extras=extras)
|
361
|
-
)
|
364
|
+
workflow_tasks.extend(workflow.tasks(start_date, queue=queue))
|
362
365
|
|
363
366
|
return workflow_tasks
|
364
367
|
|
@@ -366,24 +369,26 @@ class Schedule(BaseModel):
|
|
366
369
|
self,
|
367
370
|
*,
|
368
371
|
stop: datetime | None = None,
|
369
|
-
extras: DictData | None = None,
|
370
372
|
audit: type[Audit] | None = None,
|
371
373
|
parent_run_id: str | None = None,
|
372
374
|
) -> Result: # pragma: no cov
|
373
375
|
"""Pending this schedule tasks with the schedule package.
|
374
376
|
|
375
377
|
:param stop: A datetime value that use to stop running schedule.
|
376
|
-
:param extras: An extra parameters that pass to Loader.
|
377
378
|
:param audit: An audit class that use on the workflow task release for
|
378
379
|
writing its release audit context.
|
379
380
|
:param parent_run_id: A parent workflow running ID for this release.
|
380
381
|
"""
|
381
|
-
audit: type[Audit] = audit or get_audit()
|
382
|
+
audit: type[Audit] = audit or get_audit(extras=self.extras)
|
382
383
|
result: Result = Result().set_parent_run_id(parent_run_id)
|
383
384
|
|
384
385
|
# NOTE: Create the start and stop datetime.
|
385
|
-
start_date: datetime = datetime.now(
|
386
|
-
|
386
|
+
start_date: datetime = datetime.now(
|
387
|
+
tz=dynamic("tz", extras=self.extras)
|
388
|
+
)
|
389
|
+
stop_date: datetime = stop or (
|
390
|
+
start_date + dynamic("stop_boundary_delta", extras=self.extras)
|
391
|
+
)
|
387
392
|
|
388
393
|
# IMPORTANT: Create main mapping of queue and thread object.
|
389
394
|
queue: dict[str, ReleaseQueue] = {}
|
@@ -394,7 +399,7 @@ class Schedule(BaseModel):
|
|
394
399
|
) + timedelta(minutes=1)
|
395
400
|
|
396
401
|
scheduler_pending(
|
397
|
-
tasks=self.tasks(start_date_waiting, queue=queue
|
402
|
+
tasks=self.tasks(start_date_waiting, queue=queue),
|
398
403
|
stop=stop_date,
|
399
404
|
queue=queue,
|
400
405
|
threads=threads,
|
@@ -469,6 +474,7 @@ def schedule_task(
|
|
469
474
|
audit: type[Audit],
|
470
475
|
*,
|
471
476
|
parent_run_id: str | None = None,
|
477
|
+
extras: Optional[DictData] = None,
|
472
478
|
) -> ResultOrCancel:
|
473
479
|
"""Schedule task function that generate thread of workflow task release
|
474
480
|
method in background. This function do the same logic as the workflow poke
|
@@ -483,12 +489,13 @@ def schedule_task(
|
|
483
489
|
:param threads: A mapping of alias name and Thread object.
|
484
490
|
:param audit: An audit class that want to make audit object.
|
485
491
|
:param parent_run_id: A parent workflow running ID for this release.
|
492
|
+
:param extras: An extra parameter that want to override the core config.
|
486
493
|
|
487
494
|
:rtype: ResultOrCancel
|
488
495
|
"""
|
489
496
|
result: Result = Result().set_parent_run_id(parent_run_id)
|
490
|
-
current_date: datetime = datetime.now(tz=
|
491
|
-
if current_date > stop.replace(tzinfo=
|
497
|
+
current_date: datetime = datetime.now(tz=dynamic("tz", extras=extras))
|
498
|
+
if current_date > stop.replace(tzinfo=dynamic("tz", extras=extras)):
|
492
499
|
return CancelJob
|
493
500
|
|
494
501
|
# IMPORTANT:
|
@@ -569,7 +576,7 @@ def schedule_task(
|
|
569
576
|
|
570
577
|
threads[thread_name] = {
|
571
578
|
"thread": thread,
|
572
|
-
"start_date": datetime.now(tz=
|
579
|
+
"start_date": datetime.now(tz=dynamic("tz", extras=extras)),
|
573
580
|
"release_date": release.date,
|
574
581
|
}
|
575
582
|
|
@@ -714,8 +721,8 @@ def scheduler_pending(
|
|
714
721
|
def schedule_control(
|
715
722
|
schedules: list[str],
|
716
723
|
stop: datetime | None = None,
|
717
|
-
extras: DictData | None = None,
|
718
724
|
*,
|
725
|
+
extras: DictData | None = None,
|
719
726
|
audit: type[Audit] | None = None,
|
720
727
|
parent_run_id: str | None = None,
|
721
728
|
) -> Result: # pragma: no cov
|
@@ -732,12 +739,14 @@ def schedule_control(
|
|
732
739
|
|
733
740
|
:rtype: Result
|
734
741
|
"""
|
735
|
-
audit: type[Audit] = audit or get_audit()
|
742
|
+
audit: type[Audit] = audit or get_audit(extras=extras)
|
736
743
|
result: Result = Result().set_parent_run_id(parent_run_id)
|
737
744
|
|
738
745
|
# NOTE: Create the start and stop datetime.
|
739
|
-
start_date: datetime = datetime.now(tz=
|
740
|
-
stop_date: datetime = stop or (
|
746
|
+
start_date: datetime = datetime.now(tz=dynamic("tz", extras=extras))
|
747
|
+
stop_date: datetime = stop or (
|
748
|
+
start_date + dynamic("stop_boundary_delta", extras=extras)
|
749
|
+
)
|
741
750
|
|
742
751
|
# IMPORTANT: Create main mapping of queue and thread object.
|
743
752
|
queue: dict[str, ReleaseQueue] = {}
|
@@ -750,10 +759,10 @@ def schedule_control(
|
|
750
759
|
tasks: list[WorkflowTask] = []
|
751
760
|
for name in schedules:
|
752
761
|
tasks.extend(
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
762
|
+
(
|
763
|
+
Schedule.from_conf(name, extras=extras).tasks(
|
764
|
+
start_date_waiting, queue=queue
|
765
|
+
)
|
757
766
|
),
|
758
767
|
)
|
759
768
|
|
@@ -771,6 +780,7 @@ def schedule_control(
|
|
771
780
|
|
772
781
|
def schedule_runner(
|
773
782
|
stop: datetime | None = None,
|
783
|
+
*,
|
774
784
|
extras: DictData | None = None,
|
775
785
|
excluded: list[str] | None = None,
|
776
786
|
) -> Result: # pragma: no cov
|
@@ -803,7 +813,7 @@ def schedule_runner(
|
|
803
813
|
context: DictData = {"schedules": [], "threads": []}
|
804
814
|
|
805
815
|
with ProcessPoolExecutor(
|
806
|
-
max_workers=
|
816
|
+
max_workers=dynamic("max_schedule_process", extras=extras),
|
807
817
|
) as executor:
|
808
818
|
|
809
819
|
futures: list[Future] = [
|
@@ -811,12 +821,12 @@ def schedule_runner(
|
|
811
821
|
schedule_control,
|
812
822
|
schedules=[load[0] for load in loader],
|
813
823
|
stop=stop,
|
814
|
-
extras=
|
824
|
+
extras=extras,
|
815
825
|
parent_run_id=result.parent_run_id,
|
816
826
|
)
|
817
827
|
for loader in batch(
|
818
828
|
Loader.finds(Schedule, excluded=excluded),
|
819
|
-
n=
|
829
|
+
n=dynamic("max_schedule_per_process", extras=extras),
|
820
830
|
)
|
821
831
|
]
|
822
832
|
|
@@ -831,4 +841,4 @@ def schedule_runner(
|
|
831
841
|
context["schedule"].extend(rs.context.get("schedules", []))
|
832
842
|
context["threads"].extend(rs.context.get("threads", []))
|
833
843
|
|
834
|
-
return result.catch(status=
|
844
|
+
return result.catch(status=SUCCESS, context=context)
|