ddeutil-workflow 0.0.78__py3-none-any.whl → 0.0.80__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.
Files changed (30) hide show
  1. ddeutil/workflow/__about__.py +1 -1
  2. ddeutil/workflow/__init__.py +2 -6
  3. ddeutil/workflow/api/routes/job.py +2 -2
  4. ddeutil/workflow/api/routes/logs.py +5 -5
  5. ddeutil/workflow/api/routes/workflows.py +3 -3
  6. ddeutil/workflow/audits.py +547 -176
  7. ddeutil/workflow/cli.py +19 -1
  8. ddeutil/workflow/conf.py +10 -20
  9. ddeutil/workflow/event.py +15 -6
  10. ddeutil/workflow/job.py +147 -74
  11. ddeutil/workflow/params.py +172 -58
  12. ddeutil/workflow/plugins/__init__.py +0 -0
  13. ddeutil/workflow/plugins/providers/__init__.py +0 -0
  14. ddeutil/workflow/plugins/providers/aws.py +908 -0
  15. ddeutil/workflow/plugins/providers/az.py +1003 -0
  16. ddeutil/workflow/plugins/providers/container.py +703 -0
  17. ddeutil/workflow/plugins/providers/gcs.py +826 -0
  18. ddeutil/workflow/result.py +6 -4
  19. ddeutil/workflow/reusables.py +151 -95
  20. ddeutil/workflow/stages.py +28 -28
  21. ddeutil/workflow/traces.py +1697 -541
  22. ddeutil/workflow/utils.py +109 -67
  23. ddeutil/workflow/workflow.py +42 -30
  24. {ddeutil_workflow-0.0.78.dist-info → ddeutil_workflow-0.0.80.dist-info}/METADATA +39 -19
  25. ddeutil_workflow-0.0.80.dist-info/RECORD +36 -0
  26. ddeutil_workflow-0.0.78.dist-info/RECORD +0 -30
  27. {ddeutil_workflow-0.0.78.dist-info → ddeutil_workflow-0.0.80.dist-info}/WHEEL +0 -0
  28. {ddeutil_workflow-0.0.78.dist-info → ddeutil_workflow-0.0.80.dist-info}/entry_points.txt +0 -0
  29. {ddeutil_workflow-0.0.78.dist-info → ddeutil_workflow-0.0.80.dist-info}/licenses/LICENSE +0 -0
  30. {ddeutil_workflow-0.0.78.dist-info → ddeutil_workflow-0.0.80.dist-info}/top_level.txt +0 -0
@@ -39,7 +39,7 @@ from . import (
39
39
  WorkflowError,
40
40
  )
41
41
  from .__types import DictData
42
- from .audits import Trace, get_trace
42
+ from .audits import TraceManager, get_trace
43
43
  from .errors import ResultError
44
44
  from .utils import default_gen_id
45
45
 
@@ -188,7 +188,9 @@ class Result:
188
188
  info: DictData = field(default_factory=dict)
189
189
  run_id: str = field(default_factory=default_gen_id)
190
190
  parent_run_id: Optional[str] = field(default=None)
191
- trace: Optional[Trace] = field(default=None, compare=False, repr=False)
191
+ trace: Optional[TraceManager] = field(
192
+ default=None, compare=False, repr=False
193
+ )
192
194
 
193
195
  @model_validator(mode="after")
194
196
  def __prepare_trace(self) -> Self:
@@ -197,7 +199,7 @@ class Result:
197
199
  :rtype: Self
198
200
  """
199
201
  if self.trace is None: # pragma: no cov
200
- self.trace: Trace = get_trace(
202
+ self.trace: TraceManager = get_trace(
201
203
  self.run_id,
202
204
  parent_run_id=self.parent_run_id,
203
205
  extras=self.extras,
@@ -206,7 +208,7 @@ class Result:
206
208
  return self
207
209
 
208
210
  @classmethod
209
- def from_trace(cls, trace: Trace):
211
+ def from_trace(cls, trace: TraceManager):
210
212
  """Construct the result model from trace for clean code objective."""
211
213
  return cls(
212
214
  run_id=trace.run_id,
@@ -119,12 +119,15 @@ FilterRegistry = Union[FilterFunc, Callable[[...], Any]]
119
119
 
120
120
 
121
121
  def custom_filter(name: str) -> Callable[P, FilterFunc]:
122
- """Custom filter decorator function that set function attributes, ``filter``
123
- for making filter registries variable.
122
+ """Custom filter decorator function that sets function attributes.
124
123
 
125
- :param: name: (str) A filter name for make different use-case of a function.
124
+ This decorator sets the `filter` attribute for making filter registries variable.
126
125
 
127
- :rtype: Callable[P, FilterFunc]
126
+ Args:
127
+ name: A filter name for different use-cases of a function.
128
+
129
+ Returns:
130
+ Callable[P, FilterFunc]: Decorated function with filter attributes.
128
131
  """
