ddeutil-workflow 0.0.48__py3-none-any.whl → 0.0.50__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/logs.py CHANGED
@@ -3,8 +3,9 @@
3
3
  # Licensed under the MIT License. See LICENSE in the project root for
4
4
  # license information.
5
5
  # ------------------------------------------------------------------------------
6
- # [x] Use fix config
7
- """A Logs module contain TraceLog dataclass and AuditLog model.
6
+ # [x] Use dynamic config
7
+ # [x] Use fix config for `get_logger`, and Model initialize step.
8
+ """A Logs module contain Trace dataclass and Audit Pydantic model.
8
9
  """
9
10
  from __future__ import annotations
10
11
 
@@ -19,14 +20,14 @@ from functools import lru_cache
19
20
  from inspect import Traceback, currentframe, getframeinfo
20
21
  from pathlib import Path
21
22
  from threading import get_ident
22
- from typing import ClassVar, Literal, Optional, Union
23
+ from typing import ClassVar, Literal, Optional, TypeVar, Union
23
24
 
24
- from pydantic import BaseModel, Field, ValidationInfo
25
+ from pydantic import BaseModel, Field
25
26
  from pydantic.dataclasses import dataclass
26
27
  from pydantic.functional_validators import model_validator
27
28
  from typing_extensions import Self
28
29
 
29
- from .__types import DictData, DictStr, TupleStr
30
+ from .__types import DictData, DictStr
30
31
  from .conf import config, dynamic
31
32
  from .utils import cut_id, get_dt_now
32
33
 
@@ -60,22 +61,6 @@ def get_logger(name: str):
60
61
 
61
62
  logger = get_logger("ddeutil.workflow")
62
63
 
63
- __all__: TupleStr = (
64
- "FileTraceLog",
65
- "SQLiteTraceLog",
66
- "TraceData",
67
- "TraceMeta",
68
- "TraceLog",
69
- "get_dt_tznow",
70
- "get_logger",
71
- "get_trace",
72
- "get_trace_obj",
73
- "get_audit",
74
- "FileAudit",
75
- "SQLiteAudit",
76
- "Audit",
77
- )
78
-
79
64
 
80
65
  def get_dt_tznow() -> datetime: # pragma: no cov
81
66
  """Return the current datetime object that passing the config timezone.
@@ -170,13 +155,39 @@ class TraceData(BaseModel): # pragma: no cov
170
155
 
171
156
 
172
157
  @dataclass(frozen=True)
173
- class BaseTraceLog(ABC): # pragma: no cov
174
- """Base Trace Log dataclass object."""
158
+ class BaseTrace(ABC): # pragma: no cov
159
+ """Base Trace dataclass with abstraction class property."""
175
160
 
176
161
  run_id: str
177
162
  parent_run_id: Optional[str] = field(default=None)
178
163
  extras: DictData = field(default_factory=dict, compare=False, repr=False)
179
164
 
165
+ @classmethod
166
+ @abstractmethod
167
+ def find_traces(
168
+ cls,
169
+ path: Path | None = None,
170
+ extras: Optional[DictData] = None,
171
+ ) -> Iterator[TraceData]: # pragma: no cov
172
+ raise NotImplementedError(
173
+ "Trace dataclass should implement `find_traces` class-method."
174
+ )
175
+
176
+ @classmethod
177
+ @abstractmethod
178
+ def find_trace_with_id(
179
+ cls,
180
+ run_id: str,
181
+ force_raise: bool = True,
182
+ *,
183
+ path: Path | None = None,
184
+ extras: Optional[DictData] = None,
185
+ ) -> TraceData:
186
+ raise NotImplementedError(
187
+ "Trace dataclass should implement `find_trace_with_id` "
188
+ "class-method."
189
+ )
190
+
180
191
  @abstractmethod
181
192
  def writer(self, message: str, is_err: bool = False) -> None:
