ddeutil-workflow 0.0.58__py3-none-any.whl → 0.0.59__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.
@@ -3,16 +3,16 @@
3
3
  # Licensed under the MIT License. See LICENSE in the project root for
4
4
  # license information.
5
5
  # ------------------------------------------------------------------------------
6
- # [x] Use dynamic config
7
- """Result module. It is the data context transfer objects that use by all object
8
- in this package. This module provide Status enum object and Result dataclass.
6
+ """A Result module. It is the data context transfer objects that use by all
7
+ object in this package. This module provide Status enum object and Result
8
+ dataclass.
9
9
  """
10
10
  from __future__ import annotations
11
11
 
12
12
  from dataclasses import field
13
13
  from datetime import datetime
14
14
  from enum import IntEnum
15
- from typing import Optional
15
+ from typing import Optional, Union
16
16
 
17
17
  from pydantic import ConfigDict
18
18
  from pydantic.dataclasses import dataclass
@@ -22,7 +22,7 @@ from typing_extensions import Self
22
22
  from .__types import DictData
23
23
  from .conf import dynamic
24
24
  from .exceptions import ResultException
25
- from .logs import Trace, get_dt_tznow, get_trace
25
+ from .logs import TraceModel, get_dt_tznow, get_trace
26
26
  from .utils import default_gen_id, gen_id, get_dt_now
27
27
 
28
28
 
@@ -31,11 +31,11 @@ class Status(IntEnum):
31
31
  Result dataclass object.
32
32
  """
33
33
 
34
- SUCCESS: int = 0
35
- FAILED: int = 1
36
- WAIT: int = 2
37
- SKIP: int = 3
38
- CANCEL: int = 4
34
+ SUCCESS = 0
35
+ FAILED = 1
36
+ WAIT = 2
37
+ SKIP = 3
38
+ CANCEL = 4
39
39
 
40
40
  @property
41
41
  def emoji(self) -> str:
@@ -75,16 +75,16 @@ class Result:
75
75
  parent_run_id: Optional[str] = field(default=None, compare=False)
76
76
  ts: datetime = field(default_factory=get_dt_tznow, compare=False)
77
77
 
78
- trace: Optional[Trace] = field(default=None, compare=False, repr=False)
78
+ trace: Optional[TraceModel] = field(default=None, compare=False, repr=False)
79
79
  extras: DictData = field(default_factory=dict, compare=False, repr=False)
80
80
 
81
81
  @classmethod
82
82
  def construct_with_rs_or_id(
83
83
  cls,
84
- result: Result | None = None,
85
- run_id: str | None = None,
86
- parent_run_id: str | None = None,
87
- id_logic: str | None = None,
84
+ result: Optional[Result] = None,
85
+ run_id: Optional[str] = None,
86
+ parent_run_id: Optional[str] = None,
87
+ id_logic: Optional[str] = None,
88
88
  *,
89
89
  extras: DictData | None = None,
90
90
  ) -> Self:
@@ -121,7 +121,7 @@ class Result:
121
121
  :rtype: Self
122
122
  """
123
123
  if self.trace is None: # pragma: no cov
124
- self.trace: Trace = get_trace(
124
+ self.trace: TraceModel = get_trace(
125
125
  self.run_id,
126
126
  parent_run_id=self.parent_run_id,
127
127
  extras=self.extras,
@@ -136,14 +136,14 @@ class Result:
136
136
  :rtype: Self
137
137
  """
138
138
  self.parent_run_id: str = running_id
