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/__about__.py +1 -1
- ddeutil/workflow/__init__.py +4 -2
- ddeutil/workflow/api/api.py +2 -1
- ddeutil/workflow/api/repeat.py +2 -1
- ddeutil/workflow/api/routes/job.py +1 -1
- ddeutil/workflow/api/routes/logs.py +6 -5
- ddeutil/workflow/api/routes/schedules.py +2 -1
- ddeutil/workflow/api/routes/workflows.py +2 -2
- ddeutil/workflow/conf.py +61 -66
- ddeutil/workflow/job.py +13 -5
- ddeutil/workflow/logs.py +282 -105
- ddeutil/workflow/result.py +19 -8
- ddeutil/workflow/reusables.py +4 -5
- ddeutil/workflow/scheduler.py +70 -50
- ddeutil/workflow/stages.py +288 -83
- ddeutil/workflow/utils.py +3 -3
- ddeutil/workflow/workflow.py +135 -103
- {ddeutil_workflow-0.0.47.dist-info → ddeutil_workflow-0.0.49.dist-info}/METADATA +24 -26
- ddeutil_workflow-0.0.49.dist-info/RECORD +31 -0
- ddeutil_workflow-0.0.47.dist-info/RECORD +0 -31
- {ddeutil_workflow-0.0.47.dist-info → ddeutil_workflow-0.0.49.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.47.dist-info → ddeutil_workflow-0.0.49.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.47.dist-info → ddeutil_workflow-0.0.49.dist-info}/top_level.txt +0 -0
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
|
7
|
-
|
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
|
27
|
-
from .conf import config,
|
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
|
-
|
33
|
-
|
34
|
-
"
|
35
|
-
|
36
|
-
|
37
|
-
"
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
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(
|
67
|
-
|
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=
|
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[
|
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
|
126
|
-
"""Base Trace
|
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
|
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
|
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
|
270
|
-
"""Trace
|
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
|
274
|
-
cls,
|
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
|
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
|
285
|
-
cls,
|
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
|
-
|
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
|
-
|
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
|
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:
|
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
|
-
|
353
|
-
|
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
|
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:
|
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
|
-
|
379
|
-
|
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
|
389
|
-
"""Trace
|
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
|
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
|
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
|
-
|
416
|
-
|
417
|
-
|
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,
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
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
|
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(
|
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 =
|
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
|
-
|
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(
|
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
|
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 =
|
559
|
-
|
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
|
570
|
-
|
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:
|
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
|
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:
|
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
|
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
|
-
|
635
|
-
|
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(
|
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
|
834
|
+
if dynamic("audit_path", extras=extras).is_file():
|
658
835
|
return SQLiteAudit
|
659
836
|
return FileAudit
|