ddeutil-workflow 0.0.46__py3-none-any.whl → 0.0.48__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 +0 -1
- 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/schedules.py +2 -1
- ddeutil/workflow/api/routes/workflows.py +2 -2
- ddeutil/workflow/conf.py +34 -39
- ddeutil/workflow/job.py +3 -0
- ddeutil/workflow/logs.py +169 -56
- ddeutil/workflow/result.py +25 -21
- ddeutil/workflow/reusables.py +5 -5
- ddeutil/workflow/scheduler.py +19 -9
- ddeutil/workflow/stages.py +73 -31
- ddeutil/workflow/workflow.py +30 -18
- {ddeutil_workflow-0.0.46.dist-info → ddeutil_workflow-0.0.48.dist-info}/METADATA +27 -27
- ddeutil_workflow-0.0.48.dist-info/RECORD +31 -0
- ddeutil_workflow-0.0.46.dist-info/RECORD +0 -31
- {ddeutil_workflow-0.0.46.dist-info → ddeutil_workflow-0.0.48.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.46.dist-info → ddeutil_workflow-0.0.48.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.46.dist-info → ddeutil_workflow-0.0.48.dist-info}/top_level.txt +0 -0
ddeutil/workflow/logs.py
CHANGED
@@ -9,33 +9,65 @@
|
|
9
9
|
from __future__ import annotations
|
10
10
|
|
11
11
|
import json
|
12
|
+
import logging
|
12
13
|
import os
|
13
14
|
from abc import ABC, abstractmethod
|
14
15
|
from collections.abc import Iterator
|
16
|
+
from dataclasses import field
|
15
17
|
from datetime import datetime
|
18
|
+
from functools import lru_cache
|
16
19
|
from inspect import Traceback, currentframe, getframeinfo
|
17
20
|
from pathlib import Path
|
18
21
|
from threading import get_ident
|
19
22
|
from typing import ClassVar, Literal, Optional, Union
|
20
23
|
|
21
|
-
from pydantic import BaseModel, Field
|
24
|
+
from pydantic import BaseModel, Field, ValidationInfo
|
22
25
|
from pydantic.dataclasses import dataclass
|
23
26
|
from pydantic.functional_validators import model_validator
|
24
27
|
from typing_extensions import Self
|
25
28
|
|
26
29
|
from .__types import DictData, DictStr, TupleStr
|
27
|
-
from .conf import config,
|
30
|
+
from .conf import config, dynamic
|
28
31
|
from .utils import cut_id, get_dt_now
|
29
32
|
|
33
|
+
|
34
|
+
@lru_cache
|
35
|
+
def get_logger(name: str):
|
36
|
+
"""Return logger object with an input module name.
|
37
|
+
|
38
|
+
:param name: A module name that want to log.
|
39
|
+
"""
|
40
|
+
lg = logging.getLogger(name)
|
41
|
+
|
42
|
+
# NOTE: Developers using this package can then disable all logging just for
|
43
|
+
# this package by;
|
44
|
+
#
|
45
|
+
# `logging.getLogger('ddeutil.workflow').propagate = False`
|
46
|
+
#
|
47
|
+
lg.addHandler(logging.NullHandler())
|
48
|
+
|
49
|
+
formatter = logging.Formatter(
|
50
|
+
fmt=config.log_format,
|
51
|
+
datefmt=config.log_datetime_format,
|
52
|
+
)
|
53
|
+
stream = logging.StreamHandler()
|
54
|
+
stream.setFormatter(formatter)
|
55
|
+
lg.addHandler(stream)
|
56
|
+
|
57
|
+
lg.setLevel(logging.DEBUG if config.debug else logging.INFO)
|
58
|
+
return lg
|
59
|
+
|
60
|
+
|
30
61
|
logger = get_logger("ddeutil.workflow")
|
31
62
|
|
32
63
|
__all__: TupleStr = (
|
33
64
|
"FileTraceLog",
|
34
65
|
"SQLiteTraceLog",
|
35
66
|
"TraceData",
|
36
|
-
"
|
67
|
+
"TraceMeta",
|
37
68
|
"TraceLog",
|
38
69
|
"get_dt_tznow",
|
70
|
+
"get_logger",
|
39
71
|
"get_trace",
|
40
72
|
"get_trace_obj",
|
41
73
|
"get_audit",
|
@@ -53,7 +85,9 @@ def get_dt_tznow() -> datetime: # pragma: no cov
|
|
53
85
|
return get_dt_now(tz=config.tz)
|
54
86
|
|
55
87
|
|
56
|
-
class
|
88
|
+
class TraceMeta(BaseModel): # pragma: no cov
|
89
|
+
"""Trace Meta model."""
|
90
|
+
|
57
91
|
mode: Literal["stdout", "stderr"]
|
58
92
|
datetime: str
|
59
93
|
process: int
|
@@ -63,14 +97,28 @@ class TraceMeda(BaseModel): # pragma: no cov
|
|
63
97
|
lineno: int
|
64
98
|
|
65
99
|
@classmethod
|
66
|
-
def make(
|
67
|
-
|
100
|
+
def make(
|
101
|
+
cls,
|
102
|
+
mode: Literal["stdout", "stderr"],
|
103
|
+
message: str,
|
104
|
+
*,
|
105
|
+
extras: Optional[DictData] = None,
|
106
|
+
) -> Self:
|
107
|
+
"""Make the current TraceMeta instance that catching local state.
|
108
|
+
|
109
|
+
:rtype: Self
|
110
|
+
"""
|
68
111
|
frame_info: Traceback = getframeinfo(
|
69
112
|
currentframe().f_back.f_back.f_back
|
70
113
|
)
|
114
|
+
extras: DictData = extras or {}
|
71
115
|
return cls(
|
72
116
|
mode=mode,
|
73
|
-
datetime=
|
117
|
+
datetime=(
|
118
|
+
get_dt_now(tz=dynamic("tz", extras=extras)).strftime(
|
119
|
+
dynamic("log_datetime_format", extras=extras)
|
120
|
+
)
|
121
|
+
),
|
74
122
|
process=os.getpid(),
|
75
123
|
thread=get_ident(),
|
76
124
|
message=message,
|
@@ -84,7 +132,7 @@ class TraceData(BaseModel): # pragma: no cov
|
|
84
132
|
|
85
133
|
stdout: str = Field(description="A standard output trace data.")
|
86
134
|
stderr: str = Field(description="A standard error trace data.")
|
87
|
-
meta: list[
|
135
|
+
meta: list[TraceMeta] = Field(
|
88
136
|
default_factory=list,
|
89
137
|
description=(
|
90
138
|
"A metadata mapping of this output and error before making it to "
|
@@ -126,7 +174,8 @@ class BaseTraceLog(ABC): # pragma: no cov
|
|
126
174
|
"""Base Trace Log dataclass object."""
|
127
175
|
|
128
176
|
run_id: str
|
129
|
-
parent_run_id: Optional[str] = None
|
177
|
+
parent_run_id: Optional[str] = field(default=None)
|
178
|
+
extras: DictData = field(default_factory=dict, compare=False, repr=False)
|
130
179
|
|
131
180
|
@abstractmethod
|
132
181
|
def writer(self, message: str, is_err: bool = False) -> None:
|
@@ -148,6 +197,9 @@ class BaseTraceLog(ABC): # pragma: no cov
|
|
148
197
|
:param message:
|
149
198
|
:param is_err:
|
150
199
|
"""
|
200
|
+
raise NotImplementedError(
|
201
|
+
"Create async writer logic for this trace object before using."
|
202
|
+
)
|
151
203
|
|
152
204
|
@abstractmethod
|
153
205
|
def make_message(self, message: str) -> str:
|
@@ -169,7 +221,7 @@ class BaseTraceLog(ABC): # pragma: no cov
|
|
169
221
|
"""
|
170
222
|
msg: str = self.make_message(message)
|
171
223
|
|
172
|
-
if
|
224
|
+
if dynamic("debug", extras=self.extras):
|
173
225
|
self.writer(msg)
|
174
226
|
|
175
227
|
logger.debug(msg, stacklevel=2)
|
@@ -221,7 +273,7 @@ class BaseTraceLog(ABC): # pragma: no cov
|
|
221
273
|
:param message: (str) A message that want to log.
|
222
274
|
"""
|
223
275
|
msg: str = self.make_message(message)
|
224
|
-
if
|
276
|
+
if dynamic("debug", extras=self.extras):
|
225
277
|
await self.awriter(msg)
|
226
278
|
logger.info(msg, stacklevel=2)
|
227
279
|
|
@@ -271,21 +323,38 @@ class FileTraceLog(BaseTraceLog): # pragma: no cov
|
|
271
323
|
|
272
324
|
@classmethod
|
273
325
|
def find_logs(
|
274
|
-
cls,
|
326
|
+
cls,
|
327
|
+
path: Path | None = None,
|
328
|
+
extras: Optional[DictData] = None,
|
275
329
|
) -> Iterator[TraceData]: # pragma: no cov
|
276
|
-
"""Find trace logs.
|
330
|
+
"""Find trace logs.
|
331
|
+
|
332
|
+
:param path: (Path)
|
333
|
+
:param extras: An extra parameter that want to override core config.
|
334
|
+
"""
|
277
335
|
for file in sorted(
|
278
|
-
(path or
|
336
|
+
(path or dynamic("log_path", extras=extras)).glob("./run_id=*"),
|
279
337
|
key=lambda f: f.lstat().st_mtime,
|
280
338
|
):
|
281
339
|
yield TraceData.from_path(file)
|
282
340
|
|
283
341
|
@classmethod
|
284
342
|
def find_log_with_id(
|
285
|
-
cls,
|
343
|
+
cls,
|
344
|
+
run_id: str,
|
345
|
+
force_raise: bool = True,
|
346
|
+
*,
|
347
|
+
path: Path | None = None,
|
348
|
+
extras: Optional[DictData] = None,
|
286
349
|
) -> TraceData:
|
287
|
-
"""Find trace log with an input specific run ID.
|
288
|
-
|
350
|
+
"""Find trace log with an input specific run ID.
|
351
|
+
|
352
|
+
:param run_id: A running ID of trace log.
|
353
|
+
:param force_raise:
|
354
|
+
:param path:
|
355
|
+
:param extras: An extra parameter that want to override core config.
|
356
|
+
"""
|
357
|
+
base_path: Path = path or dynamic("log_path", extras=extras)
|
289
358
|
file: Path = base_path / f"run_id={run_id}"
|
290
359
|
if file.exists():
|
291
360
|
return TraceData.from_path(file)
|
@@ -299,7 +368,8 @@ class FileTraceLog(BaseTraceLog): # pragma: no cov
|
|
299
368
|
@property
|
300
369
|
def pointer(self) -> Path:
|
301
370
|
log_file: Path = (
|
302
|
-
|
371
|
+
dynamic("log_path", extras=self.extras)
|
372
|
+
/ f"run_id={self.parent_run_id or self.run_id}"
|
303
373
|
)
|
304
374
|
if not log_file.exists():
|
305
375
|
log_file.mkdir(parents=True)
|
@@ -340,18 +410,17 @@ class FileTraceLog(BaseTraceLog): # pragma: no cov
|
|
340
410
|
:param message: A message after making.
|
341
411
|
:param is_err: A flag for writing with an error trace or not.
|
342
412
|
"""
|
343
|
-
if not
|
413
|
+
if not dynamic("enable_write_log", extras=self.extras):
|
344
414
|
return
|
345
415
|
|
346
416
|
write_file: str = "stderr" if is_err else "stdout"
|
347
|
-
trace_meta:
|
417
|
+
trace_meta: TraceMeta = TraceMeta.make(mode=write_file, message=message)
|
348
418
|
|
349
419
|
with (self.pointer / f"{write_file}.txt").open(
|
350
420
|
mode="at", encoding="utf-8"
|
351
421
|
) as f:
|
352
|
-
|
353
|
-
|
354
|
-
)
|
422
|
+
fmt: str = dynamic("log_format_file", extras=self.extras)
|
423
|
+
f.write(f"{fmt}\n".format(**trace_meta.model_dump()))
|
355
424
|
|
356
425
|
with (self.pointer / "metadata.json").open(
|
357
426
|
mode="at", encoding="utf-8"
|
@@ -361,7 +430,7 @@ class FileTraceLog(BaseTraceLog): # pragma: no cov
|
|
361
430
|
async def awriter(
|
362
431
|
self, message: str, is_err: bool = False
|
363
432
|
) -> None: # pragma: no cov
|
364
|
-
if not
|
433
|
+
if not dynamic("enable_write_log", extras=self.extras):
|
365
434
|
return
|
366
435
|
|
367
436
|
try:
|
@@ -370,14 +439,13 @@ class FileTraceLog(BaseTraceLog): # pragma: no cov
|
|
370
439
|
raise ImportError("Async mode need aiofiles package") from e
|
371
440
|
|
372
441
|
write_file: str = "stderr" if is_err else "stdout"
|
373
|
-
trace_meta:
|
442
|
+
trace_meta: TraceMeta = TraceMeta.make(mode=write_file, message=message)
|
374
443
|
|
375
444
|
async with aiofiles.open(
|
376
445
|
self.pointer / f"{write_file}.txt", mode="at", encoding="utf-8"
|
377
446
|
) as f:
|
378
|
-
|
379
|
-
|
380
|
-
)
|
447
|
+
fmt: str = dynamic("log_format_file", extras=self.extras)
|
448
|
+
await f.write(f"{fmt}\n".format(**trace_meta.model_dump()))
|
381
449
|
|
382
450
|
async with aiofiles.open(
|
383
451
|
self.pointer / "metadata.json", mode="at", encoding="utf-8"
|
@@ -419,16 +487,38 @@ TraceLog = Union[
|
|
419
487
|
|
420
488
|
|
421
489
|
def get_trace(
|
422
|
-
run_id: str,
|
490
|
+
run_id: str,
|
491
|
+
parent_run_id: str | None = None,
|
492
|
+
*,
|
493
|
+
extras: Optional[DictData] = None,
|
423
494
|
) -> TraceLog: # pragma: no cov
|
424
|
-
"""Get dynamic TraceLog object from the setting config.
|
425
|
-
|
426
|
-
|
427
|
-
|
495
|
+
"""Get dynamic TraceLog object from the setting config.
|
496
|
+
|
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.
|
500
|
+
|
501
|
+
:rtype: TraceLog
|
502
|
+
"""
|
503
|
+
if dynamic("log_path", extras=extras).is_file():
|
504
|
+
return SQLiteTraceLog(
|
505
|
+
run_id, parent_run_id=parent_run_id, extras=(extras or {})
|
506
|
+
)
|
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.
|
428
516
|
|
517
|
+
:param extras: An extra parameter that want to override the core config.
|
429
518
|
|
430
|
-
|
431
|
-
|
519
|
+
:rtype: type[TraceLog]
|
520
|
+
"""
|
521
|
+
if dynamic("log_path", extras=extras).is_file():
|
432
522
|
return SQLiteTraceLog
|
433
523
|
return FileTraceLog
|
434
524
|
|
@@ -439,6 +529,10 @@ class BaseAudit(BaseModel, ABC):
|
|
439
529
|
subclass like file, sqlite, etc.
|
440
530
|
"""
|
441
531
|
|
532
|
+
extras: DictData = Field(
|
533
|
+
default_factory=dict,
|
534
|
+
description="An extras parameter that want to override core config",
|
535
|
+
)
|
442
536
|
name: str = Field(description="A workflow name.")
|
443
537
|
release: datetime = Field(description="A release datetime.")
|
444
538
|
type: str = Field(description="A running type before logging.")
|
@@ -454,12 +548,12 @@ class BaseAudit(BaseModel, ABC):
|
|
454
548
|
execution_time: float = Field(default=0, description="An execution time.")
|
455
549
|
|
456
550
|
@model_validator(mode="after")
|
457
|
-
def __model_action(self) -> Self:
|
551
|
+
def __model_action(self, info: ValidationInfo) -> Self:
|
458
552
|
"""Do before the Audit action with WORKFLOW_AUDIT_ENABLE_WRITE env variable.
|
459
553
|
|
460
554
|
:rtype: Self
|
461
555
|
"""
|
462
|
-
if
|
556
|
+
if dynamic("enable_write_audit", extras=info.data.get("extras")):
|
463
557
|
self.do_before()
|
464
558
|
return self
|
465
559
|
|
@@ -487,15 +581,20 @@ class FileAudit(BaseAudit):
|
|
487
581
|
self.pointer().mkdir(parents=True, exist_ok=True)
|
488
582
|
|
489
583
|
@classmethod
|
490
|
-
def find_audits(
|
584
|
+
def find_audits(
|
585
|
+
cls, name: str, *, extras: Optional[DictData] = None
|
586
|
+
) -> Iterator[Self]:
|
491
587
|
"""Generate the audit data that found from logs path with specific a
|
492
588
|
workflow name.
|
493
589
|
|
494
590
|
:param name: A workflow name that want to search release logging data.
|
591
|
+
:param extras: An extra parameter that want to override core config.
|
495
592
|
|
496
593
|
:rtype: Iterator[Self]
|
497
594
|
"""
|
498
|
-
pointer: Path =
|
595
|
+
pointer: Path = (
|
596
|
+
dynamic("audit_path", extras=extras) / f"workflow={name}"
|
597
|
+
)
|
499
598
|
if not pointer.exists():
|
500
599
|
raise FileNotFoundError(f"Pointer: {pointer.absolute()}.")
|
501
600
|
|
@@ -508,13 +607,16 @@ class FileAudit(BaseAudit):
|
|
508
607
|
cls,
|
509
608
|
name: str,
|
510
609
|
release: datetime | None = None,
|
610
|
+
*,
|
611
|
+
extras: Optional[DictData] = None,
|
511
612
|
) -> Self:
|
512
613
|
"""Return the audit data that found from logs path with specific
|
513
614
|
workflow name and release values. If a release does not pass to an input
|
514
615
|
argument, it will return the latest release from the current log path.
|
515
616
|
|
516
|
-
:param name: A workflow name that want to search log.
|
517
|
-
:param release: A release datetime that want to search log.
|
617
|
+
:param name: (str) A workflow name that want to search log.
|
618
|
+
:param release: (datetime) A release datetime that want to search log.
|
619
|
+
:param extras: An extra parameter that want to override core config.
|
518
620
|
|
519
621
|
:raise FileNotFoundError:
|
520
622
|
:raise NotImplementedError: If an input release does not pass to this
|
@@ -526,7 +628,7 @@ class FileAudit(BaseAudit):
|
|
526
628
|
raise NotImplementedError("Find latest log does not implement yet.")
|
527
629
|
|
528
630
|
pointer: Path = (
|
529
|
-
|
631
|
+
dynamic("audit_path", extras=extras)
|
530
632
|
/ f"workflow={name}/release={release:%Y%m%d%H%M%S}"
|
531
633
|
)
|
532
634
|
if not pointer.exists():
|
@@ -540,24 +642,31 @@ class FileAudit(BaseAudit):
|
|
540
642
|
return cls.model_validate(obj=json.load(f))
|
541
643
|
|
542
644
|
@classmethod
|
543
|
-
def is_pointed(
|
645
|
+
def is_pointed(
|
646
|
+
cls,
|
647
|
+
name: str,
|
648
|
+
release: datetime,
|
649
|
+
*,
|
650
|
+
extras: Optional[DictData] = None,
|
651
|
+
) -> bool:
|
544
652
|
"""Check the release log already pointed or created at the destination
|
545
653
|
log path.
|
546
654
|
|
547
|
-
:param name: A workflow name.
|
548
|
-
:param release: A release datetime.
|
655
|
+
:param name: (str) A workflow name.
|
656
|
+
:param release: (datetime) A release datetime.
|
657
|
+
:param extras: An extra parameter that want to override core config.
|
549
658
|
|
550
659
|
:rtype: bool
|
551
660
|
:return: Return False if the release log was not pointed or created.
|
552
661
|
"""
|
553
662
|
# NOTE: Return False if enable writing log flag does not set.
|
554
|
-
if not
|
663
|
+
if not dynamic("enable_write_audit", extras=extras):
|
555
664
|
return False
|
556
665
|
|
557
666
|
# NOTE: create pointer path that use the same logic of pointer method.
|
558
|
-
pointer: Path =
|
559
|
-
|
560
|
-
)
|
667
|
+
pointer: Path = dynamic(
|
668
|
+
"audit_path", extras=extras
|
669
|
+
) / cls.filename_fmt.format(name=name, release=release)
|
561
670
|
|
562
671
|
return pointer.exists()
|
563
672
|
|
@@ -566,9 +675,9 @@ class FileAudit(BaseAudit):
|
|
566
675
|
|
567
676
|
:rtype: Path
|
568
677
|
"""
|
569
|
-
return
|
570
|
-
|
571
|
-
)
|
678
|
+
return dynamic(
|
679
|
+
"audit_path", extras=self.extras
|
680
|
+
) / self.filename_fmt.format(name=self.name, release=self.release)
|
572
681
|
|
573
682
|
def save(self, excluded: list[str] | None) -> Self:
|
574
683
|
"""Save logging data that receive a context data from a workflow
|
@@ -582,7 +691,7 @@ class FileAudit(BaseAudit):
|
|
582
691
|
trace: TraceLog = get_trace(self.run_id, self.parent_run_id)
|
583
692
|
|
584
693
|
# NOTE: Check environ variable was set for real writing.
|
585
|
-
if not
|
694
|
+
if not dynamic("enable_write_audit", extras=self.extras):
|
586
695
|
trace.debug("[LOG]: Skip writing log cause config was set")
|
587
696
|
return self
|
588
697
|
|
@@ -624,7 +733,7 @@ class SQLiteAudit(BaseAudit): # pragma: no cov
|
|
624
733
|
trace: TraceLog = get_trace(self.run_id, self.parent_run_id)
|
625
734
|
|
626
735
|
# NOTE: Check environ variable was set for real writing.
|
627
|
-
if not
|
736
|
+
if not dynamic("enable_write_audit", extras=self.extras):
|
628
737
|
trace.debug("[LOG]: Skip writing log cause config was set")
|
629
738
|
return self
|
630
739
|
|
@@ -649,11 +758,15 @@ Audit = Union[
|
|
649
758
|
]
|
650
759
|
|
651
760
|
|
652
|
-
def get_audit(
|
761
|
+
def get_audit(
|
762
|
+
extras: Optional[DictData] = None,
|
763
|
+
) -> type[Audit]: # pragma: no cov
|
653
764
|
"""Get an audit class that dynamic base on the config audit path value.
|
654
765
|
|
766
|
+
:param extras: An extra parameter that want to override the core config.
|
767
|
+
|
655
768
|
:rtype: type[Audit]
|
656
769
|
"""
|
657
|
-
if
|
770
|
+
if dynamic("audit_path", extras=extras).is_file():
|
658
771
|
return SQLiteAudit
|
659
772
|
return FileAudit
|
ddeutil/workflow/result.py
CHANGED
@@ -3,6 +3,7 @@
|
|
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
|
6
7
|
"""Result module. It is the data context transfer objects that use by all object
|
7
8
|
in this package. This module provide Status enum object and Result dataclass.
|
8
9
|
"""
|
@@ -18,18 +19,10 @@ from pydantic.dataclasses import dataclass
|
|
18
19
|
from pydantic.functional_validators import model_validator
|
19
20
|
from typing_extensions import Self
|
20
21
|
|
21
|
-
from .__types import DictData
|
22
|
+
from .__types import DictData
|
23
|
+
from .conf import dynamic
|
22
24
|
from .logs import TraceLog, get_dt_tznow, get_trace
|
23
|
-
from .utils import default_gen_id, gen_id
|
24
|
-
|
25
|
-
__all__: TupleStr = (
|
26
|
-
"SUCCESS",
|
27
|
-
"FAILED",
|
28
|
-
"WAIT",
|
29
|
-
"SKIP",
|
30
|
-
"Result",
|
31
|
-
"Status",
|
32
|
-
)
|
25
|
+
from .utils import default_gen_id, gen_id, get_dt_now
|
33
26
|
|
34
27
|
|
35
28
|
class Status(IntEnum):
|
@@ -62,6 +55,10 @@ class Result:
|
|
62
55
|
|
63
56
|
For comparison property, this result will use ``status``, ``context``,
|
64
57
|
and ``_run_id`` fields to comparing with other result instance.
|
58
|
+
|
59
|
+
Warning:
|
60
|
+
I use dataclass object instead of Pydantic model object because context
|
61
|
+
field that keep dict value change its ID when update new value to it.
|
65
62
|
"""
|
66
63
|
|
67
64
|
status: Status = field(default=WAIT)
|
@@ -72,7 +69,7 @@ class Result:
|
|
72
69
|
ts: datetime = field(default_factory=get_dt_tznow, compare=False)
|
73
70
|
|
74
71
|
trace: Optional[TraceLog] = field(default=None, compare=False, repr=False)
|
75
|
-
extras: DictData = field(default_factory=dict)
|
72
|
+
extras: DictData = field(default_factory=dict, compare=False, repr=False)
|
76
73
|
|
77
74
|
@classmethod
|
78
75
|
def construct_with_rs_or_id(
|
@@ -87,11 +84,11 @@ class Result:
|
|
87
84
|
"""Create the Result object or set parent running id if passing Result
|
88
85
|
object.
|
89
86
|
|
90
|
-
:param result:
|
91
|
-
:param run_id:
|
92
|
-
:param parent_run_id:
|
93
|
-
:param id_logic:
|
94
|
-
:param extras:
|
87
|
+
:param result: A Result instance.
|
88
|
+
:param run_id: A running ID.
|
89
|
+
:param parent_run_id: A parent running ID.
|
90
|
+
:param id_logic: A logic function that use to generate a running ID.
|
91
|
+
:param extras: An extra parameter that want to override the core config.
|
95
92
|
|
96
93
|
:rtype: Self
|
97
94
|
"""
|
@@ -116,17 +113,22 @@ class Result:
|
|
116
113
|
:rtype: Self
|
117
114
|
"""
|
118
115
|
if self.trace is None: # pragma: no cov
|
119
|
-
self.trace: TraceLog = get_trace(
|
116
|
+
self.trace: TraceLog = get_trace(
|
117
|
+
self.run_id, self.parent_run_id, extras=self.extras
|
118
|
+
)
|
120
119
|
return self
|
121
120
|
|
122
121
|
def set_parent_run_id(self, running_id: str) -> Self:
|
123
122
|
"""Set a parent running ID.
|
124
123
|
|
125
|
-
:param running_id: A running ID that want to update on this model.
|
124
|
+
:param running_id: (str) A running ID that want to update on this model.
|
125
|
+
|
126
126
|
:rtype: Self
|
127
127
|
"""
|
128
128
|
self.parent_run_id: str = running_id
|
129
|
-
self.trace: TraceLog = get_trace(
|
129
|
+
self.trace: TraceLog = get_trace(
|
130
|
+
self.run_id, running_id, extras=self.extras
|
131
|
+
)
|
130
132
|
return self
|
131
133
|
|
132
134
|
def catch(
|
@@ -157,4 +159,6 @@ class Result:
|
|
157
159
|
|
158
160
|
:rtype: float
|
159
161
|
"""
|
160
|
-
return (
|
162
|
+
return (
|
163
|
+
get_dt_now(tz=dynamic("tz", extras=self.extras)) - self.ts
|
164
|
+
).total_seconds()
|
ddeutil/workflow/reusables.py
CHANGED
@@ -10,7 +10,6 @@ from __future__ import annotations
|
|
10
10
|
import inspect
|
11
11
|
import logging
|
12
12
|
from ast import Call, Constant, Expr, Module, Name, parse
|
13
|
-
from dataclasses import dataclass
|
14
13
|
from datetime import datetime
|
15
14
|
from functools import wraps
|
16
15
|
from importlib import import_module
|
@@ -23,6 +22,7 @@ except ImportError:
|
|
23
22
|
|
24
23
|
from ddeutil.core import getdot, import_string, lazy
|
25
24
|
from ddeutil.io import search_env_replace
|
25
|
+
from pydantic.dataclasses import dataclass
|
26
26
|
|
27
27
|
from .__types import DictData, Re
|
28
28
|
from .conf import dynamic
|
@@ -437,6 +437,7 @@ Registry = dict[str, Callable[[], TagFunc]]
|
|
437
437
|
|
438
438
|
def make_registry(
|
439
439
|
submodule: str,
|
440
|
+
*,
|
440
441
|
registries: Optional[list[str]] = None,
|
441
442
|
) -> dict[str, Registry]:
|
442
443
|
"""Return registries of all functions that able to called with task.
|
@@ -533,19 +534,18 @@ def extract_call(
|
|
533
534
|
|
534
535
|
call: CallSearchData = CallSearchData(**found.groupdict())
|
535
536
|
rgt: dict[str, Registry] = make_registry(
|
536
|
-
submodule=f"{call.path}",
|
537
|
-
registries=registries,
|
537
|
+
submodule=f"{call.path}", registries=registries
|
538
538
|
)
|
539
539
|
|
540
540
|
if call.func not in rgt:
|
541
541
|
raise NotImplementedError(
|
542
|
-
f"`
|
542
|
+
f"`REGISTERS.{call.path}.registries` not implement "
|
543
543
|
f"registry: {call.func!r}."
|
544
544
|
)
|
545
545
|
|
546
546
|
if call.tag not in rgt[call.func]:
|
547
547
|
raise NotImplementedError(
|
548
548
|
f"tag: {call.tag!r} not found on registry func: "
|
549
|
-
f"`REGISTER
|
549
|
+
f"`REGISTER.{call.path}.registries.{call.func}`"
|
550
550
|
)
|
551
551
|
return rgt[call.func][call.tag]
|