139
- self.trace: Trace = get_trace(
139
+ self.trace: TraceModel = get_trace(
140
140
  self.run_id, parent_run_id=running_id, extras=self.extras
141
141
  )
142
142
  return self
143
143
 
144
144
  def catch(
145
145
  self,
146
- status: int | Status,
146
+ status: Union[int, Status],
147
147
  context: DictData | None = None,
148
148
  **kwargs,
149
149
  ) -> Self:
@@ -7,6 +7,7 @@
7
7
  """Reusables module that keep any templating functions."""
8
8
  from __future__ import annotations
9
9
 
10
+ import copy
10
11
  import inspect
11
12
  import logging
12
13
  from ast import Call, Constant, Expr, Module, Name, parse
@@ -35,7 +36,7 @@ logger = logging.getLogger("ddeutil.workflow")
35
36
  logging.getLogger("asyncio").setLevel(logging.INFO)
36
37
 
37
38
 
38
- FILTERS: dict[str, callable] = { # pragma: no cov
39
+ FILTERS: dict[str, Callable] = { # pragma: no cov
39
40
  "abs": abs,
40
41
  "str": str,
41
42
  "int": int,
@@ -258,9 +259,9 @@ def str2template(
258
259
  value: str,
259
260
  params: DictData,
260
261
  *,
261
- filters: dict[str, FilterRegistry] | None = None,
262
+ filters: Optional[dict[str, FilterRegistry]] = None,
262
263
  registers: Optional[list[str]] = None,
263
- ) -> str:
264
+ ) -> Optional[str]:
264
265
  """(Sub-function) Pass param to template string that can search by
265
266
  ``RE_CALLER`` regular expression.
266
267
 
@@ -326,7 +327,7 @@ def str2template(
326
327
  def param2template(
327
328
  value: T,
328
329
  params: DictData,
329
- filters: dict[str, FilterRegistry] | None = None,
330
+ filters: Optional[dict[str, FilterRegistry]] = None,
330
331
  *,
331
332
  extras: Optional[DictData] = None,
332
333
  ) -> T:
@@ -381,7 +382,7 @@ def datetime_format(value: datetime, fmt: str = "%Y-%m-%d %H:%M:%S") -> str:
381
382
 
382
383
 
383
384
  @custom_filter("coalesce") # pragma: no cov
384
- def coalesce(value: T | None, default: Any) -> T:
385
+ def coalesce(value: Optional[T], default: Any) -> T:
385
386
  """Coalesce with default value if the main value is None."""
386
387
  return default if value is None else value
387
388
 
@@ -450,11 +451,10 @@ def make_registry(
450
451
  :rtype: dict[str, Registry]
451
452
  """
452
453
  rs: dict[str, Registry] = {}
453
- regis_calls: list[str] = dynamic(
454
- "registry_caller", f=registries
455
- ) # pragma: no cov
454
+ regis_calls: list[str] = copy.deepcopy(
455
+ dynamic("registry_caller", f=registries)
456
+ )
456
457
  regis_calls.extend(["ddeutil.vendors"])
457
-
458
458
  for module in regis_calls:
459
459
  # NOTE: try to sequential import task functions
460
460
  try:
@@ -335,9 +335,9 @@ class Schedule(BaseModel):
335
335
  def pending(
336
336
  self,
337
337
  *,
338
- stop: datetime | None = None,
338
+ stop: Optional[datetime] = None,
339
339
  audit: type[Audit] | None = None,
340
- parent_run_id: str | None = None,
340
+ parent_run_id: Optional[str] = None,
341
341
  ) -> Result: # pragma: no cov
342
342
  """Pending this schedule tasks with the schedule package.
343
343
 
@@ -384,7 +384,7 @@ DecoratorCancelJob = Callable[[ReturnResultOrCancel], ReturnResultOrCancel]
384
384
 
385
385
  def catch_exceptions(
386
386
  cancel_on_failure: bool = False,
387
- parent_run_id: str | None = None,
387
+ parent_run_id: Optional[str] = None,
388
388
  ) -> DecoratorCancelJob:
389
389
  """Catch exception error from scheduler job that running with schedule
390
390
  package and return CancelJob if this function raise an error.
@@ -440,7 +440,7 @@ def schedule_task(
440
440
  threads: ReleaseThreads,
441
441
  audit: type[Audit],
442
442
  *,
443
- parent_run_id: str | None = None,
443
+ parent_run_id: Optional[str] = None,
444
444
  extras: Optional[DictData] = None,
445
445
  ) -> ResultOrCancel:
446
446
  """Schedule task function that generate thread of workflow task release
@@ -558,7 +558,7 @@ def schedule_task(
558
558
 
559
559
  def monitor(
560
560
  threads: ReleaseThreads,
561
- parent_run_id: str | None = None,
561
+ parent_run_id: Optional[str] = None,
562
562
  ) -> None: # pragma: no cov
563
563
  """Monitoring function that running every five minute for track long-running
564
564
  thread instance from the schedule_control function that run every minute.
@@ -685,11 +685,11 @@ def scheduler_pending(
685
685
 
686
686
  def schedule_control(
687
687
  schedules: list[str],
688
- stop: datetime | None = None,
688
+ stop: Optional[datetime] = None,
689
689
  *,
690
690
  extras: DictData | None = None,
691
691
  audit: type[Audit] | None = None,
692
- parent_run_id: str | None = None,
692
+ parent_run_id: Optional[str] = None,
693
693
  ) -> Result: # pragma: no cov
694
694
  """Scheduler control function that run the chuck of schedules every minute
695
695
  and this function release monitoring thread for tracking undead thread in
@@ -744,7 +744,7 @@ def schedule_control(
744
744
 
745
745
 
746
746
  def schedule_runner(
747
- stop: datetime | None = None,
747
+ stop: Optional[datetime] = None,
748
748
  *,
749
749
  max_process: int | None = None,
750
750
  extras: DictData | None = None,
@@ -141,8 +141,8 @@ class BaseStage(BaseModel, ABC):
141
141
  self,
142
142
  params: DictData,
143
143
  *,
144
- result: Result | None = None,
145
- event: Event | None = None,
144
+ result: Optional[Result] = None,
145
+ event: Optional[Event] = None,
146
146
  ) -> Result:
147
147
  """Execute abstraction method that action something by sub-model class.
148
148
  This is important method that make this class is able to be the stage.
@@ -162,12 +162,12 @@ class BaseStage(BaseModel, ABC):
162
162
  self,
163
163
  params: DictData,
164
164
  *,
165
- run_id: str | None = None,
166
- parent_run_id: str | None = None,
167
- result: Result | None = None,
168
- event: Event | None = None,
169
- raise_error: bool | None = None,
170
- ) -> Result | DictData:
165
+ run_id: Optional[str] = None,
166
+ parent_run_id: Optional[str] = None,
167
+ result: Optional[Result] = None,
168
+ event: Optional[Event] = None,
169
+ raise_error: Optional[bool] = None,
170
+ ) -> Union[Result, DictData]:
171
171
  """Handler stage execution result from the stage `execute` method.
172
172
 
173
173
  This stage exception handler still use ok-error concept, but it
@@ -376,8 +376,8 @@ class BaseAsyncStage(BaseStage):
376
376
  self,
377
377
  params: DictData,
378
378
  *,
379
- result: Result | None = None,
380
- event: Event | None = None,
379
+ result: Optional[Result] = None,
380
+ event: Optional[Event] = None,
381
381
  ) -> Result:
382
382
  raise NotImplementedError(
383
383
  "Async Stage should implement `execute` method."
@@ -388,8 +388,8 @@ class BaseAsyncStage(BaseStage):
388
388
  self,
389
389
  params: DictData,
390
390
  *,
391
- result: Result | None = None,
392
- event: Event | None = None,
391
+ result: Optional[Result] = None,
392
+ event: Optional[Event] = None,
393
393
  ) -> Result:
394
394
  """Async execution method for this Empty stage that only logging out to
395
395
  stdout.
@@ -411,11 +411,11 @@ class BaseAsyncStage(BaseStage):
411
411
  self,
412
412
  params: DictData,
413
413
  *,
414
- run_id: str | None = None,
415
- parent_run_id: str | None = None,
416
- result: Result | None = None,
417
- event: Event | None = None,
418
- raise_error: bool | None = None,
414
+ run_id: Optional[str] = None,
415
+ parent_run_id: Optional[str] = None,
416
+ result: Optional[Result] = None,
417
+ event: Optional[Event] = None,
418
+ raise_error: Optional[bool] = None,
419
419
  ) -> Result:
