ddeutil-workflow 0.0.46__py3-none-any.whl → 0.0.47__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/result.py +10 -15
- ddeutil/workflow/reusables.py +4 -3
- ddeutil/workflow/stages.py +66 -29
- {ddeutil_workflow-0.0.46.dist-info → ddeutil_workflow-0.0.47.dist-info}/METADATA +2 -2
- {ddeutil_workflow-0.0.46.dist-info → ddeutil_workflow-0.0.47.dist-info}/RECORD +9 -9
- {ddeutil_workflow-0.0.46.dist-info → ddeutil_workflow-0.0.47.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.46.dist-info → ddeutil_workflow-0.0.47.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.46.dist-info → ddeutil_workflow-0.0.47.dist-info}/top_level.txt +0 -0
ddeutil/workflow/__about__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__: str = "0.0.
|
1
|
+
__version__: str = "0.0.47"
|
ddeutil/workflow/result.py
CHANGED
@@ -18,19 +18,10 @@ from pydantic.dataclasses import dataclass
|
|
18
18
|
from pydantic.functional_validators import model_validator
|
19
19
|
from typing_extensions import Self
|
20
20
|
|
21
|
-
from .__types import DictData
|
21
|
+
from .__types import DictData
|
22
22
|
from .logs import TraceLog, get_dt_tznow, get_trace
|
23
23
|
from .utils import default_gen_id, gen_id
|
24
24
|
|
25
|
-
__all__: TupleStr = (
|
26
|
-
"SUCCESS",
|
27
|
-
"FAILED",
|
28
|
-
"WAIT",
|
29
|
-
"SKIP",
|
30
|
-
"Result",
|
31
|
-
"Status",
|
32
|
-
)
|
33
|
-
|
34
25
|
|
35
26
|
class Status(IntEnum):
|
36
27
|
"""Status Int Enum object that use for tracking execution status to the
|
@@ -62,6 +53,10 @@ class Result:
|
|
62
53
|
|
63
54
|
For comparison property, this result will use ``status``, ``context``,
|
64
55
|
and ``_run_id`` fields to comparing with other result instance.
|
56
|
+
|
57
|
+
Warning:
|
58
|
+
I use dataclass object instead of Pydantic model object because context
|
59
|
+
field that keep dict value change its ID when update new value to it.
|
65
60
|
"""
|
66
61
|
|
67
62
|
status: Status = field(default=WAIT)
|
@@ -87,11 +82,11 @@ class Result:
|
|
87
82
|
"""Create the Result object or set parent running id if passing Result
|
88
83
|
object.
|
89
84
|
|
90
|
-
:param result:
|
91
|
-
:param run_id:
|
92
|
-
:param parent_run_id:
|
93
|
-
:param id_logic:
|
94
|
-
:param extras:
|
85
|
+
:param result: A Result instance.
|
86
|
+
:param run_id: A running ID.
|
87
|
+
:param parent_run_id: A parent running ID.
|
88
|
+
:param id_logic: A logic function that use to generate a running ID.
|
89
|
+
:param extras: An extra parameter that want to override the core config.
|
95
90
|
|
96
91
|
:rtype: Self
|
97
92
|
"""
|
ddeutil/workflow/reusables.py
CHANGED
@@ -10,7 +10,6 @@ from __future__ import annotations
|
|
10
10
|
import inspect
|
11
11
|
import logging
|
12
12
|
from ast import Call, Constant, Expr, Module, Name, parse
|
13
|
-
from dataclasses import dataclass
|
14
13
|
from datetime import datetime
|
15
14
|
from functools import wraps
|
16
15
|
from importlib import import_module
|
@@ -23,6 +22,7 @@ except ImportError:
|
|
23
22
|
|
24
23
|
from ddeutil.core import getdot, import_string, lazy
|
25
24
|
from ddeutil.io import search_env_replace
|
25
|
+
from pydantic.dataclasses import dataclass
|
26
26
|
|
27
27
|
from .__types import DictData, Re
|
28
28
|
from .conf import dynamic
|
@@ -437,6 +437,7 @@ Registry = dict[str, Callable[[], TagFunc]]
|
|
437
437
|
|
438
438
|
def make_registry(
|
439
439
|
submodule: str,
|
440
|
+
*,
|
440
441
|
registries: Optional[list[str]] = None,
|
441
442
|
) -> dict[str, Registry]:
|
442
443
|
"""Return registries of all functions that able to called with task.
|
@@ -539,13 +540,13 @@ def extract_call(
|
|
539
540
|
|
540
541
|
if call.func not in rgt:
|
541
542
|
raise NotImplementedError(
|
542
|
-
f"`
|
543
|
+
f"`REGISTERS.{call.path}.registries` not implement "
|
543
544
|
f"registry: {call.func!r}."
|
544
545
|
)
|
545
546
|
|
546
547
|
if call.tag not in rgt[call.func]:
|
547
548
|
raise NotImplementedError(
|
548
549
|
f"tag: {call.tag!r} not found on registry func: "
|
549
|
-
f"`REGISTER
|
550
|
+
f"`REGISTER.{call.path}.registries.{call.func}`"
|
550
551
|
)
|
551
552
|
return rgt[call.func][call.tag]
|
ddeutil/workflow/stages.py
CHANGED
@@ -38,12 +38,13 @@ from concurrent.futures import (
|
|
38
38
|
ThreadPoolExecutor,
|
39
39
|
as_completed,
|
40
40
|
)
|
41
|
+
from dataclasses import is_dataclass
|
41
42
|
from inspect import Parameter
|
42
43
|
from pathlib import Path
|
43
44
|
from subprocess import CompletedProcess
|
44
45
|
from textwrap import dedent
|
45
46
|
from threading import Event
|
46
|
-
from typing import Annotated, Optional, Union
|
47
|
+
from typing import Annotated, Any, Optional, Union, get_type_hints
|
47
48
|
|
48
49
|
from pydantic import BaseModel, Field
|
49
50
|
from pydantic.functional_validators import model_validator
|
@@ -814,9 +815,8 @@ class CallStage(BaseStage):
|
|
814
815
|
run_id=gen_id(self.name + (self.id or ""), unique=True)
|
815
816
|
)
|
816
817
|
|
817
|
-
|
818
|
-
|
819
|
-
t_func: TagFunc = extract_call(
|
818
|
+
has_keyword: bool = False
|
819
|
+
call_func: TagFunc = extract_call(
|
820
820
|
param2template(self.uses, params, extras=self.extras),
|
821
821
|
registries=self.extras.get("regis_call"),
|
822
822
|
)()
|
@@ -826,55 +826,92 @@ class CallStage(BaseStage):
|
|
826
826
|
args: DictData = {"result": result} | param2template(
|
827
827
|
self.args, params, extras=self.extras
|
828
828
|
)
|
829
|
-
ips = inspect.signature(
|
830
|
-
necessary_params: list[str] = [
|
831
|
-
|
832
|
-
for k in ips.parameters
|
829
|
+
ips = inspect.signature(call_func)
|
830
|
+
necessary_params: list[str] = []
|
831
|
+
for k in ips.parameters:
|
833
832
|
if (
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
833
|
+
v := ips.parameters[k]
|
834
|
+
).default == Parameter.empty and v.kind not in (
|
835
|
+
Parameter.VAR_KEYWORD,
|
836
|
+
Parameter.VAR_POSITIONAL,
|
837
|
+
):
|
838
|
+
necessary_params.append(k)
|
839
|
+
elif v.kind == Parameter.VAR_KEYWORD:
|
840
|
+
has_keyword = True
|
841
|
+
|
841
842
|
if any(
|
842
843
|
(k.removeprefix("_") not in args and k not in args)
|
843
844
|
for k in necessary_params
|
844
845
|
):
|
845
846
|
raise ValueError(
|
846
847
|
f"Necessary params, ({', '.join(necessary_params)}, ), "
|
847
|
-
f"does not set to args"
|
848
|
+
f"does not set to args, {list(args.keys())}."
|
848
849
|
)
|
849
850
|
|
850
|
-
|
851
|
-
for k in ips.parameters:
|
852
|
-
if k.removeprefix("_") in args:
|
853
|
-
args[k] = args.pop(k.removeprefix("_"))
|
854
|
-
|
855
|
-
if "result" not in ips.parameters:
|
851
|
+
if "result" not in ips.parameters and not has_keyword:
|
856
852
|
args.pop("result")
|
857
853
|
|
858
|
-
result.trace.info(
|
859
|
-
|
854
|
+
result.trace.info(
|
855
|
+
f"[STAGE]: Call-Execute: {call_func.name}@{call_func.tag}"
|
856
|
+
)
|
857
|
+
|
858
|
+
args = self.parse_model_args(call_func, args, result)
|
859
|
+
if inspect.iscoroutinefunction(call_func):
|
860
860
|
loop = asyncio.get_event_loop()
|
861
861
|
rs: DictData = loop.run_until_complete(
|
862
|
-
|
862
|
+
call_func(**param2template(args, params, extras=self.extras))
|
863
863
|
)
|
864
864
|
else:
|
865
|
-
rs: DictData =
|
865
|
+
rs: DictData = call_func(
|
866
866
|
**param2template(args, params, extras=self.extras)
|
867
867
|
)
|
868
868
|
|
869
869
|
# VALIDATE:
|
870
870
|
# Check the result type from call function, it should be dict.
|
871
|
-
if
|
871
|
+
if isinstance(rs, BaseModel):
|
872
|
+
rs: DictData = rs.model_dump()
|
873
|
+
elif not isinstance(rs, dict):
|
872
874
|
raise TypeError(
|
873
|
-
f"Return type: '{
|
874
|
-
f"to result model, you change return type to `dict`."
|
875
|
+
f"Return type: '{call_func.name}@{call_func.tag}' does not "
|
876
|
+
f"serialize to result model, you change return type to `dict`."
|
875
877
|
)
|
876
878
|
return result.catch(status=SUCCESS, context=rs)
|
877
879
|
|
880
|
+
@staticmethod
|
881
|
+
def parse_model_args(
|
882
|
+
func: TagFunc,
|
883
|
+
args: DictData,
|
884
|
+
result: Result,
|
885
|
+
) -> DictData:
|
886
|
+
"""Parse Pydantic model from any dict data before parsing to target
|
887
|
+
caller function.
|
888
|
+
"""
|
889
|
+
try:
|
890
|
+
type_hints: dict[str, Any] = get_type_hints(func)
|
891
|
+
except TypeError as e:
|
892
|
+
result.trace.warning(
|
893
|
+
f"[STAGE]: Get type hint raise TypeError: {e}, so, it skip "
|
894
|
+
f"parsing model args process."
|
895
|
+
)
|
896
|
+
return args
|
897
|
+
|
898
|
+
for arg in type_hints:
|
899
|
+
|
900
|
+
if arg == "return":
|
901
|
+
continue
|
902
|
+
|
903
|
+
if arg.removeprefix("_") in args:
|
904
|
+
args[arg] = args.pop(arg.removeprefix("_"))
|
905
|
+
|
906
|
+
t: Any = type_hints[arg]
|
907
|
+
if is_dataclass(t) and t.__name__ == "Result" and arg not in args:
|
908
|
+
args[arg] = result
|
909
|
+
|
910
|
+
if issubclass(t, BaseModel) and arg in args:
|
911
|
+
args[arg] = t.model_validate(obj=args[arg])
|
912
|
+
|
913
|
+
return args
|
914
|
+
|
878
915
|
|
879
916
|
class TriggerStage(BaseStage):
|
880
917
|
"""Trigger Workflow execution stage that execute another workflow. This
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ddeutil-workflow
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.47
|
4
4
|
Summary: Lightweight workflow orchestration
|
5
5
|
Author-email: ddeutils <korawich.anu@gmail.com>
|
6
6
|
License: MIT
|
@@ -9,7 +9,7 @@ Project-URL: Source Code, https://github.com/ddeutils/ddeutil-workflow/
|
|
9
9
|
Keywords: orchestration,workflow
|
10
10
|
Classifier: Topic :: Utilities
|
11
11
|
Classifier: Natural Language :: English
|
12
|
-
Classifier: Development Status ::
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
13
13
|
Classifier: Intended Audience :: Developers
|
14
14
|
Classifier: Operating System :: OS Independent
|
15
15
|
Classifier: Programming Language :: Python
|
@@ -1,4 +1,4 @@
|
|
1
|
-
ddeutil/workflow/__about__.py,sha256=
|
1
|
+
ddeutil/workflow/__about__.py,sha256=6qZfpuPCbzNW572qdp7t-HFsC1JN75TQi_50aMab9kk,28
|
2
2
|
ddeutil/workflow/__cron.py,sha256=h8rLeIUAAEB2SdZ4Jhch7LU1Yl3bbJ-iNNJ3tQ0eYVM,28095
|
3
3
|
ddeutil/workflow/__init__.py,sha256=m7ZTCuUOarcTKJuXOyuaXd5WTIO7NTkqCeCrNX3d5i8,1943
|
4
4
|
ddeutil/workflow/__main__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -9,10 +9,10 @@ ddeutil/workflow/exceptions.py,sha256=uLNxzav3HRcr4vaZnvbUIF_eTR6UXXZNaxroMWFOUL
|
|
9
9
|
ddeutil/workflow/job.py,sha256=uDT_lxAmtWDk6OYm6E4_rz_ngMdS5S03YF4D3WZMP8k,30676
|
10
10
|
ddeutil/workflow/logs.py,sha256=Ki1t6HkThwimzAe1OSxPPc7OQ4r-kXAc1kB63x2DsOg,21160
|
11
11
|
ddeutil/workflow/params.py,sha256=xCtFEh0-G-G-f8y_SXxyf31bU6Ox5p5Z-WbBFXrjy8M,9960
|
12
|
-
ddeutil/workflow/result.py,sha256=
|
13
|
-
ddeutil/workflow/reusables.py,sha256=
|
12
|
+
ddeutil/workflow/result.py,sha256=bysM6A194LPcivzmCrNNZsT8MphzhYAvqhjEYD6zryo,5145
|
13
|
+
ddeutil/workflow/reusables.py,sha256=vUsbh1dw5Cpojv98QTZarwBLHjvTMvR05H7XV76zUKQ,17537
|
14
14
|
ddeutil/workflow/scheduler.py,sha256=_MDsEHbBVOeF-381U8DfIMDyca_nG3XNXmgX4229_EU,27437
|
15
|
-
ddeutil/workflow/stages.py,sha256=
|
15
|
+
ddeutil/workflow/stages.py,sha256=oYEGWD-3kodtKSZ6JPAqAT5_za-sZI3GAKMJxeYUD8o,49009
|
16
16
|
ddeutil/workflow/utils.py,sha256=sblje9qOtejCHVt8EVrbC0KY98vKqvxccaR5HIkRiTA,7363
|
17
17
|
ddeutil/workflow/workflow.py,sha256=kEbPr2Wi9n5fDaCi5R26f4SHw7083_TdcIkZw-w7cEA,49716
|
18
18
|
ddeutil/workflow/api/__init__.py,sha256=F53NMBWtb9IKaDWkPU5KvybGGfKAcbehgn6TLBwHuuM,21
|
@@ -24,8 +24,8 @@ ddeutil/workflow/api/routes/job.py,sha256=YVta083i8vU8-o4WdKFwDpfdC9vN1dZ6goZSmN
|
|
24
24
|
ddeutil/workflow/api/routes/logs.py,sha256=TeRDrEelbKS2Hu_EovgLh0bOdmSv9mfnrIZsrE7uPD4,5353
|
25
25
|
ddeutil/workflow/api/routes/schedules.py,sha256=rUWBm5RgLS1PNBHSWwWXJ0l-c5mYWfl9os0BA9_OTEw,4810
|
26
26
|
ddeutil/workflow/api/routes/workflows.py,sha256=ctgQGxXfpIV6bHFDM9IQ1_qaQHT6n5-HjJ1-D4GKWpc,4527
|
27
|
-
ddeutil_workflow-0.0.
|
28
|
-
ddeutil_workflow-0.0.
|
29
|
-
ddeutil_workflow-0.0.
|
30
|
-
ddeutil_workflow-0.0.
|
31
|
-
ddeutil_workflow-0.0.
|
27
|
+
ddeutil_workflow-0.0.47.dist-info/licenses/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
|
28
|
+
ddeutil_workflow-0.0.47.dist-info/METADATA,sha256=EngsQlJUAaJf_8irHXLk04p6F71UjQnBifd84g1GhNw,19116
|
29
|
+
ddeutil_workflow-0.0.47.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
30
|
+
ddeutil_workflow-0.0.47.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
|
31
|
+
ddeutil_workflow-0.0.47.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|