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/stage.py CHANGED
@@ -12,16 +12,19 @@ can tracking logs.
12
12
  handle stage error on this stage model. I think stage model should have a lot of
13
13
  usecase and it does not worry when I want to create a new one.
14
14
 
15
- Execution --> Ok --> Result with 0
16
- --> Error --> Raise StageException
15
+ Execution --> Ok --> Result with 0
16
+ --> Error --> Raise StageException
17
+
18
+ On the context I/O that pass to stage object at execute process. The execute
19
+ method receive `{"params": {...}}` for mapping to template.
17
20
  """
18
21
  from __future__ import annotations
19
22
 
20
23
  import contextlib
21
24
  import inspect
22
- import os
23
25
  import subprocess
24
26
  import sys
27
+ import time
25
28
  import uuid
26
29
  from abc import ABC, abstractmethod
27
30
  from collections.abc import Iterator
@@ -38,12 +41,12 @@ try:
38
41
  except ImportError:
39
42
  from typing_extensions import ParamSpec
40
43
 
41
- from ddeutil.core import str2bool
42
44
  from pydantic import BaseModel, Field
43
45
  from pydantic.functional_validators import model_validator
44
46
  from typing_extensions import Self
45
47
 
46
48
  from .__types import DictData, DictStr, Re, TupleStr
49
+ from .conf import config
47
50
  from .exceptions import StageException
48
51
  from .log import get_logger
49
52
  from .utils import (
@@ -58,6 +61,8 @@ from .utils import (
58
61
  )
59
62
 
60
63
  P = ParamSpec("P")
64
+ ReturnResult = Callable[P, Result]
65
+ DecoratorResult = Callable[[ReturnResult], ReturnResult]
61
66
  logger = get_logger("ddeutil.workflow")
62
67
 
63
68
 
@@ -69,13 +74,13 @@ __all__: TupleStr = (
69
74
  "HookStage",
70
75
  "TriggerStage",
71
76
  "Stage",
72
- "HookSearch",
77
+ "HookSearchData",
73
78
  "extract_hook",
74
79
  "handler_result",
75
80
  )
76
81
 
77
82
 
78
- def handler_result(message: str | None = None) -> Callable[P, Result]:
83
+ def handler_result(message: str | None = None) -> DecoratorResult:
79
84
  """Decorator function for handler result from the stage execution. This
80
85
  function should to use with execution method only.
81
86
 
@@ -87,11 +92,21 @@ def handler_result(message: str | None = None) -> Callable[P, Result]:
87
92
  --> Error --> Raise StageException
88
93
  --> Result with 1 (if env var was set)
89
94
 
95
+ On the last step, it will set the running ID on a return result object
96
+ from current stage ID before release the final result.
97
+
90
98
  :param message: A message that want to add at prefix of exception statement.
99
+ :rtype: Callable[P, Result]
91
100
  """
101
+ # NOTE: The prefix message string that want to add on the first exception
102
+ # message dialog.
103
+ #
104
+ # ... ValueError: {message}
105
+ # ... raise value error from the stage execution process.
106
+ #
92
107
  message: str = message or ""
93
108
 
94
- def decorator(func: Callable[P, Result]) -> Callable[P, Result]:
109
+ def decorator(func: ReturnResult) -> ReturnResult:
95
110
 
96
111
  @wraps(func)
97
112
  def wrapped(self: Stage, *args, **kwargs):
@@ -103,9 +118,7 @@ def handler_result(message: str | None = None) -> Callable[P, Result]:
103
118
  logger.error(
104
119
  f"({self.run_id}) [STAGE]: {err.__class__.__name__}: {err}"
105
120
  )
106
- if str2bool(
107
- os.getenv("WORKFLOW_CORE_STAGE_RAISE_ERROR", "true")
108
- ):
121
+ if config.stage_raise_error:
109
122
  # NOTE: If error that raise from stage execution course by
110
123
  # itself, it will return that error with previous
111
124
  # dependency.
@@ -119,14 +132,14 @@ def handler_result(message: str | None = None) -> Callable[P, Result]:
119
132
  ) from None
120
133
 
121
134
  # NOTE: Catching exception error object to result with
122
- # error_message key.
123
- rs: Result = Result(
135
+ # error_message and error keys.
136
+ return Result(
124
137
  status=1,
125
138
  context={
139
+ "error": err,
126
140
  "error_message": f"{err.__class__.__name__}: {err}",
127
141
  },
128
- )
129
- return rs.set_run_id(self.run_id)
142
+ ).set_run_id(self.run_id)
130
143
 
131
144
  return wrapped
132
145
 
@@ -162,10 +175,12 @@ class BaseStage(BaseModel, ABC):
162
175
  )
163
176
 
164
177
  @model_validator(mode="after")
165
- def __prepare_running_id(self):
178
+ def __prepare_running_id(self) -> Self:
166
179
  """Prepare stage running ID that use default value of field and this