420
420
  """Async Handler stage execution result from the stage `execute` method.
421
421
 
@@ -487,8 +487,8 @@ class EmptyStage(BaseAsyncStage):
487
487
  self,
488
488
  params: DictData,
489
489
  *,
490
- result: Result | None = None,
491
- event: Event | None = None,
490
+ result: Optional[Result] = None,
491
+ event: Optional[Event] = None,
492
492
  ) -> Result:
493
493
  """Execution method for the Empty stage that do only logging out to
494
494
  stdout.
@@ -528,8 +528,8 @@ class EmptyStage(BaseAsyncStage):
528
528
  self,
529
529
  params: DictData,
530
530
  *,
531
- result: Result | None = None,
532
- event: Event | None = None,
531
+ result: Optional[Result] = None,
532
+ event: Optional[Event] = None,
533
533
  ) -> Result:
534
534
  """Async execution method for this Empty stage that only logging out to
535
535
  stdout.
@@ -599,13 +599,13 @@ class BashStage(BaseAsyncStage):
599
599
 
600
600
  @contextlib.asynccontextmanager
601
601
  async def acreate_sh_file(
602
- self, bash: str, env: DictStr, run_id: str | None = None
602
+ self, bash: str, env: DictStr, run_id: Optional[str] = None
603
603
  ) -> AsyncIterator[TupleStr]:
604
604
  """Async create and write `.sh` file with the `aiofiles` package.
