ddeutil-workflow 0.0.76__tar.gz → 0.0.78__tar.gz
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-0.0.76 → ddeutil_workflow-0.0.78}/PKG-INFO +1 -2
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/README.md +0 -1
- ddeutil_workflow-0.0.78/src/ddeutil/workflow/__about__.py +1 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil/workflow/cli.py +6 -2
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil/workflow/conf.py +28 -12
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil/workflow/errors.py +13 -15
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil/workflow/event.py +22 -35
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil/workflow/job.py +16 -21
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil/workflow/result.py +30 -34
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil/workflow/reusables.py +7 -5
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil/workflow/stages.py +58 -34
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil/workflow/traces.py +2 -1
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil/workflow/utils.py +10 -4
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil/workflow/workflow.py +58 -36
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil_workflow.egg-info/PKG-INFO +1 -2
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test_conf.py +145 -15
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test_event.py +26 -10
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test_result.py +39 -4
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test_reusables_call_tag.py +12 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test_reusables_template.py +6 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test_utils.py +4 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test_workflow.py +28 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test_workflow_exec_job.py +17 -1
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test_workflow_release.py +2 -0
- ddeutil_workflow-0.0.76/src/ddeutil/workflow/__about__.py +0 -1
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/LICENSE +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/pyproject.toml +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/setup.cfg +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil/workflow/__cron.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil/workflow/__init__.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil/workflow/__main__.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil/workflow/__types.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil/workflow/api/__init__.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil/workflow/api/log_conf.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil/workflow/api/routes/__init__.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil/workflow/api/routes/job.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil/workflow/api/routes/logs.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil/workflow/api/routes/workflows.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil/workflow/audits.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil/workflow/params.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil_workflow.egg-info/SOURCES.txt +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil_workflow.egg-info/dependency_links.txt +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil_workflow.egg-info/entry_points.txt +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil_workflow.egg-info/requires.txt +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/src/ddeutil_workflow.egg-info/top_level.txt +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test__cron.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test__regex.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test_audits.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test_cli.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test_errors.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test_job.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test_job_exec.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test_job_exec_strategy.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test_params.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test_reusables_func_model.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test_reusables_template_filter.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test_strategy.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test_traces.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test_workflow_exec.py +0 -0
- {ddeutil_workflow-0.0.76 → ddeutil_workflow-0.0.78}/tests/test_workflow_rerun.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ddeutil-workflow
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.78
|
4
4
|
Summary: Lightweight workflow orchestration with YAML template
|
5
5
|
Author-email: ddeutils <korawich.anu@gmail.com>
|
6
6
|
License: MIT
|
@@ -147,7 +147,6 @@ For comprehensive API documentation, examples, and best practices:
|
|
147
147
|
- **[Full Documentation](https://ddeutils.github.io/ddeutil-workflow/)** - Complete user guide and API reference
|
148
148
|
- **[Getting Started](https://ddeutils.github.io/ddeutil-workflow/getting-started/)** - Quick start guide
|
149
149
|
- **[API Reference](https://ddeutils.github.io/ddeutil-workflow/api/workflow/)** - Detailed API documentation
|
150
|
-
- **[Examples](https://ddeutils.github.io/ddeutil-workflow/examples/)** - Real-world usage examples
|
151
150
|
|
152
151
|
## 🎯 Usage
|
153
152
|
|
@@ -105,7 +105,6 @@ For comprehensive API documentation, examples, and best practices:
|
|
105
105
|
- **[Full Documentation](https://ddeutils.github.io/ddeutil-workflow/)** - Complete user guide and API reference
|
106
106
|
- **[Getting Started](https://ddeutils.github.io/ddeutil-workflow/getting-started/)** - Quick start guide
|
107
107
|
- **[API Reference](https://ddeutils.github.io/ddeutil-workflow/api/workflow/)** - Detailed API documentation
|
108
|
-
- **[Examples](https://ddeutils.github.io/ddeutil-workflow/examples/)** - Real-world usage examples
|
109
108
|
|
110
109
|
## 🎯 Usage
|
111
110
|
|
@@ -0,0 +1 @@
|
|
1
|
+
__version__: str = "0.0.78"
|
@@ -57,7 +57,7 @@ def init() -> None:
|
|
57
57
|
wf-example:
|
58
58
|
type: Workflow
|
59
59
|
desc: |
|
60
|
-
An example workflow template.
|
60
|
+
An example workflow template that provide the demo of workflow.
|
61
61
|
params:
|
62
62
|
name:
|
63
63
|
type: str
|
@@ -65,6 +65,10 @@ def init() -> None:
|
|
65
65
|
jobs:
|
66
66
|
first-job:
|
67
67
|
stages:
|
68
|
+
|
69
|
+
- name: "Hello Stage"
|
70
|
+
echo: "Start say hi to the console"
|
71
|
+
|
68
72
|
- name: "Call tasks"
|
69
73
|
uses: tasks/say-hello-func@example
|
70
74
|
with:
|
@@ -232,7 +236,7 @@ def workflow_json_schema(
|
|
232
236
|
json_schema = TypeAdapter(template).json_schema(by_alias=True)
|
233
237
|
template_schema: dict[str, str] = {
|
234
238
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
235
|
-
"title": "Workflow Configuration Schema",
|
239
|
+
"title": "Workflow Configuration JSON Schema",
|
236
240
|
"version": __version__,
|
237
241
|
}
|
238
242
|
with open(output, mode="w", encoding="utf-8") as f:
|
@@ -307,8 +307,6 @@ class YamlParser:
|
|
307
307
|
all_data.append((file_stat.st_mtime, data))
|
308
308
|
elif (t := data.get("type")) and t == obj_type:
|
309
309
|
all_data.append((file_stat.st_mtime, data))
|
310
|
-
else:
|
311
|
-
continue
|
312
310
|
|
313
311
|
return {} if not all_data else max(all_data, key=lambda x: x[0])[1]
|
314
312
|
|
@@ -322,25 +320,30 @@ class YamlParser:
|
|
322
320
|
excluded: Optional[list[str]] = None,
|
323
321
|
extras: Optional[DictData] = None,
|
324
322
|
ignore_filename: Optional[str] = None,
|
323
|
+
tags: Optional[list[Union[str, int]]] = None,
|
325
324
|
) -> Iterator[tuple[str, DictData]]:
|
326
325
|
"""Find all data that match with object type in config path. This class
|
327
326
|
method can use include and exclude list of identity name for filter and
|
328
327
|
adds-on.
|
329
328
|
|
330
|
-
:
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
data
|
336
|
-
|
337
|
-
|
338
|
-
|
329
|
+
Args:
|
330
|
+
obj: (object | str) An object that want to validate matching
|
331
|
+
before return.
|
332
|
+
path: (Path) A config path object.
|
333
|
+
paths: (list[Path]) A list of config path object.
|
334
|
+
excluded: An included list of data key that want to filter from
|
335
|
+
data.
|
336
|
+
extras: (DictData) An extra parameter that use to override core
|
337
|
+
config values.
|
338
|
+
ignore_filename: (str) An ignore filename. Default is
|
339
339
|
``.confignore`` filename.
|
340
|
+
tags: (list[str])
|
341
|
+
A list of tag that want to filter.
|
340
342
|
|
341
343
|
:rtype: Iterator[tuple[str, DictData]]
|
342
344
|
"""
|
343
345
|
excluded: list[str] = excluded or []
|
346
|
+
tags: list[str] = tags or []
|
344
347
|
path: Path = dynamic("conf_path", f=path, extras=extras)
|
345
348
|
paths: Optional[list[Path]] = paths or (extras or {}).get("conf_paths")
|
346
349
|
if not paths:
|
@@ -366,7 +369,17 @@ class YamlParser:
|
|
366
369
|
if key in excluded:
|
367
370
|
continue
|
368
371
|
|
372
|
+
if (
|
373
|
+
tags
|
374
|
+
and isinstance((ts := data.get("tags", [])), list)
|
375
|
+
and any(t not in ts for t in tags)
|
376
|
+
):
|
377
|
+
continue
|
378
|
+
|
369
379
|
if (t := data.get("type")) and t == obj_type:
|
380
|
+
file_stat: os.stat_result = file.lstat()
|
381
|
+
data["created_at"] = file_stat.st_ctime
|
382
|
+
data["updated_at"] = file_stat.st_mtime
|
370
383
|
marking: tuple[float, DictData] = (
|
371
384
|
file.lstat().st_mtime,
|
372
385
|
data,
|
@@ -469,7 +482,10 @@ def pass_env(value: T) -> T: # pragma: no cov
|
|
469
482
|
if isinstance(value, dict):
|
470
483
|
return {k: pass_env(value[k]) for k in value}
|
471
484
|
elif isinstance(value, (list, tuple, set)):
|
472
|
-
|
485
|
+
try:
|
486
|
+
return type(value)(pass_env(i) for i in value)
|
487
|
+
except TypeError:
|
488
|
+
return value
|
473
489
|
if not isinstance(value, str):
|
474
490
|
return value
|
475
491
|
|
@@ -56,13 +56,13 @@ def to_dict(exception: Exception, **kwargs) -> ErrorData: # pragma: no cov
|
|
56
56
|
ErrorData: Dictionary containing exception name and message
|
57
57
|
|
58
58
|
Example:
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
59
|
+
>>> try:
|
60
|
+
>>> raise ValueError("Something went wrong")
|
61
|
+
>>> except Exception as e:
|
62
|
+
>>> error_data = to_dict(e, context="workflow_execution")
|
63
|
+
>>> # Returns: {
|
64
|
+
>>> # "name": "ValueError", "message": "Something went wrong", "context": "workflow_execution"
|
65
|
+
>>> # }
|
66
66
|
"""
|
67
67
|
return {
|
68
68
|
"name": exception.__class__.__name__,
|
@@ -85,14 +85,12 @@ class BaseError(Exception):
|
|
85
85
|
params: Parameter data that was being processed when error occurred
|
86
86
|
|
87
87
|
Example:
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
print(f"Error in {e.refs}: {error_dict}")
|
95
|
-
```
|
88
|
+
>>> try:
|
89
|
+
>>> # NOTE: Some workflow operation
|
90
|
+
>>> pass
|
91
|
+
>>> except BaseError as e:
|
92
|
+
>>> error_dict = e.to_dict(with_refs=True)
|
93
|
+
>>> print(f\"Error in {e.refs}: {error_dict}\")
|
96
94
|
"""
|
97
95
|
|
98
96
|
def __init__(
|
@@ -39,7 +39,6 @@ from __future__ import annotations
|
|
39
39
|
from dataclasses import fields
|
40
40
|
from datetime import datetime
|
41
41
|
from typing import Annotated, Any, Literal, Optional, Union
|
42
|
-
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
43
42
|
|
44
43
|
from pydantic import BaseModel, ConfigDict, Field, ValidationInfo
|
45
44
|
from pydantic.functional_serializers import field_serializer
|
@@ -107,7 +106,7 @@ class BaseCrontab(BaseModel):
|
|
107
106
|
)
|
108
107
|
tz: TimeZoneName = Field(
|
109
108
|
default="UTC",
|
110
|
-
description="A timezone string value.",
|
109
|
+
description="A timezone string value that will pass to ZoneInfo.",
|
111
110
|
alias="timezone",
|
112
111
|
)
|
113
112
|
|
@@ -125,38 +124,25 @@ class BaseCrontab(BaseModel):
|
|
125
124
|
data["timezone"] = tz
|
126
125
|
return data
|
127
126
|
|
128
|
-
@field_validator("tz")
|
129
|
-
def __validate_tz(cls, value: str) -> str:
|
130
|
-
"""Validate timezone value.
|
131
|
-
|
132
|
-
Args:
|
133
|
-
value: Timezone string to validate.
|
134
|
-
|
135
|
-
Returns:
|
136
|
-
Validated timezone string.
|
137
|
-
|
138
|
-
Raises:
|
139
|
-
ValueError: If timezone is invalid.
|
140
|
-
"""
|
141
|
-
try:
|
142
|
-
_ = ZoneInfo(value)
|
143
|
-
return value
|
144
|
-
except ZoneInfoNotFoundError as e:
|
145
|
-
raise ValueError(f"Invalid timezone: {value}") from e
|
146
|
-
|
147
127
|
|
148
128
|
class CrontabValue(BaseCrontab):
|
149
129
|
"""Crontab model using interval-based specification.
|
150
130
|
|
151
131
|
Attributes:
|
152
|
-
interval:
|
153
|
-
|
132
|
+
interval: (Interval)
|
133
|
+
A scheduling interval string ('daily', 'weekly', 'monthly').
|
134
|
+
day: (str, default None)
|
135
|
+
Day specification for weekly/monthly schedules.
|
154
136
|
time: Time of day in 'HH:MM' format.
|
155
137
|
"""
|
156
138
|
|
157
|
-
interval: Interval
|
139
|
+
interval: Interval = Field(description="A scheduling interval string.")
|
158
140
|
day: Optional[str] = Field(default=None)
|
159
|
-
time: str = Field(
|
141
|
+
time: str = Field(
|
142
|
+
default="00:00",
|
143
|
+
pattern=r"\d{2}:\d{2}",
|
144
|
+
description="A time of day that pass with format 'HH:MM'.",
|
145
|
+
)
|
160
146
|
|
161
147
|
@property
|
162
148
|
def cronjob(self) -> CronJob:
|
@@ -182,10 +168,13 @@ class CrontabValue(BaseCrontab):
|
|
182
168
|
TypeError: If start parameter is neither string nor datetime.
|
183
169
|
"""
|
184
170
|
if isinstance(start, str):
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
171
|
+
return self.cronjob.schedule(
|
172
|
+
date=datetime.fromisoformat(start), tz=self.tz
|
173
|
+
)
|
174
|
+
|
175
|
+
if isinstance(start, datetime):
|
176
|
+
return self.cronjob.schedule(date=start, tz=self.tz)
|
177
|
+
raise TypeError("start value should be str or datetime type.")
|
189
178
|
|
190
179
|
def next(self, start: Union[str, datetime]) -> CronRunner:
|
191
180
|
"""Get next scheduled datetime after given start time.
|
@@ -222,11 +211,6 @@ class Crontab(BaseCrontab):
|
|
222
211
|
"A Cronjob object that use for validate and generate datetime."
|
223
212
|
),
|
224
213
|
)
|
225
|
-
tz: TimeZoneName = Field(
|
226
|
-
default="UTC",
|
227
|
-
description="A timezone string value.",
|
228
|
-
alias="timezone",
|
229
|
-
)
|
230
214
|
|
231
215
|
@model_validator(mode="before")
|
232
216
|
def __prepare_values(cls, data: Any) -> Any:
|
@@ -376,7 +360,10 @@ Cron = Annotated[
|
|
376
360
|
],
|
377
361
|
Field(
|
378
362
|
union_mode="smart",
|
379
|
-
description=
|
363
|
+
description=(
|
364
|
+
"Event model type supporting year-based, standard, and "
|
365
|
+
"interval-based cron scheduling."
|
366
|
+
),
|
380
367
|
),
|
381
368
|
] # pragma: no cov
|
382
369
|
|
@@ -48,10 +48,11 @@ from enum import Enum
|
|
48
48
|
from functools import lru_cache
|
49
49
|
from textwrap import dedent
|
50
50
|
from threading import Event
|
51
|
-
from typing import Annotated, Any,
|
51
|
+
from typing import Annotated, Any, Optional, Union
|
52
52
|
|
53
53
|
from ddeutil.core import freeze_args
|
54
54
|
from pydantic import BaseModel, Discriminator, Field, SecretStr, Tag
|
55
|
+
from pydantic.functional_serializers import field_serializer
|
55
56
|
from pydantic.functional_validators import field_validator, model_validator
|
56
57
|
from typing_extensions import Self
|
57
58
|
|
@@ -263,24 +264,20 @@ class BaseRunsOn(BaseModel): # pragma: no cov
|
|
263
264
|
object and override execute method.
|
264
265
|
"""
|
265
266
|
|
266
|
-
type: RunsOn =
|
267
|
+
type: RunsOn = LOCAL
|
267
268
|
args: DictData = Field(
|
268
269
|
default_factory=dict,
|
269
|
-
alias="with",
|
270
270
|
description=(
|
271
271
|
"An argument that pass to the runs-on execution function. This "
|
272
272
|
"args will override by this child-model with specific args model."
|
273
273
|
),
|
274
|
+
alias="with",
|
274
275
|
)
|
275
276
|
|
276
277
|
|
277
278
|
class OnLocal(BaseRunsOn): # pragma: no cov
|
278
279
|
"""Runs-on local."""
|
279
280
|
|
280
|
-
type: Literal[RunsOn.LOCAL] = Field(
|
281
|
-
default=RunsOn.LOCAL, validate_default=True
|
282
|
-
)
|
283
|
-
|
284
281
|
|
285
282
|
class SelfHostedArgs(BaseModel):
|
286
283
|
"""Self-Hosted arguments."""
|
@@ -292,9 +289,7 @@ class SelfHostedArgs(BaseModel):
|
|
292
289
|
class OnSelfHosted(BaseRunsOn): # pragma: no cov
|
293
290
|
"""Runs-on self-hosted."""
|
294
291
|
|
295
|
-
type:
|
296
|
-
default=RunsOn.SELF_HOSTED, validate_default=True
|
297
|
-
)
|
292
|
+
type: RunsOn = SELF_HOSTED
|
298
293
|
args: SelfHostedArgs = Field(alias="with")
|
299
294
|
|
300
295
|
|
@@ -310,9 +305,7 @@ class AzBatchArgs(BaseModel):
|
|
310
305
|
|
311
306
|
class OnAzBatch(BaseRunsOn): # pragma: no cov
|
312
307
|
|
313
|
-
type:
|
314
|
-
default=RunsOn.AZ_BATCH, validate_default=True
|
315
|
-
)
|
308
|
+
type: RunsOn = AZ_BATCH
|
316
309
|
args: AzBatchArgs = Field(alias="with")
|
317
310
|
|
318
311
|
|
@@ -331,23 +324,21 @@ class DockerArgs(BaseModel):
|
|
331
324
|
class OnDocker(BaseRunsOn): # pragma: no cov
|
332
325
|
"""Runs-on Docker container."""
|
333
326
|
|
334
|
-
type:
|
335
|
-
|
336
|
-
)
|
337
|
-
args: DockerArgs = Field(alias="with", default_factory=DockerArgs)
|
327
|
+
type: RunsOn = DOCKER
|
328
|
+
args: DockerArgs = Field(default_factory=DockerArgs, alias="with")
|
338
329
|
|
339
330
|
|
340
331
|
def get_discriminator_runs_on(model: dict[str, Any]) -> RunsOn:
|
341
332
|
"""Get discriminator of the RunsOn models."""
|
342
333
|
t: str = model.get("type")
|
343
|
-
return RunsOn(t) if t else
|
334
|
+
return RunsOn(t) if t else LOCAL
|
344
335
|
|
345
336
|
|
346
337
|
RunsOnModel = Annotated[
|
347
338
|
Union[
|
348
|
-
Annotated[OnSelfHosted, Tag(
|
349
|
-
Annotated[OnDocker, Tag(
|
350
|
-
Annotated[OnLocal, Tag(
|
339
|
+
Annotated[OnSelfHosted, Tag(SELF_HOSTED)],
|
340
|
+
Annotated[OnDocker, Tag(DOCKER)],
|
341
|
+
Annotated[OnLocal, Tag(LOCAL)],
|
351
342
|
],
|
352
343
|
Discriminator(get_discriminator_runs_on),
|
353
344
|
]
|
@@ -490,6 +481,10 @@ class Job(BaseModel):
|
|
490
481
|
|
491
482
|
return self
|
492
483
|
|
484
|
+
@field_serializer("runs_on")
|
485
|
+
def __serialize_runs_on(self, value: RunsOnModel):
|
486
|
+
return value.model_dump(by_alias=True)
|
487
|
+
|
493
488
|
def stage(self, stage_id: str) -> Stage:
|
494
489
|
"""Return stage instance that exists in this job via passing an input
|
495
490
|
stage ID.
|
@@ -20,7 +20,6 @@ Functions:
|
|
20
20
|
from __future__ import annotations
|
21
21
|
|
22
22
|
from dataclasses import field
|
23
|
-
from datetime import datetime
|
24
23
|
from enum import Enum
|
25
24
|
from typing import Optional, Union
|
26
25
|
|
@@ -42,7 +41,7 @@ from . import (
|
|
42
41
|
from .__types import DictData
|
43
42
|
from .audits import Trace, get_trace
|
44
43
|
from .errors import ResultError
|
45
|
-
from .utils import default_gen_id
|
44
|
+
from .utils import default_gen_id
|
46
45
|
|
47
46
|
|
48
47
|
class Status(str, Enum):
|
@@ -105,8 +104,9 @@ def validate_statuses(statuses: list[Status]) -> Status:
|
|
105
104
|
"""Determine final status from multiple status values.
|
106
105
|
|
107
106
|
Applies workflow logic to determine the overall status based on a collection
|
108
|
-
of individual status values. Follows priority order:
|
109
|
-
|
107
|
+
of individual status values. Follows priority order:
|
108
|
+
|
109
|
+
CANCEL > FAILED > WAIT > individual status consistency.
|
110
110
|
|
111
111
|
Args:
|
112
112
|
statuses: List of status values to evaluate
|
@@ -132,7 +132,7 @@ def validate_statuses(statuses: list[Status]) -> Status:
|
|
132
132
|
for status in (SUCCESS, SKIP):
|
133
133
|
if all(s == status for s in statuses):
|
134
134
|
return status
|
135
|
-
return
|
135
|
+
return SUCCESS
|
136
136
|
|
137
137
|
|
138
138
|
def get_status_from_error(
|
@@ -166,10 +166,6 @@ def get_status_from_error(
|
|
166
166
|
return FAILED
|
167
167
|
|
168
168
|
|
169
|
-
def default_context() -> DictData:
|
170
|
-
return {"status": WAIT}
|
171
|
-
|
172
|
-
|
173
169
|
@dataclass(
|
174
170
|
config=ConfigDict(arbitrary_types_allowed=True, use_enum_values=True),
|
175
171
|
)
|
@@ -186,14 +182,13 @@ class Result:
|
|
186
182
|
field that keep dict value change its ID when update new value to it.
|
187
183
|
"""
|
188
184
|
|
185
|
+
extras: DictData = field(default_factory=dict, compare=False, repr=False)
|
189
186
|
status: Status = field(default=WAIT)
|
190
|
-
context: DictData = field(
|
187
|
+
context: Optional[DictData] = field(default=None)
|
191
188
|
info: DictData = field(default_factory=dict)
|
192
|
-
run_id:
|
189
|
+
run_id: str = field(default_factory=default_gen_id)
|
193
190
|
parent_run_id: Optional[str] = field(default=None)
|
194
|
-
ts: datetime = field(default_factory=get_dt_now, compare=False)
|
195
191
|
trace: Optional[Trace] = field(default=None, compare=False, repr=False)
|
196
|
-
extras: DictData = field(default_factory=dict, compare=False, repr=False)
|
197
192
|
|
198
193
|
@model_validator(mode="after")
|
199
194
|
def __prepare_trace(self) -> Self:
|
@@ -207,20 +202,18 @@ class Result:
|
|
207
202
|
parent_run_id=self.parent_run_id,
|
208
203
|
extras=self.extras,
|
209
204
|
)
|
210
|
-
return self
|
211
|
-
|
212
|
-
def set_parent_run_id(self, running_id: str) -> Self:
|
213
|
-
"""Set a parent running ID.
|
214
205
|
|
215
|
-
|
206
|
+
return self
|
216
207
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
208
|
+
@classmethod
|
209
|
+
def from_trace(cls, trace: Trace):
|
210
|
+
"""Construct the result model from trace for clean code objective."""
|
211
|
+
return cls(
|
212
|
+
run_id=trace.run_id,
|
213
|
+
parent_run_id=trace.parent_run_id,
|
214
|
+
extras=trace.extras,
|
215
|
+
trace=trace,
|
222
216
|
)
|
223
|
-
return self
|
224
217
|
|
225
218
|
def catch(
|
226
219
|
self,
|
@@ -237,7 +230,11 @@ class Result:
|
|
237
230
|
|
238
231
|
:rtype: Self
|
239
232
|
"""
|
240
|
-
self.__dict__["context"]
|
233
|
+
if self.__dict__["context"] is None:
|
234
|
+
self.__dict__["context"] = context
|
235
|
+
else:
|
236
|
+
self.__dict__["context"].update(context or {})
|
237
|
+
|
241
238
|
self.__dict__["status"] = (
|
242
239
|
Status(status) if isinstance(status, int) else status
|
243
240
|
)
|
@@ -262,13 +259,6 @@ class Result:
|
|
262
259
|
self.__dict__["info"].update(data)
|
263
260
|
return self
|
264
261
|
|
265
|
-
def alive_time(self) -> float: # pragma: no cov
|
266
|
-
"""Return total seconds that this object use since it was created.
|
267
|
-
|
268
|
-
:rtype: float
|
269
|
-
"""
|
270
|
-
return (get_dt_now() - self.ts).total_seconds()
|
271
|
-
|
272
262
|
|
273
263
|
def catch(
|
274
264
|
context: DictData,
|
@@ -276,7 +266,13 @@ def catch(
|
|
276
266
|
updated: DictData | None = None,
|
277
267
|
**kwargs,
|
278
268
|
) -> DictData:
|
279
|
-
"""Catch updated context to the current context.
|
269
|
+
"""Catch updated context to the current context.
|
270
|
+
|
271
|
+
Args:
|
272
|
+
context: A context data that want to be the current context.
|
273
|
+
status: A status enum object.
|
274
|
+
updated: A updated data that will update to the current context.
|
275
|
+
"""
|
280
276
|
context.update(updated or {})
|
281
277
|
context["status"] = Status(status) if isinstance(status, int) else status
|
282
278
|
|
@@ -289,7 +285,7 @@ def catch(
|
|
289
285
|
context[k].update(kwargs[k])
|
290
286
|
# NOTE: Exclude the `info` key for update information data.
|
291
287
|
elif k == "info":
|
292
|
-
context
|
288
|
+
context.update({"info": kwargs["info"]})
|
293
289
|
else:
|
294
290
|
raise ResultError(f"The key {k!r} does not exists on context data.")
|
295
291
|
return context
|
@@ -421,12 +421,13 @@ def param2template(
|
|
421
421
|
for k in value
|
422
422
|
}
|
423
423
|
elif isinstance(value, (list, tuple, set)):
|
424
|
-
|
425
|
-
|
424
|
+
try:
|
425
|
+
return type(value)(
|
426
426
|
param2template(i, params, context, filters, extras=extras)
|
427
427
|
for i in value
|
428
|
-
|
429
|
-
|
428
|
+
)
|
429
|
+
except TypeError:
|
430
|
+
return value
|
430
431
|
elif not isinstance(value, str):
|
431
432
|
return value
|
432
433
|
return str2template(
|
@@ -598,7 +599,7 @@ def make_registry(
|
|
598
599
|
if not (
|
599
600
|
hasattr(func, "tag")
|
600
601
|
and hasattr(func, "name")
|
601
|
-
and str(getattr(func, "mark", "
|
602
|
+
and str(getattr(func, "mark", "NOTSET")) == "tag"
|
602
603
|
): # pragma: no cov
|
603
604
|
continue
|
604
605
|
|
@@ -616,6 +617,7 @@ def make_registry(
|
|
616
617
|
f"{module}.{submodule}, you should change this tag name or "
|
617
618
|
f"change it func name."
|
618
619
|
)
|
620
|
+
|
619
621
|
rs[func.name][func.tag] = lazy(f"{module}.{submodule}.{fstr}")
|
620
622
|
|
621
623
|
return rs
|