ddeutil-workflow 0.0.37__py3-none-any.whl → 0.0.39__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.37"
1
+ __version__: str = "0.0.39"
@@ -39,6 +39,8 @@ from .job import (
39
39
  Job,
40
40
  RunsOn,
41
41
  Strategy,
42
+ local_execute,
43
+ local_execute_strategy,
42
44
  )
43
45
  from .logs import (
44
46
  TraceData,
@@ -69,6 +71,8 @@ from .stages import (
69
71
  BashStage,
70
72
  CallStage,
71
73
  EmptyStage,
74
+ ForEachStage,
75
+ ParallelStage,
72
76
  PyStage,
73
77
  Stage,
74
78
  TriggerStage,
@@ -89,7 +93,6 @@ from .templates import (
89
93
  from .utils import (
90
94
  batch,
91
95
  cross_product,
92
- dash2underscore,
93
96
  delay,
94
97
  filter_func,
95
98
  gen_id,
@@ -61,8 +61,10 @@ class Re:
61
61
  # Regular expression:
62
62
  # - Version 1:
63
63
  # \${{\s*(?P<caller>[a-zA-Z0-9_.\s'\"\[\]\(\)\-\{}]+?)\s*(?P<post_filters>(?:\|\s*(?:[a-zA-Z0-9_]{3,}[a-zA-Z0-9_.,-\\%\s'\"[\]()\{}]+)\s*)*)}}
64
+ #
64
65
  # - Version 2: (2024-09-30):
65
66
  # \${{\s*(?P<caller>(?P<caller_prefix>(?:[a-zA-Z_-]+\.)*)(?P<caller_last>[a-zA-Z0-9_\-.'\"(\)[\]{}]+))\s*(?P<post_filters>(?:\|\s*(?:[a-zA-Z0-9_]{3,}[a-zA-Z0-9_.,-\\%\s'\"[\]()\{}]+)\s*)*)}}
67
+ #
66
68
  # - Version 3: (2024-10-05):
67
69
  # \${{\s*(?P<caller>(?P<caller_prefix>(?:[a-zA-Z_-]+\??\.)*)(?P<caller_last>[a-zA-Z0-9_\-.'\"(\)[\]{}]+\??))\s*(?P<post_filters>(?:\|\s*(?:[a-zA-Z0-9_]{3,}[a-zA-Z0-9_.,-\\%\s'\"[\]()\{}]+)\s*)*)}}
68
70
  #
@@ -45,6 +45,7 @@ async def job_execute(
45
45
  run_id=result.run_id,
46
46
  parent_run_id=result.parent_run_id,
47
47
  )
48
+ context: DictData = {}
48
49
  try:
49
50
  job.set_outputs(
50
51
  job.execute(
@@ -52,7 +53,7 @@ async def job_execute(
52
53
  run_id=rs.run_id,
53
54
  parent_run_id=rs.parent_run_id,
54
55
  ).context,
55
- to=params,
56
+ to=context,
56
57
  )
57
58
  except JobException as err:
58
59
  rs.trace.error(f"[WORKFLOW]: {err.__class__.__name__}: {err}")
@@ -70,4 +71,5 @@ async def job_execute(
70
71
  exclude_defaults=True,
71
72
  ),
72
73
  "params": params,
74
+ "context": context,
73
75
  }
@@ -6,7 +6,7 @@
6
6
  """This route include audit and trace log paths."""
7
7
  from __future__ import annotations
8
8
 
9
- from fastapi import APIRouter
9
+ from fastapi import APIRouter, Path, Query
10
10
  from fastapi import status as st
11
11
  from fastapi.responses import UJSONResponse
12
12
 
@@ -27,12 +27,17 @@ log_route = APIRouter(
27
27
  summary="Read all trace logs.",
28
28
  tags=["trace"],
29
29
  )
30
- async def get_traces():
30
+ async def get_traces(
31
+ offset: int = Query(default=0, gt=0),
32
+ limit: int = Query(default=100, gt=0),
33
+ ):
31
34
  """Return all trace logs from the current trace log path that config with
32
35
  `WORKFLOW_LOG_PATH` environment variable name.
33
36
  """
34
37
  return {
35
- "message": "Getting trace logs",
38
+ "message": (
39
+ f"Getting trace logs with offset: {offset} and limit: {limit}"
40
+ ),
36
41
  "traces": [
37
42
  trace.model_dump(
38
43
  by_alias=True,
@@ -117,7 +122,10 @@ async def get_audit_with_workflow(workflow: str):
117
122
  summary="Read all audit logs with specific workflow name and release date.",
118
123
  tags=["audit"],
119
124
  )
120
- async def get_audit_with_workflow_release(workflow: str, release: str):
125
+ async def get_audit_with_workflow_release(
126
+ workflow: str = Path(...),
127
+ release: str = Path(...),
128
+ ):
121
129
  """Return all audit logs with specific workflow name and release date from
122
130
  the current audit log path that config with `WORKFLOW_AUDIT_PATH`
123
131
  environment variable name.
ddeutil/workflow/audit.py CHANGED
@@ -20,7 +20,7 @@ from typing_extensions import Self
20
20
 
21
21
  from .__types import DictData, TupleStr
22
22
  from .conf import config
23
- from .logs import TraceLog, get_trace
23
+ from .logs import TraceLog, get_dt_tznow, get_trace
24
24
 
25
25
  __all__: TupleStr = (
26
26
  "get_audit",
@@ -43,10 +43,12 @@ class BaseAudit(BaseModel, ABC):
43
43
  default_factory=dict,
44
44
  description="A context that receive from a workflow execution result.",
45
45
  )
46
- parent_run_id: Optional[str] = Field(default=None)
47
- run_id: str
48
- update: datetime = Field(default_factory=datetime.now)
49
- execution_time: float = Field(default=0)
46
+ parent_run_id: Optional[str] = Field(
47
+ default=None, description="A parent running ID."
48
+ )
49
+ run_id: str = Field(description="A running ID")
50
+ update: datetime = Field(default_factory=get_dt_tznow)
51
+ execution_time: float = Field(default=0, description="An execution time.")
50
52
 
51
53
  @model_validator(mode="after")
52
54
  def __model_action(self) -> Self:
@@ -26,6 +26,7 @@ T = TypeVar("T")
26
26
  P = ParamSpec("P")
27
27
 
28
28
  logger = logging.getLogger("ddeutil.workflow")
29
+ logging.getLogger("asyncio").setLevel(logging.INFO)
29
30
 
30
31
 
31
32
  class TagFunc(Protocol):
@@ -47,9 +48,10 @@ def tag(
47
48
  """Tag decorator function that set function attributes, ``tag`` and ``name``
48
49
  for making registries variable.
49
50
 
50
- :param: name: A tag name for make different use-case of a function.
51
- :param: alias: A alias function name that keeping in registries. If this
52
- value does not supply, it will use original function name from __name__.
51
+ :param: name: (str) A tag name for make different use-case of a function.
52
+ :param: alias: (str) A alias function name that keeping in registries.
53
+ If this value does not supply, it will use original function name
54
+ from `__name__` argument.
53
55
 
54
56
  :rtype: Callable[P, TagFunc]
55
57
  """
@@ -60,10 +62,13 @@ def tag(
60
62
 
61
63
  @wraps(func)
62
64
  def wrapped(*args: P.args, **kwargs: P.kwargs) -> TagFunc:
63
- # NOTE: Able to do anything before calling the call function.
64
65
  return func(*args, **kwargs)
65
66
 
66
- return wrapped
67
+ @wraps(func)
68
+ async def awrapped(*args: P.args, **kwargs: P.kwargs) -> TagFunc:
69
+ return await func(*args, **kwargs)
70
+
71
+ return awrapped if inspect.iscoroutinefunction(func) else wrapped
67
72
 
68
73
  return func_internal
69
74
 
@@ -74,7 +79,7 @@ Registry = dict[str, Callable[[], TagFunc]]
74
79
  def make_registry(submodule: str) -> dict[str, Registry]:
75
80
  """Return registries of all functions that able to called with task.
76
81
 
77
- :param submodule: A module prefix that want to import registry.
82
+ :param submodule: (str) A module prefix that want to import registry.
78
83
 
79
84
  :rtype: dict[str, Registry]
80
85
  """
@@ -130,12 +135,7 @@ def extract_call(call: str) -> Callable[[], TagFunc]:
130
135
  """Extract Call function from string value to call partial function that
131
136
  does run it at runtime.
132
137
 
133
- :raise NotImplementedError: When the searching call's function result does
134
- not exist in the registry.
135
- :raise NotImplementedError: When the searching call's tag result does not
136
- exist in the registry with its function key.
137
-
138
- :param call: A call value that able to match with Task regex.
138
+ :param call: (str) A call value that able to match with Task regex.
139
139
 
140
140
  The format of call value should contain 3 regular expression groups
141
141
  which match with the below config format:
@@ -148,6 +148,11 @@ def extract_call(call: str) -> Callable[[], TagFunc]:
148
148
  >>> extract_call("tasks/return-type-not-valid@raise")
149
149
  ...
150
150
 
151
+ :raise NotImplementedError: When the searching call's function result does
152
+ not exist in the registry.
153
+ :raise NotImplementedError: When the searching call's tag result does not
154
+ exist in the registry with its function key.
155
+
151
156
  :rtype: Callable[[], TagFunc]
152
157
  """
153
158
  if not (found := Re.RE_TASK_FMT.search(call)):
@@ -0,0 +1,61 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional, Union
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+ from .__types import DictData
8
+
9
+
10
+ class ErrorContext(BaseModel): # pragma: no cov
11
+ model_config = ConfigDict(arbitrary_types_allowed=True)
12
+
13
+ obj: Exception = Field(alias="class")
14
+ name: str = Field(description="A name of exception class.")
15
+ message: str = Field(description="A exception message.")
16
+
17
+
18
+ class OutputContext(BaseModel): # pragma: no cov
19
+ outputs: DictData = Field(default_factory=dict)
20
+ errors: Optional[ErrorContext] = Field(default=None)
21
+ skipped: bool = Field(default=False)
22
+
23
+ def is_exception(self) -> bool:
24
+ return self.errors is not None
25
+
26
+
27
+ class StageContext(BaseModel): # pragma: no cov
28
+ stages: dict[str, OutputContext]
29
+ errors: Optional[ErrorContext] = Field(default=None)
30
+
31
+ def is_exception(self) -> bool:
32
+ return self.errors is not None
33
+
34
+
35
+ class MatrixContext(StageContext): # pragma: no cov
36
+ matrix: DictData = Field(default_factory=dict)
37
+
38
+
39
+ MatrixStageContext = dict[
40
+ str, Union[MatrixContext, StageContext]
41
+ ] # pragma: no cov
42
+
43
+
44
+ class StrategyContext(BaseModel): # pragma: no cov
45
+ strategies: MatrixStageContext
46
+ errors: Optional[ErrorContext] = Field(default=None)
47
+
48
+ def is_exception(self) -> bool:
49
+ return self.errors is not None
50
+
51
+
52
+ StrategyMatrixContext = Union[
53
+ StrategyContext, MatrixStageContext
54
+ ] # pragma: no cov
55
+
56
+
57
+ class JobContext(BaseModel): # pragma: no cov
58
+ params: DictData = Field(description="A parameterize value")
59
+ jobs: dict[str, StrategyMatrixContext]
60
+ errors: Optional[ErrorContext] = Field(default=None)
61
+ skipped: bool = Field(default=False)
@@ -9,8 +9,21 @@ annotate for handle error only.
9
9
  """
10
10
  from __future__ import annotations
11
11
 
12
+ from typing import Any
12
13
 
13
- class BaseWorkflowException(Exception): ...
14
+
15
+ def to_dict(exception: Exception) -> dict[str, Any]: # pragma: no cov
16
+ return {
17
+ "class": exception,
18
+ "name": exception.__class__.__name__,
19
+ "message": str(exception),
20
+ }
21
+
22
+
23
+ class BaseWorkflowException(Exception):
24
+
25
+ def to_dict(self) -> dict[str, Any]:
26
+ return to_dict(self)
14
27
 
15
28
 
16
29
  class UtilException(BaseWorkflowException): ...