605
605
 
606
606
  :param bash: (str) A bash statement.
607
607
  :param env: (DictStr) An environment variable that set before run bash.
608
- :param run_id: (str | None) A running stage ID that use for writing sh
608
+ :param run_id: (Optional[str]) A running stage ID that use for writing sh
609
609
  file instead generate by UUID4.
610
610
 
611
611
  :rtype: AsyncIterator[TupleStr]
@@ -635,14 +635,14 @@ class BashStage(BaseAsyncStage):
635
635
 
636
636
  @contextlib.contextmanager
637
637
  def create_sh_file(
638
- self, bash: str, env: DictStr, run_id: str | None = None
638
+ self, bash: str, env: DictStr, run_id: Optional[str] = None
639
639
  ) -> Iterator[TupleStr]:
640
640
  """Create and write the `.sh` file before giving this file name to
641
641
  context. After that, it will auto delete this file automatic.
642
642
 
643
643
  :param bash: (str) A bash statement.
644
644
  :param env: (DictStr) An environment variable that set before run bash.
645
- :param run_id: (str | None) A running stage ID that use for writing sh
645
+ :param run_id: (Optional[str]) A running stage ID that use for writing sh
646
646
  file instead generate by UUID4.
647
647
 
648
648
  :rtype: Iterator[TupleStr]
@@ -673,8 +673,8 @@ class BashStage(BaseAsyncStage):
673
673
  self,
674
674
  params: DictData,
675
675
  *,
676
- result: Result | None = None,
677
- event: Event | None = None,
676
+ result: Optional[Result] = None,
677
+ event: Optional[Event] = None,
678
678
  ) -> Result:
679
679
  """Execute bash statement with the Python build-in `subprocess` package.
680
680
  It will catch result from the `subprocess.run` returning output like
@@ -730,8 +730,8 @@ class BashStage(BaseAsyncStage):
730
730
  self,
731
731
  params: DictData,
732
732
  *,
733
- result: Result | None = None,
734
- event: Event | None = None,
733
+ result: Optional[Result] = None,
734
+ event: Optional[Event] = None,
735
735
  ) -> Result:
736
736
  """Async execution method for this Bash stage that only logging out to
737
737
  stdout.
@@ -858,8 +858,8 @@ class PyStage(BaseAsyncStage):
858
858
  self,
859
859
  params: DictData,
860
860
  *,
861
- result: Result | None = None,
862
- event: Event | None = None,
861
+ result: Optional[Result] = None,
862
+ event: Optional[Event] = None,
863
863
  ) -> Result:
864
864
  """Execute the Python statement that pass all globals and input params
865
865
  to globals argument on `exec` build-in function.
@@ -916,8 +916,8 @@ class PyStage(BaseAsyncStage):
916
916
  self,
917
917
  params: DictData,
918
918
  *,
919
- result: Result | None = None,
920
- event: Event | None = None,
919
+ result: Optional[Result] = None,
920
+ event: Optional[Event] = None,
921
921
  ) -> Result:
922
922
  """Async execution method for this Bash stage that only logging out to
923
923
  stdout.
@@ -1018,8 +1018,8 @@ class CallStage(BaseAsyncStage):
1018
1018
  self,
1019
1019
  params: DictData,
1020
1020
  *,
1021
- result: Result | None = None,
1022
- event: Event | None = None,
1021
+ result: Optional[Result] = None,
1022
+ event: Optional[Event] = None,
1023
1023
  ) -> Result:
1024
1024
  """Execute this caller function with its argument parameter.
1025
1025
 
@@ -1108,8 +1108,8 @@ class CallStage(BaseAsyncStage):
1108
1108
  self,
1109
1109
  params: DictData,
1110
1110
  *,