167
180
  method will validate name and id fields should not contain any template
168
181
  parameter (exclude matrix template).
182
+
183
+ :rtype: Self
169
184
  """
170
185
  if self.run_id is None:
171
186
  self.run_id = gen_id(self.name + (self.id or ""), unique=True)
@@ -199,16 +214,28 @@ class BaseStage(BaseModel, ABC):
199
214
  raise NotImplementedError("Stage should implement ``execute`` method.")
200
215
 
201
216
  def set_outputs(self, output: DictData, to: DictData) -> DictData:
202
- """Set an outputs from execution process to an input params.
217
+ """Set an outputs from execution process to the receive context. The
218
+ result from execution will pass to value of ``outputs`` key.
219
+
220
+ For example of setting output method, If you receive execute output
221
+ and want to set on the `to` like;
222
+
223
+ ... (i) output: {'foo': bar}
224
+ ... (ii) to: {}
225
+
226
+ The result of the `to` variable will be;
227
+
228
+ ... (iii) to: {
229
+ 'stages': {
230
+ '<stage-id>': {'outputs': {'foo': 'bar'}}
231
+ }
232
+ }
203
233
 
204
234
  :param output: A output data that want to extract to an output key.
205
235
  :param to: A context data that want to add output result.
206
236
  :rtype: DictData
207
237
  """