129
132
 
130
133
  def func_internal(func: Callable[[...], Any]) -> FilterFunc:
@@ -144,11 +147,13 @@ def custom_filter(name: str) -> Callable[P, FilterFunc]:
144
147
  def make_filter_registry(
145
148
  registers: Optional[list[str]] = None,
146
149
  ) -> dict[str, FilterRegistry]:
147
- """Return registries of all functions that able to called with task.
150
+ """Return registries of all functions that can be called with tasks.
148
151
 
149
- :param registers: (Optional[list[str]]) Override list of register.
152
+ Args:
153
+ registers: Optional override list of registers.
150
154
 
151
- :rtype: dict[str, FilterRegistry]
155
+ Returns:
156
+ dict[str, FilterRegistry]: Dictionary mapping filter names to functions.
152
157
  """
153
158
  rs: dict[str, FilterRegistry] = {}
154
159
  for module in dynamic("registry_filter", f=registers):
@@ -180,9 +185,14 @@ def get_args_const(
180
185
  ) -> tuple[str, list[Constant], dict[str, Constant]]:
181
186
  """Get arguments and keyword-arguments from function calling string.
182
187
 
183
- :param expr: (str) An expr string value.
188
+ Args:
189
+ expr: A string expression representing a function call.
190
+
191
+ Returns:
192
+ tuple[str, list[Constant], dict[str, Constant]]: Function name, args, and kwargs.
184
193
 
185
- :rtype: tuple[str, list[Constant], dict[str, Constant]]
194
+ Raises:
195
+ UtilError: If the expression has syntax errors or invalid format.
186
196
  """
187
197
  try:
188
198
  mod: Module = parse(expr)
@@ -223,12 +233,18 @@ def get_args_from_filter(
223
233
  filters: dict[str, FilterRegistry],
224
234
  ) -> tuple[str, FilterRegistry, list[Any], dict[Any, Any]]: # pragma: no cov
225
235
  """Get arguments and keyword-arguments from filter function calling string.
226
- and validate it with the filter functions mapping dict.
227
236
 
228
- :param ft:
229
- :param filters: A mapping of filter registry.
237
+ This function validates the filter function call with the filter functions mapping dict.
238
+
239
+ Args:
240
+ ft: Filter function calling string.
241
+ filters: A mapping of filter registry.
230
242
 
231
- :rtype: tuple[str, FilterRegistry, list[Any], dict[Any, Any]]
243
+ Returns:
244
+ tuple[str, FilterRegistry, list[Any], dict[Any, Any]]: Function name, function, args, and kwargs.
245
+
246
+ Raises:
247
+ UtilError: If the filter function is not supported or has invalid arguments.
232
248
  """
233
249
  func_name, _args, _kwargs = get_args_const(ft)
234
250
  args: list[Any] = [arg.value for arg in _args]
@@ -250,14 +266,18 @@ def map_post_filter(
250
266
  post_filter: list[str],
251
267
  filters: dict[str, FilterRegistry],
252
268
  ) -> T:
253
- """Mapping post-filter to value with sequence list of filter function name
254
- that will get from the filter registry.
269
+ """Map post-filter functions to value with sequence of filter function names.
270
+
271
+ Args:
272
+ value: A value to map with filter functions.
273
+ post_filter: A list of post-filter function names.
274
+ filters: A mapping of filter registry.
255
275
 
256
- :param value: A string value that want to map with filter function.
257
- :param post_filter: A list of post-filter function name.
258
- :param filters: A mapping of filter registry.
276
+ Returns:
277
+ T: The value after applying all post-filter functions.
259
278
 
260
- :rtype: T
279
+ Raises:
280
+ UtilError: If a post-filter function fails or is incompatible with the value.
261
281
  """
262
282
  for ft in post_filter:
