ddeutil-workflow 0.0.38__py3-none-any.whl → 0.0.40__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.38"
1
+ __version__: str = "0.0.40"
@@ -37,7 +37,7 @@ class CronYearLimit(Exception): ...
37
37
  def str2cron(value: str) -> str: # pragma: no cov
38
38
  """Convert Special String with the @ prefix to Crontab value.
39
39
 
40
- :param value: A string value that want to convert to cron value.
40
+ :param value: (str) A string value that want to convert to cron value.
41
41
  :rtype: str
42
42
 
43
43
  Table:
@@ -82,6 +82,9 @@ class Unit:
82
82
  )
83
83
 
84
84
 
85
+ Units = tuple[Unit, ...]
86
+
87
+
85
88
  @dataclass
86
89
  class Options:
87
90
  """Options dataclass for config CronPart object."""
@@ -91,7 +94,7 @@ class Options:
91
94
  output_hashes: bool = False
92
95
 
93
96
 
94
- CRON_UNITS: tuple[Unit, ...] = (
97
+ CRON_UNITS: Units = (
95
98
  Unit(
96
99
  name="minute",
97
100
  range=partial(range, 0, 60),
@@ -147,7 +150,7 @@ CRON_UNITS: tuple[Unit, ...] = (
147
150
  ),
148
151
  )
149
152
 
150
- CRON_UNITS_YEAR: tuple[Unit, ...] = CRON_UNITS + (
153
+ CRON_UNITS_YEAR: Units = CRON_UNITS + (
151
154
  Unit(
152
155
  name="year",
153
156
  range=partial(range, 1990, 2101),
@@ -220,18 +223,21 @@ class CronPart:
220
223
  return ",".join(cron_range_strings) if cron_range_strings else "?"
221
224
 
222
225
  def __repr__(self) -> str:
226
+ """Override __repr__ method."""
223
227
  return (
224
228
  f"{self.__class__.__name__}"
225
229
  f"(unit={self.unit}, values={self.__str__()!r})"
226
230
  )
227
231
 
228
232
  def __lt__(self, other) -> bool:
233
+ """Override __lt__ method."""
229
234
  if isinstance(other, CronPart):
230
235
  return self.values < other.values
231
236
  elif isinstance(other, list):
232
237
  return self.values < other
233
238
 
234
239
  def __eq__(self, other) -> bool:
240
+ """Override __eq__ method."""
235
241
  if isinstance(other, CronPart):
236
242
  return self.values == other.values
237
243
  elif isinstance(other, list):
@@ -239,18 +245,26 @@ class CronPart:
239
245
 
240
246
  @property
241
247
  def min(self) -> int:
242
- """Returns the smallest value in the range."""
248
+ """Returns the smallest value in the range.
249
+
250
+ :rtype: int
251
+ """
243
252
  return self.values[0]
244
253
 
245
254
  @property
246
255
  def max(self) -> int:
247
- """Returns the largest value in the range."""
256
+ """Returns the largest value in the range.
257
+
258
+ :rtype: int
259
+ """
248
260
  return self.values[-1]
249
261
 
250
262
  @property
251
263
  def step(self) -> Optional[int]:
252
264
  """Returns the difference between first and second elements in the
253
265
  range.
266
+
267
+ :rtype: Optional[int]
254
268
  """
255
269
  if (
256
270
  len(self.values) > 2
@@ -260,15 +274,17 @@ class CronPart:
260
274
 
261
275
  @property
262
276
  def is_full(self) -> bool:
263
- """Returns true if range has all the values of the unit."""
277
+ """Returns true if range has all the values of the unit.
278
+
279
+ :rtype: bool
280
+ """
264
281
  return len(self.values) == (self.unit.max - self.unit.min + 1)
265
282
 
266
283
  def from_str(self, value: str) -> tuple[int, ...]:
267
284
  """Parses a string as a range of positive integers. The string should
268
285
  include only `-` and `,` special strings.
269
286
 
270
- :param value: A string value that want to parse
271
- :type value: str
287
+ :param value: (str) A string value that want to parse
272
288
 
273
289
  TODO: support for `L`, `W`, and `#`
