ddeutil-workflow 0.0.58__py3-none-any.whl → 0.0.60__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/__cron.py +3 -3
- ddeutil/workflow/__types.py +9 -2
- ddeutil/workflow/conf.py +6 -4
- ddeutil/workflow/event.py +17 -14
- ddeutil/workflow/exceptions.py +6 -5
- ddeutil/workflow/job.py +40 -39
- ddeutil/workflow/logs.py +171 -73
- ddeutil/workflow/params.py +9 -5
- ddeutil/workflow/result.py +19 -19
- ddeutil/workflow/reusables.py +9 -9
- ddeutil/workflow/scheduler.py +8 -8
- ddeutil/workflow/stages.py +96 -85
- ddeutil/workflow/utils.py +11 -10
- ddeutil/workflow/workflow.py +33 -32
- {ddeutil_workflow-0.0.58.dist-info → ddeutil_workflow-0.0.60.dist-info}/METADATA +8 -3
- ddeutil_workflow-0.0.60.dist-info/RECORD +31 -0
- {ddeutil_workflow-0.0.58.dist-info → ddeutil_workflow-0.0.60.dist-info}/WHEEL +1 -1
- ddeutil_workflow-0.0.58.dist-info/RECORD +0 -31
- {ddeutil_workflow-0.0.58.dist-info → ddeutil_workflow-0.0.60.dist-info}/entry_points.txt +0 -0
- {ddeutil_workflow-0.0.58.dist-info → ddeutil_workflow-0.0.60.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.58.dist-info → ddeutil_workflow-0.0.60.dist-info}/top_level.txt +0 -0
ddeutil/workflow/logs.py
CHANGED
@@ -3,40 +3,44 @@
|
|
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
6
|
# [x] Use fix config for `get_logger`, and Model initialize step.
|
8
|
-
"""A Logs module contain Trace
|
7
|
+
"""A Logs module contain Trace and Audit Pydantic models for process log from
|
8
|
+
the core workflow engine. I separate part of log to 2 types:
|
9
|
+
- Trace: A stdout and stderr log
|
10
|
+
- Audit: An audit release log for tracking incremental running workflow.
|
9
11
|
"""
|
10
12
|
from __future__ import annotations
|
11
13
|
|
12
14
|
import json
|
13
15
|
import logging
|
14
16
|
import os
|
17
|
+
import re
|
15
18
|
from abc import ABC, abstractmethod
|
16
19
|
from collections.abc import Iterator
|
17
|
-
from dataclasses import field
|
18
20
|
from datetime import datetime
|
19
21
|
from functools import lru_cache
|
20
22
|
from inspect import Traceback, currentframe, getframeinfo
|
21
23
|
from pathlib import Path
|
22
24
|
from threading import get_ident
|
25
|
+
from types import FrameType
|
23
26
|
from typing import ClassVar, Literal, Optional, TypeVar, Union
|
24
27
|
|
25
|
-
from pydantic import BaseModel, Field
|
26
|
-
from pydantic.dataclasses import dataclass
|
28
|
+
from pydantic import BaseModel, ConfigDict, Field
|
27
29
|
from pydantic.functional_validators import model_validator
|
28
30
|
from typing_extensions import Self
|
29
31
|
|
30
|
-
from .__types import DictData
|
32
|
+
from .__types import DictData
|
31
33
|
from .conf import config, dynamic
|
32
34
|
from .utils import cut_id, get_dt_now, prepare_newline
|
33
35
|
|
36
|
+
METADATA: str = "metadata.json"
|
37
|
+
|
34
38
|
|
35
39
|
@lru_cache
|
36
40
|
def get_logger(name: str):
|
37
41
|
"""Return logger object with an input module name.
|
38
42
|
|
39
|
-
:param name: A module name that want to log.
|
43
|
+
:param name: (str) A module name that want to log.
|
40
44
|
"""
|
41
45
|
lg = logging.getLogger(name)
|
42
46
|
|
@@ -71,13 +75,55 @@ def get_dt_tznow() -> datetime: # pragma: no cov
|
|
71
75
|
|
72
76
|
|
73
77
|
PREFIX_LOGS: dict[str, dict] = {
|
74
|
-
"CALLER": {
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
"
|
79
|
-
"
|
78
|
+
"CALLER": {
|
79
|
+
"emoji": "📍",
|
80
|
+
"desc": "logs from any usage from custom caller function.",
|
81
|
+
},
|
82
|
+
"STAGE": {"emoji": "⚙️", "desc": "logs from stages module."},
|
83
|
+
"JOB": {"emoji": "⛓️", "desc": "logs from job module."},
|
84
|
+
"WORKFLOW": {"emoji": "🏃", "desc": "logs from workflow module."},
|
85
|
+
"RELEASE": {"emoji": "📅", "desc": "logs from release workflow method."},
|
86
|
+
"POKING": {"emoji": "⏰", "desc": "logs from poke workflow method."},
|
80
87
|
} # pragma: no cov
|
88
|
+
PREFIX_DEFAULT: str = "CALLER"
|
89
|
+
PREFIX_LOGS_REGEX: re.Pattern[str] = re.compile(
|
90
|
+
rf"(^\[(?P<name>{'|'.join(PREFIX_LOGS)})]:\s?)?(?P<message>.*)",
|
91
|
+
re.MULTILINE | re.DOTALL | re.ASCII | re.VERBOSE,
|
92
|
+
) # pragma: no cov
|
93
|
+
|
94
|
+
|
95
|
+
class PrefixMsg(BaseModel):
|
96
|
+
"""Prefix Message model for receive grouping dict from searching prefix data
|
97
|
+
from logging message.
|
98
|
+
"""
|
99
|
+
|
100
|
+
name: Optional[str] = Field(default=None)
|
101
|
+
message: Optional[str] = Field(default=None)
|
102
|
+
|
103
|
+
def prepare(self, extras: Optional[DictData] = None) -> str:
|
104
|
+
"""Prepare message with force add prefix before writing trace log.
|
105
|
+
|
106
|
+
:rtype: str
|
107
|
+
"""
|
108
|
+
name: str = self.name or PREFIX_DEFAULT
|
109
|
+
emoji: str = (
|
110
|
+
f"{PREFIX_LOGS[name]['emoji']} "
|
111
|
+
if (extras or {}).get("log_add_emoji", True)
|
112
|
+
else ""
|
113
|
+
)
|
114
|
+
return f"{emoji}[{name}]: {self.message}"
|
115
|
+
|
116
|
+
|
117
|
+
def extract_msg_prefix(msg: str) -> PrefixMsg:
|
118
|
+
"""Extract message prefix from an input message.
|
119
|
+
|
120
|
+
:param msg: A message that want to extract.
|
121
|
+
|
122
|
+
:rtype: PrefixMsg
|
123
|
+
"""
|
124
|
+
return PrefixMsg.model_validate(
|
125
|
+
obj=PREFIX_LOGS_REGEX.search(msg).groupdict()
|
126
|
+
)
|
81
127
|
|
82
128
|
|
83
129
|
class TraceMeta(BaseModel): # pragma: no cov
|
@@ -86,6 +132,7 @@ class TraceMeta(BaseModel): # pragma: no cov
|
|
86
132
|
"""
|
87
133
|
|
88
134
|
mode: Literal["stdout", "stderr"] = Field(description="A meta mode.")
|
135
|
+
level: str = Field(description="A log level.")
|
89
136
|
datetime: str = Field(description="A datetime in string format.")
|
90
137
|
process: int = Field(description="A process ID.")
|
91
138
|
thread: int = Field(description="A thread ID.")
|
@@ -93,29 +140,54 @@ class TraceMeta(BaseModel): # pragma: no cov
|
|
93
140
|
filename: str = Field(description="A filename of this log.")
|
94
141
|
lineno: int = Field(description="A line number of this log.")
|
95
142
|
|
143
|
+
@classmethod
|
144
|
+
def dynamic_frame(
|
145
|
+
cls, frame: FrameType, *, extras: Optional[DictData] = None
|
146
|
+
) -> Traceback:
|
147
|
+
"""Dynamic Frame information base on the `logs_trace_frame_layer` config
|
148
|
+
value that was set from the extra parameter.
|
149
|
+
|
150
|
+
:param frame: (FrameType) The current frame that want to dynamic.
|
151
|
+
:param extras: (DictData) An extra parameter that want to get the
|
152
|
+
`logs_trace_frame_layer` config value.
|
153
|
+
"""
|
154
|
+
extras: DictData = extras or {}
|
155
|
+
layer: int = extras.get("logs_trace_frame_layer", 4)
|
156
|
+
for _ in range(layer):
|
157
|
+
_frame: Optional[FrameType] = frame.f_back
|
158
|
+
if _frame is None:
|
159
|
+
raise ValueError(
|
160
|
+
f"Layer value does not valid, the maximum frame is: {_ + 1}"
|
161
|
+
)
|
162
|
+
frame: FrameType = _frame
|
163
|
+
return getframeinfo(frame)
|
164
|
+
|
96
165
|
@classmethod
|
97
166
|
def make(
|
98
167
|
cls,
|
99
168
|
mode: Literal["stdout", "stderr"],
|
100
169
|
message: str,
|
170
|
+
level: str,
|
101
171
|
*,
|
102
172
|
extras: Optional[DictData] = None,
|
103
173
|
) -> Self:
|
104
|
-
"""Make the current
|
174
|
+
"""Make the current metric for contract this TraceMeta model instance
|
175
|
+
that will catch local states like PID, thread identity.
|
105
176
|
|
106
|
-
:param mode: A metadata mode.
|
107
|
-
:param message: A message.
|
177
|
+
:param mode: (Literal["stdout", "stderr"]) A metadata mode.
|
178
|
+
:param message: (str) A message.
|
179
|
+
:param level: (str) A log level.
|
108
180
|
:param extras: (DictData) An extra parameter that want to override core
|
109
181
|
config values.
|
110
182
|
|
111
183
|
:rtype: Self
|
112
184
|
"""
|
113
|
-
|
114
|
-
|
115
|
-
)
|
185
|
+
frame: FrameType = currentframe()
|
186
|
+
frame_info: Traceback = cls.dynamic_frame(frame, extras=extras)
|
116
187
|
extras: DictData = extras or {}
|
117
188
|
return cls(
|
118
189
|
mode=mode,
|
190
|
+
level=level,
|
119
191
|
datetime=(
|
120
192
|
get_dt_now(tz=dynamic("tz", extras=extras)).strftime(
|
121
193
|
dynamic("log_datetime_format", extras=extras)
|
@@ -150,38 +222,45 @@ class TraceData(BaseModel): # pragma: no cov
|
|
150
222
|
|
151
223
|
:rtype: Self
|
152
224
|
"""
|
153
|
-
data:
|
225
|
+
data: DictData = {"stdout": "", "stderr": "", "meta": []}
|
154
226
|
|
155
227
|
for mode in ("stdout", "stderr"):
|
156
228
|
if (file / f"{mode}.txt").exists():
|
157
229
|
data[mode] = (file / f"{mode}.txt").read_text(encoding="utf-8")
|
158
230
|
|
159
|
-
if (file /
|
231
|
+
if (file / METADATA).exists():
|
160
232
|
data["meta"] = [
|
161
233
|
json.loads(line)
|
162
234
|
for line in (
|
163
|
-
(file /
|
164
|
-
.read_text(encoding="utf-8")
|
165
|
-
.splitlines()
|
235
|
+
(file / METADATA).read_text(encoding="utf-8").splitlines()
|
166
236
|
)
|
167
237
|
]
|
168
238
|
|
169
239
|
return cls.model_validate(data)
|
170
240
|
|
171
241
|
|
172
|
-
|
173
|
-
|
174
|
-
|
242
|
+
class BaseTrace(BaseModel, ABC): # pragma: no cov
|
243
|
+
"""Base Trace model with abstraction class property."""
|
244
|
+
|
245
|
+
model_config = ConfigDict(frozen=True)
|
175
246
|
|
176
|
-
run_id: str
|
177
|
-
parent_run_id: Optional[str] =
|
178
|
-
|
247
|
+
run_id: str = Field(default="A running ID")
|
248
|
+
parent_run_id: Optional[str] = Field(
|
249
|
+
default=None, description="A parent running ID"
|
250
|
+
)
|
251
|
+
extras: DictData = Field(
|
252
|
+
default_factory=dict,
|
253
|
+
description=(
|
254
|
+
"An extra parameter that want to override on the core config "
|
255
|
+
"values."
|
256
|
+
),
|
257
|
+
)
|
179
258
|
|
180
259
|
@classmethod
|
181
260
|
@abstractmethod
|
182
261
|
def find_traces(
|
183
262
|
cls,
|
184
|
-
path: Path
|
263
|
+
path: Optional[Path] = None,
|
185
264
|
extras: Optional[DictData] = None,
|
186
265
|
) -> Iterator[TraceData]: # pragma: no cov
|
187
266
|
raise NotImplementedError(
|
@@ -195,7 +274,7 @@ class BaseTrace(ABC): # pragma: no cov
|
|
195
274
|
run_id: str,
|
196
275
|
force_raise: bool = True,
|
197
276
|
*,
|
198
|
-
path: Path
|
277
|
+
path: Optional[Path] = None,
|
199
278
|
extras: Optional[DictData] = None,
|
200
279
|
) -> TraceData:
|
201
280
|
raise NotImplementedError(
|
@@ -204,24 +283,30 @@ class BaseTrace(ABC): # pragma: no cov
|
|
204
283
|
)
|
205
284
|
|
206
285
|
@abstractmethod
|
207
|
-
def writer(self, message: str, is_err: bool = False) -> None:
|
286
|
+
def writer(self, message: str, level: str, is_err: bool = False) -> None:
|
208
287
|
"""Write a trace message after making to target pointer object. The
|
209
288
|
target can be anything be inherited this class and overwrite this method
|
210
289
|
such as file, console, or database.
|
211
290
|
|
212
|
-
:param message: A message after making.
|
213
|
-
:param
|
291
|
+
:param message: (str) A message after making.
|
292
|
+
:param level: (str) A log level.
|
293
|
+
:param is_err: (bool) A flag for writing with an error trace or not.
|
294
|
+
(Default be False)
|
214
295
|
"""
|
215
296
|
raise NotImplementedError(
|
216
297
|
"Create writer logic for this trace object before using."
|
217
298
|
)
|
218
299
|
|
219
300
|
@abstractmethod
|
220
|
-
async def awriter(
|
301
|
+
async def awriter(
|
302
|
+
self, message: str, level: str, is_err: bool = False
|
303
|
+
) -> None:
|
221
304
|
"""Async Write a trace message after making to target pointer object.
|
222
305
|
|
223
|
-
:param message:
|
224
|
-
:param
|
306
|
+
:param message: (str) A message after making.
|
307
|
+
:param level: (str) A log level.
|
308
|
+
:param is_err: (bool) A flag for writing with an error trace or not.
|
309
|
+
(Default be False)
|
225
310
|
"""
|
226
311
|
raise NotImplementedError(
|
227
312
|
"Create async writer logic for this trace object before using."
|
@@ -247,12 +332,14 @@ class BaseTrace(ABC): # pragma: no cov
|
|
247
332
|
|
248
333
|
:param message: (str) A message that want to log.
|
249
334
|
"""
|
250
|
-
msg: str = prepare_newline(
|
335
|
+
msg: str = prepare_newline(
|
336
|
+
self.make_message(extract_msg_prefix(message).prepare(self.extras))
|
337
|
+
)
|
251
338
|
|
252
339
|
if mode != "debug" or (
|
253
340
|
mode == "debug" and dynamic("debug", extras=self.extras)
|
254
341
|
):
|
255
|
-
self.writer(msg, is_err=is_err)
|
342
|
+
self.writer(msg, level=mode, is_err=is_err)
|
256
343
|
|
257
344
|
getattr(logger, mode)(msg, stacklevel=3)
|
258
345
|
|
@@ -304,12 +391,14 @@ class BaseTrace(ABC): # pragma: no cov
|
|
304
391
|
|
305
392
|
:param message: (str) A message that want to log.
|
306
393
|
"""
|
307
|
-
msg: str = prepare_newline(
|
394
|
+
msg: str = prepare_newline(
|
395
|
+
self.make_message(extract_msg_prefix(message).prepare(self.extras))
|
396
|
+
)
|
308
397
|
|
309
398
|
if mode != "debug" or (
|
310
399
|
mode == "debug" and dynamic("debug", extras=self.extras)
|
311
400
|
):
|
312
|
-
await self.awriter(msg, is_err=is_err)
|
401
|
+
await self.awriter(msg, level=mode, is_err=is_err)
|
313
402
|
|
314
403
|
getattr(logger, mode)(msg, stacklevel=3)
|
315
404
|
|
@@ -360,7 +449,7 @@ class FileTrace(BaseTrace): # pragma: no cov
|
|
360
449
|
@classmethod
|
361
450
|
def find_traces(
|
362
451
|
cls,
|
363
|
-
path: Path
|
452
|
+
path: Optional[Path] = None,
|
364
453
|
extras: Optional[DictData] = None,
|
365
454
|
) -> Iterator[TraceData]: # pragma: no cov
|
366
455
|
"""Find trace logs.
|
@@ -380,7 +469,7 @@ class FileTrace(BaseTrace): # pragma: no cov
|
|
380
469
|
run_id: str,
|
381
470
|
*,
|
382
471
|
force_raise: bool = True,
|
383
|
-
path: Path
|
472
|
+
path: Optional[Path] = None,
|
384
473
|
extras: Optional[DictData] = None,
|
385
474
|
) -> TraceData:
|
386
475
|
"""Find trace log with an input specific run ID.
|
@@ -399,7 +488,7 @@ class FileTrace(BaseTrace): # pragma: no cov
|
|
399
488
|
f"Trace log on path {base_path}, does not found trace "
|
400
489
|
f"'run_id={run_id}'."
|
401
490
|
)
|
402
|
-
return
|
491
|
+
return TraceData(stdout="", stderr="")
|
403
492
|
|
404
493
|
@property
|
405
494
|
def pointer(self) -> Path:
|
@@ -433,7 +522,7 @@ class FileTrace(BaseTrace): # pragma: no cov
|
|
433
522
|
"""
|
434
523
|
return f"({self.cut_id}) {message}"
|
435
524
|
|
436
|
-
def writer(self, message: str, is_err: bool = False) -> None:
|
525
|
+
def writer(self, message: str, level: str, is_err: bool = False) -> None:
|
437
526
|
"""Write a trace message after making to target file and write metadata
|
438
527
|
in the same path of standard files.
|
439
528
|
|
@@ -443,28 +532,29 @@ class FileTrace(BaseTrace): # pragma: no cov
|
|
443
532
|
... ./logs/run_id=<run-id>/stdout.txt
|
444
533
|
... ./logs/run_id=<run-id>/stderr.txt
|
445
534
|
|
446
|
-
:param message: A message after making.
|
535
|
+
:param message: (str) A message after making.
|
536
|
+
:param level: (str) A log level.
|
447
537
|
:param is_err: A flag for writing with an error trace or not.
|
448
538
|
"""
|
449
539
|
if not dynamic("enable_write_log", extras=self.extras):
|
450
540
|
return
|
451
541
|
|
452
|
-
|
453
|
-
trace_meta: TraceMeta = TraceMeta.make(
|
542
|
+
mode: Literal["stdout", "stderr"] = "stderr" if is_err else "stdout"
|
543
|
+
trace_meta: TraceMeta = TraceMeta.make(
|
544
|
+
mode=mode, level=level, message=message, extras=self.extras
|
545
|
+
)
|
454
546
|
|
455
|
-
with (self.pointer / f"{
|
547
|
+
with (self.pointer / f"{mode}.txt").open(
|
456
548
|
mode="at", encoding="utf-8"
|
457
549
|
) as f:
|
458
550
|
fmt: str = dynamic("log_format_file", extras=self.extras)
|
459
551
|
f.write(f"{fmt}\n".format(**trace_meta.model_dump()))
|
460
552
|
|
461
|
-
with (self.pointer /
|
462
|
-
mode="at", encoding="utf-8"
|
463
|
-
) as f:
|
553
|
+
with (self.pointer / METADATA).open(mode="at", encoding="utf-8") as f:
|
464
554
|
f.write(trace_meta.model_dump_json() + "\n")
|
465
555
|
|
466
556
|
async def awriter(
|
467
|
-
self, message: str, is_err: bool = False
|
557
|
+
self, message: str, level: str, is_err: bool = False
|
468
558
|
) -> None: # pragma: no cov
|
469
559
|
"""Write with async mode."""
|
470
560
|
if not dynamic("enable_write_log", extras=self.extras):
|
@@ -475,17 +565,19 @@ class FileTrace(BaseTrace): # pragma: no cov
|
|
475
565
|
except ImportError as e:
|
476
566
|
raise ImportError("Async mode need aiofiles package") from e
|
477
567
|
|
478
|
-
|
479
|
-
trace_meta: TraceMeta = TraceMeta.make(
|
568
|
+
mode: Literal["stdout", "stderr"] = "stderr" if is_err else "stdout"
|
569
|
+
trace_meta: TraceMeta = TraceMeta.make(
|
570
|
+
mode=mode, level=level, message=message, extras=self.extras
|
571
|
+
)
|
480
572
|
|
481
573
|
async with aiofiles.open(
|
482
|
-
self.pointer / f"{
|
574
|
+
self.pointer / f"{mode}.txt", mode="at", encoding="utf-8"
|
483
575
|
) as f:
|
484
576
|
fmt: str = dynamic("log_format_file", extras=self.extras)
|
485
577
|
await f.write(f"{fmt}\n".format(**trace_meta.model_dump()))
|
486
578
|
|
487
579
|
async with aiofiles.open(
|
488
|
-
self.pointer /
|
580
|
+
self.pointer / METADATA, mode="at", encoding="utf-8"
|
489
581
|
) as f:
|
490
582
|
await f.write(trace_meta.model_dump_json() + "\n")
|
491
583
|
|
@@ -507,7 +599,7 @@ class SQLiteTrace(BaseTrace): # pragma: no cov
|
|
507
599
|
@classmethod
|
508
600
|
def find_traces(
|
509
601
|
cls,
|
510
|
-
path: Path
|
602
|
+
path: Optional[Path] = None,
|
511
603
|
extras: Optional[DictData] = None,
|
512
604
|
) -> Iterator[TraceData]: ...
|
513
605
|
|
@@ -517,15 +609,19 @@ class SQLiteTrace(BaseTrace): # pragma: no cov
|
|
517
609
|
run_id: str,
|
518
610
|
force_raise: bool = True,
|
519
611
|
*,
|
520
|
-
path: Path
|
612
|
+
path: Optional[Path] = None,
|
521
613
|
extras: Optional[DictData] = None,
|
522
614
|
) -> TraceData: ...
|
523
615
|
|
524
616
|
def make_message(self, message: str) -> str: ...
|
525
617
|
|
526
|
-
def writer(
|
618
|
+
def writer(
|
619
|
+
self, message: str, level: str, is_err: bool = False
|
620
|
+
) -> None: ...
|
527
621
|
|
528
|
-
def awriter(
|
622
|
+
def awriter(
|
623
|
+
self, message: str, level: str, is_err: bool = False
|
624
|
+
) -> None: ...
|
529
625
|
|
530
626
|
|
531
627
|
Trace = TypeVar("Trace", bound=BaseTrace)
|
@@ -538,7 +634,7 @@ TraceModel = Union[
|
|
538
634
|
def get_trace(
|
539
635
|
run_id: str,
|
540
636
|
*,
|
541
|
-
parent_run_id: str
|
637
|
+
parent_run_id: Optional[str] = None,
|
542
638
|
extras: Optional[DictData] = None,
|
543
639
|
) -> TraceModel: # pragma: no cov
|
544
640
|
"""Get dynamic Trace instance from the core config (it can override by an
|
@@ -553,9 +649,11 @@ def get_trace(
|
|
553
649
|
"""
|
554
650
|
if dynamic("trace_path", extras=extras).is_file():
|
555
651
|
return SQLiteTrace(
|
556
|
-
run_id, parent_run_id=parent_run_id, extras=(extras or {})
|
652
|
+
run_id=run_id, parent_run_id=parent_run_id, extras=(extras or {})
|
557
653
|
)
|
558
|
-
return FileTrace(
|
654
|
+
return FileTrace(
|
655
|
+
run_id=run_id, parent_run_id=parent_run_id, extras=(extras or {})
|
656
|
+
)
|
559
657
|
|
560
658
|
|
561
659
|
class BaseAudit(BaseModel, ABC):
|
@@ -619,7 +717,7 @@ class BaseAudit(BaseModel, ABC):
|
|
619
717
|
def find_audit_with_release(
|
620
718
|
cls,
|
621
719
|
name: str,
|
622
|
-
release: datetime
|
720
|
+
release: Optional[datetime] = None,
|
623
721
|
*,
|
624
722
|
extras: Optional[DictData] = None,
|
625
723
|
) -> Self:
|
@@ -631,7 +729,7 @@ class BaseAudit(BaseModel, ABC):
|
|
631
729
|
"""To something before end up of initial log model."""
|
632
730
|
|
633
731
|
@abstractmethod
|
634
|
-
def save(self, excluded: list[str]
|
732
|
+
def save(self, excluded: Optional[list[str]]) -> None: # pragma: no cov
|
635
733
|
"""Save this model logging to target logging store."""
|
636
734
|
raise NotImplementedError("Audit should implement ``save`` method.")
|
637
735
|
|
@@ -676,7 +774,7 @@ class FileAudit(BaseAudit):
|
|
676
774
|
def find_audit_with_release(
|
677
775
|
cls,
|
678
776
|
name: str,
|
679
|
-
release: datetime
|
777
|
+
release: Optional[datetime] = None,
|
680
778
|
*,
|
681
779
|
extras: Optional[DictData] = None,
|
682
780
|
) -> Self:
|
@@ -749,7 +847,7 @@ class FileAudit(BaseAudit):
|
|
749
847
|
"audit_path", extras=self.extras
|
750
848
|
) / self.filename_fmt.format(name=self.name, release=self.release)
|
751
849
|
|
752
|
-
def save(self, excluded: list[str]
|
850
|
+
def save(self, excluded: Optional[list[str]]) -> Self:
|
753
851
|
"""Save logging data that receive a context data from a workflow
|
754
852
|
execution result.
|
755
853
|
|
@@ -758,7 +856,7 @@ class FileAudit(BaseAudit):
|
|
758
856
|
|
759
857
|
:rtype: Self
|
760
858
|
"""
|
761
|
-
trace:
|
859
|
+
trace: TraceModel = get_trace(
|
762
860
|
self.run_id,
|
763
861
|
parent_run_id=self.parent_run_id,
|
764
862
|
extras=self.extras,
|
@@ -818,16 +916,16 @@ class SQLiteAudit(BaseAudit): # pragma: no cov
|
|
818
916
|
def find_audit_with_release(
|
819
917
|
cls,
|
820
918
|
name: str,
|
821
|
-
release: datetime
|
919
|
+
release: Optional[datetime] = None,
|
822
920
|
*,
|
823
921
|
extras: Optional[DictData] = None,
|
824
922
|
) -> Self: ...
|
825
923
|
|
826
|
-
def save(self, excluded: list[str]
|
924
|
+
def save(self, excluded: Optional[list[str]]) -> SQLiteAudit:
|
827
925
|
"""Save logging data that receive a context data from a workflow
|
828
926
|
execution result.
|
829
927
|
"""
|
830
|
-
trace:
|
928
|
+
trace: TraceModel = get_trace(
|
831
929
|
self.run_id,
|
832
930
|
parent_run_id=self.parent_run_id,
|
833
931
|
extras=self.extras,
|
ddeutil/workflow/params.py
CHANGED
@@ -82,7 +82,9 @@ class DateParam(DefaultParam): # pragma: no cov
|
|
82
82
|
description="A default date that make from the current date func.",
|
83
83
|
)
|
84
84
|
|
85
|
-
def receive(
|
85
|
+
def receive(
|
86
|
+
self, value: Optional[Union[str, datetime, date]] = None
|
87
|
+
) -> date:
|
86
88
|
"""Receive value that match with date. If an input value pass with
|
87
89
|
None, it will use default value instead.
|
88
90
|
|
@@ -121,7 +123,9 @@ class DatetimeParam(DefaultParam):
|
|
121
123
|
),
|
122
124
|
)
|
123
125
|
|
124
|
-
def receive(
|
126
|
+
def receive(
|
127
|
+
self, value: Optional[Union[str, datetime, date]] = None
|
128
|
+
) -> datetime:
|
125
129
|
"""Receive value that match with datetime. If an input value pass with
|
126
130
|
None, it will use default value instead.
|
127
131
|
|
@@ -155,11 +159,11 @@ class StrParam(DefaultParam):
|
|
155
159
|
|
156
160
|
type: Literal["str"] = "str"
|
157
161
|
|
158
|
-
def receive(self, value: str
|
162
|
+
def receive(self, value: Optional[str] = None) -> Optional[str]:
|
159
163
|
"""Receive value that match with str.
|
160
164
|
|
161
165
|
:param value: A value that want to validate with string parameter type.
|
162
|
-
:rtype: str
|
166
|
+
:rtype: Optional[str]
|
163
167
|
"""
|
164
168
|
if value is None:
|
165
169
|
return self.default
|
@@ -171,7 +175,7 @@ class IntParam(DefaultParam):
|
|
171
175
|
|
172
176
|
type: Literal["int"] = "int"
|
173
177
|
|
174
|
-
def receive(self, value: int
|
178
|
+
def receive(self, value: Optional[int] = None) -> Optional[int]:
|
175
179
|
"""Receive value that match with int.
|
176
180
|
|
177
181
|
:param value: A value that want to validate with integer parameter type.
|
ddeutil/workflow/result.py
CHANGED
@@ -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
|
-
|
7
|
-
|
8
|
-
|
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
|
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,14 +31,14 @@ class Status(IntEnum):
|
|
31
31
|
Result dataclass object.
|
32
32
|
"""
|
33
33
|
|
34
|
-
SUCCESS
|
35
|
-
FAILED
|
36
|
-
WAIT
|
37
|
-
SKIP
|
38
|
-
CANCEL
|
34
|
+
SUCCESS = 0
|
35
|
+
FAILED = 1
|
36
|
+
WAIT = 2
|
37
|
+
SKIP = 3
|
38
|
+
CANCEL = 4
|
39
39
|
|
40
40
|
@property
|
41
|
-
def emoji(self) -> str:
|
41
|
+
def emoji(self) -> str: # pragma: no cov
|
42
42
|
"""Return the emoji value of this status.
|
43
43
|
|
44
44
|
:rtype: 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[
|
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
|
85
|
-
run_id: str
|
86
|
-
parent_run_id: str
|
87
|
-
id_logic: str
|
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:
|
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:
|
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
|
146
|
+
status: Union[int, Status],
|
147
147
|
context: DictData | None = None,
|
148
148
|
**kwargs,
|
149
149
|
) -> Self:
|