ddeutil-workflow 0.0.13__py3-none-any.whl → 0.0.15__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/__types.py +59 -10
- ddeutil/workflow/api.py +2 -2
- ddeutil/workflow/conf.py +45 -0
- ddeutil/workflow/cron.py +19 -12
- ddeutil/workflow/job.py +191 -153
- ddeutil/workflow/log.py +28 -14
- ddeutil/workflow/scheduler.py +255 -119
- ddeutil/workflow/stage.py +77 -35
- ddeutil/workflow/utils.py +129 -51
- {ddeutil_workflow-0.0.13.dist-info → ddeutil_workflow-0.0.15.dist-info}/METADATA +6 -4
- ddeutil_workflow-0.0.15.dist-info/RECORD +22 -0
- {ddeutil_workflow-0.0.13.dist-info → ddeutil_workflow-0.0.15.dist-info}/WHEEL +1 -1
- ddeutil_workflow-0.0.13.dist-info/RECORD +0 -21
- {ddeutil_workflow-0.0.13.dist-info → ddeutil_workflow-0.0.15.dist-info}/LICENSE +0 -0
- {ddeutil_workflow-0.0.13.dist-info → ddeutil_workflow-0.0.15.dist-info}/entry_points.txt +0 -0
- {ddeutil_workflow-0.0.13.dist-info → ddeutil_workflow-0.0.15.dist-info}/top_level.txt +0 -0
ddeutil/workflow/utils.py
CHANGED
@@ -13,6 +13,7 @@ import time
|
|
13
13
|
from abc import ABC, abstractmethod
|
14
14
|
from ast import Call, Constant, Expr, Module, Name, parse
|
15
15
|
from collections.abc import Iterator
|
16
|
+
from dataclasses import field
|
16
17
|
from datetime import date, datetime
|
17
18
|
from functools import cached_property, wraps
|
18
19
|
from hashlib import md5
|
@@ -32,19 +33,22 @@ except ImportError:
|
|
32
33
|
from ddeutil.core import getdot, hasdot, hash_str, import_string, lazy, str2bool
|
33
34
|
from ddeutil.io import PathData, PathSearch, YamlFlResolve, search_env_replace
|
34
35
|
from ddeutil.io.models.lineage import dt_now
|
35
|
-
from pydantic import BaseModel, ConfigDict, Field
|
36
|
+
from pydantic import BaseModel, ConfigDict, Field
|
37
|
+
from pydantic.dataclasses import dataclass
|
36
38
|
from pydantic.functional_serializers import field_serializer
|
37
39
|
from pydantic.functional_validators import model_validator
|
38
40
|
from typing_extensions import Self
|
39
41
|
|
40
42
|
from .__types import DictData, Matrix, Re
|
43
|
+
from .conf import config
|
41
44
|
from .exceptions import ParamValueException, UtilException
|
42
45
|
|
43
|
-
logger = logging.getLogger("ddeutil.workflow")
|
44
46
|
P = ParamSpec("P")
|
45
47
|
AnyModel = TypeVar("AnyModel", bound=BaseModel)
|
46
48
|
AnyModelType = type[AnyModel]
|
47
49
|
|
50
|
+
logger = logging.getLogger("ddeutil.workflow")
|
51
|
+
|
48
52
|
|
49
53
|
def get_diff_sec(dt: datetime, tz: ZoneInfo | None = None) -> int:
|
50
54
|
"""Return second value that come from diff of an input datetime and the
|
@@ -110,7 +114,7 @@ class ConfParams(BaseModel):
|
|
110
114
|
)
|
111
115
|
|
112
116
|
|
113
|
-
def
|
117
|
+
def load_config() -> ConfParams:
|
114
118
|
"""Load Config data from ``workflows-conf.yaml`` file.
|
115
119
|
|
116
120
|
Configuration Docs:
|
@@ -158,7 +162,7 @@ class SimLoad:
|
|
158
162
|
:param externals: An external parameters
|
159
163
|
|
160
164
|
Noted:
|
161
|
-
|
165
|
+
|
162
166
|
The config data should have ``type`` key for modeling validation that
|
163
167
|
make this loader know what is config should to do pass to.
|
164
168
|
|
@@ -202,6 +206,12 @@ class SimLoad:
|
|
202
206
|
"""Find all data that match with object type in config path. This class
|
203
207
|
method can use include and exclude list of identity name for filter and
|
204
208
|
adds-on.
|
209
|
+
|
210
|
+
:param obj:
|
211
|
+
:param params:
|
212
|
+
:param include:
|
213
|
+
:param exclude:
|
214
|
+
:rtype: Iterator[tuple[str, DictData]]
|
205
215
|
"""
|
206
216
|
exclude: list[str] = exclude or []
|
207
217
|
for file in PathSearch(params.engine.paths.conf).files:
|
@@ -248,11 +258,11 @@ class Loader(SimLoad):
|
|
248
258
|
) -> DictData:
|
249
259
|
"""Override the find class method from the Simple Loader object."""
|
250
260
|
return super().finds(
|
251
|
-
obj=obj, params=
|
261
|
+
obj=obj, params=load_config(), include=include, exclude=exclude
|
252
262
|
)
|
253
263
|
|
254
264
|
def __init__(self, name: str, externals: DictData) -> None:
|
255
|
-
super().__init__(name,
|
265
|
+
super().__init__(name, load_config(), externals)
|
256
266
|
|
257
267
|
|
258
268
|
def gen_id(
|
@@ -275,15 +285,14 @@ def gen_id(
|
|
275
285
|
if not isinstance(value, str):
|
276
286
|
value: str = str(value)
|
277
287
|
|
278
|
-
tz: ZoneInfo = ZoneInfo(os.getenv("WORKFLOW_CORE_TIMEZONE", "UTC"))
|
279
288
|
if str2bool(os.getenv("WORKFLOW_CORE_PIPELINE_ID_SIMPLE", "true")):
|
280
289
|
return hash_str(f"{(value if sensitive else value.lower())}", n=10) + (
|
281
|
-
f"{datetime.now(tz=tz):%Y%m%d%H%M%S%f}" if unique else ""
|
290
|
+
f"{datetime.now(tz=config.tz):%Y%m%d%H%M%S%f}" if unique else ""
|
282
291
|
)
|
283
292
|
return md5(
|
284
293
|
(
|
285
294
|
f"{(value if sensitive else value.lower())}"
|
286
|
-
+ (f"{datetime.now(tz=tz):%Y%m%d%H%M%S%f}" if unique else "")
|
295
|
+
+ (f"{datetime.now(tz=config.tz):%Y%m%d%H%M%S%f}" if unique else "")
|
287
296
|
).encode()
|
288
297
|
).hexdigest()
|
289
298
|
|
@@ -314,19 +323,24 @@ class TagFunc(Protocol):
|
|
314
323
|
name: str
|
315
324
|
tag: str
|
316
325
|
|
317
|
-
def __call__(self, *args, **kwargs): ...
|
326
|
+
def __call__(self, *args, **kwargs): ... # pragma: no cove
|
327
|
+
|
328
|
+
|
329
|
+
ReturnTagFunc = Callable[P, TagFunc]
|
330
|
+
DecoratorTagFunc = Callable[[Callable[[...], Any]], ReturnTagFunc]
|
318
331
|
|
319
332
|
|
320
|
-
def tag(name: str, alias: str | None = None):
|
333
|
+
def tag(name: str, alias: str | None = None) -> DecoratorTagFunc:
|
321
334
|
"""Tag decorator function that set function attributes, ``tag`` and ``name``
|
322
335
|
for making registries variable.
|
323
336
|
|
324
|
-
:param: name: A tag
|
337
|
+
:param: name: A tag name for make different use-case of a function.
|
325
338
|
:param: alias: A alias function name that keeping in registries. If this
|
326
339
|
value does not supply, it will use original function name from __name__.
|
340
|
+
:rtype: Callable[P, TagFunc]
|
327
341
|
"""
|
328
342
|
|
329
|
-
def func_internal(func: Callable[[...], Any]) ->
|
343
|
+
def func_internal(func: Callable[[...], Any]) -> ReturnTagFunc:
|
330
344
|
func.tag = name
|
331
345
|
func.name = alias or func.__name__.replace("_", "-")
|
332
346
|
|
@@ -350,7 +364,7 @@ def make_registry(submodule: str) -> dict[str, Registry]:
|
|
350
364
|
:rtype: dict[str, Registry]
|
351
365
|
"""
|
352
366
|
rs: dict[str, Registry] = {}
|
353
|
-
for module in
|
367
|
+
for module in load_config().engine.registry:
|
354
368
|
# NOTE: try to sequential import task functions
|
355
369
|
try:
|
356
370
|
importer = import_module(f"{module}.{submodule}")
|
@@ -394,6 +408,10 @@ class BaseParam(BaseModel, ABC):
|
|
394
408
|
|
395
409
|
@field_serializer("type")
|
396
410
|
def __serializer_type(self, value: str) -> str:
|
411
|
+
"""Serialize the value of the type field.
|
412
|
+
|
413
|
+
:rtype: str
|
414
|
+
"""
|
397
415
|
return value
|
398
416
|
|
399
417
|
|
@@ -515,29 +533,50 @@ Param = Union[
|
|
515
533
|
]
|
516
534
|
|
517
535
|
|
518
|
-
|
519
|
-
|
520
|
-
|
536
|
+
@dataclass
|
537
|
+
class Result:
|
538
|
+
"""Result Pydantic Model for passing and receiving data context from any
|
539
|
+
module execution process like stage execution, job execution, or workflow
|
540
|
+
execution.
|
541
|
+
|
542
|
+
For comparison property, this result will use ``status``, ``context``,
|
543
|
+
and ``_run_id`` fields to comparing with other result instance.
|
521
544
|
"""
|
522
545
|
|
523
|
-
status: int =
|
524
|
-
context: DictData =
|
546
|
+
status: int = field(default=2)
|
547
|
+
context: DictData = field(default_factory=dict)
|
548
|
+
start_at: datetime = field(default_factory=dt_now, compare=False)
|
549
|
+
end_at: Optional[datetime] = field(default=None, compare=False)
|
525
550
|
|
526
551
|
# NOTE: Ignore this field to compare another result model with __eq__.
|
527
|
-
|
528
|
-
|
552
|
+
_run_id: Optional[str] = field(default=None)
|
553
|
+
_parent_run_id: Optional[str] = field(default=None, compare=False)
|
529
554
|
|
530
555
|
@model_validator(mode="after")
|
531
|
-
def __prepare_run_id(self):
|
532
|
-
if
|
533
|
-
|
556
|
+
def __prepare_run_id(self) -> Self:
|
557
|
+
"""Prepare running ID which use default ID if it initialize at the first
|
558
|
+
time
|
559
|
+
|
560
|
+
:rtype: Self
|
561
|
+
"""
|
562
|
+
self._run_id = gen_id("manual", unique=True)
|
534
563
|
return self
|
535
564
|
|
536
565
|
def set_run_id(self, running_id: str) -> Self:
|
566
|
+
"""Set a running ID.
|
567
|
+
|
568
|
+
:param running_id: A running ID that want to update on this model.
|
569
|
+
:rtype: Self
|
570
|
+
"""
|
537
571
|
self._run_id = running_id
|
538
572
|
return self
|
539
573
|
|
540
574
|
def set_parent_run_id(self, running_id: str) -> Self:
|
575
|
+
"""Set a parent running ID.
|
576
|
+
|
577
|
+
:param running_id: A running ID that want to update on this model.
|
578
|
+
:rtype: Self
|
579
|
+
"""
|
541
580
|
self._parent_run_id = running_id
|
542
581
|
return self
|
543
582
|
|
@@ -549,33 +588,55 @@ class Result(BaseModel):
|
|
549
588
|
def run_id(self):
|
550
589
|
return self._run_id
|
551
590
|
|
552
|
-
def
|
591
|
+
def catch(self, status: int, context: DictData) -> Self:
|
592
|
+
"""Catch the status and context to current data."""
|
593
|
+
self.__dict__["status"] = status
|
594
|
+
self.__dict__["context"].update(context)
|
595
|
+
return self
|
596
|
+
|
597
|
+
def receive(self, result: Result) -> Self:
|
598
|
+
"""Receive context from another result object.
|
599
|
+
|
600
|
+
:rtype: Self
|
601
|
+
"""
|
553
602
|
self.__dict__["status"] = result.status
|
554
603
|
self.__dict__["context"].update(result.context)
|
604
|
+
|
605
|
+
# NOTE: Update running ID from an incoming result.
|
555
606
|
self._parent_run_id = result.parent_run_id
|
556
607
|
self._run_id = result.run_id
|
557
608
|
return self
|
558
609
|
|
559
|
-
def receive_jobs(self, result: Result) ->
|
610
|
+
def receive_jobs(self, result: Result) -> Self:
|
611
|
+
"""Receive context from another result object that use on the workflow
|
612
|
+
execution which create a ``jobs`` keys on the context if it do not
|
613
|
+
exist.
|
614
|
+
|
615
|
+
:rtype: Self
|
616
|
+
"""
|
560
617
|
self.__dict__["status"] = result.status
|
561
618
|
|
562
619
|
# NOTE: Check the context has jobs key.
|
563
620
|
if "jobs" not in self.__dict__["context"]:
|
564
621
|
self.__dict__["context"]["jobs"] = {}
|
565
|
-
|
566
622
|
self.__dict__["context"]["jobs"].update(result.context)
|
623
|
+
|
624
|
+
# NOTE: Update running ID from an incoming result.
|
567
625
|
self._parent_run_id = result.parent_run_id
|
568
626
|
self._run_id = result.run_id
|
569
627
|
return self
|
570
628
|
|
571
629
|
|
572
|
-
def make_exec(path: str | Path):
|
573
|
-
"""Change mode of file to be executable file.
|
630
|
+
def make_exec(path: str | Path) -> None: # pragma: no cov
|
631
|
+
"""Change mode of file to be executable file.
|
632
|
+
|
633
|
+
:param path: A file path that want to make executable permission.
|
634
|
+
"""
|
574
635
|
f: Path = Path(path) if isinstance(path, str) else path
|
575
636
|
f.chmod(f.stat().st_mode | stat.S_IEXEC)
|
576
637
|
|
577
638
|
|
578
|
-
FILTERS: dict[str, callable] = {
|
639
|
+
FILTERS: dict[str, callable] = { # pragma: no cov
|
579
640
|
"abs": abs,
|
580
641
|
"str": str,
|
581
642
|
"int": int,
|
@@ -590,17 +651,18 @@ class FilterFunc(Protocol):
|
|
590
651
|
|
591
652
|
name: str
|
592
653
|
|
593
|
-
def __call__(self, *args, **kwargs): ...
|
654
|
+
def __call__(self, *args, **kwargs): ... # pragma: no cov
|
594
655
|
|
595
656
|
|
596
|
-
def custom_filter(name: str) -> Callable[P,
|
657
|
+
def custom_filter(name: str) -> Callable[P, FilterFunc]:
|
597
658
|
"""Custom filter decorator function that set function attributes, ``filter``
|
598
659
|
for making filter registries variable.
|
599
660
|
|
600
661
|
:param: name: A filter name for make different use-case of a function.
|
662
|
+
:rtype: Callable[P, FilterFunc]
|
601
663
|
"""
|
602
664
|
|
603
|
-
def func_internal(func: Callable[[...], Any]) ->
|
665
|
+
def func_internal(func: Callable[[...], Any]) -> FilterFunc:
|
604
666
|
func.filter = name
|
605
667
|
|
606
668
|
@wraps(func)
|
@@ -622,7 +684,7 @@ def make_filter_registry() -> dict[str, FilterRegistry]:
|
|
622
684
|
:rtype: dict[str, Registry]
|
623
685
|
"""
|
624
686
|
rs: dict[str, Registry] = {}
|
625
|
-
for module in
|
687
|
+
for module in load_config().engine.registry_filter:
|
626
688
|
# NOTE: try to sequential import task functions
|
627
689
|
try:
|
628
690
|
importer = import_module(module)
|
@@ -644,7 +706,10 @@ def make_filter_registry() -> dict[str, FilterRegistry]:
|
|
644
706
|
def get_args_const(
|
645
707
|
expr: str,
|
646
708
|
) -> tuple[str, list[Constant], dict[str, Constant]]:
|
647
|
-
"""Get arguments and keyword-arguments from function calling string.
|
709
|
+
"""Get arguments and keyword-arguments from function calling string.
|
710
|
+
|
711
|
+
:rtype: tuple[str, list[Constant], dict[str, Constant]]
|
712
|
+
"""
|
648
713
|
try:
|
649
714
|
mod: Module = parse(expr)
|
650
715
|
except SyntaxError:
|
@@ -678,6 +743,7 @@ def get_args_const(
|
|
678
743
|
|
679
744
|
@custom_filter("fmt")
|
680
745
|
def datetime_format(value: datetime, fmt: str = "%Y-%m-%d %H:%M:%S") -> str:
|
746
|
+
"""Format datetime object to string with the format."""
|
681
747
|
if isinstance(value, datetime):
|
682
748
|
return value.strftime(fmt)
|
683
749
|
raise UtilException(
|
@@ -699,8 +765,8 @@ def map_post_filter(
|
|
699
765
|
"""
|
700
766
|
for _filter in post_filter:
|
701
767
|
func_name, _args, _kwargs = get_args_const(_filter)
|
702
|
-
args = [arg.value for arg in _args]
|
703
|
-
kwargs = {k: v.value for k, v in _kwargs.items()}
|
768
|
+
args: list = [arg.value for arg in _args]
|
769
|
+
kwargs: dict = {k: v.value for k, v in _kwargs.items()}
|
704
770
|
|
705
771
|
if func_name not in filters:
|
706
772
|
raise UtilException(
|
@@ -741,8 +807,8 @@ def not_in_template(value: Any, *, not_in: str = "matrix.") -> bool:
|
|
741
807
|
elif not isinstance(value, str):
|
742
808
|
return False
|
743
809
|
return any(
|
744
|
-
(not found.
|
745
|
-
for found in Re.
|
810
|
+
(not found.caller.strip().startswith(not_in))
|
811
|
+
for found in Re.finditer_caller(value.strip())
|
746
812
|
)
|
747
813
|
|
748
814
|
|
@@ -783,18 +849,16 @@ def str2template(
|
|
783
849
|
|
784
850
|
# NOTE: remove space before and after this string value.
|
785
851
|
value: str = value.strip()
|
786
|
-
for found in Re.
|
852
|
+
for found in Re.finditer_caller(value):
|
787
853
|
# NOTE:
|
788
854
|
# Get caller and filter values that setting inside;
|
789
855
|
#
|
790
856
|
# ... ``${{ <caller-value> [ | <filter-value>] ... }}``
|
791
857
|
#
|
792
|
-
caller: str = found.
|
858
|
+
caller: str = found.caller
|
793
859
|
pfilter: list[str] = [
|
794
860
|
i.strip()
|
795
|
-
for i in (
|
796
|
-
found.group("post_filters").strip().removeprefix("|").split("|")
|
797
|
-
)
|
861
|
+
for i in (found.post_filters.strip().removeprefix("|").split("|"))
|
798
862
|
if i != ""
|
799
863
|
]
|
800
864
|
if not hasdot(caller, params):
|
@@ -807,7 +871,7 @@ def str2template(
|
|
807
871
|
# If type of getter caller is not string type and it does not use to
|
808
872
|
# concat other string value, it will return origin value from the
|
809
873
|
# ``getdot`` function.
|
810
|
-
if value.replace(found.
|
874
|
+
if value.replace(found.full, "", 1) == "":
|
811
875
|
return map_post_filter(getter, pfilter, filters=filters)
|
812
876
|
|
813
877
|
# NOTE: map post-filter function.
|
@@ -815,7 +879,7 @@ def str2template(
|
|
815
879
|
if not isinstance(getter, str):
|
816
880
|
getter: str = str(getter)
|
817
881
|
|
818
|
-
value: str = value.replace(found.
|
882
|
+
value: str = value.replace(found.full, getter, 1)
|
819
883
|
|
820
884
|
return search_env_replace(value)
|
821
885
|
|
@@ -845,8 +909,12 @@ def param2template(
|
|
845
909
|
|
846
910
|
|
847
911
|
def filter_func(value: Any) -> Any:
|
848
|
-
"""Filter own created function
|
849
|
-
function name. If it is built-in function, it does not
|
912
|
+
"""Filter out an own created function of any value of mapping context by
|
913
|
+
replacing it to its function name. If it is built-in function, it does not
|
914
|
+
have any changing.
|
915
|
+
|
916
|
+
:param value: A value context data that want to filter out function value.
|
917
|
+
:type: The same type of an input ``value``.
|
850
918
|
"""
|
851
919
|
if isinstance(value, dict):
|
852
920
|
return {k: filter_func(value[k]) for k in value}
|
@@ -869,14 +937,20 @@ def dash2underscore(
|
|
869
937
|
*,
|
870
938
|
fixed: str | None = None,
|
871
939
|
) -> DictData:
|
872
|
-
"""Change key name that has dash to underscore.
|
940
|
+
"""Change key name that has dash to underscore.
|
941
|
+
|
942
|
+
:rtype: DictData
|
943
|
+
"""
|
873
944
|
if key in values:
|
874
945
|
values[(fixed or key.replace("-", "_"))] = values.pop(key)
|
875
946
|
return values
|
876
947
|
|
877
948
|
|
878
949
|
def cross_product(matrix: Matrix) -> Iterator[DictData]:
|
879
|
-
"""Iterator of products value from matrix.
|
950
|
+
"""Iterator of products value from matrix.
|
951
|
+
|
952
|
+
:rtype: Iterator[DictData]
|
953
|
+
"""
|
880
954
|
yield from (
|
881
955
|
{_k: _v for e in mapped for _k, _v in e.items()}
|
882
956
|
for mapped in product(
|
@@ -897,7 +971,7 @@ def batch(iterable: Iterator[Any], n: int) -> Iterator[Any]:
|
|
897
971
|
"""
|
898
972
|
if n < 1:
|
899
973
|
raise ValueError("n must be at least one")
|
900
|
-
it = iter(iterable)
|
974
|
+
it: Iterator[Any] = iter(iterable)
|
901
975
|
while True:
|
902
976
|
chunk_it = islice(it, n)
|
903
977
|
try:
|
@@ -905,3 +979,7 @@ def batch(iterable: Iterator[Any], n: int) -> Iterator[Any]:
|
|
905
979
|
except StopIteration:
|
906
980
|
return
|
907
981
|
yield chain((first_el,), chunk_it)
|
982
|
+
|
983
|
+
|
984
|
+
def queue2str(queue: list[datetime]) -> Iterator[str]: # pragma: no cov
|
985
|
+
return (f"{q:%Y-%m-%d %H:%M:%S}" for q in queue)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ddeutil-workflow
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.15
|
4
4
|
Summary: Lightweight workflow orchestration with less dependencies
|
5
5
|
Author-email: ddeutils <korawich.anu@gmail.com>
|
6
6
|
License: MIT
|
@@ -22,12 +22,13 @@ Classifier: Programming Language :: Python :: 3.13
|
|
22
22
|
Requires-Python: >=3.9.13
|
23
23
|
Description-Content-Type: text/markdown
|
24
24
|
License-File: LICENSE
|
25
|
-
Requires-Dist: ddeutil
|
25
|
+
Requires-Dist: ddeutil >=0.4.0
|
26
|
+
Requires-Dist: ddeutil-io >=0.1.13
|
26
27
|
Requires-Dist: python-dotenv ==1.0.1
|
27
28
|
Requires-Dist: typer <1.0.0,==0.12.5
|
28
29
|
Requires-Dist: schedule <2.0.0,==1.2.2
|
29
30
|
Provides-Extra: api
|
30
|
-
Requires-Dist: fastapi <1.0.0,>=0.
|
31
|
+
Requires-Dist: fastapi <1.0.0,>=0.115.0 ; extra == 'api'
|
31
32
|
|
32
33
|
# Workflow
|
33
34
|
|
@@ -62,7 +63,7 @@ configuration. It called **Metadata Driven Data Workflow**.
|
|
62
63
|
|
63
64
|
> [!NOTE]
|
64
65
|
> _Disclaimer_: I inspire the dynamic statement from the [**GitHub Action**](https://github.com/features/actions)
|
65
|
-
> `.yml` files and all of config file from several data orchestration framework
|
66
|
+
> with `.yml` files and all of config file from several data orchestration framework
|
66
67
|
> tools from my experience on Data Engineer. :grimacing:
|
67
68
|
>
|
68
69
|
> Other workflow that I interest on them and pick some interested feature to this
|
@@ -92,6 +93,7 @@ this package with application add-ons, you should add `app` in installation;
|
|
92
93
|
> | ddeutil-workflow:python3.10 | `3.10` | :x: |
|
93
94
|
> | ddeutil-workflow:python3.11 | `3.11` | :x: |
|
94
95
|
> | ddeutil-workflow:python3.12 | `3.12` | :x: |
|
96
|
+
> | ddeutil-workflow:python3.12 | `3.13` | :x: |
|
95
97
|
|
96
98
|
## :beers: Usage
|
97
99
|
|
@@ -0,0 +1,22 @@
|
|
1
|
+
ddeutil/workflow/__about__.py,sha256=w_vBOopUg1crMbDyfdE0LgsxsncnhGYp0D39LSSnSVI,28
|
2
|
+
ddeutil/workflow/__init__.py,sha256=-DIy8SGFsD7_wqp-V-K8v8jTxacmqrcyj_SFx1WS6qg,687
|
3
|
+
ddeutil/workflow/__types.py,sha256=WWugALcayRiP0IQO-eBWK767_XxK7KGlY7SuVgyaJnk,3196
|
4
|
+
ddeutil/workflow/api.py,sha256=cwju_qhY6m0kLtaoa77QLglC9tl7RjjZ4UnJYV3SlQQ,4810
|
5
|
+
ddeutil/workflow/cli.py,sha256=Ikcq526WeIl-737-v55T0PwAZ2pNiZFxlN0Y-DjhDbQ,3374
|
6
|
+
ddeutil/workflow/conf.py,sha256=D0g7rHXilpGwOD36QwVd9I5kEwqsAUA0Z3tAINS2Pws,1287
|
7
|
+
ddeutil/workflow/cron.py,sha256=naWefHc3EnVo41Yf1zQeXOzF27YlTlnfj0XnQ6_HO-U,25514
|
8
|
+
ddeutil/workflow/exceptions.py,sha256=Uf1-Tn8rAzj0aiVHSqo4fBqO80W0za7UFZgKv24E-tg,706
|
9
|
+
ddeutil/workflow/job.py,sha256=9H_2C0ikD5y6jLVdIBj8de4CdSpS632XOfqYVhM4bHI,21582
|
10
|
+
ddeutil/workflow/log.py,sha256=Ev-Szi0KC_MmbFY4g4BWv6tUSmcLKWKZ03ZInmYPmgU,6490
|
11
|
+
ddeutil/workflow/on.py,sha256=vsZG19mNoztDSB_ObD_4ZWPKgHYpBDJMWw97ZiTavNE,7237
|
12
|
+
ddeutil/workflow/repeat.py,sha256=e3dekPTlMlxCCizfBYsZ8dD8Juy4rtfqDZJU3Iky2oA,5011
|
13
|
+
ddeutil/workflow/route.py,sha256=ABEk-WlVo9XGFc7zCPbckX33URCNH7woQFU1keX_8PQ,6970
|
14
|
+
ddeutil/workflow/scheduler.py,sha256=12Dd5CVphOVKjUwoiB8dCHt4WpYRPG3dSOt-pR6NNxc,46167
|
15
|
+
ddeutil/workflow/stage.py,sha256=Avz1Mbb8WAP6kFn0bnN0p14-EnQ_AzdKr435JRxjkao,23844
|
16
|
+
ddeutil/workflow/utils.py,sha256=XUD5hoygAyxi4xo1spTacoDNGKN2TlRob_o8qfCj4Pc,30993
|
17
|
+
ddeutil_workflow-0.0.15.dist-info/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
|
18
|
+
ddeutil_workflow-0.0.15.dist-info/METADATA,sha256=6JvY9y-cT3WnirRva45NS582Iz7ZuXJZpsiCtN57OoA,11653
|
19
|
+
ddeutil_workflow-0.0.15.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
20
|
+
ddeutil_workflow-0.0.15.dist-info/entry_points.txt,sha256=0BVOgO3LdUdXVZ-CiHHDKxzEk2c8J30jEwHeKn2YCWI,62
|
21
|
+
ddeutil_workflow-0.0.15.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
|
22
|
+
ddeutil_workflow-0.0.15.dist-info/RECORD,,
|
@@ -1,21 +0,0 @@
|
|
1
|
-
ddeutil/workflow/__about__.py,sha256=StSv8QbtF16HmqqJ8TfZlgbD1BgLyYHcubwplM-eSto,28
|
2
|
-
ddeutil/workflow/__init__.py,sha256=aEQiEWwTPGhfwpzzdb99xXaHchi5ABWUHl2iLIyT18E,664
|
3
|
-
ddeutil/workflow/__types.py,sha256=SYMoxbENQX8uPsiCZkjtpHAqqHOh8rUrarAFicAJd0E,1773
|
4
|
-
ddeutil/workflow/api.py,sha256=xVP8eGu1nnR8HM0ULTwxs9TV9tsxCOjZ68cAffw2f3o,4802
|
5
|
-
ddeutil/workflow/cli.py,sha256=Ikcq526WeIl-737-v55T0PwAZ2pNiZFxlN0Y-DjhDbQ,3374
|
6
|
-
ddeutil/workflow/cron.py,sha256=uhp3E5pl_tX_H88bsDujcwdhZmOE53csyV-ouPpPdK8,25321
|
7
|
-
ddeutil/workflow/exceptions.py,sha256=Uf1-Tn8rAzj0aiVHSqo4fBqO80W0za7UFZgKv24E-tg,706
|
8
|
-
ddeutil/workflow/job.py,sha256=iwiDUGgnId6QFkzqLZuiWFYUNfY-qYJebaGwhFnMKH8,20633
|
9
|
-
ddeutil/workflow/log.py,sha256=bZyyqf3oNBB8oRf8RI0YvII7wHHoj4wC-nmW_pQjQ1c,6036
|
10
|
-
ddeutil/workflow/on.py,sha256=vsZG19mNoztDSB_ObD_4ZWPKgHYpBDJMWw97ZiTavNE,7237
|
11
|
-
ddeutil/workflow/repeat.py,sha256=e3dekPTlMlxCCizfBYsZ8dD8Juy4rtfqDZJU3Iky2oA,5011
|
12
|
-
ddeutil/workflow/route.py,sha256=ABEk-WlVo9XGFc7zCPbckX33URCNH7woQFU1keX_8PQ,6970
|
13
|
-
ddeutil/workflow/scheduler.py,sha256=fe9NGobU8zN95C0FY2PB7eYI9tzyvyh-_K7vcUFFBO8,41674
|
14
|
-
ddeutil/workflow/stage.py,sha256=rGFdLLYj6eo8aqSRr4lkBBdah4KIzCzKefJeg0hk0O8,22289
|
15
|
-
ddeutil/workflow/utils.py,sha256=TbqgPkDDYBpqCZ7HV2TU3AH1_Mv-zfrJdwVL-l2SPUo,28559
|
16
|
-
ddeutil_workflow-0.0.13.dist-info/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
|
17
|
-
ddeutil_workflow-0.0.13.dist-info/METADATA,sha256=HuSRkM94JcefbkiCR6_3khXeUiAsb0FMirS3d7qWGHk,11556
|
18
|
-
ddeutil_workflow-0.0.13.dist-info/WHEEL,sha256=5Mi1sN9lKoFv_gxcPtisEVrJZihrm_beibeg5R6xb4I,91
|
19
|
-
ddeutil_workflow-0.0.13.dist-info/entry_points.txt,sha256=0BVOgO3LdUdXVZ-CiHHDKxzEk2c8J30jEwHeKn2YCWI,62
|
20
|
-
ddeutil_workflow-0.0.13.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
|
21
|
-
ddeutil_workflow-0.0.13.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|