1111
- result: Result | None = None,
1112
- event: Event | None = None,
1111
+ result: Optional[Result] = None,
1112
+ event: Optional[Event] = None,
1113
1113
  ) -> Result:
1114
1114
  """Async execution method for this Bash stage that only logging out to
1115
1115
  stdout.
@@ -1266,8 +1266,8 @@ class TriggerStage(BaseStage):
1266
1266
  self,
1267
1267
  params: DictData,
1268
1268
  *,
1269
- result: Result | None = None,
1270
- event: Event | None = None,
1269
+ result: Optional[Result] = None,
1270
+ event: Optional[Event] = None,
1271
1271
  ) -> Result:
1272
1272
  """Trigger another workflow execution. It will wait the trigger
1273
1273
  workflow running complete before catching its result.
@@ -1298,7 +1298,7 @@ class TriggerStage(BaseStage):
1298
1298
  event=event,
1299
1299
  )
1300
1300
  if rs.status == FAILED:
1301
- err_msg: str | None = (
1301
+ err_msg: Optional[str] = (
1302
1302
  f" with:\n{msg}"
1303
1303
  if (msg := rs.context.get("errors", {}).get("message"))
1304
1304
  else "."
@@ -1319,8 +1319,8 @@ class BaseNestedStage(BaseStage):
1319
1319
  self,
1320
1320
  params: DictData,
1321
1321
  *,
1322
- result: Result | None = None,
1323
- event: Event | None = None,
1322
+ result: Optional[Result] = None,
1323
+ event: Optional[Event] = None,
1324
1324
  ) -> Result:
1325
1325
  """Execute abstraction method that action something by sub-model class.
1326
1326
  This is important method that make this class is able to be the nested
@@ -1390,7 +1390,7 @@ class ParallelStage(BaseNestedStage):
1390
1390
  params: DictData,
1391
1391
  result: Result,
1392
1392
  *,
1393
- event: Event | None = None,
1393
+ event: Optional[Event] = None,
1394
1394
  ) -> Result:
1395
1395
  """Execute all stage with specific branch ID.
1396
1396
 
@@ -1487,8 +1487,8 @@ class ParallelStage(BaseNestedStage):
1487
1487
  self,
1488
1488
  params: DictData,
1489
1489
  *,
1490
- result: Result | None = None,
1491
- event: Event | None = None,
1490
+ result: Optional[Result] = None,
1491
+ event: Optional[Event] = None,
1492
1492
  ) -> Result:
1493
1493
  """Execute parallel each branch via multi-threading pool.
1494
1494
 
@@ -1526,7 +1526,7 @@ class ParallelStage(BaseNestedStage):
1526
1526
  context: DictData = {}
1527
1527
  status: Status = SUCCESS
1528
1528
 
1529
- futures: list[Future] = (
1529
+ futures: list[Future] = [
1530
1530
  executor.submit(
1531
1531
  self.execute_branch,
1532
1532
  branch=branch,
@@ -1535,7 +1535,7 @@ class ParallelStage(BaseNestedStage):
1535
1535
  event=event,
1536
1536
  )
1537
1537
  for branch in self.parallel
1538
- )
1538
+ ]
1539
1539
 
1540
1540
  for future in as_completed(futures):
1541
1541
  try:
@@ -1605,7 +1605,7 @@ class ForEachStage(BaseNestedStage):
1605
1605
  params: DictData,
1606
1606
  result: Result,
1607
1607
  *,
1608
- event: Event | None = None,
1608
+ event: Optional[Event] = None,
1609
1609
  ) -> Result:
1610
1610
  """Execute all nested stage that set on this stage with specific foreach
1611
1611
  item parameter.
@@ -1709,8 +1709,8 @@ class ForEachStage(BaseNestedStage):
1709
1709
  self,
1710
1710
  params: DictData,
1711
1711
  *,
1712
- result: Result | None = None,
1713
- event: Event | None = None,
1712
+ result: Optional[Result] = None,
1713
+ event: Optional[Event] = None,
1714
1714
  ) -> Result:
