ddeutil-workflow 0.0.39__tar.gz → 0.0.41__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.39/src/ddeutil_workflow.egg-info → ddeutil_workflow-0.0.41}/PKG-INFO +10 -4
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/pyproject.toml +10 -3
- ddeutil_workflow-0.0.41/src/ddeutil/workflow/__about__.py +1 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/__cron.py +89 -25
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/__init__.py +21 -25
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/api/api.py +1 -1
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/api/routes/logs.py +1 -2
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/api/routes/workflows.py +1 -1
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/conf.py +19 -24
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/job.py +55 -4
- ddeutil_workflow-0.0.41/src/ddeutil/workflow/logs.py +593 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/params.py +1 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/result.py +6 -15
- ddeutil_workflow-0.0.39/src/ddeutil/workflow/templates.py → ddeutil_workflow-0.0.41/src/ddeutil/workflow/reusables.py +195 -9
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/scheduler.py +41 -2
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/stages.py +188 -24
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/utils.py +11 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/workflow.py +61 -14
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41/src/ddeutil_workflow.egg-info}/PKG-INFO +10 -4
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil_workflow.egg-info/SOURCES.txt +6 -8
- ddeutil_workflow-0.0.41/src/ddeutil_workflow.egg-info/requires.txt +21 -0
- ddeutil_workflow-0.0.39/tests/test_audit.py → ddeutil_workflow-0.0.41/tests/test_logs_audit.py +1 -1
- ddeutil_workflow-0.0.39/tests/test_call_tag.py → ddeutil_workflow-0.0.41/tests/test_reusables_call_tag.py +9 -3
- ddeutil_workflow-0.0.39/tests/test_templates.py → ddeutil_workflow-0.0.41/tests/test_reusables_template.py +1 -1
- ddeutil_workflow-0.0.41/tests/test_reusables_template_filter.py +108 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_schedule.py +15 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_workflow.py +43 -1
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_workflow_task.py +1 -1
- ddeutil_workflow-0.0.39/src/ddeutil/workflow/__about__.py +0 -1
- ddeutil_workflow-0.0.39/src/ddeutil/workflow/audit.py +0 -257
- ddeutil_workflow-0.0.39/src/ddeutil/workflow/caller.py +0 -179
- ddeutil_workflow-0.0.39/src/ddeutil/workflow/logs.py +0 -331
- ddeutil_workflow-0.0.39/src/ddeutil_workflow.egg-info/requires.txt +0 -14
- ddeutil_workflow-0.0.39/tests/test_templates_filter.py +0 -102
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/LICENSE +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/README.md +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/setup.cfg +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/__types.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/api/__init__.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/api/log.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/api/repeat.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/api/routes/__init__.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/api/routes/job.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/api/routes/schedules.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/context.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/cron.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/exceptions.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil_workflow.egg-info/dependency_links.txt +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil_workflow.egg-info/top_level.txt +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test__cron.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test__regex.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_conf.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_context.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_cron_on.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_job.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_job_exec.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_job_exec_strategy.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_job_strategy.py +0 -0
- /ddeutil_workflow-0.0.39/tests/test_logs.py → /ddeutil_workflow-0.0.41/tests/test_logs_trace.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_params.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_release.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_release_queue.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_result.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_schedule_pending.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_schedule_tasks.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_schedule_workflow.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_scheduler_control.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_stage.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_stage_handler_exec.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_utils.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_workflow_exec.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_workflow_exec_job.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_workflow_exec_poke.py +0 -0
- {ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/tests/test_workflow_exec_release.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ddeutil-workflow
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.41
|
4
4
|
Summary: Lightweight workflow orchestration
|
5
5
|
Author-email: ddeutils <korawich.anu@gmail.com>
|
6
6
|
License: MIT
|
@@ -23,10 +23,16 @@ 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
|
+
Provides-Extra: all
|
31
|
+
Requires-Dist: fastapi<1.0.0,>=0.115.0; extra == "all"
|
32
|
+
Requires-Dist: httpx; extra == "all"
|
33
|
+
Requires-Dist: ujson; extra == "all"
|
34
|
+
Requires-Dist: aiofiles; extra == "all"
|
35
|
+
Requires-Dist: aiohttp; extra == "all"
|
30
36
|
Provides-Extra: api
|
31
37
|
Requires-Dist: fastapi<1.0.0,>=0.115.0; extra == "api"
|
32
38
|
Requires-Dist: httpx; extra == "api"
|
@@ -27,14 +27,21 @@ classifiers = [
|
|
27
27
|
requires-python = ">=3.9.13"
|
28
28
|
dependencies = [
|
29
29
|
"ddeutil>=0.4.6",
|
30
|
-
"ddeutil-io[yaml,toml]>=0.2.
|
31
|
-
"pydantic==2.
|
32
|
-
"python-dotenv==1.0
|
30
|
+
"ddeutil-io[yaml,toml]>=0.2.10",
|
31
|
+
"pydantic==2.11.1",
|
32
|
+
"python-dotenv==1.1.0",
|
33
33
|
"schedule==1.2.2,<2.0.0",
|
34
34
|
]
|
35
35
|
dynamic = ["version"]
|
36
36
|
|
37
37
|
[project.optional-dependencies]
|
38
|
+
all = [
|
39
|
+
"fastapi>=0.115.0,<1.0.0",
|
40
|
+
"httpx",
|
41
|
+
"ujson",
|
42
|
+
"aiofiles",
|
43
|
+
"aiohttp",
|
44
|
+
]
|
38
45
|
api = [
|
39
46
|
"fastapi>=0.115.0,<1.0.0",
|
40
47
|
"httpx",
|
@@ -0,0 +1 @@
|
|
1
|
+
__version__: str = "0.0.41"
|
@@ -37,7 +37,7 @@ class CronYearLimit(Exception): ...
|
|
37
37
|
def str2cron(value: str) -> str: # pragma: no cov
|
38
38
|
"""Convert Special String with the @ prefix to Crontab value.
|
39
39
|
|
40
|
-
:param value: A string value that want to convert to cron value.
|
40
|
+
:param value: (str) A string value that want to convert to cron value.
|
41
41
|
:rtype: str
|
42
42
|
|
43
43
|
Table:
|
@@ -82,6 +82,9 @@ class Unit:
|
|
82
82
|
)
|
83
83
|
|
84
84
|
|
85
|
+
Units = tuple[Unit, ...]
|
86
|
+
|
87
|
+
|
85
88
|
@dataclass
|
86
89
|
class Options:
|
87
90
|
"""Options dataclass for config CronPart object."""
|
@@ -91,7 +94,7 @@ class Options:
|
|
91
94
|
output_hashes: bool = False
|
92
95
|
|
93
96
|
|
94
|
-
CRON_UNITS:
|
97
|
+
CRON_UNITS: Units = (
|
95
98
|
Unit(
|
96
99
|
name="minute",
|
97
100
|
range=partial(range, 0, 60),
|
@@ -147,7 +150,7 @@ CRON_UNITS: tuple[Unit, ...] = (
|
|
147
150
|
),
|
148
151
|
)
|
149
152
|
|
150
|
-
CRON_UNITS_YEAR:
|
153
|
+
CRON_UNITS_YEAR: Units = CRON_UNITS + (
|
151
154
|
Unit(
|
152
155
|
name="year",
|
153
156
|
range=partial(range, 1990, 2101),
|
@@ -220,18 +223,21 @@ class CronPart:
|
|
220
223
|
return ",".join(cron_range_strings) if cron_range_strings else "?"
|
221
224
|
|
222
225
|
def __repr__(self) -> str:
|
226
|
+
"""Override __repr__ method."""
|
223
227
|
return (
|
224
228
|
f"{self.__class__.__name__}"
|
225
229
|
f"(unit={self.unit}, values={self.__str__()!r})"
|
226
230
|
)
|
227
231
|
|
228
232
|
def __lt__(self, other) -> bool:
|
233
|
+
"""Override __lt__ method."""
|
229
234
|
if isinstance(other, CronPart):
|
230
235
|
return self.values < other.values
|
231
236
|
elif isinstance(other, list):
|
232
237
|
return self.values < other
|
233
238
|
|
234
239
|
def __eq__(self, other) -> bool:
|
240
|
+
"""Override __eq__ method."""
|
235
241
|
if isinstance(other, CronPart):
|
236
242
|
return self.values == other.values
|
237
243
|
elif isinstance(other, list):
|
@@ -239,18 +245,26 @@ class CronPart:
|
|
239
245
|
|
240
246
|
@property
|
241
247
|
def min(self) -> int:
|
242
|
-
"""Returns the smallest value in the range.
|
248
|
+
"""Returns the smallest value in the range.
|
249
|
+
|
250
|
+
:rtype: int
|
251
|
+
"""
|
243
252
|
return self.values[0]
|
244
253
|
|
245
254
|
@property
|
246
255
|
def max(self) -> int:
|
247
|
-
"""Returns the largest value in the range.
|
256
|
+
"""Returns the largest value in the range.
|
257
|
+
|
258
|
+
:rtype: int
|
259
|
+
"""
|
248
260
|
return self.values[-1]
|
249
261
|
|
250
262
|
@property
|
251
263
|
def step(self) -> Optional[int]:
|
252
264
|
"""Returns the difference between first and second elements in the
|
253
265
|
range.
|
266
|
+
|
267
|
+
:rtype: Optional[int]
|
254
268
|
"""
|
255
269
|
if (
|
256
270
|
len(self.values) > 2
|
@@ -260,15 +274,17 @@ class CronPart:
|
|
260
274
|
|
261
275
|
@property
|
262
276
|
def is_full(self) -> bool:
|
263
|
-
"""Returns true if range has all the values of the unit.
|
277
|
+
"""Returns true if range has all the values of the unit.
|
278
|
+
|
279
|
+
:rtype: bool
|
280
|
+
"""
|
264
281
|
return len(self.values) == (self.unit.max - self.unit.min + 1)
|
265
282
|
|
266
283
|
def from_str(self, value: str) -> tuple[int, ...]:
|
267
284
|
"""Parses a string as a range of positive integers. The string should
|
268
285
|
include only `-` and `,` special strings.
|
269
286
|
|
270
|
-
:param value: A string value that want to parse
|
271
|
-
:type value: str
|
287
|
+
:param value: (str) A string value that want to parse
|
272
288
|
|
273
289
|
TODO: support for `L`, `W`, and `#`
|
274
290
|
---
|
@@ -351,6 +367,8 @@ class CronPart:
|
|
351
367
|
For example if value == 'JAN,AUG' it will replace to '1,8'.
|
352
368
|
|
353
369
|
:param value: A string value that want to replace alternative to int.
|
370
|
+
|
371
|
+
:rtype: str
|
354
372
|
"""
|
355
373
|
for i, alt in enumerate(self.unit.alt):
|
356
374
|
if alt in value:
|
@@ -361,6 +379,7 @@ class CronPart:
|
|
361
379
|
"""Replaces all 7 with 0 as Sunday can be represented by both.
|
362
380
|
|
363
381
|
:param values: list or iter of int that want to mode by 7
|
382
|
+
|
364
383
|
:rtype: list[int]
|
365
384
|
"""
|
366
385
|
if self.unit.name == "weekday":
|
@@ -388,7 +407,13 @@ class CronPart:
|
|
388
407
|
return values
|
389
408
|
|
390
409
|
def _parse_range(self, value: str) -> list[int]:
|
391
|
-
"""Parses a range string.
|
410
|
+
"""Parses a range string from a cron-part.
|
411
|
+
|
412
|
+
:param value: (str) A cron-part string value that want to parse.
|
413
|
+
|
414
|
+
:rtype: list[int]
|
415
|
+
:return: A list of parse range.
|
416
|
+
"""
|
392
417
|
if value == "*":
|
393
418
|
return list(self.unit.range())
|
394
419
|
elif value.count("-") > 1:
|
@@ -410,7 +435,13 @@ class CronPart:
|
|
410
435
|
values: list[int],
|
411
436
|
step: int | None = None,
|
412
437
|
) -> list[int]:
|
413
|
-
"""Applies an interval step to a collection of values.
|
438
|
+
"""Applies an interval step to a collection of values.
|
439
|
+
|
440
|
+
:param values:
|
441
|
+
:param step:
|
442
|
+
|
443
|
+
:rtype: list[int]
|
444
|
+
"""
|
414
445
|
if not step:
|
415
446
|
return values
|
416
447
|
elif (_step := int(step)) < 1:
|
@@ -427,7 +458,10 @@ class CronPart:
|
|
427
458
|
|
428
459
|
@property
|
429
460
|
def is_interval(self) -> bool:
|
430
|
-
"""Returns true if the range can be represented as an interval.
|
461
|
+
"""Returns true if the range can be represented as an interval.
|
462
|
+
|
463
|
+
:rtype: bool
|
464
|
+
"""
|
431
465
|
if not (step := self.step):
|
432
466
|
return False
|
433
467
|
for idx, value in enumerate(self.values):
|
@@ -439,7 +473,10 @@ class CronPart:
|
|
439
473
|
|
440
474
|
@property
|
441
475
|
def is_full_interval(self) -> bool:
|
442
|
-
"""Returns true if the range contains all the interval values.
|
476
|
+
"""Returns true if the range contains all the interval values.
|
477
|
+
|
478
|
+
:rtype: bool
|
479
|
+
"""
|
443
480
|
if step := self.step:
|
444
481
|
return (
|
445
482
|
self.min == self.unit.min
|
@@ -482,8 +519,7 @@ class CronPart:
|
|
482
519
|
"""Formats weekday and month names as string when the relevant options
|
483
520
|
are set.
|
484
521
|
|
485
|
-
:param value:
|
486
|
-
:type value: int
|
522
|
+
:param value: (int) An int value that want to get from the unit.
|
487
523
|
|
488
524
|
:rtype: int | str
|
489
525
|
"""
|
@@ -538,8 +574,8 @@ class CronJob:
|
|
538
574
|
monitor-data-warehouse-schedule.html
|
539
575
|
"""
|
540
576
|
|
541
|
-
cron_length: int = 5
|
542
|
-
cron_units:
|
577
|
+
cron_length: ClassVar[int] = 5
|
578
|
+
cron_units: ClassVar[Units] = CRON_UNITS
|
543
579
|
|
544
580
|
def __init__(
|
545
581
|
self,
|
@@ -600,38 +636,64 @@ class CronJob:
|
|
600
636
|
|
601
637
|
@property
|
602
638
|
def parts_order(self) -> Iterator[CronPart]:
|
639
|
+
"""Return iterator of CronPart instance.
|
640
|
+
|
641
|
+
:rtype: Iterator[CronPart]
|
642
|
+
"""
|
603
643
|
return reversed(self.parts[:3] + [self.parts[4], self.parts[3]])
|
604
644
|
|
605
645
|
@property
|
606
646
|
def minute(self) -> CronPart:
|
607
|
-
"""Return part of minute.
|
647
|
+
"""Return part of minute with the CronPart instance.
|
648
|
+
|
649
|
+
:rtype: CronPart
|
650
|
+
"""
|
608
651
|
return self.parts[0]
|
609
652
|
|
610
653
|
@property
|
611
654
|
def hour(self) -> CronPart:
|
612
|
-
"""Return part of hour.
|
655
|
+
"""Return part of hour with the CronPart instance.
|
656
|
+
|
657
|
+
:rtype: CronPart
|
658
|
+
"""
|
613
659
|
return self.parts[1]
|
614
660
|
|
615
661
|
@property
|
616
662
|
def day(self) -> CronPart:
|
617
|
-
"""Return part of day.
|
663
|
+
"""Return part of day with the CronPart instance.
|
664
|
+
|
665
|
+
:rtype: CronPart
|
666
|
+
"""
|
618
667
|
return self.parts[2]
|
619
668
|
|
620
669
|
@property
|
621
670
|
def month(self) -> CronPart:
|
622
|
-
"""Return part of month.
|
671
|
+
"""Return part of month with the CronPart instance.
|
672
|
+
|
673
|
+
:rtype: CronPart
|
674
|
+
"""
|
623
675
|
return self.parts[3]
|
624
676
|
|
625
677
|
@property
|
626
678
|
def dow(self) -> CronPart:
|
627
|
-
"""Return part of day of month.
|
679
|
+
"""Return part of day of month with the CronPart instance.
|
680
|
+
|
681
|
+
:rtype: CronPart
|
682
|
+
"""
|
628
683
|
return self.parts[4]
|
629
684
|
|
630
685
|
def to_list(self) -> list[list[int]]:
|
631
|
-
"""Returns the cron schedule as a 2-dimensional list of integers.
|
686
|
+
"""Returns the cron schedule as a 2-dimensional list of integers.
|
687
|
+
|
688
|
+
:rtype: list[list[int]]
|
689
|
+
"""
|
632
690
|
return [part.values for part in self.parts]
|
633
691
|
|
634
692
|
def check(self, date: datetime, mode: str) -> bool:
|
693
|
+
"""Check the date value with the mode.
|
694
|
+
|
695
|
+
:rtype: bool
|
696
|
+
"""
|
635
697
|
assert mode in ("year", "month", "day", "hour", "minute")
|
636
698
|
return getattr(date, mode) in getattr(self, mode).values
|
637
699
|
|
@@ -667,12 +729,14 @@ class CronJobYear(CronJob):
|
|
667
729
|
(vi) year (1990 - 2100)
|
668
730
|
"""
|
669
731
|
|
670
|
-
cron_length = 6
|
671
|
-
cron_units = CRON_UNITS_YEAR
|
732
|
+
cron_length: ClassVar[int] = 6
|
733
|
+
cron_units: ClassVar[Units] = CRON_UNITS_YEAR
|
672
734
|
|
673
735
|
@property
|
674
736
|
def year(self) -> CronPart:
|
675
|
-
"""Return part of year.
|
737
|
+
"""Return part of year with the CronPart instance.
|
738
|
+
|
739
|
+
:rtype: CronPart"""
|
676
740
|
return self.parts[5]
|
677
741
|
|
678
742
|
|
@@ -5,17 +5,6 @@
|
|
5
5
|
# ------------------------------------------------------------------------------
|
6
6
|
from .__cron import CronJob, CronRunner
|
7
7
|
from .__types import Re
|
8
|
-
from .audit import (
|
9
|
-
Audit,
|
10
|
-
get_audit,
|
11
|
-
)
|
12
|
-
from .caller import (
|
13
|
-
ReturnTagFunc,
|
14
|
-
TagFunc,
|
15
|
-
extract_call,
|
16
|
-
make_registry,
|
17
|
-
tag,
|
18
|
-
)
|
19
8
|
from .conf import (
|
20
9
|
Config,
|
21
10
|
Loader,
|
@@ -43,8 +32,10 @@ from .job import (
|
|
43
32
|
local_execute_strategy,
|
44
33
|
)
|
45
34
|
from .logs import (
|
35
|
+
Audit,
|
46
36
|
TraceData,
|
47
37
|
TraceLog,
|
38
|
+
get_audit,
|
48
39
|
get_dt_tznow,
|
49
40
|
get_trace,
|
50
41
|
)
|
@@ -58,7 +49,24 @@ from .params import (
|
|
58
49
|
from .result import (
|
59
50
|
Result,
|
60
51
|
Status,
|
61
|
-
|
52
|
+
)
|
53
|
+
from .reusables import (
|
54
|
+
FILTERS,
|
55
|
+
FilterFunc,
|
56
|
+
FilterRegistry,
|
57
|
+
ReturnTagFunc,
|
58
|
+
TagFunc,
|
59
|
+
custom_filter,
|
60
|
+
extract_call,
|
61
|
+
get_args_const,
|
62
|
+
has_template,
|
63
|
+
make_filter_registry,
|
64
|
+
make_registry,
|
65
|
+
map_post_filter,
|
66
|
+
not_in_template,
|
67
|
+
param2template,
|
68
|
+
str2template,
|
69
|
+
tag,
|
62
70
|
)
|
63
71
|
from .scheduler import (
|
64
72
|
Schedule,
|
@@ -77,22 +85,10 @@ from .stages import (
|
|
77
85
|
Stage,
|
78
86
|
TriggerStage,
|
79
87
|
)
|
80
|
-
from .templates import (
|
81
|
-
FILTERS,
|
82
|
-
FilterFunc,
|
83
|
-
FilterRegistry,
|
84
|
-
custom_filter,
|
85
|
-
get_args_const,
|
86
|
-
has_template,
|
87
|
-
make_filter_registry,
|
88
|
-
map_post_filter,
|
89
|
-
not_in_template,
|
90
|
-
param2template,
|
91
|
-
str2template,
|
92
|
-
)
|
93
88
|
from .utils import (
|
94
89
|
batch,
|
95
90
|
cross_product,
|
91
|
+
default_gen_id,
|
96
92
|
delay,
|
97
93
|
filter_func,
|
98
94
|
gen_id,
|
@@ -109,7 +109,7 @@ if config.enable_route_workflow:
|
|
109
109
|
|
110
110
|
# NOTE: Enable the schedules route.
|
111
111
|
if config.enable_route_schedule:
|
112
|
-
from ..
|
112
|
+
from ..logs import get_audit
|
113
113
|
from ..scheduler import schedule_task
|
114
114
|
from .routes import schedule
|
115
115
|
|
@@ -10,8 +10,7 @@ from fastapi import APIRouter, Path, Query
|
|
10
10
|
from fastapi import status as st
|
11
11
|
from fastapi.responses import UJSONResponse
|
12
12
|
|
13
|
-
from ...
|
14
|
-
from ...logs import get_trace_obj
|
13
|
+
from ...logs import get_audit, get_trace_obj
|
15
14
|
|
16
15
|
log_route = APIRouter(
|
17
16
|
prefix="/logs",
|
{ddeutil_workflow-0.0.39 → ddeutil_workflow-0.0.41}/src/ddeutil/workflow/api/routes/workflows.py
RENAMED
@@ -15,8 +15,8 @@ from fastapi.responses import UJSONResponse
|
|
15
15
|
from pydantic import BaseModel
|
16
16
|
|
17
17
|
from ...__types import DictData
|
18
|
-
from ...audit import Audit, get_audit
|
19
18
|
from ...conf import Loader, get_logger
|
19
|
+
from ...logs import Audit, get_audit
|
20
20
|
from ...result import Result
|
21
21
|
from ...workflow import Workflow
|
22
22
|
|
@@ -16,6 +16,7 @@ from zoneinfo import ZoneInfo
|
|
16
16
|
|
17
17
|
from ddeutil.core import str2bool
|
18
18
|
from ddeutil.io import YamlFlResolve
|
19
|
+
from ddeutil.io.paths import glob_files, is_ignored, read_ignore
|
19
20
|
|
20
21
|
from .__types import DictData, TupleStr
|
21
22
|
|
@@ -23,13 +24,10 @@ PREFIX: str = "WORKFLOW"
|
|
23
24
|
|
24
25
|
|
25
26
|
def env(var: str, default: str | None = None) -> str | None: # pragma: no cov
|
27
|
+
"""Get environment variable with uppercase and adding prefix string."""
|
26
28
|
return os.getenv(f"{PREFIX}_{var.upper().replace(' ', '_')}", default)
|
27
29
|
|
28
30
|
|
29
|
-
def glob_files(path: Path) -> Iterator[Path]: # pragma: no cov
|
30
|
-
yield from (file for file in path.rglob("*") if file.is_file())
|
31
|
-
|
32
|
-
|
33
31
|
__all__: TupleStr = (
|
34
32
|
"env",
|
35
33
|
"get_logger",
|
@@ -37,7 +35,6 @@ __all__: TupleStr = (
|
|
37
35
|
"SimLoad",
|
38
36
|
"Loader",
|
39
37
|
"config",
|
40
|
-
"glob_files",
|
41
38
|
)
|
42
39
|
|
43
40
|
|
@@ -98,11 +95,19 @@ class Config(BaseConfig): # pragma: no cov
|
|
98
95
|
# NOTE: Register
|
99
96
|
@property
|
100
97
|
def regis_call(self) -> list[str]:
|
98
|
+
"""Register Caller module importer str.
|
99
|
+
|
100
|
+
:rtype: list[str]
|
101
|
+
"""
|
101
102
|
regis_call_str: str = env("CORE_REGISTRY", ".")
|
102
103
|
return [r.strip() for r in regis_call_str.split(",")]
|
103
104
|
|
104
105
|
@property
|
105
106
|
def regis_filter(self) -> list[str]:
|
107
|
+
"""Register Filter module.
|
108
|
+
|
109
|
+
:rtype: list[str]
|
110
|
+
"""
|
106
111
|
regis_filter_str: str = env(
|
107
112
|
"CORE_REGISTRY_FILTER", "ddeutil.workflow.templates"
|
108
113
|
)
|
@@ -273,7 +278,7 @@ class SimLoad:
|
|
273
278
|
if self.is_ignore(file, conf_path):
|
274
279
|
continue
|
275
280
|
|
276
|
-
if data := self.
|
281
|
+
if data := self.filter_yaml(file, name=name):
|
277
282
|
self.data = data
|
278
283
|
|
279
284
|
# VALIDATE: check the data that reading should not empty.
|
@@ -307,15 +312,15 @@ class SimLoad:
|
|
307
312
|
exclude: list[str] = excluded or []
|
308
313
|
for file in glob_files(conf_path):
|
309
314
|
|
310
|
-
|
315
|
+
if cls.is_ignore(file, conf_path):
|
316
|
+
continue
|
311
317
|
|
312
|
-
|
313
|
-
continue
|
318
|
+
for key, data in cls.filter_yaml(file).items():
|
314
319
|
|
315
320
|
if key in exclude:
|
316
321
|
continue
|
317
322
|
|
318
|
-
if data
|
323
|
+
if data.get("type", "") == obj.__name__:
|
319
324
|
yield key, (
|
320
325
|
{k: data[k] for k in data if k in included}
|
321
326
|
if included
|
@@ -324,24 +329,14 @@ class SimLoad:
|
|
324
329
|
|
325
330
|
@classmethod
|
326
331
|
def is_ignore(cls, file: Path, conf_path: Path) -> bool:
|
327
|
-
|
328
|
-
ignore: list[str] = []
|
329
|
-
if ignore_file.exists():
|
330
|
-
ignore = ignore_file.read_text(encoding="utf-8").splitlines()
|
331
|
-
|
332
|
-
if any(
|
333
|
-
(file.match(f"**/{pattern}/*") or file.match(f"**/{pattern}*"))
|
334
|
-
for pattern in ignore
|
335
|
-
):
|
336
|
-
return True
|
337
|
-
return False
|
332
|
+
return is_ignored(file, read_ignore(conf_path / ".confignore"))
|
338
333
|
|
339
334
|
@classmethod
|
340
|
-
def
|
335
|
+
def filter_yaml(cls, file: Path, name: str | None = None) -> DictData:
|
341
336
|
if any(file.suffix.endswith(s) for s in (".yml", ".yaml")):
|
342
337
|
values: DictData = YamlFlResolve(file).read()
|
343
|
-
|
344
|
-
|
338
|
+
if values is not None:
|
339
|
+
return values.get(name, {}) if name else values
|
345
340
|
return {}
|
346
341
|
|
347
342
|
@cached_property
|
@@ -39,8 +39,8 @@ from .exceptions import (
|
|
39
39
|
UtilException,
|
40
40
|
)
|
41
41
|
from .result import Result, Status
|
42
|
+
from .reusables import has_template, param2template
|
42
43
|
from .stages import Stage
|
43
|
-
from .templates import has_template, param2template
|
44
44
|
from .utils import cross_product, filter_func, gen_id
|
45
45
|
|
46
46
|
MatrixFilter = list[dict[str, Union[str, int]]]
|
@@ -344,6 +344,10 @@ class Job(BaseModel):
|
|
344
344
|
default_factory=Strategy,
|
345
345
|
description="A strategy matrix that want to generate.",
|
346
346
|
)
|
347
|
+
extras: DictData = Field(
|
348
|
+
default_factory=dict,
|
349
|
+
description="An extra override values.",
|
350
|
+
)
|
347
351
|
|
348
352
|
@field_validator("desc", mode="after")
|
349
353
|
def ___prepare_desc__(cls, value: str) -> str:
|
@@ -394,6 +398,8 @@ class Job(BaseModel):
|
|
394
398
|
"""
|
395
399
|
for stage in self.stages:
|
396
400
|
if stage_id == (stage.id or ""):
|
401
|
+
if self.extras:
|
402
|
+
stage.extras = self.extras
|
397
403
|
return stage
|
398
404
|
raise ValueError(f"Stage ID {stage_id} does not exists")
|
399
405
|
|
@@ -596,7 +602,7 @@ def local_execute_strategy(
|
|
596
602
|
*,
|
597
603
|
result: Result | None = None,
|
598
604
|
event: Event | None = None,
|
599
|
-
raise_error: bool =
|
605
|
+
raise_error: bool | None = None,
|
600
606
|
) -> Result:
|
601
607
|
"""Local job strategy execution with passing dynamic parameters from the
|
602
608
|
workflow execution to strategy matrix.
|
@@ -694,12 +700,16 @@ def local_execute_strategy(
|
|
694
700
|
params=context,
|
695
701
|
run_id=result.run_id,
|
696
702
|
parent_run_id=result.parent_run_id,
|
703
|
+
event=event,
|
697
704
|
).context,
|
698
705
|
to=context,
|
699
706
|
)
|
700
707
|
except (StageException, UtilException) as err:
|
701
708
|
result.trace.error(f"[JOB]: {err.__class__.__name__}: {err}")
|
702
|
-
|
709
|
+
do_raise: bool = (
|
710
|
+
config.job_raise_error if raise_error is None else raise_error
|
711
|
+
)
|
712
|
+
if do_raise:
|
703
713
|
raise JobException(
|
704
714
|
f"Stage execution error: {err.__class__.__name__}: "
|
705
715
|
f"{err}"
|
@@ -735,7 +745,7 @@ def local_execute(
|
|
735
745
|
parent_run_id: str | None = None,
|
736
746
|
result: Result | None = None,
|
737
747
|
event: Event | None = None,
|
738
|
-
raise_error: bool =
|
748
|
+
raise_error: bool | None = None,
|
739
749
|
) -> Result:
|
740
750
|
"""Local job execution with passing dynamic parameters from the workflow
|
741
751
|
execution. It will generate matrix values at the first step and run
|
@@ -865,3 +875,44 @@ def local_execute(
|
|
865
875
|
context.update({"errors": err.to_dict()})
|
866
876
|
|
867
877
|
return result.catch(status=status, context=context)
|
878
|
+
|
879
|
+
|
880
|
+
def self_hosted_execute(
|
881
|
+
job: Job,
|
882
|
+
params: DictData,
|
883
|
+
*,
|
884
|
+
run_id: str | None = None,
|
885
|
+
parent_run_id: str | None = None,
|
886
|
+
result: Result | None = None,
|
887
|
+
event: Event | None = None,
|
888
|
+
raise_error: bool | None = None,
|
889
|
+
) -> Result: # pragma: no cov
|
890
|
+
result: Result = Result.construct_with_rs_or_id(
|
891
|
+
result,
|
892
|
+
run_id=run_id,
|
893
|
+
parent_run_id=parent_run_id,
|
894
|
+
id_logic=(job.id or "not-set"),
|
895
|
+
)
|
896
|
+
|
897
|
+
if event and event.is_set():
|
898
|
+
return result.catch(status=Status.FAILED)
|
899
|
+
|
900
|
+
import requests
|
901
|
+
|
902
|
+
resp = requests.post(
|
903
|
+
job.runs_on.args.host,
|
904
|
+
data={"job": job.model_dump(), "params": params},
|
905
|
+
)
|
906
|
+
|
907
|
+
if resp.status_code != 200:
|
908
|
+
do_raise: bool = (
|
909
|
+
config.job_raise_error if raise_error is None else raise_error
|
910
|
+
)
|
911
|
+
if do_raise:
|
912
|
+
raise JobException(
|
913
|
+
f"Job execution error from request to self-hosted: "
|
914
|
+
f"{job.runs_on.args.host!r}"
|
915
|
+
)
|
916
|
+
|
917
|
+
return result.catch(status=Status.FAILED)
|
918
|
+
return result.catch(status=Status.SUCCESS)
|