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.
@@ -1 +1 @@
1
- __version__: str = "0.0.46"
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, TupleStr
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"`REGISTER-MODULES.{call.path}.registries` not implement "
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-MODULES.{call.path}.registries.{call.func}`"
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
- print("Extras in CallStage", self.extras)
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(t_func)
830
- necessary_params: list[str] = [
831
- k
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
- (v := ips.parameters[k]).default == Parameter.empty
835
- and (
836
- v.kind != Parameter.VAR_KEYWORD
837
- or v.kind != Parameter.VAR_POSITIONAL
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
- # NOTE: add '_' prefix if it wants to use.
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(f"[STAGE]: Call-Execute: {t_func.name}@{t_func.tag}")
859
- if inspect.iscoroutinefunction(t_func): # pragma: no cov
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
- t_func(**param2template(args, params, extras=self.extras))
862
+ call_func(**param2template(args, params, extras=self.extras))
863
863
  )
864
864
  else:
865
- rs: DictData = t_func(
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 not isinstance(rs, dict):
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: '{t_func.name}@{t_func.tag}' does not serialize "
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.46
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 :: 5 - Production/Stable
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=yDFXJ7qonVJ1xTa_rhZ4F9WS1AblaopGDHPdNrytYtM,28
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=9tbCmP0Sjy7h9GKWyD5e1bjAzNOWZcnvBFuC6to_f-8,4929
13
- ddeutil/workflow/reusables.py,sha256=ZE8WfD0WyQUKRV5aujJpGG6g6ODJz-wtgwHbQiCrN-E,17536
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=G9TtXx2_HzcvgOetcncW0-GzMapqho6VmxENFyoYmt0,47829
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.46.dist-info/licenses/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
28
- ddeutil_workflow-0.0.46.dist-info/METADATA,sha256=_HZpnC_wIzRdBFhLXDVsJpfPBevg5YXKDYll6KGg4pE,19129
29
- ddeutil_workflow-0.0.46.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
30
- ddeutil_workflow-0.0.46.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
31
- ddeutil_workflow-0.0.46.dist-info/RECORD,,
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,,