274
290
  ---
@@ -351,6 +367,8 @@ class CronPart:
351
367
  For example if value == 'JAN,AUG' it will replace to '1,8'.
352
368
 
353
369
  :param value: A string value that want to replace alternative to int.
370
+
371
+ :rtype: str
354
372
  """
355
373
  for i, alt in enumerate(self.unit.alt):
356
374
  if alt in value:
@@ -361,6 +379,7 @@ class CronPart:
361
379
  """Replaces all 7 with 0 as Sunday can be represented by both.
362
380
 
363
381
  :param values: list or iter of int that want to mode by 7
382
+
364
383
  :rtype: list[int]
365
384
  """
366
385
  if self.unit.name == "weekday":
@@ -388,7 +407,13 @@ class CronPart:
388
407
  return values
389
408
 
390
409
  def _parse_range(self, value: str) -> list[int]:
391
- """Parses a range string."""
410
+ """Parses a range string from a cron-part.
411
+
412
+ :param value: (str) A cron-part string value that want to parse.
413
+
414
+ :rtype: list[int]
415
+ :return: A list of parse range.
416
+ """
392
417
  if value == "*":
393
418
  return list(self.unit.range())
394
419
  elif value.count("-") > 1:
@@ -410,7 +435,13 @@ class CronPart:
410
435
  values: list[int],
411
436
  step: int | None = None,
412
437
  ) -> list[int]:
413
- """Applies an interval step to a collection of values."""
438
+ """Applies an interval step to a collection of values.
439
+
440
+ :param values:
441
+ :param step:
442
+
443
+ :rtype: list[int]
444
+ """
414
445
  if not step:
415
446
  return values
416
447
  elif (_step := int(step)) < 1:
@@ -427,7 +458,10 @@ class CronPart:
427
458
 
428
459
  @property
429
460
  def is_interval(self) -> bool:
430
- """Returns true if the range can be represented as an interval."""
461
+ """Returns true if the range can be represented as an interval.
462
+
463
+ :rtype: bool
464
+ """
431
465
  if not (step := self.step):
432
466
  return False
433
467
  for idx, value in enumerate(self.values):
@@ -439,7 +473,10 @@ class CronPart:
439
473
 
440
474
  @property
441
475
  def is_full_interval(self) -> bool:
442
- """Returns true if the range contains all the interval values."""
476
+ """Returns true if the range contains all the interval values.
477
+
478
+ :rtype: bool
479
+ """
443
480
  if step := self.step:
444
481
  return (
445
482
  self.min == self.unit.min
@@ -482,8 +519,7 @@ class CronPart:
482
519
  """Formats weekday and month names as string when the relevant options
483
520
  are set.
484
521
 
485
- :param value: a int value
486
- :type value: int
522
+ :param value: (int) An int value that want to get from the unit.
487
523
 
488
524
  :rtype: int | str
489
525
  """
@@ -538,8 +574,8 @@ class CronJob:
538
574
  monitor-data-warehouse-schedule.html
539
575
  """
540
576
 
541
- cron_length: int = 5
542
- cron_units: tuple[Unit, ...] = CRON_UNITS
577
+ cron_length: ClassVar[int] = 5
578
+ cron_units: ClassVar[Units] = CRON_UNITS
543
579
 