1715
1715
  """Execute the stages that pass each item form the foreach field.
1716
1716
 
@@ -1775,7 +1775,7 @@ class ForEachStage(BaseNestedStage):
1775
1775
  status: Status = SUCCESS
1776
1776
 
1777
1777
  done, not_done = wait(futures, return_when=FIRST_EXCEPTION)
1778
- if len(done) != len(futures):
1778
+ if len(list(done)) != len(futures):
1779
1779
  result.trace.warning(
1780
1780
  "[STAGE]: Set event for stop pending for-each stage."
1781
1781
  )
@@ -1795,7 +1795,7 @@ class ForEachStage(BaseNestedStage):
1795
1795
  result.trace.debug(
1796
1796
  f"[STAGE]: ... Foreach-Stage set failed event{nd}"
1797
1797
  )
1798
- done: list[Future] = as_completed(futures)
1798
+ done: Iterator[Future] = as_completed(futures)
1799
1799
 
1800
1800
  for future in done:
1801
1801
  try:
@@ -1862,7 +1862,7 @@ class UntilStage(BaseNestedStage):
1862
1862
  loop: int,
1863
1863
  params: DictData,
1864
1864
  result: Result,
1865
- event: Event | None = None,
1865
+ event: Optional[Event] = None,
1866
1866
  ) -> tuple[Result, T]:
1867
1867
  """Execute all stage with specific loop and item.
1868
1868
 
@@ -1975,8 +1975,8 @@ class UntilStage(BaseNestedStage):
1975
1975
  self,
1976
1976
  params: DictData,
1977
1977
  *,
1978
- result: Result | None = None,
1979
- event: Event | None = None,
1978
+ result: Optional[Result] = None,
1979
+ event: Optional[Event] = None,
1980
1980
  ) -> Result:
1981
1981
  """Execute until loop with checking until condition.
1982
1982
 
@@ -2113,7 +2113,7 @@ class CaseStage(BaseNestedStage):
2113
2113
  params: DictData,
2114
2114
  result: Result,
2115
2115
  *,
2116
- event: Event | None = None,
2116
+ event: Optional[Event] = None,
2117
2117
  ) -> Result:
2118
2118
  """Execute case.
2119
2119
 
@@ -2198,8 +2198,8 @@ class CaseStage(BaseNestedStage):
2198
2198
  self,
2199
2199
  params: DictData,
2200
2200
  *,
2201
- result: Result | None = None,
2202
- event: Event | None = None,
2201
+ result: Optional[Result] = None,
2202
+ event: Optional[Event] = None,
2203
2203
  ) -> Result:
2204
2204
  """Execute case-match condition that pass to the case field.
2205
2205
 
@@ -2291,8 +2291,8 @@ class RaiseStage(BaseAsyncStage):
2291
2291
  self,
2292
2292
  params: DictData,
2293
2293
  *,
2294
- result: Result | None = None,
2295
- event: Event | None = None,
2294
+ result: Optional[Result] = None,
2295
+ event: Optional[Event] = None,
2296
2296
  ) -> Result:
2297
2297
  """Raise the StageException object with the message field execution.
2298
2298
 
@@ -2313,8 +2313,8 @@ class RaiseStage(BaseAsyncStage):
2313
2313
  self,
2314
2314
  params: DictData,
2315
2315
  *,
2316
- result: Result | None = None,
2317
- event: Event | None = None,
2316
+ result: Optional[Result] = None,
2317
+ event: Optional[Event] = None,
2318
2318
  ) -> Result:
2319
2319
  """Async execution method for this Empty stage that only logging out to
2320
2320
  stdout.
@@ -2367,7 +2367,7 @@ class DockerStage(BaseStage): # pragma: no cov
2367
2367
  env: DictData = Field(
2368
2368
  default_factory=dict,
2369
2369
  description=(
2370
- "An environment variable that want pass to Docker container.",
2370
+ "An environment variable that want pass to Docker container."
2371
2371
  ),
2372
2372
  )
2373
2373
  volume: DictData = Field(
@@ -2385,7 +2385,7 @@ class DockerStage(BaseStage): # pragma: no cov
2385
2385
  self,
2386
2386
  params: DictData,
2387
2387
  result: Result,
2388
- event: Event | None = None,
2388
+ event: Optional[Event] = None,
2389
2389
  ) -> Result:
2390
2390
  """Execute Docker container task.
2391
2391
 
@@ -2473,8 +2473,8 @@ class DockerStage(BaseStage): # pragma: no cov
2473
2473
  self,
2474
2474
  params: DictData,
2475
2475
  *,
