ddeutil-workflow 0.0.73__py3-none-any.whl → 0.0.75__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 +20 -12
- 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 +71 -26
- ddeutil/workflow/errors.py +86 -19
- ddeutil/workflow/event.py +268 -169
- ddeutil/workflow/job.py +331 -192
- ddeutil/workflow/params.py +43 -11
- ddeutil/workflow/result.py +96 -70
- ddeutil/workflow/reusables.py +56 -6
- ddeutil/workflow/stages.py +1059 -572
- ddeutil/workflow/traces.py +205 -124
- ddeutil/workflow/utils.py +58 -19
- ddeutil/workflow/workflow.py +435 -296
- {ddeutil_workflow-0.0.73.dist-info → ddeutil_workflow-0.0.75.dist-info}/METADATA +27 -17
- ddeutil_workflow-0.0.75.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.75.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.73.dist-info → ddeutil_workflow-0.0.75.dist-info}/entry_points.txt +0 -0
- {ddeutil_workflow-0.0.73.dist-info → ddeutil_workflow-0.0.75.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.73.dist-info → ddeutil_workflow-0.0.75.dist-info}/top_level.txt +0 -0
ddeutil/workflow/audits.py
CHANGED
@@ -3,6 +3,37 @@
|
|
3
3
|
# Licensed under the MIT License. See LICENSE in the project root for
|
4
4
|
# license information.
|
5
5
|
# ------------------------------------------------------------------------------
|
6
|
+
"""Audit and Execution Tracking Module.
|
7
|
+
|
8
|
+
This module provides comprehensive audit capabilities for workflow execution
|
9
|
+
tracking and monitoring. It supports multiple audit backends for capturing
|
10
|
+
execution metadata, status information, and detailed logging.
|
11
|
+
|
12
|
+
The audit system tracks workflow, job, and stage executions with configurable
|
13
|
+
storage backends including file-based JSON storage and database persistence.
|
14
|
+
|
15
|
+
Classes:
|
16
|
+
Audit: Pydantic model for audit data validation
|
17
|
+
FileAudit: File-based audit storage implementation
|
18
|
+
|
19
|
+
Functions:
|
20
|
+
get_audit_model: Factory function for creating audit instances
|
21
|
+
|
22
|
+
Example:
|
23
|
+
|
24
|
+
```python
|
25
|
+
from ddeutil.workflow.audits import get_audit_model
|
26
|
+
|
27
|
+
# NOTE: Create file-based Audit.
|
28
|
+
audit = get_audit_model(run_id="run-123")
|
29
|
+
audit.info("Workflow execution started")
|
30
|
+
audit.success("Workflow completed successfully")
|
31
|
+
```
|
32
|
+
|
33
|
+
Note:
|
34
|
+
Audit instances are automatically configured based on the workflow
|
35
|
+
configuration and provide detailed execution tracking capabilities.
|
36
|
+
"""
|
6
37
|
from __future__ import annotations
|
7
38
|
|
8
39
|
import json
|
@@ -12,15 +43,17 @@ from abc import ABC, abstractmethod
|
|
12
43
|
from collections.abc import Iterator
|
13
44
|
from datetime import datetime
|
14
45
|
from pathlib import Path
|
15
|
-
from typing import ClassVar, Optional,
|
46
|
+
from typing import ClassVar, Optional, Union
|
47
|
+
from urllib.parse import ParseResult
|
16
48
|
|
17
49
|
from pydantic import BaseModel, Field
|
50
|
+
from pydantic.functional_serializers import field_serializer
|
18
51
|
from pydantic.functional_validators import model_validator
|
19
52
|
from typing_extensions import Self
|
20
53
|
|
21
54
|
from .__types import DictData
|
22
55
|
from .conf import dynamic
|
23
|
-
from .traces import
|
56
|
+
from .traces import Trace, get_trace, set_logging
|
24
57
|
|
25
58
|
logger = logging.getLogger("ddeutil.workflow")
|
26
59
|
|
@@ -108,42 +141,6 @@ class BaseAudit(BaseModel, ABC):
|
|
108
141
|
raise NotImplementedError("Audit should implement `save` method.")
|
109
142
|
|
110
143
|
|
111
|
-
class NullAudit(BaseAudit):
|
112
|
-
|
113
|
-
@classmethod
|
114
|
-
def is_pointed(
|
115
|
-
cls,
|
116
|
-
name: str,
|
117
|
-
release: datetime,
|
118
|
-
*,
|
119
|
-
extras: Optional[DictData] = None,
|
120
|
-
) -> bool:
|
121
|
-
return False
|
122
|
-
|
123
|
-
@classmethod
|
124
|
-
def find_audits(
|
125
|
-
cls,
|
126
|
-
name: str,
|
127
|
-
*,
|
128
|
-
extras: Optional[DictData] = None,
|
129
|
-
) -> Iterator[Self]:
|
130
|
-
raise NotImplementedError()
|
131
|
-
|
132
|
-
@classmethod
|
133
|
-
def find_audit_with_release(
|
134
|
-
cls,
|
135
|
-
name: str,
|
136
|
-
release: Optional[datetime] = None,
|
137
|
-
*,
|
138
|
-
extras: Optional[DictData] = None,
|
139
|
-
) -> Self:
|
140
|
-
raise NotImplementedError()
|
141
|
-
|
142
|
-
def save(self, excluded: Optional[list[str]]) -> None:
|
143
|
-
"""Do nothing when do not set audit."""
|
144
|
-
return
|
145
|
-
|
146
|
-
|
147
144
|
class FileAudit(BaseAudit):
|
148
145
|
"""File Audit Pydantic Model that use to saving log data from result of
|
149
146
|
workflow execution. It inherits from BaseAudit model that implement the
|
@@ -154,6 +151,13 @@ class FileAudit(BaseAudit):
|
|
154
151
|
"workflow={name}/release={release:%Y%m%d%H%M%S}"
|
155
152
|
)
|
156
153
|
|
154
|
+
@field_serializer("extras")
|
155
|
+
def __serialize_extras(self, value: DictData) -> DictData:
|
156
|
+
return {
|
157
|
+
k: (v.geturl() if isinstance(v, ParseResult) else v)
|
158
|
+
for k, v in value.items()
|
159
|
+
}
|
160
|
+
|
157
161
|
def do_before(self) -> None:
|
158
162
|
"""Create directory of release before saving log file."""
|
159
163
|
self.pointer().mkdir(parents=True, exist_ok=True)
|
@@ -171,7 +175,7 @@ class FileAudit(BaseAudit):
|
|
171
175
|
:rtype: Iterator[Self]
|
172
176
|
"""
|
173
177
|
pointer: Path = (
|
174
|
-
dynamic("
|
178
|
+
Path(dynamic("audit_url", extras=extras).path) / f"workflow={name}"
|
175
179
|
)
|
176
180
|
if not pointer.exists():
|
177
181
|
raise FileNotFoundError(f"Pointer: {pointer.absolute()}.")
|
@@ -206,7 +210,7 @@ class FileAudit(BaseAudit):
|
|
206
210
|
raise NotImplementedError("Find latest log does not implement yet.")
|
207
211
|
|
208
212
|
pointer: Path = (
|
209
|
-
dynamic("
|
213
|
+
Path(dynamic("audit_url", extras=extras).path)
|
210
214
|
/ f"workflow={name}/release={release:%Y%m%d%H%M%S}"
|
211
215
|
)
|
212
216
|
if not pointer.exists():
|
@@ -242,8 +246,8 @@ class FileAudit(BaseAudit):
|
|
242
246
|
return False
|
243
247
|
|
244
248
|
# NOTE: create pointer path that use the same logic of pointer method.
|
245
|
-
pointer: Path =
|
246
|
-
"
|
249
|
+
pointer: Path = Path(
|
250
|
+
dynamic("audit_url", extras=extras).path
|
247
251
|
) / cls.filename_fmt.format(name=name, release=release)
|
248
252
|
|
249
253
|
return pointer.exists()
|
@@ -253,8 +257,8 @@ class FileAudit(BaseAudit):
|
|
253
257
|
|
254
258
|
:rtype: Path
|
255
259
|
"""
|
256
|
-
return
|
257
|
-
"
|
260
|
+
return Path(
|
261
|
+
dynamic("audit_url", extras=self.extras).path
|
258
262
|
) / self.filename_fmt.format(name=self.name, release=self.release)
|
259
263
|
|
260
264
|
def save(self, excluded: Optional[list[str]] = None) -> Self:
|
@@ -266,7 +270,7 @@ class FileAudit(BaseAudit):
|
|
266
270
|
|
267
271
|
:rtype: Self
|
268
272
|
"""
|
269
|
-
trace:
|
273
|
+
trace: Trace = get_trace(
|
270
274
|
self.run_id,
|
271
275
|
parent_run_id=self.parent_run_id,
|
272
276
|
extras=self.extras,
|
@@ -338,7 +342,7 @@ class SQLiteAudit(BaseAudit): # pragma: no cov
|
|
338
342
|
"""Save logging data that receive a context data from a workflow
|
339
343
|
execution result.
|
340
344
|
"""
|
341
|
-
trace:
|
345
|
+
trace: Trace = get_trace(
|
342
346
|
self.run_id,
|
343
347
|
parent_run_id=self.parent_run_id,
|
344
348
|
extras=self.extras,
|
@@ -352,23 +356,34 @@ class SQLiteAudit(BaseAudit): # pragma: no cov
|
|
352
356
|
raise NotImplementedError("SQLiteAudit does not implement yet.")
|
353
357
|
|
354
358
|
|
355
|
-
Audit =
|
356
|
-
AuditModel = Union[
|
357
|
-
NullAudit,
|
359
|
+
Audit = Union[
|
358
360
|
FileAudit,
|
359
361
|
SQLiteAudit,
|
362
|
+
BaseAudit,
|
360
363
|
]
|
361
364
|
|
362
365
|
|
363
|
-
def
|
366
|
+
def get_audit_model(
|
364
367
|
extras: Optional[DictData] = None,
|
365
|
-
) -> type[
|
366
|
-
"""Get an audit
|
368
|
+
) -> type[Audit]: # pragma: no cov
|
369
|
+
"""Get an audit model that dynamic base on the config audit path value.
|
367
370
|
|
368
371
|
:param extras: An extra parameter that want to override the core config.
|
369
372
|
|
370
373
|
:rtype: type[Audit]
|
371
374
|
"""
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
+
# NOTE: Allow you to override trace model by the extra parameter.
|
376
|
+
map_audit_models: dict[str, type[Trace]] = extras.get(
|
377
|
+
"audit_model_mapping", {}
|
378
|
+
)
|
379
|
+
url: ParseResult
|
380
|
+
if (url := dynamic("audit_url", extras=extras)).scheme and (
|
381
|
+
url.scheme == "sqlite"
|
382
|
+
or (url.scheme == "file" and Path(url.path).is_file())
|
383
|
+
):
|
384
|
+
return map_audit_models.get("sqlite", FileAudit)
|
385
|
+
elif url.scheme and url.scheme != "file":
|
386
|
+
raise NotImplementedError(
|
387
|
+
f"Does not implement the audit model support for URL: {url}"
|
388
|
+
)
|
389
|
+
return map_audit_models.get("file", FileAudit)
|
ddeutil/workflow/cli.py
CHANGED
@@ -16,7 +16,6 @@ from pydantic import Field, TypeAdapter
|
|
16
16
|
from .__about__ import __version__
|
17
17
|
from .__types import DictData
|
18
18
|
from .errors import JobError
|
19
|
-
from .event import Crontab
|
20
19
|
from .job import Job
|
21
20
|
from .params import Param
|
22
21
|
from .result import Result
|
@@ -153,19 +152,6 @@ class WorkflowSchema(Workflow):
|
|
153
152
|
default_factory=dict,
|
154
153
|
description="A parameters that need to use on this workflow.",
|
155
154
|
)
|
156
|
-
on: Union[list[Union[Crontab, str]], str] = Field(
|
157
|
-
default_factory=list,
|
158
|
-
description="A list of Crontab instance for this workflow schedule.",
|
159
|
-
)
|
160
|
-
|
161
|
-
|
162
|
-
CRONTAB_TYPE = Literal["Crontab"]
|
163
|
-
|
164
|
-
|
165
|
-
class CrontabSchema(Crontab):
|
166
|
-
"""Override crontab model fields for generate JSON schema file."""
|
167
|
-
|
168
|
-
type: CRONTAB_TYPE = Field(description="A type of crontab template.")
|
169
155
|
|
170
156
|
|
171
157
|
@workflow_app.command(name="json-schema")
|
@@ -176,7 +162,7 @@ def workflow_json_schema(
|
|
176
162
|
] = Path("./json-schema.json"),
|
177
163
|
) -> None:
|
178
164
|
"""Generate JSON schema file from the Workflow model."""
|
179
|
-
template = dict[str,
|
165
|
+
template = dict[str, WorkflowSchema]
|
180
166
|
json_schema = TypeAdapter(template).json_schema(by_alias=True)
|
181
167
|
template_schema: dict[str, str] = {
|
182
168
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
ddeutil/workflow/conf.py
CHANGED
@@ -3,14 +3,49 @@
|
|
3
3
|
# Licensed under the MIT License. See LICENSE in the project root for
|
4
4
|
# license information.
|
5
5
|
# ------------------------------------------------------------------------------
|
6
|
-
|
7
|
-
|
6
|
+
"""Configuration Management for Workflow System.
|
7
|
+
|
8
|
+
This module provides comprehensive configuration management for the workflow
|
9
|
+
system, including YAML parsing, dynamic configuration loading, environment
|
10
|
+
variable handling, and configuration validation.
|
11
|
+
|
12
|
+
The configuration system supports hierarchical configuration files, environment
|
13
|
+
variable substitution, and dynamic parameter resolution for flexible workflow
|
14
|
+
deployment across different environments.
|
15
|
+
|
16
|
+
Classes:
|
17
|
+
Config: Main configuration class with validation
|
18
|
+
YamlParser: YAML configuration file parser and loader
|
19
|
+
|
20
|
+
Functions:
|
21
|
+
dynamic: Get dynamic configuration values with fallbacks
|
22
|
+
pass_env: Process environment variable substitution
|
23
|
+
api_config: Get API-specific configuration settings
|
24
|
+
|
25
|
+
Example:
|
26
|
+
```python
|
27
|
+
from ddeutil.workflow.conf import Config, YamlParser
|
28
|
+
|
29
|
+
# Load workflow configuration
|
30
|
+
parser = YamlParser("my-workflow")
|
31
|
+
workflow_config = parser.data
|
32
|
+
|
33
|
+
# Access dynamic configuration
|
34
|
+
from ddeutil.workflow.conf import dynamic
|
35
|
+
log_level = dynamic("log_level", default="INFO")
|
36
|
+
```
|
37
|
+
|
38
|
+
Note:
|
39
|
+
Configuration files support environment variable substitution using
|
40
|
+
${VAR_NAME} syntax and provide extensive validation capabilities.
|
41
|
+
"""
|
8
42
|
import copy
|
9
43
|
import os
|
10
44
|
from collections.abc import Iterator
|
11
45
|
from functools import cached_property
|
12
46
|
from pathlib import Path
|
13
47
|
from typing import Final, Optional, TypeVar, Union
|
48
|
+
from urllib.parse import ParseResult, urlparse
|
14
49
|
from zoneinfo import ZoneInfo
|
15
50
|
|
16
51
|
from ddeutil.core import str2bool
|
@@ -28,10 +63,12 @@ PREFIX: Final[str] = "WORKFLOW"
|
|
28
63
|
def env(var: str, default: Optional[str] = None) -> Optional[str]:
|
29
64
|
"""Get environment variable with uppercase and adding prefix string.
|
30
65
|
|
31
|
-
:
|
32
|
-
|
66
|
+
Args:
|
67
|
+
var: A env variable name.
|
68
|
+
default: A default value if an env var does not set.
|
33
69
|
|
34
|
-
:
|
70
|
+
Returns:
|
71
|
+
Optional[str]: The environment variable value or default.
|
35
72
|
"""
|
36
73
|
return os.getenv(f"{PREFIX}_{var.upper().replace(' ', '_')}", default)
|
37
74
|
|
@@ -47,25 +84,18 @@ class Config: # pragma: no cov
|
|
47
84
|
def conf_path(self) -> Path:
|
48
85
|
"""Config path that keep all workflow template YAML files.
|
49
86
|
|
50
|
-
:
|
87
|
+
Returns:
|
88
|
+
Path: The configuration path for workflow templates.
|
51
89
|
"""
|
52
90
|
return Path(env("CORE_CONF_PATH", "./conf"))
|
53
91
|
|
54
|
-
@property
|
55
|
-
def tz(self) -> ZoneInfo:
|
56
|
-
"""Timezone value that return with the `ZoneInfo` object and use for all
|
57
|
-
datetime object in this workflow engine.
|
58
|
-
|
59
|
-
:rtype: ZoneInfo
|
60
|
-
"""
|
61
|
-
return ZoneInfo(env("CORE_TIMEZONE", "UTC"))
|
62
|
-
|
63
92
|
@property
|
64
93
|
def generate_id_simple_mode(self) -> bool:
|
65
94
|
"""Flag for generate running ID with simple mode. That does not use
|
66
95
|
`md5` function after generate simple mode.
|
67
96
|
|
68
|
-
:
|
97
|
+
Returns:
|
98
|
+
bool: True if simple mode ID generation is enabled.
|
69
99
|
"""
|
70
100
|
return str2bool(env("CORE_GENERATE_ID_SIMPLE_MODE", "true"))
|
71
101
|
|
@@ -92,8 +122,8 @@ class Config: # pragma: no cov
|
|
92
122
|
return [r.strip() for r in regis_filter_str.split(",")]
|
93
123
|
|
94
124
|
@property
|
95
|
-
def
|
96
|
-
return
|
125
|
+
def trace_url(self) -> ParseResult:
|
126
|
+
return urlparse(env("LOG_TRACE_URL", "file:./logs"))
|
97
127
|
|
98
128
|
@property
|
99
129
|
def debug(self) -> bool:
|
@@ -103,6 +133,16 @@ class Config: # pragma: no cov
|
|
103
133
|
"""
|
104
134
|
return str2bool(env("LOG_DEBUG_MODE", "true"))
|
105
135
|
|
136
|
+
@property
|
137
|
+
def log_tz(self) -> ZoneInfo:
|
138
|
+
"""Timezone value that return with the `ZoneInfo` object and use for all
|
139
|
+
datetime object in this workflow engine.
|
140
|
+
|
141
|
+
Returns:
|
142
|
+
ZoneInfo: The timezone configuration for the workflow engine.
|
143
|
+
"""
|
144
|
+
return ZoneInfo(env("LOG_TIMEZONE", "UTC"))
|
145
|
+
|
106
146
|
@property
|
107
147
|
def log_format(self) -> str:
|
108
148
|
return env(
|
@@ -129,8 +169,8 @@ class Config: # pragma: no cov
|
|
129
169
|
return str2bool(env("LOG_TRACE_ENABLE_WRITE", "false"))
|
130
170
|
|
131
171
|
@property
|
132
|
-
def
|
133
|
-
return
|
172
|
+
def audit_url(self) -> ParseResult:
|
173
|
+
return urlparse(env("LOG_AUDIT_URL", "file:./audits"))
|
134
174
|
|
135
175
|
@property
|
136
176
|
def enable_write_audit(self) -> bool:
|
@@ -190,8 +230,8 @@ class YamlParser:
|
|
190
230
|
name: str,
|
191
231
|
*,
|
192
232
|
path: Optional[Union[str, Path]] = None,
|
193
|
-
externals: DictData
|
194
|
-
extras: DictData
|
233
|
+
externals: Optional[DictData] = None,
|
234
|
+
extras: Optional[DictData] = None,
|
195
235
|
obj: Optional[Union[object, str]] = None,
|
196
236
|
) -> None:
|
197
237
|
self.path: Path = Path(dynamic("conf_path", f=path, extras=extras))
|
@@ -260,10 +300,13 @@ class YamlParser:
|
|
260
300
|
continue
|
261
301
|
|
262
302
|
if data := cls.filter_yaml(file, name=name):
|
303
|
+
file_stat: os.stat_result = file.lstat()
|
304
|
+
data["created_at"] = file_stat.st_ctime
|
305
|
+
data["updated_at"] = file_stat.st_mtime
|
263
306
|
if not obj_type:
|
264
|
-
all_data.append((
|
307
|
+
all_data.append((file_stat.st_mtime, data))
|
265
308
|
elif (t := data.get("type")) and t == obj_type:
|
266
|
-
all_data.append((
|
309
|
+
all_data.append((file_stat.st_mtime, data))
|
267
310
|
else:
|
268
311
|
continue
|
269
312
|
|
@@ -435,10 +478,12 @@ def pass_env(value: T) -> T: # pragma: no cov
|
|
435
478
|
|
436
479
|
|
437
480
|
class CallerSecret(SecretStr): # pragma: no cov
|
438
|
-
"""Workflow Secret String model
|
481
|
+
"""Workflow Secret String model that was inherited from the SecretStr model
|
482
|
+
and override the `get_secret_value` method only.
|
483
|
+
"""
|
439
484
|
|
440
485
|
def get_secret_value(self) -> str:
|
441
|
-
"""Override get_secret_value by adding pass_env before return the
|
486
|
+
"""Override the `get_secret_value` by adding pass_env before return the
|
442
487
|
real-value.
|
443
488
|
|
444
489
|
:rtype: str
|
ddeutil/workflow/errors.py
CHANGED
@@ -3,9 +3,22 @@
|
|
3
3
|
# Licensed under the MIT License. See LICENSE in the project root for
|
4
4
|
# license information.
|
5
5
|
# ------------------------------------------------------------------------------
|
6
|
-
"""Exception
|
7
|
-
|
8
|
-
|
6
|
+
"""Exception Classes for Workflow Orchestration.
|
7
|
+
|
8
|
+
This module provides a comprehensive exception hierarchy for the workflow system.
|
9
|
+
The exceptions are designed to be lightweight while providing sufficient context
|
10
|
+
for error handling and debugging.
|
11
|
+
|
12
|
+
Classes:
|
13
|
+
BaseError: Base exception class with context support
|
14
|
+
StageError: Exceptions related to stage execution
|
15
|
+
JobError: Exceptions related to job execution
|
16
|
+
WorkflowError: Exceptions related to workflow execution
|
17
|
+
ParamError: Exceptions related to parameter validation
|
18
|
+
ResultError: Exceptions related to result processing
|
19
|
+
|
20
|
+
Functions:
|
21
|
+
to_dict: Convert exception instances to dictionary format
|
9
22
|
"""
|
10
23
|
from __future__ import annotations
|
11
24
|
|
@@ -15,8 +28,14 @@ from .__types import DictData, StrOrInt
|
|
15
28
|
|
16
29
|
|
17
30
|
class ErrorData(TypedDict):
|
18
|
-
"""Error data
|
19
|
-
|
31
|
+
"""Error data structure for exception serialization.
|
32
|
+
|
33
|
+
This TypedDict defines the standard structure for converting exceptions
|
34
|
+
to dictionary format for consistent error handling across the system.
|
35
|
+
|
36
|
+
Attributes:
|
37
|
+
name: Exception class name
|
38
|
+
message: Exception message content
|
20
39
|
"""
|
21
40
|
|
22
41
|
name: str
|
@@ -24,11 +43,26 @@ class ErrorData(TypedDict):
|
|
24
43
|
|
25
44
|
|
26
45
|
def to_dict(exception: Exception, **kwargs) -> ErrorData: # pragma: no cov
|
27
|
-
"""Create
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
46
|
+
"""Create dictionary data from exception instance.
|
47
|
+
|
48
|
+
Converts an exception object to a standardized dictionary format
|
49
|
+
for consistent error handling and serialization.
|
50
|
+
|
51
|
+
Args:
|
52
|
+
exception: Exception object to convert
|
53
|
+
**kwargs: Additional key-value pairs to include in result
|
54
|
+
|
55
|
+
Returns:
|
56
|
+
ErrorData: Dictionary containing exception name and message
|
57
|
+
|
58
|
+
Example:
|
59
|
+
```python
|
60
|
+
try:
|
61
|
+
raise ValueError("Something went wrong")
|
62
|
+
except Exception as e:
|
63
|
+
error_data = to_dict(e, context="workflow_execution")
|
64
|
+
# Returns: {"name": "ValueError", "message": "Something went wrong", "context": "workflow_execution"}
|
65
|
+
```
|
32
66
|
"""
|
33
67
|
return {
|
34
68
|
"name": exception.__class__.__name__,
|
@@ -38,14 +72,27 @@ def to_dict(exception: Exception, **kwargs) -> ErrorData: # pragma: no cov
|
|
38
72
|
|
39
73
|
|
40
74
|
class BaseError(Exception):
|
41
|
-
"""Base
|
42
|
-
making an error context to the result context.
|
75
|
+
"""Base exception class for all workflow-related errors.
|
43
76
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
77
|
+
BaseError provides the foundation for all workflow exceptions, offering
|
78
|
+
enhanced context management and error tracking capabilities. It supports
|
79
|
+
reference IDs for error correlation and maintains context information
|
80
|
+
for debugging purposes.
|
48
81
|
|
82
|
+
Attributes:
|
83
|
+
refs: Optional reference identifier for error correlation
|
84
|
+
context: Additional context data related to the error
|
85
|
+
params: Parameter data that was being processed when error occurred
|
86
|
+
|
87
|
+
Example:
|
88
|
+
```python
|
89
|
+
try:
|
90
|
+
# Some workflow operation
|
91
|
+
pass
|
92
|
+
except BaseError as e:
|
93
|
+
error_dict = e.to_dict(with_refs=True)
|
94
|
+
print(f"Error in {e.refs}: {error_dict}")
|
95
|
+
```
|
49
96
|
"""
|
50
97
|
|
51
98
|
def __init__(
|
@@ -76,10 +123,30 @@ class BaseError(Exception):
|
|
76
123
|
with_refs: bool = False,
|
77
124
|
**kwargs,
|
78
125
|
) -> Union[ErrorData, dict[str, ErrorData]]:
|
79
|
-
"""
|
80
|
-
|
126
|
+
"""Convert exception to dictionary format.
|
127
|
+
|
128
|
+
Serializes the exception to a standardized dictionary format.
|
129
|
+
Optionally includes reference mapping for error correlation.
|
130
|
+
|
131
|
+
Args:
|
132
|
+
with_refs: Include reference ID mapping in result
|
133
|
+
**kwargs: Additional key-value pairs to include
|
134
|
+
|
135
|
+
Returns:
|
136
|
+
ErrorData or dict: Exception data, optionally mapped by reference ID
|
137
|
+
|
138
|
+
Example:
|
139
|
+
```python
|
140
|
+
error = BaseError("Something failed", refs="stage-1")
|
141
|
+
|
142
|
+
# Simple format
|
143
|
+
data = error.to_dict()
|
144
|
+
# Returns: {"name": "BaseError", "message": "Something failed"}
|
81
145
|
|
82
|
-
|
146
|
+
# With reference mapping
|
147
|
+
ref_data = error.to_dict(with_refs=True)
|
148
|
+
# Returns: {"stage-1": {"name": "BaseError", "message": "Something failed"}}
|
149
|
+
```
|
83
150
|
"""
|
84
151
|
data: ErrorData = to_dict(self)
|
85
152
|
if with_refs and (self.refs is not None and self.refs != "EMPTY"):
|