544
580
  def __init__(
545
581
  self,
@@ -600,38 +636,64 @@ class CronJob:
600
636
 
601
637
  @property
602
638
  def parts_order(self) -> Iterator[CronPart]:
639
+ """Return iterator of CronPart instance.
640
+
641
+ :rtype: Iterator[CronPart]
642
+ """
603
643
  return reversed(self.parts[:3] + [self.parts[4], self.parts[3]])
604
644
 
605
645
  @property
606
646
  def minute(self) -> CronPart:
607
- """Return part of minute."""
647
+ """Return part of minute with the CronPart instance.
648
+
649
+ :rtype: CronPart
650
+ """
608
651
  return self.parts[0]
609
652
 
610
653
  @property
611
654
  def hour(self) -> CronPart:
612
- """Return part of hour."""
655
+ """Return part of hour with the CronPart instance.
656
+
657
+ :rtype: CronPart
658
+ """
613
659
  return self.parts[1]
614
660
 
615
661
  @property
616
662
  def day(self) -> CronPart:
617
- """Return part of day."""
663
+ """Return part of day with the CronPart instance.
664
+
665
+ :rtype: CronPart
666
+ """
618
667
  return self.parts[2]
619
668
 
620
669
  @property
621
670
  def month(self) -> CronPart:
622
- """Return part of month."""
671
+ """Return part of month with the CronPart instance.
672
+
673
+ :rtype: CronPart
674
+ """
623
675
  return self.parts[3]
624
676
 
625
677
  @property
626
678
  def dow(self) -> CronPart:
627
- """Return part of day of month."""
679
+ """Return part of day of month with the CronPart instance.
680
+
681
+ :rtype: CronPart
682
+ """
628
683
  return self.parts[4]
629
684
 
630
685
  def to_list(self) -> list[list[int]]:
631
- """Returns the cron schedule as a 2-dimensional list of integers."""
686
+ """Returns the cron schedule as a 2-dimensional list of integers.
687
+
688
+ :rtype: list[list[int]]
689
+ """
632
690
  return [part.values for part in self.parts]
633
691
 
634
692
  def check(self, date: datetime, mode: str) -> bool:
693
+ """Check the date value with the mode.
694
+
695
+ :rtype: bool
696
+ """
635
697
  assert mode in ("year", "month", "day", "hour", "minute")
636
698
  return getattr(date, mode) in getattr(self, mode).values
637
699
 
@@ -667,12 +729,14 @@ class CronJobYear(CronJob):
667
729
  (vi) year (1990 - 2100)
668
730
  """
669
731
 
670
- cron_length = 6
671
- cron_units = CRON_UNITS_YEAR
732
+ cron_length: ClassVar[int] = 6
733
+ cron_units: ClassVar[Units] = CRON_UNITS_YEAR
672
734
 
673
735
  @property
674
736
  def year(self) -> CronPart:
675
- """Return part of year."""
737
+ """Return part of year with the CronPart instance.
738
+
739
+ :rtype: CronPart"""
676
740
  return self.parts[5]
677
741
 
678
742
 
@@ -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
  #
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:
@@ -48,9 +48,10 @@ def tag(
48
48
  """Tag decorator function that set function attributes, ``tag`` and ``name``
49
49
  for making registries variable.
50
50
 
51
- :param: name: A tag name for make different use-case of a function.
52
- :param: alias: A alias function name that keeping in registries. If this
53
- 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.
54
55
 
55
56
  :rtype: Callable[P, TagFunc]
56
57
  """
@@ -78,7 +79,7 @@ Registry = dict[str, Callable[[], TagFunc]]
78
79
  def make_registry(submodule: str) -> dict[str, Registry]:
79
80
  """Return registries of all functions that able to called with task.
80
81
 
81
- :param submodule: A module prefix that want to import registry.
82
+ :param submodule: (str) A module prefix that want to import registry.
82
83
 
83
84
  :rtype: dict[str, Registry]
84
85
  """
@@ -134,12 +135,7 @@ def extract_call(call: str) -> Callable[[], TagFunc]:
134
135
  """Extract Call function from string value to call partial function that
135
136
  does run it at runtime.
136
137
 
137
- :raise NotImplementedError: When the searching call's function result does
138
- not exist in the registry.
139
- :raise NotImplementedError: When the searching call's tag result does not
140
- exist in the registry with its function key.
141
-
142
- :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.
143
139
 
144
140
  The format of call value should contain 3 regular expression groups
145
141
  which match with the below config format:
@@ -152,6 +148,11 @@ def extract_call(call: str) -> Callable[[], TagFunc]:
152
148
  >>> extract_call("tasks/return-type-not-valid@raise")
153
149
  ...
154
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
+
155
156
  :rtype: Callable[[], TagFunc]
156
157
  """
157
158
  if not (found := Re.RE_TASK_FMT.search(call)):
ddeutil/workflow/conf.py CHANGED
@@ -16,6 +16,7 @@ from zoneinfo import ZoneInfo
16
16
 
17
17
  from ddeutil.core import str2bool
18
18
  from ddeutil.io import YamlFlResolve
19
+ from ddeutil.io.paths import glob_files, is_ignored, read_ignore
19
20
 
20
21
  from .__types import DictData, TupleStr
21
22
 
@@ -26,10 +27,6 @@ def env(var: str, default: str | None = None) -> str | None: # pragma: no cov
26
27
  return os.getenv(f"{PREFIX}_{var.upper().replace(' ', '_')}", default)
27
28
 
28
29
 
29
- def glob_files(path: Path) -> Iterator[Path]: # pragma: no cov
30
- yield from (file for file in path.rglob("*") if file.is_file())
31
-
32
-
33
30
  __all__: TupleStr = (
34
31
  "env",
35
32
  "get_logger",
@@ -37,7 +34,6 @@ __all__: TupleStr = (
37
34
  "SimLoad",
38
35
  "Loader",
39
36
  "config",
40
- "glob_files",
41
37
  )
42
38
 
43
39
 
@@ -273,7 +269,7 @@ class SimLoad:
273
269
  if self.is_ignore(file, conf_path):
274
270
  continue
275
271
 
276
- if data := self.filter_suffix(file, name=name):
272
+ if data := self.filter_yaml(file, name=name):
277
273
  self.data = data
278
274
 
279
275
  # VALIDATE: check the data that reading should not empty.
@@ -307,15 +303,15 @@ class SimLoad:
307
303
  exclude: list[str] = excluded or []
308
304
  for file in glob_files(conf_path):
309
305
 
310
- for key, data in cls.filter_suffix(file).items():
306
+ if cls.is_ignore(file, conf_path):
307
+ continue
311
308
 
312
- if cls.is_ignore(file, conf_path):
313
- continue
309
+ for key, data in cls.filter_yaml(file).items():
314
310
 
315
311
  if key in exclude:
316
312
  continue
317
313
 
318
- if data["type"] == obj.__name__:
314
+ if data.get("type", "") == obj.__name__:
319
315
  yield key, (
320
316
  {k: data[k] for k in data if k in included}
321
317
  if included
@@ -324,24 +320,13 @@ class SimLoad:
324
320
 
325
321
  @classmethod
326
322
  def is_ignore(cls, file: Path, conf_path: Path) -> bool:
327
- ignore_file: Path = conf_path / ".confignore"
328
- ignore: list[str] = []
329
- if ignore_file.exists():
330
- ignore = ignore_file.read_text(encoding="utf-8").splitlines()
331
-
332
- if any(
333
- (file.match(f"**/{pattern}/*") or file.match(f"**/{pattern}*"))
334
- for pattern in ignore
335
- ):
336
- return True
337
- return False
323
+ return is_ignored(file, read_ignore(conf_path / ".confignore"))
338
324
 
339
325
  @classmethod
340
- def filter_suffix(cls, file: Path, name: str | None = None) -> DictData:
326
+ def filter_yaml(cls, file: Path, name: str | None = None) -> DictData:
341
327
  if any(file.suffix.endswith(s) for s in (".yml", ".yaml")):
342
328
  values: DictData = YamlFlResolve(file).read()
343
329
  return values.get(name, {}) if name else values
344
-
345
330
  return {}
346
331
 
347
332
  @cached_property
@@ -18,6 +18,7 @@ class ErrorContext(BaseModel): # pragma: no cov
18
18
  class OutputContext(BaseModel): # pragma: no cov
19
19
  outputs: DictData = Field(default_factory=dict)
20
20
  errors: Optional[ErrorContext] = Field(default=None)
21
+ skipped: bool = Field(default=False)
21
22
 
22
23
  def is_exception(self) -> bool:
23
24
  return self.errors is not None
@@ -57,3 +58,4 @@ class JobContext(BaseModel): # pragma: no cov
57
58
  params: DictData = Field(description="A parameterize value")
58
59
  jobs: dict[str, StrategyMatrixContext]
59
60
  errors: Optional[ErrorContext] = Field(default=None)
61
+ skipped: bool = Field(default=False)