2476
- result: Result | None = None,
2477
- event: Event | None = None,
2476
+ result: Optional[Result] = None,
2477
+ event: Optional[Event] = None,
2478
2478
  ) -> Result:
2479
2479
  """Execute the Docker image via Python API.
2480
2480
 
@@ -2518,7 +2518,7 @@ class VirtualPyStage(PyStage): # pragma: no cov
2518
2518
  py: str,
2519
2519
  values: DictData,
2520
2520
  deps: list[str],
2521
- run_id: str | None = None,
2521
+ run_id: Optional[str] = None,
2522
2522
  ) -> Iterator[str]:
2523
2523
  """Create the .py file with an input Python string statement.
2524
2524
 
@@ -2526,7 +2526,7 @@ class VirtualPyStage(PyStage): # pragma: no cov
2526
2526
  :param values: A variable that want to set before running this
2527
2527
  :param deps: An additional Python dependencies that want install before
2528
2528
  run this python stage.
2529
- :param run_id: (str | None) A running ID of this stage execution.
2529
+ :param run_id: (Optional[str]) A running ID of this stage execution.
2530
2530
  """
2531
2531
  run_id: str = run_id or uuid.uuid4()
2532
2532
  f_name: str = f"{run_id}.py"
@@ -2565,8 +2565,8 @@ class VirtualPyStage(PyStage): # pragma: no cov
2565
2565
  self,
2566
2566
  params: DictData,
2567
2567
  *,
2568
- result: Result | None = None,
2569
- event: Event | None = None,
2568
+ result: Optional[Result] = None,
2569
+ event: Optional[Event] = None,
2570
2570
  ) -> Result:
2571
2571
  """Execute the Python statement via Python virtual environment.
2572
2572
 
ddeutil/workflow/utils.py CHANGED
@@ -15,7 +15,7 @@ from inspect import isfunction
15
15
  from itertools import chain, islice, product
16
16
  from pathlib import Path
17
17
  from random import randrange
18
- from typing import Any, Final, TypeVar
18
+ from typing import Any, Final, Optional, TypeVar, Union
19
19
  from zoneinfo import ZoneInfo
20
20
 
21
21
  from ddeutil.core import hash_str
@@ -63,7 +63,7 @@ def clear_tz(dt: datetime) -> datetime:
63
63
  return dt.replace(tzinfo=None)
64
64
 
65
65
 
66
- def get_dt_now(tz: ZoneInfo | None = None, offset: float = 0.0) -> datetime:
66
+ def get_dt_now(tz: Optional[ZoneInfo] = None, offset: float = 0.0) -> datetime:
67
67
  """Return the current datetime object.
68
68
 
69
69
  :param tz: A ZoneInfo object for replace timezone of return datetime object.
@@ -76,7 +76,7 @@ def get_dt_now(tz: ZoneInfo | None = None, offset: float = 0.0) -> datetime:
76
76
 
77
77
 
78
78
  def get_d_now(
79
- tz: ZoneInfo | None = None, offset: float = 0.0
79
+ tz: Optional[ZoneInfo] = None, offset: float = 0.0
80
80
  ) -> date: # pragma: no cov
81
81
  """Return the current date object.
82
82
 
@@ -147,7 +147,7 @@ def gen_id(
147
147
  *,
148
148
  sensitive: bool = True,
149
149
  unique: bool = False,
150
- simple_mode: bool | None = None,
150
+ simple_mode: Optional[bool] = None,
151
151
  extras: DictData | None = None,
152
152
  ) -> str:
153
153
  """Generate running ID for able to tracking. This generates process use
@@ -197,7 +197,7 @@ def default_gen_id() -> str:
197
197
  return gen_id("manual", unique=True)
198
198
 
199
199
 
200
- def make_exec(path: str | Path) -> None:
200
+ def make_exec(path: Union[Path, str]) -> None:
201
201
  """Change mode of file to be executable file.
202
202
 
203
203
  :param path: A file path that want to make executable permission.
@@ -244,7 +244,7 @@ def cross_product(matrix: Matrix) -> Iterator[DictData]:
244
244
  )
245
245
 
246
246
 
247
- def batch(iterable: Iterator[Any] | range, n: int) -> Iterator[Any]:
247
+ def batch(iterable: Union[Iterator[Any], range], n: int) -> Iterator[Any]:
248
248
  """Batch data into iterators of length n. The last batch may be shorter.
249
249
 
250
250
  Example: