ddeutil-workflow 0.0.46__tar.gz → 0.0.47__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.46 → ddeutil_workflow-0.0.47}/PKG-INFO +2 -2
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/pyproject.toml +1 -1
- ddeutil_workflow-0.0.47/src/ddeutil/workflow/__about__.py +1 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/result.py +10 -15
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/reusables.py +4 -3
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/stages.py +66 -29
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil_workflow.egg-info/PKG-INFO +2 -2
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_reusables_call_tag.py +92 -2
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_stage_handler_exec.py +19 -5
- ddeutil_workflow-0.0.46/src/ddeutil/workflow/__about__.py +0 -1
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/LICENSE +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/README.md +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/setup.cfg +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/__cron.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/__init__.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/__main__.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/__types.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/api/__init__.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/api/api.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/api/log.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/api/repeat.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/api/routes/__init__.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/api/routes/job.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/api/routes/logs.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/api/routes/schedules.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/api/routes/workflows.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/conf.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/cron.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/exceptions.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/job.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/logs.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/params.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/scheduler.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/utils.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/workflow.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil_workflow.egg-info/SOURCES.txt +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil_workflow.egg-info/dependency_links.txt +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil_workflow.egg-info/requires.txt +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil_workflow.egg-info/top_level.txt +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test__cron.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test__regex.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_conf.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_cron_on.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_job.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_job_exec.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_job_exec_strategy.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_job_strategy.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_logs_audit.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_logs_trace.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_params.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_release.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_release_queue.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_result.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_reusables_template.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_reusables_template_filter.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_schedule.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_schedule_pending.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_schedule_tasks.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_schedule_workflow.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_scheduler_control.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_stage.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_utils.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_workflow.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_workflow_exec.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_workflow_exec_job.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_workflow_exec_poke.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_workflow_exec_release.py +0 -0
- {ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/tests/test_workflow_task.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.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
|
@@ -12,7 +12,7 @@ keywords = ['orchestration', 'workflow']
|
|
12
12
|
classifiers = [
|
13
13
|
"Topic :: Utilities",
|
14
14
|
"Natural Language :: English",
|
15
|
-
"Development Status ::
|
15
|
+
"Development Status :: 4 - Beta",
|
16
16
|
"Intended Audience :: Developers",
|
17
17
|
"Operating System :: OS Independent",
|
18
18
|
"Programming Language :: Python",
|
@@ -0,0 +1 @@
|
|
1
|
+
__version__: str = "0.0.47"
|
@@ -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
|
"""
|
@@ -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]
|
@@ -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
|
@@ -3,11 +3,14 @@ from __future__ import annotations
|
|
3
3
|
import asyncio
|
4
4
|
import inspect
|
5
5
|
import shutil
|
6
|
+
import typing
|
7
|
+
from dataclasses import is_dataclass
|
6
8
|
from pathlib import Path
|
7
9
|
from textwrap import dedent
|
8
10
|
|
9
11
|
import pytest
|
10
|
-
from ddeutil.workflow.reusables import Registry, make_registry
|
12
|
+
from ddeutil.workflow.reusables import Registry, extract_call, make_registry
|
13
|
+
from pydantic import BaseModel
|
11
14
|
|
12
15
|
|
13
16
|
@pytest.fixture(scope="module")
|
@@ -94,10 +97,10 @@ def test_make_registry(call_function):
|
|
94
97
|
|
95
98
|
def test_make_registry_from_env():
|
96
99
|
rs: dict[str, Registry] = make_registry("tasks")
|
97
|
-
print(rs)
|
98
100
|
assert set(rs.keys()) == {
|
99
101
|
"async-el-csv-to-parquet",
|
100
102
|
"get-items",
|
103
|
+
"gen-type",
|
101
104
|
"mssql-proc",
|
102
105
|
"el-csv-to-parquet",
|
103
106
|
"return-type-not-valid",
|
@@ -116,12 +119,36 @@ def test_make_registry_raise(call_function_dup):
|
|
116
119
|
make_registry("new_tasks_dup")
|
117
120
|
|
118
121
|
|
122
|
+
def test_extract_call():
|
123
|
+
func = extract_call("tasks/el-csv-to-parquet@polars-dir")
|
124
|
+
call_func = func()
|
125
|
+
assert call_func.name == "el-csv-to-parquet"
|
126
|
+
assert call_func.tag == "polars-dir"
|
127
|
+
|
128
|
+
|
129
|
+
def test_extract_call_args_type():
|
130
|
+
func = extract_call("tasks/gen-type@demo")
|
131
|
+
call_func = func()
|
132
|
+
|
133
|
+
get_types = typing.get_type_hints(call_func)
|
134
|
+
for p in get_types:
|
135
|
+
if is_dataclass(get_types[p]) and get_types[p].__name__ == "Result":
|
136
|
+
print("found result", p, get_types[p])
|
137
|
+
if issubclass(get_types[p], BaseModel):
|
138
|
+
print(p, "with type:", get_types[p])
|
139
|
+
|
140
|
+
|
119
141
|
@pytest.mark.skip("Skip because it uses for local test only.")
|
120
142
|
def test_inspec_func():
|
121
143
|
|
122
144
|
def demo_func(
|
123
145
|
args_1: str, args_2: Path, *args, kwargs_1: str | None = None, **kwargs
|
124
146
|
): # pragma: no cov
|
147
|
+
_ = args_1
|
148
|
+
_ = args_2
|
149
|
+
_ = args
|
150
|
+
_ = kwargs_1
|
151
|
+
_ = kwargs
|
125
152
|
pass
|
126
153
|
|
127
154
|
assert inspect.isfunction(demo_func)
|
@@ -141,6 +168,11 @@ def test_inspec_func():
|
|
141
168
|
args_1: str, args_2: Path, *args, kwargs_1: str | None = None, **kwargs
|
142
169
|
): # pragma: no cov
|
143
170
|
await asyncio.sleep(0.1)
|
171
|
+
_ = args_1
|
172
|
+
_ = args_2
|
173
|
+
_ = args
|
174
|
+
_ = kwargs_1
|
175
|
+
_ = kwargs
|
144
176
|
pass
|
145
177
|
|
146
178
|
print(inspect.isfunction(ademo_func))
|
@@ -158,3 +190,61 @@ def test_inspec_func():
|
|
158
190
|
# print(v.default)
|
159
191
|
# print(v.kind, " (", type(v.kind), ")")
|
160
192
|
# print("-----")
|
193
|
+
|
194
|
+
|
195
|
+
class MockModel(BaseModel): # pragma: no cov
|
196
|
+
name: str
|
197
|
+
|
198
|
+
|
199
|
+
def outside_func(args: MockModel) -> MockModel: # pragma: no cov
|
200
|
+
_ = args
|
201
|
+
pass
|
202
|
+
|
203
|
+
|
204
|
+
@pytest.mark.skip("Skip because it uses for local test only.")
|
205
|
+
def test_inspec_with_pydantic_model_args():
|
206
|
+
from pydantic import BaseModel
|
207
|
+
|
208
|
+
class MockModelLocal(BaseModel):
|
209
|
+
name: str
|
210
|
+
|
211
|
+
def demo_func(
|
212
|
+
args_1: MockModel,
|
213
|
+
args_2: MockModelLocal,
|
214
|
+
*args,
|
215
|
+
kwargs_1: str = None,
|
216
|
+
**kwargs,
|
217
|
+
) -> MockModel: # pragma: no cov
|
218
|
+
_ = args_1
|
219
|
+
_ = args_2
|
220
|
+
_ = args
|
221
|
+
_ = kwargs_1
|
222
|
+
_ = kwargs
|
223
|
+
pass
|
224
|
+
|
225
|
+
# ips = inspect.signature(demo_func)
|
226
|
+
# for k, v in ips.parameters.items():
|
227
|
+
# print(k)
|
228
|
+
# print(ips.parameters[k].default)
|
229
|
+
# print(v)
|
230
|
+
# print(v.name)
|
231
|
+
# print(v.annotation, "type:", type(v.annotation))
|
232
|
+
# print(v.default)
|
233
|
+
# print(v.kind, " (", type(v.kind), ")")
|
234
|
+
# print("-----")
|
235
|
+
#
|
236
|
+
# print(ips.return_annotation, "type:", type(ips.return_annotation))
|
237
|
+
# print(ips.return_annotation is MockModel)
|
238
|
+
# print(ips.return_annotation.__parameter__)
|
239
|
+
|
240
|
+
import typing
|
241
|
+
|
242
|
+
rs = typing.get_type_hints(demo_func, localns=locals(), globalns=globals())
|
243
|
+
print(rs)
|
244
|
+
|
245
|
+
print(demo_func.__annotations__)
|
246
|
+
print(globals()["MockModel"])
|
247
|
+
print(locals()["MockModelLocal"])
|
248
|
+
|
249
|
+
rs = typing.get_type_hints(outside_func)
|
250
|
+
print(rs)
|
@@ -97,20 +97,29 @@ def test_stage_exec_call(test_path):
|
|
97
97
|
with:
|
98
98
|
source: src
|
99
99
|
sink: sink
|
100
|
+
- name: "Return with Pydantic Model"
|
101
|
+
id: return-model
|
102
|
+
uses: tasks/gen-type@demo
|
103
|
+
with:
|
104
|
+
args1: foo
|
105
|
+
args2: conf/path
|
106
|
+
args3:
|
107
|
+
name: test
|
108
|
+
data:
|
109
|
+
input: hello
|
100
110
|
""",
|
101
111
|
):
|
102
112
|
workflow = Workflow.from_conf(name="tmp-wf-call-return-type")
|
103
113
|
|
104
114
|
stage: Stage = workflow.job("second-job").stage("extract-load")
|
105
115
|
rs: Result = stage.handler_execute({})
|
106
|
-
|
116
|
+
assert 0 == rs.status
|
117
|
+
assert {"records": 1} == rs.context
|
107
118
|
|
108
119
|
stage: Stage = workflow.job("second-job").stage("async-extract-load")
|
109
120
|
rs: Result = stage.handler_execute({})
|
110
|
-
|
111
|
-
|
112
|
-
assert 0 == rs.status
|
113
|
-
assert {"records": 1} == rs.context
|
121
|
+
assert rs.status == 0
|
122
|
+
assert rs.context == {"records": 1}
|
114
123
|
|
115
124
|
# NOTE: Raise because invalid return type.
|
116
125
|
with pytest.raises(StageException):
|
@@ -132,6 +141,11 @@ def test_stage_exec_call(test_path):
|
|
132
141
|
stage: Stage = workflow.job("first-job").stage("call-not-register")
|
133
142
|
stage.handler_execute({})
|
134
143
|
|
144
|
+
stage: Stage = workflow.job("second-job").stage("return-model")
|
145
|
+
rs: Result = stage.handler_execute({})
|
146
|
+
assert rs.status == 0
|
147
|
+
assert rs.context == {"name": "foo", "data": {"key": "value"}}
|
148
|
+
|
135
149
|
|
136
150
|
@mock.patch.object(Config, "stage_raise_error", True)
|
137
151
|
def test_stage_exec_py_raise():
|
@@ -1 +0,0 @@
|
|
1
|
-
__version__: str = "0.0.46"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/api/routes/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
{ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/api/routes/schedules.py
RENAMED
File without changes
|
{ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil/workflow/api/routes/workflows.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil_workflow.egg-info/SOURCES.txt
RENAMED
File without changes
|
File without changes
|
{ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil_workflow.egg-info/requires.txt
RENAMED
File without changes
|
{ddeutil_workflow-0.0.46 → ddeutil_workflow-0.0.47}/src/ddeutil_workflow.egg-info/top_level.txt
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|