ddeutil-workflow 0.0.77__py3-none-any.whl → 0.0.79__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ddeutil/workflow/__about__.py +1 -1
- ddeutil/workflow/__init__.py +1 -5
- ddeutil/workflow/api/routes/job.py +2 -2
- ddeutil/workflow/audits.py +554 -112
- ddeutil/workflow/cli.py +25 -3
- ddeutil/workflow/conf.py +16 -28
- ddeutil/workflow/errors.py +13 -15
- ddeutil/workflow/event.py +37 -41
- ddeutil/workflow/job.py +161 -92
- ddeutil/workflow/params.py +172 -58
- ddeutil/workflow/plugins/__init__.py +0 -0
- ddeutil/workflow/plugins/providers/__init__.py +0 -0
- ddeutil/workflow/plugins/providers/aws.py +908 -0
- ddeutil/workflow/plugins/providers/az.py +1003 -0
- ddeutil/workflow/plugins/providers/container.py +703 -0
- ddeutil/workflow/plugins/providers/gcs.py +826 -0
- ddeutil/workflow/result.py +35 -37
- ddeutil/workflow/reusables.py +153 -96
- ddeutil/workflow/stages.py +84 -60
- ddeutil/workflow/traces.py +1660 -521
- ddeutil/workflow/utils.py +111 -69
- ddeutil/workflow/workflow.py +74 -47
- {ddeutil_workflow-0.0.77.dist-info → ddeutil_workflow-0.0.79.dist-info}/METADATA +52 -20
- ddeutil_workflow-0.0.79.dist-info/RECORD +36 -0
- ddeutil_workflow-0.0.77.dist-info/RECORD +0 -30
- {ddeutil_workflow-0.0.77.dist-info → ddeutil_workflow-0.0.79.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.77.dist-info → ddeutil_workflow-0.0.79.dist-info}/entry_points.txt +0 -0
- {ddeutil_workflow-0.0.77.dist-info → ddeutil_workflow-0.0.79.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.77.dist-info → ddeutil_workflow-0.0.79.dist-info}/top_level.txt +0 -0
ddeutil/workflow/utils.py
CHANGED
@@ -10,16 +10,24 @@ system for ID generation, datetime handling, string processing, template
|
|
10
10
|
operations, and other common tasks.
|
11
11
|
|
12
12
|
Functions:
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
delay: Create delays in execution
|
18
|
-
to_train: Convert strings to train-case format
|
13
|
+
to_train: Convert camel case strings to train case format
|
14
|
+
prepare_newline: Format messages with multiple newlines
|
15
|
+
replace_sec: Replace seconds and microseconds in datetime objects
|
16
|
+
clear_tz: Clear timezone info from datetime objects
|
19
17
|
get_dt_now: Get current datetime with timezone
|
20
18
|
get_d_now: Get current date
|
19
|
+
get_diff_sec: Calculate time difference in seconds
|
20
|
+
reach_next_minute: Check if datetime reaches next minute
|
21
|
+
wait_until_next_minute: Wait until next minute
|
22
|
+
delay: Add random delay to execution
|
23
|
+
gen_id: Generate unique identifiers for workflow components
|
24
|
+
default_gen_id: Generate default running ID
|
25
|
+
make_exec: Make files executable
|
26
|
+
filter_func: Filter function objects from data structures
|
21
27
|
cross_product: Generate cross product of matrix values
|
22
|
-
|
28
|
+
cut_id: Cut running ID to specified length
|
29
|
+
dump_all: Serialize nested BaseModel objects to dictionaries
|
30
|
+
obj_name: Get object name or class name
|
23
31
|
|
24
32
|
Example:
|
25
33
|
```python
|
@@ -116,10 +124,11 @@ def clear_tz(dt: datetime) -> datetime:
|
|
116
124
|
def get_dt_now(offset: float = 0.0) -> datetime:
|
117
125
|
"""Return the current datetime object.
|
118
126
|
|
119
|
-
:
|
127
|
+
Args:
|
128
|
+
offset: An offset second value to subtract from current time.
|
120
129
|
|
121
|
-
:
|
122
|
-
|
130
|
+
Returns:
|
131
|
+
datetime: The current datetime object with UTC timezone.
|
123
132
|
"""
|
124
133
|
return datetime.now().replace(tzinfo=UTC) - timedelta(seconds=offset)
|
125
134
|
|
@@ -127,10 +136,11 @@ def get_dt_now(offset: float = 0.0) -> datetime:
|
|
127
136
|
def get_d_now(offset: float = 0.0) -> date: # pragma: no cov
|
128
137
|
"""Return the current date object.
|
129
138
|
|
130
|
-
:
|
139
|
+
Args:
|
140
|
+
offset: An offset second value to subtract from current time.
|
131
141
|
|
132
|
-
:
|
133
|
-
|
142
|
+
Returns:
|
143
|
+
date: The current date object.
|
134
144
|
"""
|
135
145
|
return (
|
136
146
|
datetime.now().replace(tzinfo=UTC) - timedelta(seconds=offset)
|
@@ -138,13 +148,14 @@ def get_d_now(offset: float = 0.0) -> date: # pragma: no cov
|
|
138
148
|
|
139
149
|
|
140
150
|
def get_diff_sec(dt: datetime, offset: float = 0.0) -> int:
|
141
|
-
"""Return second value
|
142
|
-
current datetime with specific timezone.
|
151
|
+
"""Return second value from difference between input datetime and current datetime.
|
143
152
|
|
144
|
-
:
|
145
|
-
|
153
|
+
Args:
|
154
|
+
dt: A datetime object to calculate difference from.
|
155
|
+
offset: An offset second value to add to the difference.
|
146
156
|
|
147
|
-
:
|
157
|
+
Returns:
|
158
|
+
int: The difference in seconds between the input datetime and current time.
|
148
159
|
"""
|
149
160
|
return round(
|
150
161
|
(
|
@@ -154,11 +165,17 @@ def get_diff_sec(dt: datetime, offset: float = 0.0) -> int:
|
|
154
165
|
|
155
166
|
|
156
167
|
def reach_next_minute(dt: datetime, offset: float = 0.0) -> bool:
|
157
|
-
"""Check
|
158
|
-
datetime.
|
168
|
+
"""Check if datetime object reaches the next minute level.
|
159
169
|
|
160
|
-
:
|
161
|
-
|
170
|
+
Args:
|
171
|
+
dt: A datetime object to check.
|
172
|
+
offset: An offset second value.
|
173
|
+
|
174
|
+
Returns:
|
175
|
+
bool: True if datetime reaches next minute, False otherwise.
|
176
|
+
|
177
|
+
Raises:
|
178
|
+
ValueError: If the input datetime is less than current date.
|
162
179
|
"""
|
163
180
|
diff: float = (
|
164
181
|
replace_sec(clear_tz(dt)) - replace_sec(get_dt_now(offset=offset))
|
@@ -176,16 +193,21 @@ def reach_next_minute(dt: datetime, offset: float = 0.0) -> bool:
|
|
176
193
|
def wait_until_next_minute(
|
177
194
|
dt: datetime, second: float = 0
|
178
195
|
) -> None: # pragma: no cov
|
179
|
-
"""Wait with sleep to the next minute with an offset second value.
|
196
|
+
"""Wait with sleep to the next minute with an offset second value.
|
197
|
+
|
198
|
+
Args:
|
199
|
+
dt: The datetime to wait until next minute from.
|
200
|
+
second: Additional seconds to wait after reaching next minute.
|
201
|
+
"""
|
180
202
|
future: datetime = replace_sec(dt) + timedelta(minutes=1)
|
181
203
|
time.sleep((future - dt).total_seconds() + second)
|
182
204
|
|
183
205
|
|
184
206
|
def delay(second: float = 0) -> None: # pragma: no cov
|
185
|
-
"""Delay
|
186
|
-
0.00 - 0.99 seconds.
|
207
|
+
"""Delay execution with time.sleep and random second value between 0.00-0.99 seconds.
|
187
208
|
|
188
|
-
:
|
209
|
+
Args:
|
210
|
+
second: Additional seconds to add to the random delay.
|
189
211
|
"""
|
190
212
|
global _DELAY_INDEX
|
191
213
|
cached_random = _CACHED_DELAYS[_DELAY_INDEX % len(_CACHED_DELAYS)]
|
@@ -201,26 +223,24 @@ def gen_id(
|
|
201
223
|
simple_mode: Optional[bool] = None,
|
202
224
|
extras: DictData | None = None,
|
203
225
|
) -> str:
|
204
|
-
"""Generate running ID for
|
205
|
-
|
206
|
-
|
207
|
-
value
|
208
|
-
|
209
|
-
Simple Mode:
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
:
|
221
|
-
|
222
|
-
|
223
|
-
:rtype: str
|
226
|
+
"""Generate running ID for tracking purposes.
|
227
|
+
|
228
|
+
This function uses MD5 algorithm if simple mode is disabled, or cuts the
|
229
|
+
hashing value length to 10 if simple mode is enabled.
|
230
|
+
|
231
|
+
Simple Mode Format:
|
232
|
+
YYYYMMDDHHMMSSffffffTxxxxxxxxxx
|
233
|
+
year month day hour minute second microsecond sep simple-id
|
234
|
+
|
235
|
+
Args:
|
236
|
+
value: A value to add as prefix before hashing with MD5.
|
237
|
+
sensitive: Flag to convert value to lowercase before hashing.
|
238
|
+
unique: Flag to add timestamp at microsecond level before hashing.
|
239
|
+
simple_mode: Flag to generate ID using simple mode.
|
240
|
+
extras: Extra parameters to override config values.
|
241
|
+
|
242
|
+
Returns:
|
243
|
+
str: Generated unique identifier.
|
224
244
|
"""
|
225
245
|
from .conf import dynamic
|
226
246
|
|
@@ -242,31 +262,37 @@ def gen_id(
|
|
242
262
|
|
243
263
|
|
244
264
|
def default_gen_id() -> str:
|
245
|
-
"""Return running ID
|
246
|
-
|
265
|
+
"""Return running ID for making default ID for the Result model.
|
266
|
+
|
267
|
+
This function is used when a run_id field is initialized for the first time.
|
247
268
|
|
248
|
-
:
|
269
|
+
Returns:
|
270
|
+
str: Generated default running ID.
|
249
271
|
"""
|
250
|
-
return gen_id("
|
272
|
+
return gen_id("MOCK", unique=True)
|
251
273
|
|
252
274
|
|
253
275
|
def make_exec(path: Union[Path, str]) -> None:
|
254
|
-
"""Change mode
|
276
|
+
"""Change file mode to be executable.
|
255
277
|
|
256
|
-
:
|
257
|
-
|
278
|
+
Args:
|
279
|
+
path: A file path to make executable.
|
258
280
|
"""
|
259
281
|
f: Path = Path(path) if isinstance(path, str) else path
|
260
282
|
f.chmod(f.stat().st_mode | stat.S_IEXEC)
|
261
283
|
|
262
284
|
|
263
285
|
def filter_func(value: T) -> T:
|
264
|
-
"""Filter out
|
265
|
-
|
266
|
-
|
286
|
+
"""Filter out custom functions from mapping context by replacing with function names.
|
287
|
+
|
288
|
+
This function replaces custom functions with their function names in data
|
289
|
+
structures. Built-in functions remain unchanged.
|
267
290
|
|
268
|
-
:
|
269
|
-
|
291
|
+
Args:
|
292
|
+
value: A value or data structure to filter function values from.
|
293
|
+
|
294
|
+
Returns:
|
295
|
+
T: The filtered value with functions replaced by their names.
|
270
296
|
"""
|
271
297
|
if isinstance(value, dict):
|
272
298
|
return {k: filter_func(value[k]) for k in value}
|
@@ -287,11 +313,13 @@ def filter_func(value: T) -> T:
|
|
287
313
|
|
288
314
|
|
289
315
|
def cross_product(matrix: Matrix) -> Iterator[DictData]:
|
290
|
-
"""
|
316
|
+
"""Generate iterator of product values from matrix.
|
291
317
|
|
292
|
-
:
|
318
|
+
Args:
|
319
|
+
matrix: A matrix to generate cross products from.
|
293
320
|
|
294
|
-
:
|
321
|
+
Returns:
|
322
|
+
Iterator[DictData]: Iterator of dictionary combinations.
|
295
323
|
"""
|
296
324
|
yield from (
|
297
325
|
{_k: _v for e in mapped for _k, _v in e.items()}
|
@@ -302,16 +330,18 @@ def cross_product(matrix: Matrix) -> Iterator[DictData]:
|
|
302
330
|
|
303
331
|
|
304
332
|
def cut_id(run_id: str, *, num: int = 6) -> str:
|
305
|
-
"""
|
333
|
+
"""Cut running ID to specified length.
|
306
334
|
|
307
335
|
Example:
|
308
336
|
>>> cut_id(run_id='20240101081330000000T1354680202')
|
309
337
|
'202401010813680202'
|
310
338
|
|
311
|
-
:
|
312
|
-
|
339
|
+
Args:
|
340
|
+
run_id: A running ID to cut.
|
341
|
+
num: Number of characters to keep from the end.
|
313
342
|
|
314
|
-
:
|
343
|
+
Returns:
|
344
|
+
str: The cut running ID.
|
315
345
|
"""
|
316
346
|
if "T" in run_id:
|
317
347
|
dt, simple = run_id.split("T", maxsplit=1)
|
@@ -333,10 +363,14 @@ def dump_all(
|
|
333
363
|
value: Union[T, BaseModel],
|
334
364
|
by_alias: bool = False,
|
335
365
|
) -> Union[T, DictData]:
|
336
|
-
"""Dump all nested BaseModel
|
366
|
+
"""Dump all nested BaseModel objects to dictionary objects.
|
337
367
|
|
338
|
-
:
|
339
|
-
|
368
|
+
Args:
|
369
|
+
value: A value that may contain BaseModel objects.
|
370
|
+
by_alias: Whether to use field aliases when dumping.
|
371
|
+
|
372
|
+
Returns:
|
373
|
+
Union[T, DictData]: The value with BaseModel objects converted to dictionaries.
|
340
374
|
"""
|
341
375
|
if isinstance(value, dict):
|
342
376
|
return {k: dump_all(value[k], by_alias=by_alias) for k in value}
|
@@ -351,8 +385,16 @@ def dump_all(
|
|
351
385
|
|
352
386
|
|
353
387
|
def obj_name(obj: Optional[Union[str, object]] = None) -> Optional[str]:
|
388
|
+
"""Get object name or class name.
|
389
|
+
|
390
|
+
Args:
|
391
|
+
obj: An object or string to get the name from.
|
392
|
+
|
393
|
+
Returns:
|
394
|
+
Optional[str]: The object name, class name, or None if obj is None.
|
395
|
+
"""
|
354
396
|
if not obj:
|
355
|
-
|
397
|
+
return None
|
356
398
|
elif isinstance(obj, str):
|
357
399
|
obj_type: str = obj
|
358
400
|
elif isclass(obj):
|
ddeutil/workflow/workflow.py
CHANGED
@@ -61,7 +61,7 @@ from .result import (
|
|
61
61
|
validate_statuses,
|
62
62
|
)
|
63
63
|
from .reusables import has_template, param2template
|
64
|
-
from .traces import
|
64
|
+
from .traces import TraceManager, get_trace
|
65
65
|
from .utils import (
|
66
66
|
UTC,
|
67
67
|
gen_id,
|
@@ -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
|
@@ -368,7 +407,7 @@ class Workflow(BaseModel):
|
|
368
407
|
with the set `on` field.
|
369
408
|
|
370
409
|
Args:
|
371
|
-
dt: A datetime object that want to validate.
|
410
|
+
dt (datetime): A datetime object that want to validate.
|
372
411
|
|
373
412
|
Returns:
|
374
413
|
datetime: The validated release datetime.
|
@@ -377,7 +416,9 @@ class Workflow(BaseModel):
|
|
377
416
|
dt = dt.replace(tzinfo=UTC)
|
378
417
|
|
379
418
|
release: datetime = replace_sec(dt.astimezone(UTC))
|
380
|
-
|
419
|
+
|
420
|
+
# NOTE: Return itself if schedule event does not set.
|
421
|
+
if not self.on.schedule:
|
381
422
|
return release
|
382
423
|
|
383
424
|
for on in self.on.schedule:
|
@@ -415,7 +456,7 @@ class Workflow(BaseModel):
|
|
415
456
|
- Writing result audit
|
416
457
|
|
417
458
|
Args:
|
418
|
-
release
|
459
|
+
release (datetime): A release datetime.
|
419
460
|
params: A workflow parameter that pass to execute method.
|
420
461
|
release_type:
|
421
462
|
run_id: (str) A workflow running ID.
|
@@ -442,7 +483,7 @@ class Workflow(BaseModel):
|
|
442
483
|
parent_run_id: str = run_id
|
443
484
|
|
444
485
|
context: DictData = {"status": WAIT}
|
445
|
-
trace:
|
486
|
+
trace: TraceManager = get_trace(
|
446
487
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
447
488
|
)
|
448
489
|
release: datetime = self.validate_release(dt=release)
|
@@ -537,7 +578,7 @@ class Workflow(BaseModel):
|
|
537
578
|
Returns:
|
538
579
|
tuple[Status, DictData]: The pair of status and result context data.
|
539
580
|
"""
|
540
|
-
trace:
|
581
|
+
trace: TraceManager = get_trace(
|
541
582
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
542
583
|
)
|
543
584
|
if event and event.is_set():
|
@@ -654,7 +695,7 @@ class Workflow(BaseModel):
|
|
654
695
|
ts: float = time.monotonic()
|
655
696
|
parent_run_id: Optional[str] = run_id
|
656
697
|
run_id: str = gen_id(self.name, extras=self.extras)
|
657
|
-
trace:
|
698
|
+
trace: TraceManager = get_trace(
|
658
699
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
659
700
|
)
|
660
701
|
context: DictData = self.parameterize(params)
|
@@ -711,8 +752,12 @@ class Workflow(BaseModel):
|
|
711
752
|
|
712
753
|
with ThreadPoolExecutor(max_job_parallel, "wf") as executor:
|
713
754
|
futures: list[Future] = []
|
714
|
-
|
715
|
-
|
755
|
+
|
756
|
+
# NOTE: Start with smaller sleep time
|
757
|
+
backoff_sleep: float = 0.01
|
758
|
+
|
759
|
+
# NOTE: Track consecutive wait states
|
760
|
+
consecutive_waits: int = 0
|
716
761
|
|
717
762
|
while not job_queue.empty() and (
|
718
763
|
not_timeout_flag := ((time.monotonic() - ts) < timeout)
|
@@ -760,6 +805,7 @@ class Workflow(BaseModel):
|
|
760
805
|
skip_count += 1
|
761
806
|
continue
|
762
807
|
|
808
|
+
# IMPORTANT: Start execution with parallel mode.
|
763
809
|
if max_job_parallel > 1:
|
764
810
|
futures.append(
|
765
811
|
executor.submit(
|
@@ -792,7 +838,9 @@ class Workflow(BaseModel):
|
|
792
838
|
st, _ = future.result()
|
793
839
|
sequence_statuses.append(st)
|
794
840
|
job_queue.put(job_id)
|
795
|
-
|
841
|
+
# NOTE: The release future can not track a cancelled status
|
842
|
+
# because it only has one future.
|
843
|
+
elif future.cancelled(): # pragma: no cov
|
796
844
|
sequence_statuses.append(CANCEL)
|
797
845
|
job_queue.put(job_id)
|
798
846
|
elif future.running() or "state=pending" in str(future):
|
@@ -813,7 +861,7 @@ class Workflow(BaseModel):
|
|
813
861
|
for total, future in enumerate(as_completed(futures), start=0):
|
814
862
|
try:
|
815
863
|
statuses[total], _ = future.result()
|
816
|
-
except WorkflowError as e:
|
864
|
+
except (WorkflowError, Exception) as e:
|
817
865
|
statuses[total] = get_status_from_error(e)
|
818
866
|
|
819
867
|
# NOTE: Update skipped status from the job trigger.
|
@@ -871,7 +919,7 @@ class Workflow(BaseModel):
|
|
871
919
|
event: Optional[ThreadEvent] = None,
|
872
920
|
timeout: float = 3600,
|
873
921
|
max_job_parallel: int = 2,
|
874
|
-
) -> Result:
|
922
|
+
) -> Result: # pragma: no cov
|
875
923
|
"""Re-Execute workflow with passing the error context data.
|
876
924
|
|
877
925
|
:param context: A context result that get the failed status.
|
@@ -892,7 +940,7 @@ class Workflow(BaseModel):
|
|
892
940
|
ts: float = time.monotonic()
|
893
941
|
parent_run_id: str = run_id
|
894
942
|
run_id: str = gen_id(self.name, extras=self.extras)
|
895
|
-
trace:
|
943
|
+
trace: TraceManager = get_trace(
|
896
944
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
897
945
|
)
|
898
946
|
if context["status"] == SUCCESS:
|
@@ -900,12 +948,9 @@ class Workflow(BaseModel):
|
|
900
948
|
"[WORKFLOW]: Does not rerun because it already executed with "
|
901
949
|
"success status."
|
902
950
|
)
|
903
|
-
return Result(
|
904
|
-
run_id=run_id,
|
905
|
-
parent_run_id=parent_run_id,
|
951
|
+
return Result.from_trace(trace).catch(
|
906
952
|
status=SUCCESS,
|
907
953
|
context=catch(context=context, status=SUCCESS),
|
908
|
-
extras=self.extras,
|
909
954
|
)
|
910
955
|
|
911
956
|
err: dict[str, str] = context.get("errors", {})
|
@@ -921,12 +966,9 @@ class Workflow(BaseModel):
|
|
921
966
|
)
|
922
967
|
if not self.jobs:
|
923
968
|
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,
|
969
|
+
return Result.from_trace(trace).catch(
|
927
970
|
status=SUCCESS,
|
928
971
|
context=catch(context=context, status=SUCCESS),
|
929
|
-
extras=self.extras,
|
930
972
|
)
|
931
973
|
|
932
974
|
# NOTE: Prepare the new context variable for rerun process.
|
@@ -951,12 +993,9 @@ class Workflow(BaseModel):
|
|
951
993
|
"[WORKFLOW]: It does not have job to rerun. it will change "
|
952
994
|
"status to skip."
|
953
995
|
)
|
954
|
-
return Result(
|
955
|
-
run_id=run_id,
|
956
|
-
parent_run_id=parent_run_id,
|
996
|
+
return Result.from_trace(trace).catch(
|
957
997
|
status=SKIP,
|
958
998
|
context=catch(context=context, status=SKIP),
|
959
|
-
extras=self.extras,
|
960
999
|
)
|
961
1000
|
|
962
1001
|
not_timeout_flag: bool = True
|
@@ -969,9 +1008,7 @@ class Workflow(BaseModel):
|
|
969
1008
|
|
970
1009
|
catch(context, status=WAIT)
|
971
1010
|
if event and event.is_set():
|
972
|
-
return Result(
|
973
|
-
run_id=run_id,
|
974
|
-
parent_run_id=parent_run_id,
|
1011
|
+
return Result.from_trace(trace).catch(
|
975
1012
|
status=CANCEL,
|
976
1013
|
context=catch(
|
977
1014
|
context,
|
@@ -983,7 +1020,6 @@ class Workflow(BaseModel):
|
|
983
1020
|
).to_dict(),
|
984
1021
|
},
|
985
1022
|
),
|
986
|
-
extras=self.extras,
|
987
1023
|
)
|
988
1024
|
|
989
1025
|
with ThreadPoolExecutor(max_job_parallel, "wf") as executor:
|
@@ -1011,9 +1047,7 @@ class Workflow(BaseModel):
|
|
1011
1047
|
backoff_sleep = 0.01
|
1012
1048
|
|
1013
1049
|
if check == FAILED: # pragma: no cov
|
1014
|
-
return Result(
|
1015
|
-
run_id=run_id,
|
1016
|
-
parent_run_id=parent_run_id,
|
1050
|
+
return Result.from_trace(trace).catch(
|
1017
1051
|
status=FAILED,
|
1018
1052
|
context=catch(
|
1019
1053
|
context,
|
@@ -1026,7 +1060,6 @@ class Workflow(BaseModel):
|
|
1026
1060
|
).to_dict(),
|
1027
1061
|
},
|
1028
1062
|
),
|
1029
|
-
extras=self.extras,
|
1030
1063
|
)
|
1031
1064
|
elif check == SKIP: # pragma: no cov
|
1032
1065
|
trace.info(
|
@@ -1102,12 +1135,9 @@ class Workflow(BaseModel):
|
|
1102
1135
|
statuses[total + 1 + skip_count + i] = s
|
1103
1136
|
|
1104
1137
|
st: Status = validate_statuses(statuses)
|
1105
|
-
return Result(
|
1106
|
-
run_id=run_id,
|
1107
|
-
parent_run_id=parent_run_id,
|
1138
|
+
return Result.from_trace(trace).catch(
|
1108
1139
|
status=st,
|
1109
1140
|
context=catch(context, status=st),
|
1110
|
-
extras=self.extras,
|
1111
1141
|
)
|
1112
1142
|
|
1113
1143
|
event.set()
|
@@ -1121,9 +1151,7 @@ class Workflow(BaseModel):
|
|
1121
1151
|
|
1122
1152
|
time.sleep(0.0025)
|
1123
1153
|
|
1124
|
-
return Result(
|
1125
|
-
run_id=run_id,
|
1126
|
-
parent_run_id=parent_run_id,
|
1154
|
+
return Result.from_trace(trace).catch(
|
1127
1155
|
status=FAILED,
|
1128
1156
|
context=catch(
|
1129
1157
|
context,
|
@@ -1135,5 +1163,4 @@ class Workflow(BaseModel):
|
|
1135
1163
|
).to_dict(),
|
1136
1164
|
},
|
1137
1165
|
),
|
1138
|
-
extras=self.extras,
|
1139
1166
|
)
|