208
- if not (
209
- self.id
210
- or str2bool(os.getenv("WORKFLOW_CORE_STAGE_DEFAULT_ID", "false"))
211
- ):
238
+ if not (self.id or config.stage_default_id):
212
239
  logger.debug(
213
240
  f"({self.run_id}) [STAGE]: Output does not set because this "
214
241
  f"stage does not set ID or default stage ID config flag not be "
@@ -220,16 +247,15 @@ class BaseStage(BaseModel, ABC):
220
247
  if "stages" not in to:
221
248
  to["stages"] = {}
222
249
 
223
- if self.id:
224
- _id: str = param2template(self.id, params=to)
225
- else:
226
- # NOTE: If the stage ID did not set, it will use its name instead.
227
- _id: str = gen_id(param2template(self.name, params=to))
228
-
229
- # NOTE: Set the output to that stage generated ID.
230
- logger.debug(
231
- f"({self.run_id}) [STAGE]: Set output complete with stage ID: {_id}"
250
+ # NOTE: If the stage ID did not set, it will use its name instead.
251
+ _id: str = (
252
+ param2template(self.id, params=to)
253
+ if self.id
254
+ else gen_id(param2template(self.name, params=to))
232
255
  )
256
+
257
+ # NOTE: Set the output to that stage generated ID with ``outputs`` key.
258
+ logger.debug(f"({self.run_id}) [STAGE]: Set outputs on: {_id}")
233
259
  to["stages"][_id] = {"outputs": output}
234
260
  return to
235
261
 
@@ -240,10 +266,10 @@ class BaseStage(BaseModel, ABC):
240
266
  :param params: A parameters that want to pass to condition template.
241
267
  :rtype: bool
242
268
  """
243
- params: DictData = params or {}
244
269
  if self.condition is None:
245
270
  return False
246
271
 
272
+ params: DictData = {} if params is None else params
247
273
  _g: DictData = globals() | params
248
274
  try:
249
275
  rs: bool = eval(param2template(self.condition, params), _g, {})
@@ -270,6 +296,10 @@ class EmptyStage(BaseStage):
270
296
  default=None,
271
297
  description="A string statement that want to logging",
272
298
  )
299
+ sleep: float = Field(
300
+ default=0,
301
+ description="A second value to sleep before finish execution",
302
+ )
273
303
 
274
304
  def execute(self, params: DictData) -> Result:
275
305
  """Execution method for the Empty stage that do only logging out to
@@ -287,6 +317,8 @@ class EmptyStage(BaseStage):
287
317
  f"({self.run_id}) [STAGE]: Empty-Execute: {self.name!r}: "
288
318
  f"( {param2template(self.echo, params=params) or '...'} )"
289
319
  )
320
+ if self.sleep > 0:
321
+ time.sleep(self.sleep)
290
322
  return Result(status=0, context={})
291
323
 
292
324
 
@@ -462,12 +494,13 @@ class PyStage(BaseStage):
462
494
  exec(run, _globals, _locals)
463
495
 
464
496
  return Result(
465
- status=0, context={"locals": _locals, "globals": _globals}
497
+ status=0,
498
+ context={"locals": _locals, "globals": _globals},
466
499
  )
467
500
 
468
501
 
469
502
  @dataclass(frozen=True)
470
- class HookSearch:
503
+ class HookSearchData:
471
504
  """Hook Search dataclass that use for receive regular expression grouping
472
505
  dict from searching hook string value.
473
506
  """
@@ -490,7 +523,7 @@ def extract_hook(hook: str) -> Callable[[], TagFunc]:
490
523
  )
491
524
 
492
525
  # NOTE: Pass the searching hook string to `path`, `func`, and `tag`.
493
- hook: HookSearch = HookSearch(**found.groupdict())
526
+ hook: HookSearchData = HookSearchData(**found.groupdict())
494
527
 
495
528
  # NOTE: Registry object should implement on this package only.
496
529
  rgt: dict[str, Registry] = make_registry(f"{hook.path}")
@@ -596,7 +629,11 @@ class TriggerStage(BaseStage):
596
629
  ... }
597
630
  """
598
631
 
599
- trigger: str = Field(description="A trigger workflow name.")
632
+ trigger: str = Field(
633
+ description=(
634
+ "A trigger workflow name that should already exist on the config."
635
+ ),
636
+ )
600
637
  params: DictData = Field(
601
638
  default_factory=dict,
602
639
  description="A parameter that want to pass to workflow execution.",
@@ -610,6 +647,7 @@ class TriggerStage(BaseStage):
610
647
  :param params: A parameter data that want to use in this execution.
611
648
  :rtype: Result
612
649
  """
650
+ # NOTE: Lazy import this workflow object.
613
651
  from . import Workflow
614
652
 
615
653
  # NOTE: Loading workflow object from trigger name.
@@ -624,7 +662,11 @@ class TriggerStage(BaseStage):
624
662
  return wf.execute(params=param2template(self.params, params))
625
663
 
626
664
 
627
- # NOTE: An order of parsing stage model.
665
+ # NOTE:
666
+ # An order of parsing stage model on the Job model with ``stages`` field.
667
+ # From the current build-in stages, they do not have stage that have the same
668
+ # fields that be cause of parsing on the Job's stages key.
669
+ #
628
670
  Stage = Union[
629
671
  PyStage,
630
672
  BashStage,