182
193
  """Write a trace message after making to target pointer object. The
@@ -318,11 +329,11 @@ class BaseTraceLog(ABC): # pragma: no cov
318
329
  logger.exception(msg, stacklevel=2)
319
330
 
320
331
 
321
- class FileTraceLog(BaseTraceLog): # pragma: no cov
322
- """Trace Log object that write file to the local storage."""
332
+ class FileTrace(BaseTrace): # pragma: no cov
333
+ """File Trace dataclass that write file to the local storage."""
323
334
 
324
335
  @classmethod
325
- def find_logs(
336
+ def find_traces(
326
337
  cls,
327
338
  path: Path | None = None,
328
339
  extras: Optional[DictData] = None,
@@ -333,13 +344,13 @@ class FileTraceLog(BaseTraceLog): # pragma: no cov
333
344
  :param extras: An extra parameter that want to override core config.
334
345
  """
335
346
  for file in sorted(
336
- (path or dynamic("log_path", extras=extras)).glob("./run_id=*"),
347
+ (path or dynamic("trace_path", extras=extras)).glob("./run_id=*"),
337
348
  key=lambda f: f.lstat().st_mtime,
338
349
  ):
339
350
  yield TraceData.from_path(file)
340
351
 
341
352
  @classmethod
342
- def find_log_with_id(
353
+ def find_trace_with_id(
343
354
  cls,
344
355
  run_id: str,
345
356
  force_raise: bool = True,
@@ -354,7 +365,7 @@ class FileTraceLog(BaseTraceLog): # pragma: no cov
354
365
  :param path:
355
366
  :param extras: An extra parameter that want to override core config.
356
367
  """
357
- base_path: Path = path or dynamic("log_path", extras=extras)
368
+ base_path: Path = path or dynamic("trace_path", extras=extras)
358
369
  file: Path = base_path / f"run_id={run_id}"
359
370
  if file.exists():
360
371
  return TraceData.from_path(file)
@@ -368,7 +379,7 @@ class FileTraceLog(BaseTraceLog): # pragma: no cov
368
379
  @property
369
380
  def pointer(self) -> Path:
370
381
  log_file: Path = (
371
- dynamic("log_path", extras=self.extras)
382
+ dynamic("trace_path", extras=self.extras)
372
383
  / f"run_id={self.parent_run_id or self.run_id}"
373
384
  )
374
385
  if not log_file.exists():
@@ -383,7 +394,7 @@ class FileTraceLog(BaseTraceLog): # pragma: no cov
383
394
  """
384
395
  cut_run_id: str = cut_id(self.run_id)
385
396
  if not self.parent_run_id:
386
- return f"{cut_run_id} -> {' ' * 6}"
397
+ return f"{cut_run_id}"
387
398
 
388
399
  cut_parent_run_id: str = cut_id(self.parent_run_id)
389
400
  return f"{cut_parent_run_id} -> {cut_run_id}"
@@ -453,8 +464,8 @@ class FileTraceLog(BaseTraceLog): # pragma: no cov
453
464
  await f.write(trace_meta.model_dump_json() + "\n")
454
465
 
455
466
 
456
- class SQLiteTraceLog(BaseTraceLog): # pragma: no cov
457
- """Trace Log object that write trace log to the SQLite database file."""
467
+ class SQLiteTrace(BaseTrace): # pragma: no cov
468
+ """SQLite Trace dataclass that write trace log to the SQLite database file."""
458
469
 
459
470
  table_name: ClassVar[str] = "audits"
460
471
  schemas: ClassVar[
@@ -468,10 +479,21 @@ class SQLiteTraceLog(BaseTraceLog): # pragma: no cov
468
479
  """
469
480
 
470
481
  @classmethod
471
- def find_logs(cls) -> Iterator[DictStr]: ...
482
+ def find_traces(
483
+ cls,
484
+ path: Path | None = None,
485
+ extras: Optional[DictData] = None,
486
+ ) -> Iterator[TraceData]: ...
472
487
 
473
488
  @classmethod
474
- def find_log_with_id(cls, run_id: str) -> DictStr: ...
489
+ def find_trace_with_id(
490
+ cls,
491
+ run_id: str,
492
+ force_raise: bool = True,
493
+ *,
494
+ path: Path | None = None,
495
+ extras: Optional[DictData] = None,
496
+ ) -> TraceData: ...
475
497
 
476
498
  def make_message(self, message: str) -> str: ...
477
499
 
@@ -480,47 +502,34 @@ class SQLiteTraceLog(BaseTraceLog): # pragma: no cov
480
502
  def awriter(self, message: str, is_err: bool = False) -> None: ...
481
503
 
482
504
 
483
- TraceLog = Union[
484
- FileTraceLog,
485
- SQLiteTraceLog,
505
+ Trace = TypeVar("Trace", bound=BaseTrace)
506
+ TraceModel = Union[
507
+ FileTrace,
508
+ SQLiteTrace,
486
509
  ]
487
510
 
488
511
 
489
512
  def get_trace(
490
513
  run_id: str,
491
- parent_run_id: str | None = None,
492
514
  *,
515
+ parent_run_id: str | None = None,
493
516
  extras: Optional[DictData] = None,
494
- ) -> TraceLog: # pragma: no cov
495
- """Get dynamic TraceLog object from the setting config.
517
+ ) -> TraceModel: # pragma: no cov
518
+ """Get dynamic Trace instance from the core config (it can override by an
519
+ extras argument) that passing running ID and parent running ID.
496
520
 
497
- :param run_id: A running ID.
498
- :param parent_run_id: A parent running ID.
499
- :param extras: An extra parameter that want to override the core config.
521
+ :param run_id: (str) A running ID.
522
+ :param parent_run_id: (str) A parent running ID.
523
+ :param extras: (DictData) An extra parameter that want to override the core
524
+ config values.
500
525
 
501
526
  :rtype: TraceLog
502
527
  """
503
- if dynamic("log_path", extras=extras).is_file():
504
- return SQLiteTraceLog(
528
+ if dynamic("trace_path", extras=extras).is_file():
529
+ return SQLiteTrace(
505
530
  run_id, parent_run_id=parent_run_id, extras=(extras or {})
506
531
  )
507
- return FileTraceLog(
508
- run_id, parent_run_id=parent_run_id, extras=(extras or {})
509
- )
510
-
511
-
512
- def get_trace_obj(
513
- extras: Optional[DictData] = None,
514
- ) -> type[TraceLog]: # pragma: no cov
515
- """Get trace object.
516
-
517
- :param extras: An extra parameter that want to override the core config.
518
-
519
- :rtype: type[TraceLog]
520
- """
521
- if dynamic("log_path", extras=extras).is_file():
522
- return SQLiteTraceLog
523
- return FileTraceLog
532
+ return FileTrace(run_id, parent_run_id=parent_run_id, extras=(extras or {}))
524
533
 
525
534
 
526
535
  class BaseAudit(BaseModel, ABC):
@@ -548,15 +557,50 @@ class BaseAudit(BaseModel, ABC):
548
557
  execution_time: float = Field(default=0, description="An execution time.")
549
558
 
550
559
  @model_validator(mode="after")
551
- def __model_action(self, info: ValidationInfo) -> Self:
560
+ def __model_action(self) -> Self:
552
561
  """Do before the Audit action with WORKFLOW_AUDIT_ENABLE_WRITE env variable.
553
562
 
554
563
  :rtype: Self
555
564
  """
556
- if dynamic("enable_write_audit", extras=info.data.get("extras")):
565
+ if dynamic("enable_write_audit", extras=self.extras):
557
566
  self.do_before()
558
567
  return self
559
568
 
569
+ @classmethod
570
+ @abstractmethod
571
+ def is_pointed(
572
+ cls,
573
+ name: str,
574
+ release: datetime,
575
+ *,
576
+ extras: Optional[DictData] = None,
577
+ ) -> bool:
578
+ raise NotImplementedError(
579
+ "Audit should implement `is_pointed` class-method"
580
+ )
581
+
582
+ @classmethod
583
+ @abstractmethod
584
+ def find_audits(
585
+ cls, name: str, *, extras: Optional[DictData] = None
586
+ ) -> Iterator[Self]:
587
+ raise NotImplementedError(
588
+ "Audit should implement `find_audits` class-method"
589
+ )
590
+
591
+ @classmethod
592
+ @abstractmethod
593
+ def find_audit_with_release(
594
+ cls,
595
+ name: str,
596
+ release: datetime | None = None,
597
+ *,
598
+ extras: Optional[DictData] = None,
599
+ ) -> Self:
600
+ raise NotImplementedError(
601
+ "Audit should implement `find_audit_with_release` class-method"
602
+ )
603
+
560
604
  def do_before(self) -> None: # pragma: no cov
561
605
  """To something before end up of initial log model."""
562
606
 
@@ -688,7 +732,11 @@ class FileAudit(BaseAudit):
688
732
 
689
733
  :rtype: Self
690
734
  """
691
- trace: TraceLog = get_trace(self.run_id, self.parent_run_id)
735
+ trace: Trace = get_trace(
736
+ self.run_id,
737
+ parent_run_id=self.parent_run_id,
738
+ extras=self.extras,
739
+ )
692
740
 
693
741
  # NOTE: Check environ variable was set for real writing.
694
742
  if not dynamic("enable_write_audit", extras=self.extras):
@@ -726,11 +774,38 @@ class SQLiteAudit(BaseAudit): # pragma: no cov
726
774
  primary key ( run_id )
727
775
  """
728
776
 
777
+ @classmethod
778
+ def is_pointed(
779
+ cls,
780
+ name: str,
781
+ release: datetime,
782
+ *,
783
+ extras: Optional[DictData] = None,
784
+ ) -> bool: ...
785
+
786
+ @classmethod
787
+ def find_audits(
788
+ cls, name: str, *, extras: Optional[DictData] = None
789
+ ) -> Iterator[Self]: ...
790
+
791
+ @classmethod
792
+ def find_audit_with_release(
793
+ cls,
794
+ name: str,
795
+ release: datetime | None = None,
796
+ *,
797
+ extras: Optional[DictData] = None,
798
+ ) -> Self: ...
799
+
729
800
  def save(self, excluded: list[str] | None) -> SQLiteAudit:
730
801
  """Save logging data that receive a context data from a workflow
731
802
  execution result.
732
803
  """
733
- trace: TraceLog = get_trace(self.run_id, self.parent_run_id)
804
+ trace: Trace = get_trace(
805
+ self.run_id,
806
+ parent_run_id=self.parent_run_id,
807
+ extras=self.extras,
808
+ )
734
809
 
735
810
  # NOTE: Check environ variable was set for real writing.
736
811
  if not dynamic("enable_write_audit", extras=self.extras):
@@ -740,19 +815,8 @@ class SQLiteAudit(BaseAudit): # pragma: no cov
740
815
  raise NotImplementedError("SQLiteAudit does not implement yet.")
741
816
 
742
817
 
743
- class RemoteFileAudit(FileAudit): # pragma: no cov
744
- """Remote File Audit Pydantic Model."""
745
-
746
- def save(self, excluded: list[str] | None) -> RemoteFileAudit: ...
747
-
748
-
749
- class RedisAudit(BaseAudit): # pragma: no cov
750
- """Redis Audit Pydantic Model."""
751
-
752
- def save(self, excluded: list[str] | None) -> RedisAudit: ...
753
-
754
-
755
- Audit = Union[
818
+ Audit = TypeVar("Audit", bound=BaseAudit)
819
+ AuditModel = Union[
756
820
  FileAudit,
757
821
  SQLiteAudit,
758
822
  ]
@@ -760,7 +824,7 @@ Audit = Union[
760
824
 
761
825
  def get_audit(
762
826
  extras: Optional[DictData] = None,
763
- ) -> type[Audit]: # pragma: no cov
827
+ ) -> type[AuditModel]: # pragma: no cov
764
828
  """Get an audit class that dynamic base on the config audit path value.
765
829
 
766
830
  :param extras: An extra parameter that want to override the core config.
@@ -21,7 +21,8 @@ from typing_extensions import Self
21
21
 
22
22
  from .__types import DictData
23
23
  from .conf import dynamic
24
- from .logs import TraceLog, get_dt_tznow, get_trace
24
+ from .exceptions import ResultException
25
+ from .logs import Trace, get_dt_tznow, get_trace
25
26
  from .utils import default_gen_id, gen_id, get_dt_now
26
27
 
27
28
 
@@ -34,12 +35,14 @@ class Status(IntEnum):
34
35
  FAILED: int = 1
35
36
  WAIT: int = 2
36
37
  SKIP: int = 3
38
+ CANCEL: int = 4
37
39
 
38
40
 
39
41
  SUCCESS = Status.SUCCESS
40
42
  FAILED = Status.FAILED
41
43
  WAIT = Status.WAIT
42
44
  SKIP = Status.SKIP
45
+ CANCEL = Status.CANCEL
43
46
 
44
47
 
45
48
  @dataclass(
@@ -63,12 +66,11 @@ class Result:
63
66
 
64
67
  status: Status = field(default=WAIT)
65
68
  context: DictData = field(default_factory=dict)
66
- errors: DictData = field(default_factory=dict)
67
69
  run_id: Optional[str] = field(default_factory=default_gen_id)
68
70
  parent_run_id: Optional[str] = field(default=None, compare=False)
69
71
  ts: datetime = field(default_factory=get_dt_tznow, compare=False)
70
72
 
71
- trace: Optional[TraceLog] = field(default=None, compare=False, repr=False)
73
+ trace: Optional[Trace] = field(default=None, compare=False, repr=False)
72
74
  extras: DictData = field(default_factory=dict, compare=False, repr=False)
73
75
 
74
76
  @classmethod
@@ -113,8 +115,10 @@ class Result:
113
115
  :rtype: Self
114
116
  """
115
117
  if self.trace is None: # pragma: no cov
116
- self.trace: TraceLog = get_trace(
117
- self.run_id, self.parent_run_id, extras=self.extras
118
+ self.trace: Trace = get_trace(
119
+ self.run_id,
120
+ parent_run_id=self.parent_run_id,
121
+ extras=self.extras,
118
122
  )
119
123
  return self
120
124
 
@@ -126,8 +130,8 @@ class Result:
126
130
  :rtype: Self
127
131
  """
128
132
  self.parent_run_id: str = running_id
129
- self.trace: TraceLog = get_trace(
130
- self.run_id, running_id, extras=self.extras
133
+ self.trace: Trace = get_trace(
134
+ self.run_id, parent_run_id=running_id, extras=self.extras
131
135
  )
132
136
  return self
133
137
 
@@ -135,7 +139,7 @@ class Result:
135
139
  self,
136
140
  status: int | Status,
137
141
  context: DictData | None = None,
138
- error: DictData | None = None,
142
+ **kwargs,
139
143
  ) -> Self:
140
144
  """Catch the status and context to this Result object. This method will
141
145
  use between a child execution return a result, and it wants to pass
@@ -143,7 +147,6 @@ class Result:
143
147
 
144
148
  :param status: A status enum object.
145
149
  :param context: A context data that will update to the current context.
146
- :param error: An error data that will update to the current errors.
147
150
 
148
151
  :rtype: Self
149
152
  """
@@ -151,7 +154,14 @@ class Result:
151
154
  Status(status) if isinstance(status, int) else status
152
155
  )
153
156
  self.__dict__["context"].update(context or {})
154
- self.__dict__["errors"].update(error or {})
157
+ if kwargs:
158
+ for k in kwargs:
159
+ if k in self.__dict__["context"]:
160
+ self.__dict__["context"][k].update(kwargs[k])
161
+ else:
162
+ raise ResultException(
163
+ f"The key {k!r} does not exists on context data."
164
+ )
155
165
  return self
156
166
 
157
167
  def alive_time(self) -> float: # pragma: no cov
@@ -91,7 +91,7 @@ def make_filter_registry(
91
91
  :rtype: dict[str, FilterRegistry]
92
92
  """
93
93
  rs: dict[str, FilterRegistry] = {}
94
- for module in dynamic("regis_filter", f=registers):
94
+ for module in dynamic("registry_filter", f=registers):
95
95
  # NOTE: try to sequential import task functions
96
96
  try:
97
97
  importer = import_module(module)
@@ -343,7 +343,7 @@ def param2template(
343
343
  :returns: An any getter value from the params input.
344
344
  """
345
345
  registers: Optional[list[str]] = (
346
- extras.get("regis_filter") if extras else None
346
+ extras.get("registry_filter") if extras else None
347
347
  )
348
348
  filters: dict[str, FilterRegistry] = filters or make_filter_registry(
349
349
  registers=registers
@@ -449,7 +449,7 @@ def make_registry(
449
449
  """
450
450
  rs: dict[str, Registry] = {}
451
451
  regis_calls: list[str] = dynamic(
452
- "regis_call", f=registries
452
+ "registry_caller", f=registries
453
453
  ) # pragma: no cov
454
454
  regis_calls.extend(["ddeutil.vendors"])
455
455