ddeutil-workflow 0.0.77__py3-none-any.whl → 0.0.78__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/cli.py +6 -2
- ddeutil/workflow/conf.py +7 -7
- ddeutil/workflow/errors.py +13 -15
- ddeutil/workflow/event.py +22 -35
- ddeutil/workflow/job.py +16 -21
- ddeutil/workflow/result.py +30 -34
- ddeutil/workflow/reusables.py +2 -1
- ddeutil/workflow/stages.py +56 -32
- ddeutil/workflow/traces.py +2 -1
- ddeutil/workflow/utils.py +2 -2
- ddeutil/workflow/workflow.py +54 -36
- {ddeutil_workflow-0.0.77.dist-info → ddeutil_workflow-0.0.78.dist-info}/METADATA +1 -2
- ddeutil_workflow-0.0.78.dist-info/RECORD +30 -0
- ddeutil_workflow-0.0.77.dist-info/RECORD +0 -30
- {ddeutil_workflow-0.0.77.dist-info → ddeutil_workflow-0.0.78.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.77.dist-info → ddeutil_workflow-0.0.78.dist-info}/entry_points.txt +0 -0
- {ddeutil_workflow-0.0.77.dist-info → ddeutil_workflow-0.0.78.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.77.dist-info → ddeutil_workflow-0.0.78.dist-info}/top_level.txt +0 -0
ddeutil/workflow/__about__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__: str = "0.0.
|
1
|
+
__version__: str = "0.0.78"
|
ddeutil/workflow/cli.py
CHANGED
@@ -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:
|
ddeutil/workflow/conf.py
CHANGED
@@ -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,7 +320,7 @@ class YamlParser:
|
|
322
320
|
excluded: Optional[list[str]] = None,
|
323
321
|
extras: Optional[DictData] = None,
|
324
322
|
ignore_filename: Optional[str] = None,
|
325
|
-
tags: Optional[list[str]] = None,
|
323
|
+
tags: Optional[list[Union[str, int]]] = None,
|
326
324
|
) -> Iterator[tuple[str, DictData]]:
|
327
325
|
"""Find all data that match with object type in config path. This class
|
328
326
|
method can use include and exclude list of identity name for filter and
|
@@ -373,13 +371,15 @@ class YamlParser:
|
|
373
371
|
|
374
372
|
if (
|
375
373
|
tags
|
376
|
-
and (ts := data
|
377
|
-
and
|
378
|
-
|
379
|
-
): # pragma: no cov
|
374
|
+
and isinstance((ts := data.get("tags", [])), list)
|
375
|
+
and any(t not in ts for t in tags)
|
376
|
+
):
|
380
377
|
continue
|
381
378
|
|
382
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
|
383
383
|
marking: tuple[float, DictData] = (
|
384
384
|
file.lstat().st_mtime,
|
385
385
|
data,
|
ddeutil/workflow/errors.py
CHANGED
@@ -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__(
|
ddeutil/workflow/event.py
CHANGED
@@ -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
|
|
ddeutil/workflow/job.py
CHANGED
@@ -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.
|
ddeutil/workflow/result.py
CHANGED
@@ -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
|
ddeutil/workflow/reusables.py
CHANGED
@@ -599,7 +599,7 @@ def make_registry(
|
|
599
599
|
if not (
|
600
600
|
hasattr(func, "tag")
|
601
601
|
and hasattr(func, "name")
|
602
|
-
and str(getattr(func, "mark", "
|
602
|
+
and str(getattr(func, "mark", "NOTSET")) == "tag"
|
603
603
|
): # pragma: no cov
|
604
604
|
continue
|
605
605
|
|
@@ -617,6 +617,7 @@ def make_registry(
|
|
617
617
|
f"{module}.{submodule}, you should change this tag name or "
|
618
618
|
f"change it func name."
|
619
619
|
)
|
620
|
+
|
620
621
|
rs[func.name][func.tag] = lazy(f"{module}.{submodule}.{fstr}")
|
621
622
|
|
622
623
|
return rs
|
ddeutil/workflow/stages.py
CHANGED
@@ -77,7 +77,15 @@ from pathlib import Path
|
|
77
77
|
from subprocess import CompletedProcess
|
78
78
|
from textwrap import dedent
|
79
79
|
from threading import Event
|
80
|
-
from typing import
|
80
|
+
from typing import (
|
81
|
+
Annotated,
|
82
|
+
Any,
|
83
|
+
Callable,
|
84
|
+
Optional,
|
85
|
+
TypeVar,
|
86
|
+
Union,
|
87
|
+
get_type_hints,
|
88
|
+
)
|
81
89
|
|
82
90
|
from ddeutil.core import str2list
|
83
91
|
from pydantic import BaseModel, Field, ValidationError
|
@@ -177,7 +185,7 @@ class BaseStage(BaseModel, ABC):
|
|
177
185
|
"A stage description that use to logging when start execution."
|
178
186
|
),
|
179
187
|
)
|
180
|
-
condition:
|
188
|
+
condition: Optional[Union[str, bool]] = Field(
|
181
189
|
default=None,
|
182
190
|
description=(
|
183
191
|
"A stage condition statement to allow stage executable. This field "
|
@@ -513,6 +521,9 @@ class BaseStage(BaseModel, ABC):
|
|
513
521
|
if not self.condition:
|
514
522
|
return False
|
515
523
|
|
524
|
+
if isinstance(self.condition, bool):
|
525
|
+
return self.condition
|
526
|
+
|
516
527
|
try:
|
517
528
|
# WARNING: The eval build-in function is very dangerous. So, it
|
518
529
|
# should use the `re` module to validate eval-string before
|
@@ -1580,15 +1591,23 @@ class CallStage(BaseRetryStage):
|
|
1580
1591
|
|
1581
1592
|
:rtype: Any
|
1582
1593
|
"""
|
1583
|
-
if isinstance(value, dict)
|
1584
|
-
|
1585
|
-
|
1586
|
-
|
1587
|
-
|
1588
|
-
|
1589
|
-
|
1594
|
+
if isinstance(value, dict) and any(
|
1595
|
+
k in value for k in ("result", "extras")
|
1596
|
+
):
|
1597
|
+
raise ValueError(
|
1598
|
+
"The argument on workflow template for the caller stage "
|
1599
|
+
"should not pass `result` and `extras`. They are special "
|
1600
|
+
"arguments."
|
1601
|
+
)
|
1590
1602
|
return value
|
1591
1603
|
|
1604
|
+
def get_caller(self, params: DictData) -> Callable[[], TagFunc]:
|
1605
|
+
"""Get the lazy TagFuc object from registry."""
|
1606
|
+
return extract_call(
|
1607
|
+
param2template(self.uses, params, extras=self.extras),
|
1608
|
+
registries=self.extras.get("registry_caller"),
|
1609
|
+
)
|
1610
|
+
|
1592
1611
|
def process(
|
1593
1612
|
self,
|
1594
1613
|
params: DictData,
|
@@ -1615,11 +1634,7 @@ class CallStage(BaseRetryStage):
|
|
1615
1634
|
trace: Trace = get_trace(
|
1616
1635
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
1617
1636
|
)
|
1618
|
-
call_func: TagFunc =
|
1619
|
-
param2template(self.uses, params, extras=self.extras),
|
1620
|
-
registries=self.extras.get("registry_caller"),
|
1621
|
-
)()
|
1622
|
-
|
1637
|
+
call_func: TagFunc = self.get_caller(params=params)()
|
1623
1638
|
trace.info(f"[STAGE]: Caller Func: '{call_func.name}@{call_func.tag}'")
|
1624
1639
|
|
1625
1640
|
# VALIDATE: check input task caller parameters that exists before
|
@@ -1677,7 +1692,7 @@ class CallStage(BaseRetryStage):
|
|
1677
1692
|
)
|
1678
1693
|
|
1679
1694
|
args: DictData = self.validate_model_args(
|
1680
|
-
call_func, args, run_id, parent_run_id
|
1695
|
+
call_func, args, run_id, parent_run_id, extras=self.extras
|
1681
1696
|
)
|
1682
1697
|
if inspect.iscoroutinefunction(call_func):
|
1683
1698
|
loop = asyncio.get_event_loop()
|
@@ -1738,11 +1753,7 @@ class CallStage(BaseRetryStage):
|
|
1738
1753
|
trace: Trace = get_trace(
|
1739
1754
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
1740
1755
|
)
|
1741
|
-
call_func: TagFunc =
|
1742
|
-
param2template(self.uses, params, extras=self.extras),
|
1743
|
-
registries=self.extras.get("registry_caller"),
|
1744
|
-
)()
|
1745
|
-
|
1756
|
+
call_func: TagFunc = self.get_caller(params=params)()
|
1746
1757
|
await trace.ainfo(
|
1747
1758
|
f"[STAGE]: Caller Func: '{call_func.name}@{call_func.tag}'"
|
1748
1759
|
)
|
@@ -1795,8 +1806,13 @@ class CallStage(BaseRetryStage):
|
|
1795
1806
|
if "extras" not in sig.parameters and not has_keyword:
|
1796
1807
|
args.pop("extras")
|
1797
1808
|
|
1809
|
+
if event and event.is_set():
|
1810
|
+
raise StageCancelError(
|
1811
|
+
"Execution was canceled from the event before start parallel."
|
1812
|
+
)
|
1813
|
+
|
1798
1814
|
args: DictData = self.validate_model_args(
|
1799
|
-
call_func, args, run_id, parent_run_id
|
1815
|
+
call_func, args, run_id, parent_run_id, extras=self.extras
|
1800
1816
|
)
|
1801
1817
|
if inspect.iscoroutinefunction(call_func):
|
1802
1818
|
rs: DictOrModel = await call_func(
|
@@ -1829,12 +1845,13 @@ class CallStage(BaseRetryStage):
|
|
1829
1845
|
extras=self.extras,
|
1830
1846
|
)
|
1831
1847
|
|
1848
|
+
@staticmethod
|
1832
1849
|
def validate_model_args(
|
1833
|
-
self,
|
1834
1850
|
func: TagFunc,
|
1835
1851
|
args: DictData,
|
1836
1852
|
run_id: str,
|
1837
1853
|
parent_run_id: Optional[str] = None,
|
1854
|
+
extras: Optional[DictData] = None,
|
1838
1855
|
) -> DictData:
|
1839
1856
|
"""Validate an input arguments before passing to the caller function.
|
1840
1857
|
|
@@ -1846,18 +1863,18 @@ class CallStage(BaseRetryStage):
|
|
1846
1863
|
:rtype: DictData
|
1847
1864
|
"""
|
1848
1865
|
try:
|
1849
|
-
|
1850
|
-
func
|
1851
|
-
)
|
1852
|
-
override: DictData = dict(model_instance)
|
1866
|
+
override: DictData = dict(
|
1867
|
+
create_model_from_caller(func).model_validate(args)
|
1868
|
+
)
|
1853
1869
|
args.update(override)
|
1870
|
+
|
1854
1871
|
type_hints: dict[str, Any] = get_type_hints(func)
|
1855
1872
|
for arg in type_hints:
|
1856
1873
|
|
1857
1874
|
if arg == "return":
|
1858
1875
|
continue
|
1859
1876
|
|
1860
|
-
if arg.removeprefix("_") in args:
|
1877
|
+
if arg.startswith("_") and arg.removeprefix("_") in args:
|
1861
1878
|
args[arg] = args.pop(arg.removeprefix("_"))
|
1862
1879
|
continue
|
1863
1880
|
|
@@ -1868,7 +1885,7 @@ class CallStage(BaseRetryStage):
|
|
1868
1885
|
) from e
|
1869
1886
|
except TypeError as e:
|
1870
1887
|
trace: Trace = get_trace(
|
1871
|
-
run_id, parent_run_id=parent_run_id, extras=
|
1888
|
+
run_id, parent_run_id=parent_run_id, extras=extras
|
1872
1889
|
)
|
1873
1890
|
trace.warning(
|
1874
1891
|
f"[STAGE]: Get type hint raise TypeError: {e}, so, it skip "
|
@@ -2295,7 +2312,13 @@ class ForEachStage(BaseNestedStage):
|
|
2295
2312
|
... }
|
2296
2313
|
"""
|
2297
2314
|
|
2298
|
-
foreach: Union[
|
2315
|
+
foreach: Union[
|
2316
|
+
list[str],
|
2317
|
+
list[int],
|
2318
|
+
str,
|
2319
|
+
dict[str, Any],
|
2320
|
+
dict[int, Any],
|
2321
|
+
] = Field(
|
2299
2322
|
description=(
|
2300
2323
|
"A items for passing to stages via ${{ item }} template parameter."
|
2301
2324
|
),
|
@@ -2501,7 +2524,7 @@ class ForEachStage(BaseNestedStage):
|
|
2501
2524
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
2502
2525
|
)
|
2503
2526
|
event: Event = event or Event()
|
2504
|
-
foreach: Union[list[str], list[int]] = pass_env(
|
2527
|
+
foreach: Union[list[str], list[int], str] = pass_env(
|
2505
2528
|
param2template(self.foreach, params, extras=self.extras)
|
2506
2529
|
)
|
2507
2530
|
|
@@ -2516,9 +2539,10 @@ class ForEachStage(BaseNestedStage):
|
|
2516
2539
|
) from e
|
2517
2540
|
|
2518
2541
|
# [VALIDATE]: Type of the foreach should be `list` type.
|
2519
|
-
elif
|
2542
|
+
elif isinstance(foreach, dict):
|
2520
2543
|
raise TypeError(
|
2521
|
-
f"Does not support foreach: {foreach!r} ({type(foreach)})"
|
2544
|
+
f"Does not support dict foreach: {foreach!r} ({type(foreach)}) "
|
2545
|
+
f"yet."
|
2522
2546
|
)
|
2523
2547
|
# [Validate]: Value in the foreach item should not be duplicate when the
|
2524
2548
|
# `use_index_as_key` field did not set.
|
ddeutil/workflow/traces.py
CHANGED
@@ -32,6 +32,7 @@ Example:
|
|
32
32
|
>>> trace = get_trace("running-id-101", parent_run_id="workflow-001")
|
33
33
|
>>> trace.info("Workflow execution started")
|
34
34
|
>>> trace.debug("Processing stage 1")
|
35
|
+
|
35
36
|
"""
|
36
37
|
from __future__ import annotations
|
37
38
|
|
@@ -848,7 +849,7 @@ def get_trace(
|
|
848
849
|
:rtype: Trace
|
849
850
|
"""
|
850
851
|
# NOTE: Allow you to override trace model by the extra parameter.
|
851
|
-
map_trace_models: dict[str, type[Trace]] = extras.get(
|
852
|
+
map_trace_models: dict[str, type[Trace]] = (extras or {}).get(
|
852
853
|
"trace_model_mapping", {}
|
853
854
|
)
|
854
855
|
url: ParseResult
|
ddeutil/workflow/utils.py
CHANGED
@@ -247,7 +247,7 @@ def default_gen_id() -> str:
|
|
247
247
|
|
248
248
|
:rtype: str
|
249
249
|
"""
|
250
|
-
return gen_id("
|
250
|
+
return gen_id("MOCK", unique=True)
|
251
251
|
|
252
252
|
|
253
253
|
def make_exec(path: Union[Path, str]) -> None:
|
@@ -352,7 +352,7 @@ def dump_all(
|
|
352
352
|
|
353
353
|
def obj_name(obj: Optional[Union[str, object]] = None) -> Optional[str]:
|
354
354
|
if not obj:
|
355
|
-
|
355
|
+
return None
|
356
356
|
elif isinstance(obj, str):
|
357
357
|
obj_type: str = obj
|
358
358
|
elif isclass(obj):
|
ddeutil/workflow/workflow.py
CHANGED
@@ -145,7 +145,7 @@ class Workflow(BaseModel):
|
|
145
145
|
description="A parameters that need to use on this workflow.",
|
146
146
|
)
|
147
147
|
on: Event = Field(
|
148
|
-
default_factory=
|
148
|
+
default_factory=Event,
|
149
149
|
description="An events for this workflow.",
|
150
150
|
)
|
151
151
|
jobs: dict[str, Job] = Field(
|
@@ -211,11 +211,6 @@ class Workflow(BaseModel):
|
|
211
211
|
```
|
212
212
|
"""
|
213
213
|
load: YamlParser = YamlParser(name, path=path, extras=extras, obj=cls)
|
214
|
-
|
215
|
-
# NOTE: Validate the config type match with current connection model
|
216
|
-
if load.type != cls.__name__:
|
217
|
-
raise ValueError(f"Type {load.type} does not match with {cls}")
|
218
|
-
|
219
214
|
data: DictData = copy.deepcopy(load.data)
|
220
215
|
data["name"] = name
|
221
216
|
|
@@ -289,6 +284,50 @@ class Workflow(BaseModel):
|
|
289
284
|
|
290
285
|
return self
|
291
286
|
|
287
|
+
def detail(self) -> DictData: # pragma: no cov
|
288
|
+
"""Return the detail of this workflow for generate markdown."""
|
289
|
+
return self.model_dump(by_alias=True)
|
290
|
+
|
291
|
+
def md(self, author: Optional[str] = None) -> str: # pragma: no cov
|
292
|
+
"""Generate the markdown template."""
|
293
|
+
|
294
|
+
def align_newline(value: str) -> str:
|
295
|
+
return value.rstrip("\n").replace("\n", "\n ")
|
296
|
+
|
297
|
+
info: str = (
|
298
|
+
f"| Author: {author or 'nobody'} "
|
299
|
+
f"| created_at: `{self.created_at:%Y-%m-%d %H:%M:%S}` "
|
300
|
+
f"| updated_at: `{self.updated_dt:%Y-%m-%d %H:%M:%S}` |\n"
|
301
|
+
f"| --- | --- | --- |"
|
302
|
+
)
|
303
|
+
jobs: str = ""
|
304
|
+
for job in self.jobs:
|
305
|
+
job_model: Job = self.jobs[job]
|
306
|
+
jobs += f"### {job}\n{job_model.desc or ''}\n"
|
307
|
+
stags: str = ""
|
308
|
+
for stage_model in job_model.stages:
|
309
|
+
stags += (
|
310
|
+
f"#### {stage_model.name}\n\n"
|
311
|
+
f"Stage ID: {stage_model.id or ''}\n"
|
312
|
+
f"Stage Model: {stage_model.__class__.__name__}\n\n"
|
313
|
+
)
|
314
|
+
jobs += f"{stags}\n"
|
315
|
+
return dedent(
|
316
|
+
f"""
|
317
|
+
# Workflow: {self.name}\n
|
318
|
+
{align_newline(info)}\n
|
319
|
+
{align_newline(self.desc)}\n
|
320
|
+
## Parameters\n
|
321
|
+
| name | type | default | description |
|
322
|
+
| --- | --- | --- | : --- : |
|
323
|
+
|
324
|
+
## Jobs\n
|
325
|
+
{align_newline(jobs)}
|
326
|
+
""".lstrip(
|
327
|
+
"\n"
|
328
|
+
)
|
329
|
+
)
|
330
|
+
|
292
331
|
def job(self, name: str) -> Job:
|
293
332
|
"""Return the workflow's Job model that getting by an input job's name
|
294
333
|
or job's ID. This method will pass an extra parameter from this model
|
@@ -377,7 +416,7 @@ class Workflow(BaseModel):
|
|
377
416
|
dt = dt.replace(tzinfo=UTC)
|
378
417
|
|
379
418
|
release: datetime = replace_sec(dt.astimezone(UTC))
|
380
|
-
if not self.on:
|
419
|
+
if not self.on.schedule:
|
381
420
|
return release
|
382
421
|
|
383
422
|
for on in self.on.schedule:
|
@@ -871,7 +910,7 @@ class Workflow(BaseModel):
|
|
871
910
|
event: Optional[ThreadEvent] = None,
|
872
911
|
timeout: float = 3600,
|
873
912
|
max_job_parallel: int = 2,
|
874
|
-
) -> Result:
|
913
|
+
) -> Result: # pragma: no cov
|
875
914
|
"""Re-Execute workflow with passing the error context data.
|
876
915
|
|
877
916
|
:param context: A context result that get the failed status.
|
@@ -900,12 +939,9 @@ class Workflow(BaseModel):
|
|
900
939
|
"[WORKFLOW]: Does not rerun because it already executed with "
|
901
940
|
"success status."
|
902
941
|
)
|
903
|
-
return Result(
|
904
|
-
run_id=run_id,
|
905
|
-
parent_run_id=parent_run_id,
|
942
|
+
return Result.from_trace(trace).catch(
|
906
943
|
status=SUCCESS,
|
907
944
|
context=catch(context=context, status=SUCCESS),
|
908
|
-
extras=self.extras,
|
909
945
|
)
|
910
946
|
|
911
947
|
err: dict[str, str] = context.get("errors", {})
|
@@ -921,12 +957,9 @@ class Workflow(BaseModel):
|
|
921
957
|
)
|
922
958
|
if not self.jobs:
|
923
959
|
trace.warning(f"[WORKFLOW]: {self.name!r} does not set jobs")
|
924
|
-
return Result(
|
925
|
-
run_id=run_id,
|
926
|
-
parent_run_id=parent_run_id,
|
960
|
+
return Result.from_trace(trace).catch(
|
927
961
|
status=SUCCESS,
|
928
962
|
context=catch(context=context, status=SUCCESS),
|
929
|
-
extras=self.extras,
|
930
963
|
)
|
931
964
|
|
932
965
|
# NOTE: Prepare the new context variable for rerun process.
|
@@ -951,12 +984,9 @@ class Workflow(BaseModel):
|
|
951
984
|
"[WORKFLOW]: It does not have job to rerun. it will change "
|
952
985
|
"status to skip."
|
953
986
|
)
|
954
|
-
return Result(
|
955
|
-
run_id=run_id,
|
956
|
-
parent_run_id=parent_run_id,
|
987
|
+
return Result.from_trace(trace).catch(
|
957
988
|
status=SKIP,
|
958
989
|
context=catch(context=context, status=SKIP),
|
959
|
-
extras=self.extras,
|
960
990
|
)
|
961
991
|
|
962
992
|
not_timeout_flag: bool = True
|
@@ -969,9 +999,7 @@ class Workflow(BaseModel):
|
|
969
999
|
|
970
1000
|
catch(context, status=WAIT)
|
971
1001
|
if event and event.is_set():
|
972
|
-
return Result(
|
973
|
-
run_id=run_id,
|
974
|
-
parent_run_id=parent_run_id,
|
1002
|
+
return Result.from_trace(trace).catch(
|
975
1003
|
status=CANCEL,
|
976
1004
|
context=catch(
|
977
1005
|
context,
|
@@ -983,7 +1011,6 @@ class Workflow(BaseModel):
|
|
983
1011
|
).to_dict(),
|
984
1012
|
},
|
985
1013
|
),
|
986
|
-
extras=self.extras,
|
987
1014
|
)
|
988
1015
|
|
989
1016
|
with ThreadPoolExecutor(max_job_parallel, "wf") as executor:
|
@@ -1011,9 +1038,7 @@ class Workflow(BaseModel):
|
|
1011
1038
|
backoff_sleep = 0.01
|
1012
1039
|
|
1013
1040
|
if check == FAILED: # pragma: no cov
|
1014
|
-
return Result(
|
1015
|
-
run_id=run_id,
|
1016
|
-
parent_run_id=parent_run_id,
|
1041
|
+
return Result.from_trace(trace).catch(
|
1017
1042
|
status=FAILED,
|
1018
1043
|
context=catch(
|
1019
1044
|
context,
|
@@ -1026,7 +1051,6 @@ class Workflow(BaseModel):
|
|
1026
1051
|
).to_dict(),
|
1027
1052
|
},
|
1028
1053
|
),
|
1029
|
-
extras=self.extras,
|
1030
1054
|
)
|
1031
1055
|
elif check == SKIP: # pragma: no cov
|
1032
1056
|
trace.info(
|
@@ -1102,12 +1126,9 @@ class Workflow(BaseModel):
|
|
1102
1126
|
statuses[total + 1 + skip_count + i] = s
|
1103
1127
|
|
1104
1128
|
st: Status = validate_statuses(statuses)
|
1105
|
-
return Result(
|
1106
|
-
run_id=run_id,
|
1107
|
-
parent_run_id=parent_run_id,
|
1129
|
+
return Result.from_trace(trace).catch(
|
1108
1130
|
status=st,
|
1109
1131
|
context=catch(context, status=st),
|
1110
|
-
extras=self.extras,
|
1111
1132
|
)
|
1112
1133
|
|
1113
1134
|
event.set()
|
@@ -1121,9 +1142,7 @@ class Workflow(BaseModel):
|
|
1121
1142
|
|
1122
1143
|
time.sleep(0.0025)
|
1123
1144
|
|
1124
|
-
return Result(
|
1125
|
-
run_id=run_id,
|
1126
|
-
parent_run_id=parent_run_id,
|
1145
|
+
return Result.from_trace(trace).catch(
|
1127
1146
|
status=FAILED,
|
1128
1147
|
context=catch(
|
1129
1148
|
context,
|
@@ -1135,5 +1154,4 @@ class Workflow(BaseModel):
|
|
1135
1154
|
).to_dict(),
|
1136
1155
|
},
|
1137
1156
|
),
|
1138
|
-
extras=self.extras,
|
1139
1157
|
)
|
@@ -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
|
|
@@ -0,0 +1,30 @@
|
|
1
|
+
ddeutil/workflow/__about__.py,sha256=9g_DJubkaHxt4W_0-r1PmUT5x3bCo_72c-f60EDtlq8,28
|
2
|
+
ddeutil/workflow/__cron.py,sha256=avOagaHl9xXOmizeRWm13cOrty9Tw0vRjFq-xoEgpAY,29167
|
3
|
+
ddeutil/workflow/__init__.py,sha256=_8sP-CTPOfwsFFhmdwQ2Gp7yY7qJemP7TYsIWgd5jc0,3300
|
4
|
+
ddeutil/workflow/__main__.py,sha256=Qd-f8z2Q2vpiEP2x6PBFsJrpACWDVxFKQk820MhFmHo,59
|
5
|
+
ddeutil/workflow/__types.py,sha256=tA2vsr6mzTSzbWB1sb62c5GgxODlfVRz6FvgLNJtQao,4788
|
6
|
+
ddeutil/workflow/audits.py,sha256=wANG0jEQ7slUSgVZG4JbjlR5PtmF8mHpM9RH-zpYM_g,12679
|
7
|
+
ddeutil/workflow/cli.py,sha256=WnUkxqs2hCc5JVuTvWuEGKp8_EcS_wmhVvSXDhj0eEM,7544
|
8
|
+
ddeutil/workflow/conf.py,sha256=7yMSBVh2W9KcDOZxQjeabJZizB_3ydCLICo4JI0syWU,16892
|
9
|
+
ddeutil/workflow/errors.py,sha256=UpUIqoyqkvzqjuxtUQ9535l1HeAsyh-plEG0PgDVR2w,5541
|
10
|
+
ddeutil/workflow/event.py,sha256=6d5_UvnPI8xRLcX_5wptmvWoXUIGs_JZbjq7khz5oYE,13355
|
11
|
+
ddeutil/workflow/job.py,sha256=4_IxtoIwl1qVuSaNngelYNkqQl9ZOa-PHdKCnXvXu0M,43943
|
12
|
+
ddeutil/workflow/params.py,sha256=Cyz142OcvENIZrM7Efc2xuGPmmFBhROifP5ojoaCezg,13658
|
13
|
+
ddeutil/workflow/result.py,sha256=Xi07E3WuMHS1jLcJg7p4DPuaMFGp0yEDaWCJRotOH6g,8921
|
14
|
+
ddeutil/workflow/reusables.py,sha256=pbCHsEl2V3jGWDRcGyxDvGN5rP5kaRxZNgv9x6-pINQ,23338
|
15
|
+
ddeutil/workflow/stages.py,sha256=ocsk64duS4BHEOLT3Agkx-fbY6iYmygxCaQhwp6YyyM,122482
|
16
|
+
ddeutil/workflow/traces.py,sha256=h7oDlb4Q8LJUp0pste2dWJYOqEaN64KsLTNMfeRUqx8,28475
|
17
|
+
ddeutil/workflow/utils.py,sha256=vSGdpaFgQ5vUPxWvVfbNC2__tu5q16B9mhx1BRbEuJo,10968
|
18
|
+
ddeutil/workflow/workflow.py,sha256=0KaRCTAipPvvmi9SOC7T0V0CAusgebCM6qju0ethGF0,42409
|
19
|
+
ddeutil/workflow/api/__init__.py,sha256=5DzYL3ngceoRshh5HYCSVWChqNJSiP01E1bEd8XxPi0,4799
|
20
|
+
ddeutil/workflow/api/log_conf.py,sha256=WfS3udDLSyrP-C80lWOvxxmhd_XWKvQPkwDqKblcH3E,1834
|
21
|
+
ddeutil/workflow/api/routes/__init__.py,sha256=JRaJZB0D6mgR17MbZo8yLtdYDtD62AA8MdKlFqhG84M,420
|
22
|
+
ddeutil/workflow/api/routes/job.py,sha256=-lbZ_hS9pEdSy6zeke5qrXEgdNxtQ2w9in7cHuM2Jzs,2536
|
23
|
+
ddeutil/workflow/api/routes/logs.py,sha256=RiZ62eQVMWArPHE3lpan955U4DdLLkethlvSMlwF7Mg,5312
|
24
|
+
ddeutil/workflow/api/routes/workflows.py,sha256=1Mqx4Hft4uJglgJI-Wcw-JzkhomFYZrtP0DnQDBkAFQ,4410
|
25
|
+
ddeutil_workflow-0.0.78.dist-info/licenses/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
|
26
|
+
ddeutil_workflow-0.0.78.dist-info/METADATA,sha256=2phJ3JRH9o0Sbr22iOo1YeQBr8nyYx6areM2rwNp_Eg,15681
|
27
|
+
ddeutil_workflow-0.0.78.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
28
|
+
ddeutil_workflow-0.0.78.dist-info/entry_points.txt,sha256=qDTpPSauL0ciO6T4iSVt8bJeYrVEkkoEEw_RlGx6Kgk,63
|
29
|
+
ddeutil_workflow-0.0.78.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
|
30
|
+
ddeutil_workflow-0.0.78.dist-info/RECORD,,
|
@@ -1,30 +0,0 @@
|
|
1
|
-
ddeutil/workflow/__about__.py,sha256=aU8Cs3gP8PwasjEnV2TWve1NsqO6d4yk5KpYOnhSHz0,28
|
2
|
-
ddeutil/workflow/__cron.py,sha256=avOagaHl9xXOmizeRWm13cOrty9Tw0vRjFq-xoEgpAY,29167
|
3
|
-
ddeutil/workflow/__init__.py,sha256=_8sP-CTPOfwsFFhmdwQ2Gp7yY7qJemP7TYsIWgd5jc0,3300
|
4
|
-
ddeutil/workflow/__main__.py,sha256=Qd-f8z2Q2vpiEP2x6PBFsJrpACWDVxFKQk820MhFmHo,59
|
5
|
-
ddeutil/workflow/__types.py,sha256=tA2vsr6mzTSzbWB1sb62c5GgxODlfVRz6FvgLNJtQao,4788
|
6
|
-
ddeutil/workflow/audits.py,sha256=wANG0jEQ7slUSgVZG4JbjlR5PtmF8mHpM9RH-zpYM_g,12679
|
7
|
-
ddeutil/workflow/cli.py,sha256=eAwRZMSEJu-NONc_un0D_1swFlENMjl3C-iXYnyTTPY,7411
|
8
|
-
ddeutil/workflow/conf.py,sha256=CL_LFJyrocNCSGN9NwErVAtN_JdetcBqeYIJ9x7e2nE,16796
|
9
|
-
ddeutil/workflow/errors.py,sha256=tHS6ekxBmZ6sIeLaxHHSaMfhVvlWnndfb2-Aq-bL2So,5509
|
10
|
-
ddeutil/workflow/event.py,sha256=siChcBhsu4ejzW1fK0tjHPXQVaSUCSxPYDgDrh6duwo,13676
|
11
|
-
ddeutil/workflow/job.py,sha256=_NOPWPs2FuiMvNE-L6c9mpXEChXmgQ8zmD33ZzqVi0A,44146
|
12
|
-
ddeutil/workflow/params.py,sha256=Cyz142OcvENIZrM7Efc2xuGPmmFBhROifP5ojoaCezg,13658
|
13
|
-
ddeutil/workflow/result.py,sha256=Fz6y6apivLW-94gAxcT42z-mGqWMk6-O3RJ2GGSNUHM,9146
|
14
|
-
ddeutil/workflow/reusables.py,sha256=3_TV3lpwzqW2lnBJbgt9MkPXk8lFvp2NhYSCdjyOQI8,23338
|
15
|
-
ddeutil/workflow/stages.py,sha256=KchpcPSgrkvPHhpF1YYNOclk1nhdpLL-AG1M71G6QV8,121972
|
16
|
-
ddeutil/workflow/traces.py,sha256=0n6Mytp6oeNjOV8lIsFitzZ6TrtuSNVFkUmodBiE_vA,28466
|
17
|
-
ddeutil/workflow/utils.py,sha256=N8dVsBYOBVXdFOlUETo7zPFefqp3w0XK1940s7k7iOE,10989
|
18
|
-
ddeutil/workflow/workflow.py,sha256=iQ9z6eOoj-66w6p8wGu28doQGGnL569BcjI3dTK561o,41616
|
19
|
-
ddeutil/workflow/api/__init__.py,sha256=5DzYL3ngceoRshh5HYCSVWChqNJSiP01E1bEd8XxPi0,4799
|
20
|
-
ddeutil/workflow/api/log_conf.py,sha256=WfS3udDLSyrP-C80lWOvxxmhd_XWKvQPkwDqKblcH3E,1834
|
21
|
-
ddeutil/workflow/api/routes/__init__.py,sha256=JRaJZB0D6mgR17MbZo8yLtdYDtD62AA8MdKlFqhG84M,420
|
22
|
-
ddeutil/workflow/api/routes/job.py,sha256=-lbZ_hS9pEdSy6zeke5qrXEgdNxtQ2w9in7cHuM2Jzs,2536
|
23
|
-
ddeutil/workflow/api/routes/logs.py,sha256=RiZ62eQVMWArPHE3lpan955U4DdLLkethlvSMlwF7Mg,5312
|
24
|
-
ddeutil/workflow/api/routes/workflows.py,sha256=1Mqx4Hft4uJglgJI-Wcw-JzkhomFYZrtP0DnQDBkAFQ,4410
|
25
|
-
ddeutil_workflow-0.0.77.dist-info/licenses/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
|
26
|
-
ddeutil_workflow-0.0.77.dist-info/METADATA,sha256=hplZltt-c1l8T6x2nJidJ-2U79x-6MhS5c5hXws1oGo,15781
|
27
|
-
ddeutil_workflow-0.0.77.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
28
|
-
ddeutil_workflow-0.0.77.dist-info/entry_points.txt,sha256=qDTpPSauL0ciO6T4iSVt8bJeYrVEkkoEEw_RlGx6Kgk,63
|
29
|
-
ddeutil_workflow-0.0.77.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
|
30
|
-
ddeutil_workflow-0.0.77.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|