ddeutil-workflow 0.0.47__py3-none-any.whl → 0.0.49__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,46 +3,63 @@
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
 
11
12
  import json
13
+ import logging
12
14
  import os
13
15
  from abc import ABC, abstractmethod
14
16
  from collections.abc import Iterator
17
+ from dataclasses import field
15
18
  from datetime import datetime
19
+ from functools import lru_cache
16
20
  from inspect import Traceback, currentframe, getframeinfo
17
21
  from pathlib import Path
18
22
  from threading import get_ident
19
- from typing import ClassVar, Literal, Optional, Union
23
+ from typing import ClassVar, Literal, Optional, TypeVar, Union
20
24
 
21
25
  from pydantic import BaseModel, Field
22
26
  from pydantic.dataclasses import dataclass
23
27
  from pydantic.functional_validators import model_validator
24
28
  from typing_extensions import Self
25
29
 
26
- from .__types import DictData, DictStr, TupleStr
27
- from .conf import config, get_logger
30
+ from .__types import DictData, DictStr
31
+ from .conf import config, dynamic
28
32
  from .utils import cut_id, get_dt_now
29
33
 
30
- logger = get_logger("ddeutil.workflow")
31
34
 
32
- __all__: TupleStr = (
33
- "FileTraceLog",
34
- "SQLiteTraceLog",
35
- "TraceData",
36
- "TraceMeda",
37
- "TraceLog",
38
- "get_dt_tznow",
39
- "get_trace",
40
- "get_trace_obj",
41
- "get_audit",
42
- "FileAudit",
43
- "SQLiteAudit",
44
- "Audit",
45
- )
35
+ @lru_cache
36
+ def get_logger(name: str):
37
+ """Return logger object with an input module name.
38
+
39
+ :param name: A module name that want to log.
40
+ """
41
+ lg = logging.getLogger(name)
42
+
43
+ # NOTE: Developers using this package can then disable all logging just for
44
+ # this package by;
45
+ #
46
+ # `logging.getLogger('ddeutil.workflow').propagate = False`
47
+ #
48
+ lg.addHandler(logging.NullHandler())
49
+
50
+ formatter = logging.Formatter(
51
+ fmt=config.log_format,
52
+ datefmt=config.log_datetime_format,
53
+ )
54
+ stream = logging.StreamHandler()
55
+ stream.setFormatter(formatter)
56
+ lg.addHandler(stream)
57
+
58
+ lg.setLevel(logging.DEBUG if config.debug else logging.INFO)
59
+ return lg
60
+
61
+
62
+ logger = get_logger("ddeutil.workflow")
46
63
 
47
64
 
48
65
  def get_dt_tznow() -> datetime: # pragma: no cov
@@ -53,7 +70,9 @@ def get_dt_tznow() -> datetime: # pragma: no cov
53
70
  return get_dt_now(tz=config.tz)
54
71
 
55
72
 
56
- class TraceMeda(BaseModel): # pragma: no cov
73
+ class TraceMeta(BaseModel): # pragma: no cov
74
+ """Trace Meta model."""
75
+
57
76
  mode: Literal["stdout", "stderr"]
58
77
  datetime: str
59
78
  process: int
@@ -63,14 +82,28 @@ class TraceMeda(BaseModel): # pragma: no cov
63
82
  lineno: int
64
83
 
65
84
  @classmethod
66
- def make(cls, mode: Literal["stdout", "stderr"], message: str) -> Self:
67
- """Make a TraceMeda instance."""
85
+ def make(
86
+ cls,
87
+ mode: Literal["stdout", "stderr"],
88
+ message: str,
89
+ *,
90
+ extras: Optional[DictData] = None,
91
+ ) -> Self:
92
+ """Make the current TraceMeta instance that catching local state.
93
+
94
+ :rtype: Self
95
+ """
68
96
  frame_info: Traceback = getframeinfo(
69
97
  currentframe().f_back.f_back.f_back
70
98
  )
