ddeutil-workflow 0.0.73__py3-none-any.whl → 0.0.74__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 +14 -8
- ddeutil/workflow/__init__.py +119 -10
- ddeutil/workflow/__types.py +53 -41
- ddeutil/workflow/api/__init__.py +74 -3
- ddeutil/workflow/api/routes/job.py +15 -29
- ddeutil/workflow/api/routes/logs.py +9 -9
- ddeutil/workflow/api/routes/workflows.py +3 -3
- ddeutil/workflow/audits.py +70 -55
- ddeutil/workflow/cli.py +1 -15
- ddeutil/workflow/conf.py +63 -18
- ddeutil/workflow/errors.py +86 -19
- ddeutil/workflow/event.py +268 -169
- ddeutil/workflow/job.py +331 -192
- ddeutil/workflow/params.py +37 -7
- ddeutil/workflow/result.py +96 -70
- ddeutil/workflow/reusables.py +56 -6
- ddeutil/workflow/stages.py +1059 -572
- ddeutil/workflow/traces.py +199 -120
- ddeutil/workflow/utils.py +60 -8
- ddeutil/workflow/workflow.py +424 -290
- {ddeutil_workflow-0.0.73.dist-info → ddeutil_workflow-0.0.74.dist-info}/METADATA +27 -17
- ddeutil_workflow-0.0.74.dist-info/RECORD +30 -0
- ddeutil_workflow-0.0.73.dist-info/RECORD +0 -30
- {ddeutil_workflow-0.0.73.dist-info → ddeutil_workflow-0.0.74.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.73.dist-info → ddeutil_workflow-0.0.74.dist-info}/entry_points.txt +0 -0
- {ddeutil_workflow-0.0.73.dist-info → ddeutil_workflow-0.0.74.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.73.dist-info → ddeutil_workflow-0.0.74.dist-info}/top_level.txt +0 -0
ddeutil/workflow/traces.py
CHANGED
@@ -3,7 +3,36 @@
|
|
3
3
|
# Licensed under the MIT License. See LICENSE in the project root for
|
4
4
|
# license information.
|
5
5
|
# ------------------------------------------------------------------------------
|
6
|
-
|
6
|
+
"""Tracing and Logging Module for Workflow Execution.
|
7
|
+
|
8
|
+
This module provides comprehensive tracing and logging capabilities for workflow
|
9
|
+
execution monitoring. It supports multiple trace backends including console output,
|
10
|
+
file-based logging, and SQLite database storage.
|
11
|
+
|
12
|
+
The tracing system captures detailed execution metadata including process IDs,
|
13
|
+
thread identifiers, timestamps, and contextual information for debugging and
|
14
|
+
monitoring workflow executions.
|
15
|
+
|
16
|
+
Classes:
|
17
|
+
Message: Log message model with prefix parsing
|
18
|
+
TraceMeta: Metadata model for execution context
|
19
|
+
TraceData: Container for trace information
|
20
|
+
BaseTrace: Abstract base class for trace implementations
|
21
|
+
ConsoleTrace: Console-based trace output
|
22
|
+
FileTrace: File-based trace storage
|
23
|
+
SQLiteTrace: Database-based trace storage
|
24
|
+
|
25
|
+
Functions:
|
26
|
+
set_logging: Configure logger with custom formatting
|
27
|
+
get_trace: Factory function for trace instances
|
28
|
+
|
29
|
+
Example:
|
30
|
+
>>> from ddeutil.workflow.traces import get_trace
|
31
|
+
>>> # Create file-based trace
|
32
|
+
>>> trace = get_trace("running-id-101", parent_run_id="workflow-001")
|
33
|
+
>>> trace.info("Workflow execution started")
|
34
|
+
>>> trace.debug("Processing stage 1")
|
35
|
+
"""
|
7
36
|
from __future__ import annotations
|
8
37
|
|
9
38
|
import json
|
@@ -17,9 +46,12 @@ from inspect import Traceback, currentframe, getframeinfo
|
|
17
46
|
from pathlib import Path
|
18
47
|
from threading import get_ident
|
19
48
|
from types import FrameType
|
20
|
-
from typing import ClassVar, Final, Literal, Optional,
|
49
|
+
from typing import ClassVar, Final, Literal, Optional, Union
|
50
|
+
from urllib.parse import ParseResult, unquote_plus, urlparse
|
21
51
|
|
22
52
|
from pydantic import BaseModel, ConfigDict, Field
|
53
|
+
from pydantic.functional_serializers import field_serializer
|
54
|
+
from pydantic.functional_validators import field_validator
|
23
55
|
from typing_extensions import Self
|
24
56
|
|
25
57
|
from .__types import DictData
|
@@ -32,12 +64,23 @@ logger = logging.getLogger("ddeutil.workflow")
|
|
32
64
|
|
33
65
|
@lru_cache
|
34
66
|
def set_logging(name: str) -> logging.Logger:
|
35
|
-
"""
|
36
|
-
custom handler and formatter from this package config.
|
67
|
+
"""Configure logger with custom formatting and handlers.
|
37
68
|
|
38
|
-
|
69
|
+
Creates and configures a logger instance with the custom formatter and
|
70
|
+
handlers defined in the package configuration. The logger includes both
|
71
|
+
console output and proper formatting for workflow execution tracking.
|
39
72
|
|
40
|
-
:
|
73
|
+
Args:
|
74
|
+
name: Module name to create logger for
|
75
|
+
|
76
|
+
Returns:
|
77
|
+
logging.Logger: Configured logger instance with custom formatting
|
78
|
+
|
79
|
+
Example:
|
80
|
+
```python
|
81
|
+
logger = set_logging("ddeutil.workflow.stages")
|
82
|
+
logger.info("Stage execution started")
|
83
|
+
```
|
41
84
|
"""
|
42
85
|
_logger = logging.getLogger(name)
|
43
86
|
|
@@ -101,10 +144,12 @@ class Message(BaseModel):
|
|
101
144
|
def prepare(self, extras: Optional[DictData] = None) -> str:
|
102
145
|
"""Prepare message with force add prefix before writing trace log.
|
103
146
|
|
104
|
-
:
|
105
|
-
|
147
|
+
Args:
|
148
|
+
extras: An extra parameter that want to get the
|
149
|
+
`log_add_emoji` flag.
|
106
150
|
|
107
|
-
:
|
151
|
+
Returns:
|
152
|
+
str: The prepared message with prefix and optional emoji.
|
108
153
|
"""
|
109
154
|
name: str = self.name or PREFIX_DEFAULT
|
110
155
|
emoji: str = (
|
@@ -139,9 +184,13 @@ class TraceMeta(BaseModel): # pragma: no cov
|
|
139
184
|
"""Dynamic Frame information base on the `logs_trace_frame_layer` config
|
140
185
|
value that was set from the extra parameter.
|
141
186
|
|
142
|
-
:
|
143
|
-
|
144
|
-
|
187
|
+
Args:
|
188
|
+
frame: The current frame that want to dynamic.
|
189
|
+
extras: An extra parameter that want to get the
|
190
|
+
`logs_trace_frame_layer` config value.
|
191
|
+
|
192
|
+
Returns:
|
193
|
+
Traceback: The frame information at the specified layer.
|
145
194
|
"""
|
146
195
|
extras: DictData = extras or {}
|
147
196
|
layer: int = extras.get("logs_trace_frame_layer", 4)
|
@@ -167,14 +216,16 @@ class TraceMeta(BaseModel): # pragma: no cov
|
|
167
216
|
"""Make the current metric for contract this TraceMeta model instance
|
168
217
|
that will catch local states like PID, thread identity.
|
169
218
|
|
170
|
-
:
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
219
|
+
Args:
|
220
|
+
mode: A metadata mode.
|
221
|
+
message: A message.
|
222
|
+
level: A log level.
|
223
|
+
cutting_id: A cutting ID string.
|
224
|
+
extras: An extra parameter that want to override core
|
225
|
+
config values.
|
176
226
|
|
177
|
-
:
|
227
|
+
Returns:
|
228
|
+
Self: The constructed TraceMeta instance.
|
178
229
|
"""
|
179
230
|
frame: FrameType = currentframe()
|
180
231
|
frame_info: Traceback = cls.dynamic_frame(frame, extras=extras)
|
@@ -252,43 +303,6 @@ class BaseTrace(BaseModel, ABC): # pragma: no cov
|
|
252
303
|
description="A parent running ID",
|
253
304
|
)
|
254
305
|
|
255
|
-
@classmethod
|
256
|
-
@abstractmethod
|
257
|
-
def find_traces(
|
258
|
-
cls,
|
259
|
-
path: Optional[Path] = None,
|
260
|
-
extras: Optional[DictData] = None,
|
261
|
-
) -> Iterator[TraceData]: # pragma: no cov
|
262
|
-
"""Return iterator of TraceData models from the target pointer.
|
263
|
-
|
264
|
-
Args:
|
265
|
-
path (:obj:`Path`, optional): A pointer path that want to override.
|
266
|
-
extras (:obj:`DictData`, optional): An extras parameter that want to
|
267
|
-
override default engine config.
|
268
|
-
|
269
|
-
Returns:
|
270
|
-
Iterator[TracData]: An iterator object that generate a TracData
|
271
|
-
model.
|
272
|
-
"""
|
273
|
-
raise NotImplementedError(
|
274
|
-
"Trace dataclass should implement `find_traces` class-method."
|
275
|
-
)
|
276
|
-
|
277
|
-
@classmethod
|
278
|
-
@abstractmethod
|
279
|
-
def find_trace_with_id(
|
280
|
-
cls,
|
281
|
-
run_id: str,
|
282
|
-
force_raise: bool = True,
|
283
|
-
*,
|
284
|
-
path: Optional[Path] = None,
|
285
|
-
extras: Optional[DictData] = None,
|
286
|
-
) -> TraceData:
|
287
|
-
raise NotImplementedError(
|
288
|
-
"Trace dataclass should implement `find_trace_with_id` "
|
289
|
-
"class-method."
|
290
|
-
)
|
291
|
-
|
292
306
|
@abstractmethod
|
293
307
|
def writer(
|
294
308
|
self,
|
@@ -340,7 +354,7 @@ class BaseTrace(BaseModel, ABC): # pragma: no cov
|
|
340
354
|
)
|
341
355
|
|
342
356
|
@abstractmethod
|
343
|
-
def
|
357
|
+
def emit(
|
344
358
|
self,
|
345
359
|
message: str,
|
346
360
|
mode: str,
|
@@ -364,7 +378,7 @@ class BaseTrace(BaseModel, ABC): # pragma: no cov
|
|
364
378
|
|
365
379
|
:param message: (str) A message that want to log.
|
366
380
|
"""
|
367
|
-
self.
|
381
|
+
self.emit(message, mode="debug")
|
368
382
|
|
369
383
|
def info(self, message: str) -> None:
|
370
384
|
"""Write trace log with append mode and logging this message with the
|
@@ -372,7 +386,7 @@ class BaseTrace(BaseModel, ABC): # pragma: no cov
|
|
372
386
|
|
373
387
|
:param message: (str) A message that want to log.
|
374
388
|
"""
|
375
|
-
self.
|
389
|
+
self.emit(message, mode="info")
|
376
390
|
|
377
391
|
def warning(self, message: str) -> None:
|
378
392
|
"""Write trace log with append mode and logging this message with the
|
@@ -380,7 +394,7 @@ class BaseTrace(BaseModel, ABC): # pragma: no cov
|
|
380
394
|
|
381
395
|
:param message: (str) A message that want to log.
|
382
396
|
"""
|
383
|
-
self.
|
397
|
+
self.emit(message, mode="warning")
|
384
398
|
|
385
399
|
def error(self, message: str) -> None:
|
386
400
|
"""Write trace log with append mode and logging this message with the
|
@@ -388,7 +402,7 @@ class BaseTrace(BaseModel, ABC): # pragma: no cov
|
|
388
402
|
|
389
403
|
:param message: (str) A message that want to log.
|
390
404
|
"""
|
391
|
-
self.
|
405
|
+
self.emit(message, mode="error", is_err=True)
|
392
406
|
|
393
407
|
def exception(self, message: str) -> None:
|
394
408
|
"""Write trace log with append mode and logging this message with the
|
@@ -396,10 +410,10 @@ class BaseTrace(BaseModel, ABC): # pragma: no cov
|
|
396
410
|
|
397
411
|
:param message: (str) A message that want to log.
|
398
412
|
"""
|
399
|
-
self.
|
413
|
+
self.emit(message, mode="exception", is_err=True)
|
400
414
|
|
401
415
|
@abstractmethod
|
402
|
-
async def
|
416
|
+
async def amit(
|
403
417
|
self,
|
404
418
|
message: str,
|
405
419
|
mode: str,
|
@@ -423,7 +437,7 @@ class BaseTrace(BaseModel, ABC): # pragma: no cov
|
|
423
437
|
|
424
438
|
:param message: (str) A message that want to log.
|
425
439
|
"""
|
426
|
-
await self.
|
440
|
+
await self.amit(message, mode="debug")
|
427
441
|
|
428
442
|
async def ainfo(self, message: str) -> None: # pragma: no cov
|
429
443
|
"""Async write trace log with append mode and logging this message with
|
@@ -431,7 +445,7 @@ class BaseTrace(BaseModel, ABC): # pragma: no cov
|
|
431
445
|
|
432
446
|
:param message: (str) A message that want to log.
|
433
447
|
"""
|
434
|
-
await self.
|
448
|
+
await self.amit(message, mode="info")
|
435
449
|
|
436
450
|
async def awarning(self, message: str) -> None: # pragma: no cov
|
437
451
|
"""Async write trace log with append mode and logging this message with
|
@@ -439,7 +453,7 @@ class BaseTrace(BaseModel, ABC): # pragma: no cov
|
|
439
453
|
|
440
454
|
:param message: (str) A message that want to log.
|
441
455
|
"""
|
442
|
-
await self.
|
456
|
+
await self.amit(message, mode="warning")
|
443
457
|
|
444
458
|
async def aerror(self, message: str) -> None: # pragma: no cov
|
445
459
|
"""Async write trace log with append mode and logging this message with
|
@@ -447,7 +461,7 @@ class BaseTrace(BaseModel, ABC): # pragma: no cov
|
|
447
461
|
|
448
462
|
:param message: (str) A message that want to log.
|
449
463
|
"""
|
450
|
-
await self.
|
464
|
+
await self.amit(message, mode="error", is_err=True)
|
451
465
|
|
452
466
|
async def aexception(self, message: str) -> None: # pragma: no cov
|
453
467
|
"""Async write trace log with append mode and logging this message with
|
@@ -455,36 +469,12 @@ class BaseTrace(BaseModel, ABC): # pragma: no cov
|
|
455
469
|
|
456
470
|
:param message: (str) A message that want to log.
|
457
471
|
"""
|
458
|
-
await self.
|
472
|
+
await self.amit(message, mode="exception", is_err=True)
|
459
473
|
|
460
474
|
|
461
475
|
class ConsoleTrace(BaseTrace): # pragma: no cov
|
462
476
|
"""Console Trace log model."""
|
463
477
|
|
464
|
-
@classmethod
|
465
|
-
def find_traces(
|
466
|
-
cls,
|
467
|
-
path: Optional[Path] = None,
|
468
|
-
extras: Optional[DictData] = None,
|
469
|
-
) -> Iterator[TraceData]: # pragma: no cov
|
470
|
-
raise NotImplementedError(
|
471
|
-
"Console Trace does not support to find history traces data."
|
472
|
-
)
|
473
|
-
|
474
|
-
@classmethod
|
475
|
-
def find_trace_with_id(
|
476
|
-
cls,
|
477
|
-
run_id: str,
|
478
|
-
force_raise: bool = True,
|
479
|
-
*,
|
480
|
-
path: Optional[Path] = None,
|
481
|
-
extras: Optional[DictData] = None,
|
482
|
-
) -> TraceData:
|
483
|
-
raise NotImplementedError(
|
484
|
-
"Console Trace does not support to find history traces data with "
|
485
|
-
"the specific running ID."
|
486
|
-
)
|
487
|
-
|
488
478
|
def writer(
|
489
479
|
self,
|
490
480
|
message: str,
|
@@ -537,13 +527,13 @@ class ConsoleTrace(BaseTrace): # pragma: no cov
|
|
537
527
|
"""
|
538
528
|
return prepare_newline(Message.from_str(message).prepare(self.extras))
|
539
529
|
|
540
|
-
def
|
541
|
-
self, message: str, mode: str, *, is_err: bool = False
|
542
|
-
) -> None:
|
530
|
+
def emit(self, message: str, mode: str, *, is_err: bool = False) -> None:
|
543
531
|
"""Write trace log with append mode and logging this message with any
|
544
532
|
logging level.
|
545
533
|
|
546
534
|
:param message: (str) A message that want to log.
|
535
|
+
:param mode: (str)
|
536
|
+
:param is_err: (bool)
|
547
537
|
"""
|
548
538
|
msg: str = self.make_message(message)
|
549
539
|
|
@@ -554,13 +544,15 @@ class ConsoleTrace(BaseTrace): # pragma: no cov
|
|
554
544
|
|
555
545
|
getattr(logger, mode)(msg, stacklevel=3, extra={"cut_id": self.cut_id})
|
556
546
|
|
557
|
-
async def
|
547
|
+
async def amit(
|
558
548
|
self, message: str, mode: str, *, is_err: bool = False
|
559
549
|
) -> None:
|
560
550
|
"""Write trace log with append mode and logging this message with any
|
561
551
|
logging level.
|
562
552
|
|
563
553
|
:param message: (str) A message that want to log.
|
554
|
+
:param mode: (str)
|
555
|
+
:param is_err: (bool)
|
564
556
|
"""
|
565
557
|
msg: str = self.make_message(message)
|
566
558
|
|
@@ -572,7 +564,62 @@ class ConsoleTrace(BaseTrace): # pragma: no cov
|
|
572
564
|
getattr(logger, mode)(msg, stacklevel=3, extra={"cut_id": self.cut_id})
|
573
565
|
|
574
566
|
|
575
|
-
class
|
567
|
+
class OutsideTrace(ConsoleTrace, ABC):
|
568
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
569
|
+
|
570
|
+
url: ParseResult = Field(description="An URL for create pointer.")
|
571
|
+
|
572
|
+
@field_validator(
|
573
|
+
"url", mode="before", json_schema_input_type=Union[ParseResult, str]
|
574
|
+
)
|
575
|
+
def __parse_url(cls, value: Union[ParseResult, str]) -> ParseResult:
|
576
|
+
if isinstance(value, str):
|
577
|
+
return urlparse(value)
|
578
|
+
return value
|
579
|
+
|
580
|
+
@field_serializer("url")
|
581
|
+
def __serialize_url(self, value: ParseResult) -> str:
|
582
|
+
return value.geturl()
|
583
|
+
|
584
|
+
@classmethod
|
585
|
+
@abstractmethod
|
586
|
+
def find_traces(
|
587
|
+
cls,
|
588
|
+
path: Optional[Path] = None,
|
589
|
+
extras: Optional[DictData] = None,
|
590
|
+
) -> Iterator[TraceData]: # pragma: no cov
|
591
|
+
"""Return iterator of TraceData models from the target pointer.
|
592
|
+
|
593
|
+
Args:
|
594
|
+
path (:obj:`Path`, optional): A pointer path that want to override.
|
595
|
+
extras (:obj:`DictData`, optional): An extras parameter that want to
|
596
|
+
override default engine config.
|
597
|
+
|
598
|
+
Returns:
|
599
|
+
Iterator[TracData]: An iterator object that generate a TracData
|
600
|
+
model.
|
601
|
+
"""
|
602
|
+
raise NotImplementedError(
|
603
|
+
"Trace dataclass should implement `find_traces` class-method."
|
604
|
+
)
|
605
|
+
|
606
|
+
@classmethod
|
607
|
+
@abstractmethod
|
608
|
+
def find_trace_with_id(
|
609
|
+
cls,
|
610
|
+
run_id: str,
|
611
|
+
force_raise: bool = True,
|
612
|
+
*,
|
613
|
+
path: Optional[Path] = None,
|
614
|
+
extras: Optional[DictData] = None,
|
615
|
+
) -> TraceData:
|
616
|
+
raise NotImplementedError(
|
617
|
+
"Trace dataclass should implement `find_trace_with_id` "
|
618
|
+
"class-method."
|
619
|
+
)
|
620
|
+
|
621
|
+
|
622
|
+
class FileTrace(OutsideTrace): # pragma: no cov
|
576
623
|
"""File Trace dataclass that write file to the local storage."""
|
577
624
|
|
578
625
|
@classmethod
|
@@ -587,7 +634,9 @@ class FileTrace(ConsoleTrace): # pragma: no cov
|
|
587
634
|
:param extras: An extra parameter that want to override core config.
|
588
635
|
"""
|
589
636
|
for file in sorted(
|
590
|
-
(path or dynamic("
|
637
|
+
(path or Path(dynamic("trace_url", extras=extras).path)).glob(
|
638
|
+
"./run_id=*"
|
639
|
+
),
|
591
640
|
key=lambda f: f.lstat().st_mtime,
|
592
641
|
):
|
593
642
|
yield TraceData.from_path(file)
|
@@ -608,7 +657,7 @@ class FileTrace(ConsoleTrace): # pragma: no cov
|
|
608
657
|
:param path: (Path)
|
609
658
|
:param extras: An extra parameter that want to override core config.
|
610
659
|
"""
|
611
|
-
base_path: Path = path or dynamic("
|
660
|
+
base_path: Path = path or Path(dynamic("trace_url", extras=extras).path)
|
612
661
|
file: Path = base_path / f"run_id={run_id}"
|
613
662
|
if file.exists():
|
614
663
|
return TraceData.from_path(file)
|
@@ -624,10 +673,14 @@ class FileTrace(ConsoleTrace): # pragma: no cov
|
|
624
673
|
"""Pointer of the target path that use to writing trace log or searching
|
625
674
|
trace log.
|
626
675
|
|
676
|
+
This running ID folder that use to keeping trace log data will use
|
677
|
+
a parent running ID first. If it does not set, it will use running ID
|
678
|
+
instead.
|
679
|
+
|
627
680
|
:rtype: Path
|
628
681
|
"""
|
629
682
|
log_file: Path = (
|
630
|
-
|
683
|
+
Path(unquote_plus(self.url.path))
|
631
684
|
/ f"run_id={self.parent_run_id or self.run_id}"
|
632
685
|
)
|
633
686
|
if not log_file.exists():
|
@@ -710,17 +763,20 @@ class FileTrace(ConsoleTrace): # pragma: no cov
|
|
710
763
|
await f.write(trace_meta.model_dump_json() + "\n")
|
711
764
|
|
712
765
|
|
713
|
-
class SQLiteTrace(
|
766
|
+
class SQLiteTrace(OutsideTrace): # pragma: no cov
|
714
767
|
"""SQLite Trace dataclass that write trace log to the SQLite database file."""
|
715
768
|
|
716
769
|
table_name: ClassVar[str] = "audits"
|
717
770
|
schemas: ClassVar[
|
718
771
|
str
|
719
772
|
] = """
|
720
|
-
run_id
|
721
|
-
|
722
|
-
|
723
|
-
|
773
|
+
run_id str
|
774
|
+
, parent_run_id str
|
775
|
+
, type str
|
776
|
+
, text str
|
777
|
+
, metadata JSON
|
778
|
+
, created_at datetime
|
779
|
+
, updated_at datetime
|
724
780
|
primary key ( run_id )
|
725
781
|
"""
|
726
782
|
|
@@ -729,7 +785,8 @@ class SQLiteTrace(ConsoleTrace): # pragma: no cov
|
|
729
785
|
cls,
|
730
786
|
path: Optional[Path] = None,
|
731
787
|
extras: Optional[DictData] = None,
|
732
|
-
) -> Iterator[TraceData]:
|
788
|
+
) -> Iterator[TraceData]:
|
789
|
+
raise NotImplementedError("SQLiteTrace does not implement yet.")
|
733
790
|
|
734
791
|
@classmethod
|
735
792
|
def find_trace_with_id(
|
@@ -739,30 +796,33 @@ class SQLiteTrace(ConsoleTrace): # pragma: no cov
|
|
739
796
|
*,
|
740
797
|
path: Optional[Path] = None,
|
741
798
|
extras: Optional[DictData] = None,
|
742
|
-
) -> TraceData:
|
799
|
+
) -> TraceData:
|
800
|
+
raise NotImplementedError("SQLiteTrace does not implement yet.")
|
743
801
|
|
744
|
-
def make_message(self, message: str) -> str:
|
802
|
+
def make_message(self, message: str) -> str:
|
803
|
+
raise NotImplementedError("SQLiteTrace does not implement yet.")
|
745
804
|
|
746
805
|
def writer(
|
747
806
|
self,
|
748
807
|
message: str,
|
749
808
|
level: str,
|
750
809
|
is_err: bool = False,
|
751
|
-
) -> None:
|
810
|
+
) -> None:
|
811
|
+
raise NotImplementedError("SQLiteTrace does not implement yet.")
|
752
812
|
|
753
813
|
def awriter(
|
754
814
|
self,
|
755
815
|
message: str,
|
756
816
|
level: str,
|
757
817
|
is_err: bool = False,
|
758
|
-
) -> None:
|
818
|
+
) -> None:
|
819
|
+
raise NotImplementedError("SQLiteTrace does not implement yet.")
|
759
820
|
|
760
821
|
|
761
|
-
Trace =
|
762
|
-
TraceModel = Union[
|
763
|
-
ConsoleTrace,
|
822
|
+
Trace = Union[
|
764
823
|
FileTrace,
|
765
824
|
SQLiteTrace,
|
825
|
+
OutsideTrace,
|
766
826
|
]
|
767
827
|
|
768
828
|
|
@@ -771,7 +831,7 @@ def get_trace(
|
|
771
831
|
*,
|
772
832
|
parent_run_id: Optional[str] = None,
|
773
833
|
extras: Optional[DictData] = None,
|
774
|
-
) ->
|
834
|
+
) -> Trace: # pragma: no cov
|
775
835
|
"""Get dynamic Trace instance from the core config (it can override by an
|
776
836
|
extras argument) that passing running ID and parent running ID.
|
777
837
|
|
@@ -780,12 +840,31 @@ def get_trace(
|
|
780
840
|
:param extras: (DictData) An extra parameter that want to override the core
|
781
841
|
config values.
|
782
842
|
|
783
|
-
:rtype:
|
843
|
+
:rtype: Trace
|
784
844
|
"""
|
785
|
-
|
786
|
-
|
787
|
-
|
845
|
+
# NOTE: Allow you to override trace model by the extra parameter.
|
846
|
+
map_trace_models: dict[str, type[Trace]] = extras.get(
|
847
|
+
"trace_model_mapping", {}
|
848
|
+
)
|
849
|
+
url: ParseResult
|
850
|
+
if (url := dynamic("trace_url", extras=extras)).scheme and (
|
851
|
+
url.scheme == "sqlite"
|
852
|
+
or (url.scheme == "file" and Path(url.path).is_file())
|
853
|
+
):
|
854
|
+
return map_trace_models.get("sqlite", SQLiteTrace)(
|
855
|
+
url=url,
|
856
|
+
run_id=run_id,
|
857
|
+
parent_run_id=parent_run_id,
|
858
|
+
extras=(extras or {}),
|
859
|
+
)
|
860
|
+
elif url.scheme and url.scheme != "file":
|
861
|
+
raise NotImplementedError(
|
862
|
+
f"Does not implement the outside trace model support for URL: {url}"
|
788
863
|
)
|
789
|
-
|
790
|
-
|
864
|
+
|
865
|
+
return map_trace_models.get("file", FileTrace)(
|
866
|
+
url=url,
|
867
|
+
run_id=run_id,
|
868
|
+
parent_run_id=parent_run_id,
|
869
|
+
extras=(extras or {}),
|
791
870
|
)
|
ddeutil/workflow/utils.py
CHANGED
@@ -3,7 +3,35 @@
|
|
3
3
|
# Licensed under the MIT License. See LICENSE in the project root for
|
4
4
|
# license information.
|
5
5
|
# ------------------------------------------------------------------------------
|
6
|
-
"""Utility
|
6
|
+
"""Utility Functions for Workflow Operations.
|
7
|
+
|
8
|
+
This module provides essential utility functions used throughout the workflow
|
9
|
+
system for ID generation, datetime handling, string processing, template
|
10
|
+
operations, and other common tasks.
|
11
|
+
|
12
|
+
Functions:
|
13
|
+
gen_id: Generate unique identifiers for workflow components
|
14
|
+
make_exec: Create executable strings for shell commands
|
15
|
+
filter_func: Filter functions based on criteria
|
16
|
+
dump_all: Serialize data to various formats
|
17
|
+
delay: Create delays in execution
|
18
|
+
to_train: Convert strings to train-case format
|
19
|
+
get_dt_now: Get current datetime with timezone
|
20
|
+
get_d_now: Get current date
|
21
|
+
cross_product: Generate cross product of matrix values
|
22
|
+
replace_sec: Replace template variables in strings
|
23
|
+
|
24
|
+
Example:
|
25
|
+
```python
|
26
|
+
from ddeutil.workflow.utils import gen_id, get_dt_now
|
27
|
+
|
28
|
+
# Generate unique ID
|
29
|
+
run_id = gen_id("workflow")
|
30
|
+
|
31
|
+
# Get current datetime
|
32
|
+
now = get_dt_now()
|
33
|
+
```
|
34
|
+
"""
|
7
35
|
from __future__ import annotations
|
8
36
|
|
9
37
|
import stat
|
@@ -27,13 +55,19 @@ T = TypeVar("T")
|
|
27
55
|
UTC: Final[ZoneInfo] = ZoneInfo("UTC")
|
28
56
|
MARK_NEWLINE: Final[str] = "||"
|
29
57
|
|
58
|
+
# Cache for random delay values to avoid repeated randrange calls
|
59
|
+
_CACHED_DELAYS = [randrange(0, 99, step=10) / 100 for _ in range(100)]
|
60
|
+
_DELAY_INDEX = 0
|
61
|
+
|
30
62
|
|
31
63
|
def to_train(camel: str) -> str:
|
32
64
|
"""Convert camel case string to train case.
|
33
65
|
|
34
|
-
:
|
66
|
+
Args:
|
67
|
+
camel: A camel case string that want to convert.
|
35
68
|
|
36
|
-
:
|
69
|
+
Returns:
|
70
|
+
str: The converted train-case string.
|
37
71
|
"""
|
38
72
|
return "".join("-" + i if i.isupper() else i for i in camel).lstrip("-")
|
39
73
|
|
@@ -41,9 +75,11 @@ def to_train(camel: str) -> str:
|
|
41
75
|
def prepare_newline(msg: str) -> str:
|
42
76
|
"""Prepare message that has multiple newline char.
|
43
77
|
|
44
|
-
:
|
78
|
+
Args:
|
79
|
+
msg: A message that want to prepare.
|
45
80
|
|
46
|
-
:
|
81
|
+
Returns:
|
82
|
+
str: The prepared message with formatted newlines.
|
47
83
|
"""
|
48
84
|
# NOTE: Remove ending with "\n" and replace "\n" with the "||" value.
|
49
85
|
msg: str = msg.strip("\n").replace("\n", MARK_NEWLINE)
|
@@ -63,9 +99,11 @@ def prepare_newline(msg: str) -> str:
|
|
63
99
|
def replace_sec(dt: datetime) -> datetime:
|
64
100
|
"""Replace second and microsecond values to 0.
|
65
101
|
|
66
|
-
:
|
102
|
+
Args:
|
103
|
+
dt: A datetime object that want to replace.
|
67
104
|
|
68
|
-
:
|
105
|
+
Returns:
|
106
|
+
datetime: The datetime with seconds and microseconds set to 0.
|
69
107
|
"""
|
70
108
|
return dt.replace(second=0, microsecond=0)
|
71
109
|
|
@@ -87,6 +125,17 @@ def get_dt_now(tz: Optional[ZoneInfo] = None, offset: float = 0.0) -> datetime:
|
|
87
125
|
return datetime.now(tz=tz) - timedelta(seconds=offset)
|
88
126
|
|
89
127
|
|
128
|
+
def get_dt_ntz_now() -> datetime: # pragma: no cov
|
129
|
+
"""Get current datetime with no timezone.
|
130
|
+
|
131
|
+
Returns the current datetime object using the None timezone.
|
132
|
+
|
133
|
+
Returns:
|
134
|
+
datetime: Current datetime with no timezone
|
135
|
+
"""
|
136
|
+
return get_dt_now(tz=None)
|
137
|
+
|
138
|
+
|
90
139
|
def get_d_now(
|
91
140
|
tz: Optional[ZoneInfo] = None, offset: float = 0.0
|
92
141
|
) -> date: # pragma: no cov
|
@@ -151,7 +200,10 @@ def delay(second: float = 0) -> None: # pragma: no cov
|
|
151
200
|
|
152
201
|
:param second: (float) A second number that want to adds-on random value.
|
153
202
|
"""
|
154
|
-
|
203
|
+
global _DELAY_INDEX
|
204
|
+
cached_random = _CACHED_DELAYS[_DELAY_INDEX % len(_CACHED_DELAYS)]
|
205
|
+
_DELAY_INDEX = (_DELAY_INDEX + 1) % len(_CACHED_DELAYS)
|
206
|
+
time.sleep(second + cached_random)
|
155
207
|
|
156
208
|
|
157
209
|
def gen_id(
|