263
283
  func_name, f_func, args, kwargs = get_args_from_filter(ft, filters)
@@ -278,13 +298,15 @@ def map_post_filter(
278
298
 
279
299
 
280
300
  def not_in_template(value: Any, *, not_in: str = "matrix.") -> bool:
281
- """Check value should not pass template with not_in value prefix.
301
+ """Check if value should not pass template with not_in value prefix.
282
302
 
283
- :param value: A value that want to find parameter template prefix.
284
- :param not_in: The not-in string that use in the `.startswith` function.
285
- (Default is `matrix.`)
303
+ Args:
304
+ value: A value to check for parameter template prefix.
305
+ not_in: The not-in string to use in the `.startswith` function.
306
+ Default is `matrix.`.
286
307
 
287
- :rtype: bool
308
+ Returns:
309
+ bool: True if value should not pass template, False otherwise.
288
310
  """
289
311
  if isinstance(value, dict):
290
312
  return any(not_in_template(value[k], not_in=not_in) for k in value)
@@ -299,11 +321,13 @@ def not_in_template(value: Any, *, not_in: str = "matrix.") -> bool:
299
321
 
300
322
 
301
323
  def has_template(value: Any) -> bool:
302
- """Check value include templating string.
324
+ """Check if value includes templating string.
303
325
 
304
- :param value: A value that want to find parameter template.
326
+ Args:
327
+ value: A value to check for parameter template.
305
328
 
306
- :rtype: bool
329
+ Returns:
330
+ bool: True if value contains template variables, False otherwise.
307
331
  """
308
332
  if isinstance(value, dict):
309
333
  return any(has_template(value[k]) for k in value)
@@ -322,21 +346,24 @@ def str2template(
322
346
  filters: Optional[dict[str, FilterRegistry]] = None,
323
347
  registers: Optional[list[str]] = None,
324
348
  ) -> Optional[str]:
325
- """(Sub-function) Pass param to template string that can search by
326
- ``RE_CALLER`` regular expression.
349
+ """Pass parameters to template string using RE_CALLER regular expression.
350
+
351
+ This is a sub-function that processes template strings. The getter value
352
+ that maps a template should have typing support aligned with workflow
353
+ parameter types: `str`, `int`, `datetime`, and `list`.
327
354
 
328
- The getter value that map a template should have typing support align
329
- with the workflow parameter types that is `str`, `int`, `datetime`, and
330
- `list`.
355
+ Args:
356
+ value: A string value to map with parameters.
357
+ params: Parameter values to get with matched regular expression.
358
+ context: Optional context data.
359
+ filters: Optional mapping of filter registry.
360
+ registers: Optional override list of registers.
331
361
 
332
- :param value: (str) A string value that want to map with params.
333
- :param params: (DictData) A parameter value that getting with matched
334
- regular expression.
335
- :param context: (DictData)
336
- :param filters: (dict[str, FilterRegistry]) A mapping of filter registry.
337
- :param registers: (Optional[list[str]]) Override list of register.
362
+ Returns:
363
+ Optional[str]: The processed template string or None if value is "None".
338
364
 
339
- :rtype: str
365
+ Raises:
366
+ UtilError: If parameters cannot be retrieved or template processing fails.
340
367
  """
341
368
  filters: dict[str, FilterRegistry] = filters or make_filter_registry(
342
369
  registers=registers
@@ -395,19 +422,17 @@ def param2template(
395
422
  *,
396
423
  extras: Optional[DictData] = None,
397
424
  ) -> Any:
398
- """Pass param to template string that can search by ``RE_CALLER`` regular
399
- expression.
400
-
401
- :param value: (Any) A value that want to map with params.
402
- :param params: (DictData) A parameter value that getting with matched
403
- regular expression.
404
- :param context: (DictData)
405
- :param filters: (dict[str, FilterRegistry]) A filter mapping for mapping
406
- with `map_post_filter` func.
407
- :param extras: (Optional[list[str]]) An Override extras.
408
-
409
- :rtype: Any
410
- :returns: An any getter value from the params input.
425
+ """Pass parameters to template string using RE_CALLER regular expression.
426
+
427
+ Args:
428
+ value: A value to map with parameters.
429
+ params: Parameter values to get with matched regular expression.
430
+ context: Optional context data.
431
+ filters: Optional filter mapping for use with `map_post_filter` function.
432
+ extras: Optional override extras.
433
+
434
+ Returns:
435
+ Any: The processed value with template variables replaced.
411
436
  """
412
437
  registers: Optional[list[str]] = (
413
438
  extras.get("registry_filter") if extras else None
@@ -437,19 +462,21 @@ def param2template(
437
462
 
438
463
  @custom_filter("fmt") # pragma: no cov
439
464
  def datetime_format(value: datetime, fmt: str = "%Y-%m-%d %H:%M:%S") -> str:
440
- """Format datetime object to string with the format.
465
+ """Format datetime object to string with the specified format.
441
466
 
442
467
  Examples:
443
-
444
468
  >>> "${{ start-date | fmt('%Y%m%d') }}"
445
469
  >>> "${{ start-date | fmt }}"
446
470
 
447
- :param value: (datetime) A datetime value that want to format to string
448
- value.
449
- :param fmt: (str) A format string pattern that passing to the `dt.strftime`
450
- method.
471
+ Args:
472
+ value: A datetime value to format to string.
473
+ fmt: A format string pattern to pass to the `dt.strftime` method.
451
474
 
452
- :rtype: str
475
+ Returns:
476
+ str: The formatted datetime string.
477
+
478
+ Raises:
479
+ UtilError: If the input value is not a datetime object.
453
480
  """
454
481
  if isinstance(value, datetime):
455
482
  return value.strftime(fmt)
@@ -463,12 +490,14 @@ def coalesce(value: Optional[T], default: Any) -> T:
463
490
  """Coalesce with default value if the main value is None.
464
491
 
465
492
  Examples:
466
-
467
493
  >>> "${{ value | coalesce('foo') }}"
468
494
 
469
- :param value: A value that want to check nullable.
470
- :param default: A default value that use to returned value if an input
471
- value was null.
495
+ Args:
496
+ value: A value to check for null.
497
+ default: A default value to return if the input value is null.
498
+
499
+ Returns:
500
+ T: The original value if not None, otherwise the default value.
472
501
  """
473
502
  return default if value is None else value
474
503
 
@@ -477,13 +506,22 @@ def coalesce(value: Optional[T], default: Any) -> T:
477
506
  def get_item(
478
507
  value: DictData, key: Union[str, int], default: Optional[Any] = None
479
508
  ) -> Any:
480
- """Get a value with an input specific key.
509
+ """Get a value with a specific key.
481
510
 
482
511
  Examples:
483
-
484
512
  >>> "${{ value | getitem('key') }}"
485
513
  >>> "${{ value | getitem('key', 'default') }}"
486
514
 
515
+ Args:
516
+ value: A dictionary to get the value from.
517
+ key: The key to look up in the dictionary.
518
+ default: Optional default value if key is not found.
519
+
520
+ Returns:
521
+ Any: The value associated with the key, or the default value.
522
+
523
+ Raises:
524
+ UtilError: If the value is not a dictionary.
487
525
  """
488
526
  if not isinstance(value, dict):
489
527
  raise UtilError(
@@ -495,12 +533,20 @@ def get_item(
495
533
 
496
534
  @custom_filter("getindex") # pragma: no cov
497
535
  def get_index(value: list[Any], index: int) -> Any:
498
- """Get a value with an input specific index.
536
+ """Get a value with a specific index.
499
537
 
500
538
  Examples:
501
-
502
539
  >>> "${{ value | getindex(1) }}"
503
540
 
541
+ Args:
542
+ value: A list to get the value from.
543
+ index: The index to access in the list.
544
+
545
+ Returns:
546
+ Any: The value at the specified index.
547
+
548
+ Raises:
549
+ UtilError: If the value is not a list or if the index is out of range.
504
550
  """
505
551
  if not isinstance(value, list):
506
552
  raise UtilError(
@@ -534,16 +580,18 @@ def tag(
534
580
  name: Optional[str] = None,
535
581
  alias: Optional[str] = None,
536
582
  ) -> DecoratorTagFunc: # pragma: no cov
537
- """Tag decorator function that set function attributes, ``tag`` and ``name``
538
- for making registries variable.
583
+ """Tag decorator function that sets function attributes for registry.
539
584
 
540
- :param: name: (str) A tag name for make different use-case of a function.
541
- It will use 'latest' if this tag name does not set.
542
- :param: alias: (str) A alias function name that keeping in registries.
543
- If this value does not supply, it will use original function name
544
- from `__name__` argument.
585
+ This decorator sets the `tag` and `name` attributes for making registries variable.
545
586
 
546
- :rtype: Callable[P, TagFunc]
587
+ Args:
588
+ name: A tag name for different use-cases of a function.
589
+ Uses 'latest' if not set.
590
+ alias: An alias function name to keep in registries.
591
+ Uses original function name from `__name__` if not supplied.
592
+
593
+ Returns:
594
+ DecoratorTagFunc: Decorated function with tag attributes.
547
595
  """
548
596
 
549
597
  def func_internal(func: Callable[[...], Any]) -> ReturnTagFunc:
@@ -574,12 +622,17 @@ def make_registry(
574
622
  *,
575
623
  registries: Optional[list[str]] = None,
576
624
  ) -> dict[str, Registry]:
577
- """Return registries of all functions that able to called with task.
625
+ """Return registries of all functions that can be called with tasks.
626
+
627
+ Args:
628
+ submodule: A module prefix to import registry from.
629
+ registries: Optional list of registries.
578
630
 
579
- :param submodule: (str) A module prefix that want to import registry.
580
- :param registries: (Optional[list[str]]) A list of registry.
631
+ Returns:
632
+ dict[str, Registry]: Dictionary mapping function names to their registries.
581
633
 
582
- :rtype: dict[str, Registry]
634
+ Raises:
635
+ ValueError: If a tag already exists for a function name.
583
636
  """
584
637
  rs: dict[str, Registry] = {}
585
638
  regis_calls: list[str] = copy.deepcopy(
@@ -639,13 +692,9 @@ def extract_call(
639
692
  *,
640
693
  registries: Optional[list[str]] = None,
641
694
  ) -> Callable[[], TagFunc]:
642
- """Extract Call function from string value to call partial function that
643
- does run it at runtime.
644
-
645
- :param call: (str) A call value that able to match with Task regex.
646
- :param registries: (Optional[list[str]]) A list of registry.
695
+ """Extract call function from string value to call partial function at runtime.
647
696
 
648
- The format of call value should contain 3 regular expression groups
697
+ The format of call value should contain 3 regular expression groups
649
698
  which match with the below config format:
650
699
 
651
700
  >>> "^(?P<path>[^/@]+)/(?P<func>[^@]+)@(?P<tag>.+)$"
@@ -656,12 +705,16 @@ def extract_call(
656
705
  >>> extract_call("tasks/return-type-not-valid@raise")
657
706
  ...
658
707
 
659
- :raise NotImplementedError: When the searching call's function result does
660
- not exist in the registry.
661
- :raise NotImplementedError: When the searching call's tag result does not
662
- exist in the registry with its function key.
708
+ Args:
709
+ call: A call value that matches the Task regex.
710
+ registries: Optional list of registries.
663
711
 
664
- :rtype: Callable[[], TagFunc]
712
+ Returns:
713
+ Callable[[], TagFunc]: A callable function that can be executed.
714
+
715
+ Raises:
716
+ ValueError: If the call does not match the regex format.
717
+ NotImplementedError: If the function or tag does not exist in the registry.
665
718
  """
666
719
  if not (found := Re.RE_TASK_FMT.search(call)):
667
720
  raise ValueError(
@@ -697,17 +750,20 @@ class BaseCallerArgs(BaseModel): # pragma: no cov
697
750
 
698
751
 
699
752
  def create_model_from_caller(func: Callable) -> BaseModel: # pragma: no cov
700
- """Create model from the caller function. This function will use for
701
- validate the caller function argument typed-hint that valid with the args
702
- field.
753
+ """Create model from the caller function.
754
+
755
+ This function is used to validate the caller function argument type hints
756
+ that are valid with the args field.
703
757
 
704
758
  Reference:
705
759
  - https://github.com/lmmx/pydantic-function-models
706
760
  - https://docs.pydantic.dev/1.10/usage/models/#dynamic-model-creation
707
761
 
708
- :param func: (Callable) A caller function.
762
+ Args:
763
+ func: A caller function to create a model from.
709
764
 
710
- :rtype: BaseModel
765
+ Returns:
766
+ BaseModel: A Pydantic model created from the function signature.
711
767
  """
712
768
  sig: inspect.Signature = inspect.signature(func)
713
769
  type_hints: dict[str, Any] = get_type_hints(func)