ddeutil-workflow 0.0.82__py3-none-any.whl → 0.0.83__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 +1 -1
- ddeutil/workflow/__init__.py +3 -2
- ddeutil/workflow/__types.py +10 -1
- ddeutil/workflow/audits.py +64 -41
- ddeutil/workflow/errors.py +3 -0
- ddeutil/workflow/event.py +34 -11
- ddeutil/workflow/job.py +5 -15
- ddeutil/workflow/result.py +41 -12
- ddeutil/workflow/stages.py +504 -292
- ddeutil/workflow/traces.py +9 -5
- ddeutil/workflow/utils.py +34 -20
- ddeutil/workflow/workflow.py +32 -50
- {ddeutil_workflow-0.0.82.dist-info → ddeutil_workflow-0.0.83.dist-info}/METADATA +1 -1
- {ddeutil_workflow-0.0.82.dist-info → ddeutil_workflow-0.0.83.dist-info}/RECORD +19 -19
- {ddeutil_workflow-0.0.82.dist-info → ddeutil_workflow-0.0.83.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.82.dist-info → ddeutil_workflow-0.0.83.dist-info}/entry_points.txt +0 -0
- {ddeutil_workflow-0.0.82.dist-info → ddeutil_workflow-0.0.83.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.82.dist-info → ddeutil_workflow-0.0.83.dist-info}/top_level.txt +0 -0
ddeutil/workflow/traces.py
CHANGED
@@ -51,6 +51,8 @@ from .utils import cut_id, get_dt_now, prepare_newline
|
|
51
51
|
|
52
52
|
logger = logging.getLogger("ddeutil.workflow")
|
53
53
|
Level = Literal["debug", "info", "warning", "error", "exception"]
|
54
|
+
EMJ_ALERT: str = "🚨"
|
55
|
+
EMJ_SKIP: str = "⏭️"
|
54
56
|
|
55
57
|
|
56
58
|
@lru_cache
|
@@ -239,7 +241,7 @@ class Metadata(BaseModel): # pragma: no cov
|
|
239
241
|
default=None, description="Environment (dev, staging, prod)."
|
240
242
|
)
|
241
243
|
|
242
|
-
# System context
|
244
|
+
# NOTE: System context
|
243
245
|
hostname: Optional[str] = Field(
|
244
246
|
default=None, description="Hostname where workflow is running."
|
245
247
|
)
|
@@ -253,7 +255,7 @@ class Metadata(BaseModel): # pragma: no cov
|
|
253
255
|
default=None, description="Workflow package version."
|
254
256
|
)
|
255
257
|
|
256
|
-
# Custom metadata
|
258
|
+
# NOTE: Custom metadata
|
257
259
|
tags: Optional[list[str]] = Field(
|
258
260
|
default_factory=list, description="Custom tags for categorization."
|
259
261
|
)
|
@@ -320,6 +322,8 @@ class Metadata(BaseModel): # pragma: no cov
|
|
320
322
|
import socket
|
321
323
|
import sys
|
322
324
|
|
325
|
+
from .__about__ import __version__
|
326
|
+
|
323
327
|
frame: Optional[FrameType] = currentframe()
|
324
328
|
if frame is None:
|
325
329
|
raise ValueError("Cannot get current frame")
|
@@ -384,7 +388,7 @@ class Metadata(BaseModel): # pragma: no cov
|
|
384
388
|
hostname=hostname,
|
385
389
|
ip_address=ip_address,
|
386
390
|
python_version=python_version,
|
387
|
-
package_version=
|
391
|
+
package_version=__version__,
|
388
392
|
# NOTE: Custom metadata
|
389
393
|
tags=extras_data.get("tags", []),
|
390
394
|
metadata=extras_data.get("metadata", {}),
|
@@ -2046,7 +2050,7 @@ def get_trace(
|
|
2046
2050
|
Args:
|
2047
2051
|
run_id (str): A running ID.
|
2048
2052
|
parent_run_id (str | None, default None): A parent running ID.
|
2049
|
-
handlers:
|
2053
|
+
handlers (list):
|
2050
2054
|
extras: An extra parameter that want to override the core
|
2051
2055
|
config values.
|
2052
2056
|
auto_pre_process (bool, default False)
|
@@ -2057,7 +2061,7 @@ def get_trace(
|
|
2057
2061
|
handlers: list[DictData] = dynamic(
|
2058
2062
|
"trace_handlers", f=handlers, extras=extras
|
2059
2063
|
)
|
2060
|
-
trace = Trace.model_validate(
|
2064
|
+
trace: Trace = Trace.model_validate(
|
2061
2065
|
{
|
2062
2066
|
"run_id": run_id,
|
2063
2067
|
"parent_run_id": parent_run_id,
|
ddeutil/workflow/utils.py
CHANGED
@@ -8,26 +8,6 @@
|
|
8
8
|
This module provides essential utility functions used throughout the workflow
|
9
9
|
system for ID generation, datetime handling, string processing, template
|
10
10
|
operations, and other common tasks.
|
11
|
-
|
12
|
-
Functions:
|
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
|
17
|
-
get_dt_now: Get current datetime with timezone
|
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
|
27
|
-
cross_product: Generate cross product of matrix values
|
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
|
31
11
|
"""
|
32
12
|
from __future__ import annotations
|
33
13
|
|
@@ -253,6 +233,33 @@ def gen_id(
|
|
253
233
|
).hexdigest()
|
254
234
|
|
255
235
|
|
236
|
+
def extract_id(
|
237
|
+
name: str,
|
238
|
+
run_id: Optional[str] = None,
|
239
|
+
extras: Optional[DictData] = None,
|
240
|
+
) -> tuple[str, str]:
|
241
|
+
"""Extract the parent ID and running ID. If the `run_id` parameter was
|
242
|
+
passed, it will replace the parent_run_id with this value and re-generate
|
243
|
+
new running ID for it instead.
|
244
|
+
|
245
|
+
Args:
|
246
|
+
name (str): A name for generate hashing value for the `gen_id` function.
|
247
|
+
run_id (str | None, default None):
|
248
|
+
extras:
|
249
|
+
|
250
|
+
Returns:
|
251
|
+
tuple[str, str]: A pair of parent running ID and running ID.
|
252
|
+
"""
|
253
|
+
generated = gen_id(name, unique=True, extras=extras)
|
254
|
+
if run_id:
|
255
|
+
parent_run_id: str = run_id
|
256
|
+
run_id: str = generated
|
257
|
+
else:
|
258
|
+
run_id: str = generated
|
259
|
+
parent_run_id: str = run_id
|
260
|
+
return parent_run_id, run_id
|
261
|
+
|
262
|
+
|
256
263
|
def default_gen_id() -> str:
|
257
264
|
"""Return running ID for making default ID for the Result model.
|
258
265
|
|
@@ -327,6 +334,8 @@ def cut_id(run_id: str, *, num: int = 8) -> str:
|
|
327
334
|
Example:
|
328
335
|
>>> cut_id(run_id='20240101081330000000T1354680202')
|
329
336
|
'202401010813680202'
|
337
|
+
>>> cut_id(run_id='20240101081330000000T1354680202')
|
338
|
+
'54680202'
|
330
339
|
|
331
340
|
Args:
|
332
341
|
run_id: A running ID to cut.
|
@@ -394,3 +403,8 @@ def obj_name(obj: Optional[Union[str, object]] = None) -> Optional[str]:
|
|
394
403
|
else:
|
395
404
|
obj_type: str = obj.__class__.__name__
|
396
405
|
return obj_type
|
406
|
+
|
407
|
+
|
408
|
+
def remove_sys_extras(extras: DictData) -> DictData:
|
409
|
+
"""Remove key that starts with `__sys_` from the extra dict parameter."""
|
410
|
+
return {k: extras[k] for k in extras if not k.startswith("__sys_")}
|
ddeutil/workflow/workflow.py
CHANGED
@@ -37,6 +37,7 @@ from threading import Event as ThreadEvent
|
|
37
37
|
from typing import Any, Literal, Optional, Union
|
38
38
|
|
39
39
|
from pydantic import BaseModel, Field
|
40
|
+
from pydantic.functional_serializers import field_serializer
|
40
41
|
from pydantic.functional_validators import field_validator, model_validator
|
41
42
|
from typing_extensions import Self
|
42
43
|
|
@@ -62,10 +63,10 @@ from .result import (
|
|
62
63
|
from .reusables import has_template, param2template
|
63
64
|
from .traces import Trace, get_trace
|
64
65
|
from .utils import (
|
65
|
-
|
66
|
+
extract_id,
|
66
67
|
gen_id,
|
67
68
|
get_dt_now,
|
68
|
-
|
69
|
+
remove_sys_extras,
|
69
70
|
)
|
70
71
|
|
71
72
|
|
@@ -243,8 +244,15 @@ class Workflow(BaseModel):
|
|
243
244
|
f"{self.name!r}."
|
244
245
|
)
|
245
246
|
|
247
|
+
# NOTE: Force update internal extras for handler circle execution.
|
248
|
+
self.extras.update({"__sys_break_circle_exec": self.name})
|
249
|
+
|
246
250
|
return self
|
247
251
|
|
252
|
+
@field_serializer("extras")
|
253
|
+
def __serialize_extras(self, extras: DictData) -> DictData:
|
254
|
+
return remove_sys_extras(extras)
|
255
|
+
|
248
256
|
def detail(self) -> DictData: # pragma: no cov
|
249
257
|
"""Return the detail of this workflow for generate markdown."""
|
250
258
|
return self.model_dump(by_alias=True)
|
@@ -257,7 +265,8 @@ class Workflow(BaseModel):
|
|
257
265
|
"""
|
258
266
|
|
259
267
|
def align_newline(value: str) -> str:
|
260
|
-
|
268
|
+
space: str = " " * 16
|
269
|
+
return value.rstrip("\n").replace("\n", f"\n{space}")
|
261
270
|
|
262
271
|
info: str = (
|
263
272
|
f"| Author: {author or 'nobody'} "
|
@@ -284,8 +293,7 @@ class Workflow(BaseModel):
|
|
284
293
|
{align_newline(self.desc)}\n
|
285
294
|
## Parameters\n
|
286
295
|
| name | type | default | description |
|
287
|
-
| --- | --- | --- | : --- :
|
288
|
-
|
296
|
+
| --- | --- | --- | : --- : |\n\n
|
289
297
|
## Jobs\n
|
290
298
|
{align_newline(jobs)}
|
291
299
|
""".lstrip(
|
@@ -314,8 +322,7 @@ class Workflow(BaseModel):
|
|
314
322
|
f"{self.name!r}"
|
315
323
|
)
|
316
324
|
job: Job = self.jobs[name]
|
317
|
-
|
318
|
-
job.extras = self.extras
|
325
|
+
job.extras = self.extras
|
319
326
|
return job
|
320
327
|
|
321
328
|
def parameterize(self, params: DictData) -> DictData:
|
@@ -334,8 +341,8 @@ class Workflow(BaseModel):
|
|
334
341
|
execute method.
|
335
342
|
|
336
343
|
Returns:
|
337
|
-
DictData: The parameter value that validate with its parameter fields
|
338
|
-
adding jobs key to this parameter.
|
344
|
+
DictData: The parameter value that validate with its parameter fields
|
345
|
+
and adding jobs key to this parameter.
|
339
346
|
|
340
347
|
Raises:
|
341
348
|
WorkflowError: If parameter value that want to validate does
|
@@ -366,33 +373,6 @@ class Workflow(BaseModel):
|
|
366
373
|
"jobs": {},
|
367
374
|
}
|
368
375
|
|
369
|
-
def validate_release(self, dt: datetime) -> datetime:
|
370
|
-
"""Validate the release datetime that should was replaced second and
|
371
|
-
millisecond to 0 and replaced timezone to None before checking it match
|
372
|
-
with the set `on` field.
|
373
|
-
|
374
|
-
Args:
|
375
|
-
dt (datetime): A datetime object that want to validate.
|
376
|
-
|
377
|
-
Returns:
|
378
|
-
datetime: The validated release datetime.
|
379
|
-
"""
|
380
|
-
if dt.tzinfo is None:
|
381
|
-
dt = dt.replace(tzinfo=UTC)
|
382
|
-
|
383
|
-
release: datetime = replace_sec(dt.astimezone(UTC))
|
384
|
-
|
385
|
-
# NOTE: Return itself if schedule event does not set.
|
386
|
-
if not self.on.schedule:
|
387
|
-
return release
|
388
|
-
|
389
|
-
for on in self.on.schedule:
|
390
|
-
if release == on.cronjob.schedule(release, tz=UTC).next:
|
391
|
-
return release
|
392
|
-
raise WorkflowError(
|
393
|
-
"Release datetime does not support for this workflow"
|
394
|
-
)
|
395
|
-
|
396
376
|
def release(
|
397
377
|
self,
|
398
378
|
release: datetime,
|
@@ -422,11 +402,12 @@ class Workflow(BaseModel):
|
|
422
402
|
|
423
403
|
Args:
|
424
404
|
release (datetime): A release datetime.
|
425
|
-
params: A workflow parameter that pass to execute method.
|
426
|
-
release_type:
|
405
|
+
params (DictData): A workflow parameter that pass to execute method.
|
406
|
+
release_type (ReleaseType): A release type that want to execute.
|
427
407
|
run_id: (str) A workflow running ID.
|
428
408
|
runs_metadata: (DictData)
|
429
|
-
audit: An audit
|
409
|
+
audit (Audit): An audit model that use to manage release log of this
|
410
|
+
execution.
|
430
411
|
override_log_name: (str) An override logging name that use
|
431
412
|
instead the workflow name.
|
432
413
|
timeout: (int) A workflow execution time out in second unit.
|
@@ -441,13 +422,9 @@ class Workflow(BaseModel):
|
|
441
422
|
audit: Audit = audit or get_audit(extras=self.extras)
|
442
423
|
|
443
424
|
# NOTE: Generate the parent running ID with not None value.
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
else:
|
448
|
-
run_id: str = gen_id(name, unique=True)
|
449
|
-
parent_run_id: str = run_id
|
450
|
-
|
425
|
+
parent_run_id, run_id = extract_id(
|
426
|
+
name, run_id=run_id, extras=self.extras
|
427
|
+
)
|
451
428
|
context: DictData = {"status": WAIT}
|
452
429
|
audit_data: DictData = {
|
453
430
|
"name": name,
|
@@ -460,7 +437,7 @@ class Workflow(BaseModel):
|
|
460
437
|
trace: Trace = get_trace(
|
461
438
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
462
439
|
)
|
463
|
-
release: datetime = self.
|
440
|
+
release: datetime = self.on.validate_dt(dt=release)
|
464
441
|
trace.info(f"[RELEASE]: Start {name!r} : {release:%Y-%m-%d %H:%M:%S}")
|
465
442
|
values: DictData = param2template(
|
466
443
|
params,
|
@@ -536,7 +513,7 @@ class Workflow(BaseModel):
|
|
536
513
|
**(context["errors"] if "errors" in context else {}),
|
537
514
|
},
|
538
515
|
),
|
539
|
-
extras=self.extras,
|
516
|
+
extras=remove_sys_extras(self.extras),
|
540
517
|
)
|
541
518
|
|
542
519
|
def execute_job(
|
@@ -684,8 +661,9 @@ class Workflow(BaseModel):
|
|
684
661
|
:rtype: Result
|
685
662
|
"""
|
686
663
|
ts: float = time.monotonic()
|
687
|
-
parent_run_id
|
688
|
-
|
664
|
+
parent_run_id, run_id = extract_id(
|
665
|
+
self.name, run_id=run_id, extras=self.extras
|
666
|
+
)
|
689
667
|
trace: Trace = get_trace(
|
690
668
|
run_id, parent_run_id=parent_run_id, extras=self.extras
|
691
669
|
)
|
@@ -913,6 +891,10 @@ class Workflow(BaseModel):
|
|
913
891
|
) -> Result: # pragma: no cov
|
914
892
|
"""Re-Execute workflow with passing the error context data.
|
915
893
|
|
894
|
+
Warnings:
|
895
|
+
This rerun method allow to rerun job execution level only. That mean
|
896
|
+
it does not support rerun only stage.
|
897
|
+
|
916
898
|
Args:
|
917
899
|
context: A context result that get the failed status.
|
918
900
|
run_id: (Optional[str]) A workflow running ID.
|
@@ -1,20 +1,20 @@
|
|
1
|
-
ddeutil/workflow/__about__.py,sha256=
|
2
|
-
ddeutil/workflow/__cron.py,sha256
|
3
|
-
ddeutil/workflow/__init__.py,sha256=
|
1
|
+
ddeutil/workflow/__about__.py,sha256=6n_dCde_BwTTJlP4Yu4HNgiRti7QydfShki703lt6Ro,60
|
2
|
+
ddeutil/workflow/__cron.py,sha256=-1tqZG7GtUmusdl6NTy_Ck7nM_tGYTXYB7TB7tKeO60,29184
|
3
|
+
ddeutil/workflow/__init__.py,sha256=Dvfjs7LpLerGCYGnbqKwznViTw7ire_6LR8obC1I4aM,3456
|
4
4
|
ddeutil/workflow/__main__.py,sha256=Nqk5aO-HsZVKV2BmuJYeJEufJluipvCD9R1k2kMoJ3Y,8581
|
5
|
-
ddeutil/workflow/__types.py,sha256=
|
6
|
-
ddeutil/workflow/audits.py,sha256=
|
5
|
+
ddeutil/workflow/__types.py,sha256=IOKuJCxTUPHh8Z2JoLu_K7a85oq0VOcKBhpabiJ6qEE,5001
|
6
|
+
ddeutil/workflow/audits.py,sha256=H8yuMzXs_QAAKNox-HXdojk9CcilHYYQtklJYetoZv8,26955
|
7
7
|
ddeutil/workflow/conf.py,sha256=VfPmwaBYEgOj8bu4eim13ayZwJ4Liy7I702aQf7vS8g,17644
|
8
|
-
ddeutil/workflow/errors.py,sha256=
|
9
|
-
ddeutil/workflow/event.py,sha256=
|
10
|
-
ddeutil/workflow/job.py,sha256=
|
8
|
+
ddeutil/workflow/errors.py,sha256=J4bEbtI7qtBX7eghod4djLf0y5i1r4mCz_uFU4roLhY,5713
|
9
|
+
ddeutil/workflow/event.py,sha256=OumcZBlOZD0_J53GS4V2XJEqQ9HEcIl3UicQrCyL46M,14684
|
10
|
+
ddeutil/workflow/job.py,sha256=VVTpxVR2iVEkjvP8r0O0LRtAPnrbsguYbKzHpe2TAVo,48146
|
11
11
|
ddeutil/workflow/params.py,sha256=y9f6DEIyae1j4awbj3Kbeq75-U2UPFlKv9K57Hdo_Go,17188
|
12
|
-
ddeutil/workflow/result.py,sha256=
|
12
|
+
ddeutil/workflow/result.py,sha256=0W3z5wAs3Dyr8r2vRMY5hl1MkvdsyXWJmQD4NmsDDOM,10194
|
13
13
|
ddeutil/workflow/reusables.py,sha256=SBLJSxR8ELoWJErBfSMZS3Rr1O_93T-fFBpfn2AvxuA,25007
|
14
|
-
ddeutil/workflow/stages.py,sha256=
|
15
|
-
ddeutil/workflow/traces.py,sha256=
|
16
|
-
ddeutil/workflow/utils.py,sha256=
|
17
|
-
ddeutil/workflow/workflow.py,sha256=
|
14
|
+
ddeutil/workflow/stages.py,sha256=lWlzvpJ6YyhDf0ks5q_fzHjm4-o6UZfhiYp9CG-ffro,129661
|
15
|
+
ddeutil/workflow/traces.py,sha256=pq1lOg2UMgDiSDmjHxXPoTaBHnfc7uzzlo1u2TCwN2Q,74733
|
16
|
+
ddeutil/workflow/utils.py,sha256=XsH8DkcTiMmWt1e59b4bFQofsBdo7uW1-7gC2rghuW8,12128
|
17
|
+
ddeutil/workflow/workflow.py,sha256=uc71PJh7e-Bjb3Xg7T83wlLrKTPGvmX7Qjsn6SJ1GDI,42544
|
18
18
|
ddeutil/workflow/api/__init__.py,sha256=5DzYL3ngceoRshh5HYCSVWChqNJSiP01E1bEd8XxPi0,4799
|
19
19
|
ddeutil/workflow/api/log_conf.py,sha256=WfS3udDLSyrP-C80lWOvxxmhd_XWKvQPkwDqKblcH3E,1834
|
20
20
|
ddeutil/workflow/api/routes/__init__.py,sha256=JRaJZB0D6mgR17MbZo8yLtdYDtD62AA8MdKlFqhG84M,420
|
@@ -27,9 +27,9 @@ ddeutil/workflow/plugins/providers/aws.py,sha256=61uIFBEWt-_D5Sui24qUPier1Hiqlw_
|
|
27
27
|
ddeutil/workflow/plugins/providers/az.py,sha256=o3dh011lEtmr7-d7FPZJPgXdT0ytFzKfc5xnVxSyXGU,34867
|
28
28
|
ddeutil/workflow/plugins/providers/container.py,sha256=DSN0RWxMjTJN5ANheeMauDaPa3X6Z2E1eGUcctYkENw,22134
|
29
29
|
ddeutil/workflow/plugins/providers/gcs.py,sha256=KgAOdMBvdbMLTH_z_FwVriBFtZfKEYx8_34jzUOVjTY,27460
|
30
|
-
ddeutil_workflow-0.0.
|
31
|
-
ddeutil_workflow-0.0.
|
32
|
-
ddeutil_workflow-0.0.
|
33
|
-
ddeutil_workflow-0.0.
|
34
|
-
ddeutil_workflow-0.0.
|
35
|
-
ddeutil_workflow-0.0.
|
30
|
+
ddeutil_workflow-0.0.83.dist-info/licenses/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
|
31
|
+
ddeutil_workflow-0.0.83.dist-info/METADATA,sha256=pxD6FyTSV4ra5DujoPvWQnb2Z9WWHHqi97HBADqeeAo,16087
|
32
|
+
ddeutil_workflow-0.0.83.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
33
|
+
ddeutil_workflow-0.0.83.dist-info/entry_points.txt,sha256=qDTpPSauL0ciO6T4iSVt8bJeYrVEkkoEEw_RlGx6Kgk,63
|
34
|
+
ddeutil_workflow-0.0.83.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
|
35
|
+
ddeutil_workflow-0.0.83.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|