99
+ extras: DictData = extras or {}
71
100
  return cls(
72
101
  mode=mode,
73
- datetime=get_dt_tznow().strftime(config.log_datetime_format),
102
+ datetime=(
103
+ get_dt_now(tz=dynamic("tz", extras=extras)).strftime(
104
+ dynamic("log_datetime_format", extras=extras)
105
+ )
106
+ ),
74
107
  process=os.getpid(),
75
108
  thread=get_ident(),
76
109
  message=message,
@@ -84,7 +117,7 @@ class TraceData(BaseModel): # pragma: no cov
84
117
 
85
118
  stdout: str = Field(description="A standard output trace data.")
86
119
  stderr: str = Field(description="A standard error trace data.")
87
- meta: list[TraceMeda] = Field(
120
+ meta: list[TraceMeta] = Field(
88
121
  default_factory=list,
89
122
  description=(
90
123
  "A metadata mapping of this output and error before making it to "
@@ -122,11 +155,38 @@ class TraceData(BaseModel): # pragma: no cov
122
155
 
123
156
 
124
157
  @dataclass(frozen=True)
125
- class BaseTraceLog(ABC): # pragma: no cov
126
- """Base Trace Log dataclass object."""
158
+ class BaseTrace(ABC): # pragma: no cov
159
+ """Base Trace dataclass with abstraction class property."""
127
160
 
128
161
  run_id: str
129
- parent_run_id: Optional[str] = None
162
+ parent_run_id: Optional[str] = field(default=None)
163
+ extras: DictData = field(default_factory=dict, compare=False, repr=False)
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
+ )
130
190
 
131
191
  @abstractmethod
132
192
  def writer(self, message: str, is_err: bool = False) -> None:
@@ -148,6 +208,9 @@ class BaseTraceLog(ABC): # pragma: no cov
148
208
  :param message:
149
209
  :param is_err:
150
210
  """
211
+ raise NotImplementedError(
212
+ "Create async writer logic for this trace object before using."
213
+ )
151
214
 
152
215
  @abstractmethod
153
216
  def make_message(self, message: str) -> str:
@@ -169,7 +232,7 @@ class BaseTraceLog(ABC): # pragma: no cov
169
232
  """
170
233
  msg: str = self.make_message(message)
171
234
 
172
- if config.debug:
235
+ if dynamic("debug", extras=self.extras):
173
236
  self.writer(msg)
174
237
 
175
238
  logger.debug(msg, stacklevel=2)
@@ -221,7 +284,7 @@ class BaseTraceLog(ABC): # pragma: no cov
221
284
  :param message: (str) A message that want to log.
222
285
  """
223
286
  msg: str = self.make_message(message)
224
- if config.debug:
287
+ if dynamic("debug", extras=self.extras):
225
288
  await self.awriter(msg)
226
289
  logger.info(msg, stacklevel=2)
227
290
 
@@ -266,26 +329,43 @@ class BaseTraceLog(ABC): # pragma: no cov
266
329
  logger.exception(msg, stacklevel=2)
267
330
 
268
331
 
269
- class FileTraceLog(BaseTraceLog): # pragma: no cov
270
- """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."""
271
334
 
272
335
  @classmethod
273
- def find_logs(
274
- cls, path: Path | None = None
336
+ def find_traces(
337
+ cls,
338
+ path: Path | None = None,
339
+ extras: Optional[DictData] = None,
275
340
  ) -> Iterator[TraceData]: # pragma: no cov
276
- """Find trace logs."""
341
+ """Find trace logs.
342
+
343
+ :param path: (Path)
344
+ :param extras: An extra parameter that want to override core config.
345
+ """
277
346
  for file in sorted(
278
- (path or config.log_path).glob("./run_id=*"),
347
+ (path or dynamic("trace_path", extras=extras)).glob("./run_id=*"),
279
348
  key=lambda f: f.lstat().st_mtime,
280
349
  ):
281
350
  yield TraceData.from_path(file)
282
351
 
283
352
  @classmethod
284
- def find_log_with_id(
285
- cls, run_id: str, force_raise: bool = True, *, path: Path | None = None
353
+ def find_trace_with_id(
354
+ cls,
355
+ run_id: str,
356
+ force_raise: bool = True,
357
+ *,
358
+ path: Path | None = None,
359
+ extras: Optional[DictData] = None,
286
360
  ) -> TraceData:
287
- """Find trace log with an input specific run ID."""
288
- base_path: Path = path or config.log_path
361
+ """Find trace log with an input specific run ID.
362
+
363
+ :param run_id: A running ID of trace log.
364
+ :param force_raise:
365
+ :param path:
366
+ :param extras: An extra parameter that want to override core config.
367
+ """
368
+ base_path: Path = path or dynamic("trace_path", extras=extras)
289
369
  file: Path = base_path / f"run_id={run_id}"
290
370
  if file.exists():
291
371
  return TraceData.from_path(file)
@@ -299,7 +379,8 @@ class FileTraceLog(BaseTraceLog): # pragma: no cov
299
379
  @property
300
380
  def pointer(self) -> Path:
301
381
  log_file: Path = (
302
- config.log_path / f"run_id={self.parent_run_id or self.run_id}"
382
+ dynamic("trace_path", extras=self.extras)
383
+ / f"run_id={self.parent_run_id or self.run_id}"
303
384
  )
304
385
  if not log_file.exists():
305
386
  log_file.mkdir(parents=True)
@@ -340,18 +421,17 @@ class FileTraceLog(BaseTraceLog): # pragma: no cov
340
421
  :param message: A message after making.
341
422
  :param is_err: A flag for writing with an error trace or not.
342
423
  """
343
- if not config.enable_write_log:
424
+ if not dynamic("enable_write_log", extras=self.extras):
344
425
  return
345
426
 
346
427
  write_file: str = "stderr" if is_err else "stdout"
347
- trace_meta: TraceMeda = TraceMeda.make(mode=write_file, message=message)
428
+ trace_meta: TraceMeta = TraceMeta.make(mode=write_file, message=message)
348
429
 
349
430
  with (self.pointer / f"{write_file}.txt").open(
350
431
  mode="at", encoding="utf-8"
351
432
  ) as f:
352
- f.write(
353
- f"{config.log_format_file}\n".format(**trace_meta.model_dump())
354
- )
433
+ fmt: str = dynamic("log_format_file", extras=self.extras)
434
+ f.write(f"{fmt}\n".format(**trace_meta.model_dump()))
355
435
 
356
436
  with (self.pointer / "metadata.json").open(
357
437
  mode="at", encoding="utf-8"
@@ -361,7 +441,7 @@ class FileTraceLog(BaseTraceLog): # pragma: no cov
361
441
  async def awriter(
362
442
  self, message: str, is_err: bool = False
363
443
  ) -> None: # pragma: no cov
364
- if not config.enable_write_log:
444
+ if not dynamic("enable_write_log", extras=self.extras):
365
445
  return
366
446
 
367
447
  try:
@@ -370,14 +450,13 @@ class FileTraceLog(BaseTraceLog): # pragma: no cov
370
450
  raise ImportError("Async mode need aiofiles package") from e
371
451
 
372
452
  write_file: str = "stderr" if is_err else "stdout"
373
- trace_meta: TraceMeda = TraceMeda.make(mode=write_file, message=message)
453
+ trace_meta: TraceMeta = TraceMeta.make(mode=write_file, message=message)
374
454
 
375
455
  async with aiofiles.open(
376
456
  self.pointer / f"{write_file}.txt", mode="at", encoding="utf-8"
377
457
  ) as f:
378
- await f.write(
379
- f"{config.log_format_file}\n".format(**trace_meta.model_dump())
380
- )
458
+ fmt: str = dynamic("log_format_file", extras=self.extras)
459
+ await f.write(f"{fmt}\n".format(**trace_meta.model_dump()))
381
460
 
382
461
  async with aiofiles.open(
383
462
  self.pointer / "metadata.json", mode="at", encoding="utf-8"
@@ -385,8 +464,8 @@ class FileTraceLog(BaseTraceLog): # pragma: no cov
385
464
  await f.write(trace_meta.model_dump_json() + "\n")
386
465
 
387
466
 
388
- class SQLiteTraceLog(BaseTraceLog): # pragma: no cov
389
- """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."""
390
469
 
391
470
  table_name: ClassVar[str] = "audits"
392
471
  schemas: ClassVar[
@@ -400,10 +479,21 @@ class SQLiteTraceLog(BaseTraceLog): # pragma: no cov
400
479
  """
401
480
 
402
481
  @classmethod
403
- 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]: ...
404
487
 
405
488
  @classmethod
406
- 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: ...
407
497
 
408
498
  def make_message(self, message: str) -> str: ...
409
499
 
@@ -412,25 +502,34 @@ class SQLiteTraceLog(BaseTraceLog): # pragma: no cov
412
502
  def awriter(self, message: str, is_err: bool = False) -> None: ...
413
503
 
414
504
 
415
- TraceLog = Union[
416
- FileTraceLog,
417
- SQLiteTraceLog,
505
+ Trace = TypeVar("Trace", bound=BaseTrace)
506
+ TraceModel = Union[
507
+ FileTrace,
508
+ SQLiteTrace,
418
509
  ]
419
510
 
420
511
 
421
512
  def get_trace(
422
- run_id: str, parent_run_id: str | None = None
423
- ) -> TraceLog: # pragma: no cov
424
- """Get dynamic TraceLog object from the setting config."""
425
- if config.log_path.is_file():
426
- return SQLiteTraceLog(run_id, parent_run_id=parent_run_id)
427
- return FileTraceLog(run_id, parent_run_id=parent_run_id)
428
-
429
-
430
- def get_trace_obj() -> type[TraceLog]: # pragma: no cov
431
- if config.log_path.is_file():
432
- return SQLiteTraceLog
433
- return FileTraceLog
513
+ run_id: str,
514
+ *,
515
+ parent_run_id: str | None = None,
516
+ extras: Optional[DictData] = None,
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.
520
+
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.
525
+
526
+ :rtype: TraceLog
527
+ """
528
+ if dynamic("trace_path", extras=extras).is_file():
529
+ return SQLiteTrace(
530
+ run_id, parent_run_id=parent_run_id, extras=(extras or {})
531
+ )
532
+ return FileTrace(run_id, parent_run_id=parent_run_id, extras=(extras or {}))
434
533
 
435
534
 
436
535
  class BaseAudit(BaseModel, ABC):
@@ -439,6 +538,10 @@ class BaseAudit(BaseModel, ABC):
439
538
  subclass like file, sqlite, etc.
440
539
  """
441
540
 
541
+ extras: DictData = Field(
542
+ default_factory=dict,
543
+ description="An extras parameter that want to override core config",
544
+ )
442
545
  name: str = Field(description="A workflow name.")
443
546
  release: datetime = Field(description="A release datetime.")
444
547
  type: str = Field(description="A running type before logging.")
@@ -459,10 +562,45 @@ class BaseAudit(BaseModel, ABC):
459
562
 
460
563
  :rtype: Self
461
564
  """
462
- if config.enable_write_audit:
565
+ if dynamic("enable_write_audit", extras=self.extras):
463
566
  self.do_before()
464
567
  return self
465
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
+
466
604
  def do_before(self) -> None: # pragma: no cov
467
605
  """To something before end up of initial log model."""
468
606
 
@@ -487,15 +625,20 @@ class FileAudit(BaseAudit):
487
625
  self.pointer().mkdir(parents=True, exist_ok=True)
488
626
 
489
627
  @classmethod
490
- def find_audits(cls, name: str) -> Iterator[Self]:
628
+ def find_audits(
629
+ cls, name: str, *, extras: Optional[DictData] = None
630
+ ) -> Iterator[Self]:
491
631
  """Generate the audit data that found from logs path with specific a
492
632
  workflow name.
493
633
 
494
634
  :param name: A workflow name that want to search release logging data.
635
+ :param extras: An extra parameter that want to override core config.
495
636
 
496
637
  :rtype: Iterator[Self]
497
638
  """
498
- pointer: Path = config.audit_path / f"workflow={name}"
639
+ pointer: Path = (
640
+ dynamic("audit_path", extras=extras) / f"workflow={name}"
641
+ )
499
642
  if not pointer.exists():
500
643
  raise FileNotFoundError(f"Pointer: {pointer.absolute()}.")
501
644
 
@@ -508,13 +651,16 @@ class FileAudit(BaseAudit):
508
651
  cls,
509
652
  name: str,
510
653
  release: datetime | None = None,
654
+ *,
655
+ extras: Optional[DictData] = None,
511
656
  ) -> Self:
512
657
  """Return the audit data that found from logs path with specific
513
658
  workflow name and release values. If a release does not pass to an input
514
659
  argument, it will return the latest release from the current log path.
515
660
 
516
- :param name: A workflow name that want to search log.
517
- :param release: A release datetime that want to search log.
661
+ :param name: (str) A workflow name that want to search log.
662
+ :param release: (datetime) A release datetime that want to search log.
663
+ :param extras: An extra parameter that want to override core config.
518
664
 
519
665
  :raise FileNotFoundError:
520
666
  :raise NotImplementedError: If an input release does not pass to this
@@ -526,7 +672,7 @@ class FileAudit(BaseAudit):
526
672
  raise NotImplementedError("Find latest log does not implement yet.")
527
673
 
528
674
  pointer: Path = (
529
- config.audit_path
675
+ dynamic("audit_path", extras=extras)
530
676
  / f"workflow={name}/release={release:%Y%m%d%H%M%S}"
531
677
  )
532
678
  if not pointer.exists():
@@ -540,24 +686,31 @@ class FileAudit(BaseAudit):
540
686
  return cls.model_validate(obj=json.load(f))
541
687
 
542
688
  @classmethod
543
- def is_pointed(cls, name: str, release: datetime) -> bool:
689
+ def is_pointed(
690
+ cls,
691
+ name: str,
692
+ release: datetime,
693
+ *,
694
+ extras: Optional[DictData] = None,
695
+ ) -> bool:
544
696
  """Check the release log already pointed or created at the destination
545
697
  log path.
546
698
 
547
- :param name: A workflow name.
548
- :param release: A release datetime.
699
+ :param name: (str) A workflow name.
700
+ :param release: (datetime) A release datetime.
701
+ :param extras: An extra parameter that want to override core config.
549
702
 
550
703
  :rtype: bool
551
704
  :return: Return False if the release log was not pointed or created.
552
705
  """
553
706
  # NOTE: Return False if enable writing log flag does not set.
554
- if not config.enable_write_audit:
707
+ if not dynamic("enable_write_audit", extras=extras):
555
708
  return False
556
709
 
557
710
  # NOTE: create pointer path that use the same logic of pointer method.
558
- pointer: Path = config.audit_path / cls.filename_fmt.format(
559
- name=name, release=release
560
- )
711
+ pointer: Path = dynamic(
712
+ "audit_path", extras=extras
713
+ ) / cls.filename_fmt.format(name=name, release=release)
561
714
 
562
715
  return pointer.exists()
563
716
 
@@ -566,9 +719,9 @@ class FileAudit(BaseAudit):
566
719
 
567
720
  :rtype: Path
568
721
  """
569
- return config.audit_path / self.filename_fmt.format(
570
- name=self.name, release=self.release
571
- )
722
+ return dynamic(
723
+ "audit_path", extras=self.extras
724
+ ) / self.filename_fmt.format(name=self.name, release=self.release)
572
725
 
573
726
  def save(self, excluded: list[str] | None) -> Self:
574
727
  """Save logging data that receive a context data from a workflow
@@ -579,10 +732,14 @@ class FileAudit(BaseAudit):
579
732
 
580
733
  :rtype: Self
581
734
  """
582
- 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
+ )
583
740
 
584
741
  # NOTE: Check environ variable was set for real writing.
585
- if not config.enable_write_audit:
742
+ if not dynamic("enable_write_audit", extras=self.extras):
586
743
  trace.debug("[LOG]: Skip writing log cause config was set")
587
744
  return self
588
745
 
@@ -617,43 +774,63 @@ class SQLiteAudit(BaseAudit): # pragma: no cov
617
774
  primary key ( run_id )
618
775
  """
619
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
+
620
800
  def save(self, excluded: list[str] | None) -> SQLiteAudit:
621
801
  """Save logging data that receive a context data from a workflow
622
802
  execution result.
623
803
  """
624
- 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
+ )
625
809
 
626
810
  # NOTE: Check environ variable was set for real writing.
627
- if not config.enable_write_audit:
811
+ if not dynamic("enable_write_audit", extras=self.extras):
628
812
  trace.debug("[LOG]: Skip writing log cause config was set")
629
813
  return self
630
814
 
631
815
  raise NotImplementedError("SQLiteAudit does not implement yet.")
632
816
 
633
817
 
634
- class RemoteFileAudit(FileAudit): # pragma: no cov
635
- """Remote File Audit Pydantic Model."""
636
-
637
- def save(self, excluded: list[str] | None) -> RemoteFileAudit: ...
638
-
639
-
640
- class RedisAudit(BaseAudit): # pragma: no cov
641
- """Redis Audit Pydantic Model."""
642
-
643
- def save(self, excluded: list[str] | None) -> RedisAudit: ...
644
-
645
-
646
- Audit = Union[
818
+ Audit = TypeVar("Audit", bound=BaseAudit)
819
+ AuditModel = Union[
647
820
  FileAudit,
648
821
  SQLiteAudit,
649
822
  ]
650
823
 
651
824
 
652
- def get_audit() -> type[Audit]: # pragma: no cov
825
+ def get_audit(
826
+ extras: Optional[DictData] = None,
827
+ ) -> type[AuditModel]: # pragma: no cov
653
828
  """Get an audit class that dynamic base on the config audit path value.
654
829
 
830
+ :param extras: An extra parameter that want to override the core config.
831
+
655
832
  :rtype: type[Audit]
656
833
  """
657
- if config.audit_path.is_file():
834
+ if dynamic("audit_path", extras=extras).is_file():
658
835
  return SQLiteAudit
659
836
  return FileAudit