ddeutil-workflow 0.0.80__py3-none-any.whl → 0.0.81__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 +39 -29
- ddeutil/workflow/conf.py +21 -21
- ddeutil/workflow/errors.py +3 -2
- ddeutil/workflow/reusables.py +16 -17
- ddeutil/workflow/utils.py +0 -11
- ddeutil/workflow/workflow.py +9 -11
- {ddeutil_workflow-0.0.80.dist-info → ddeutil_workflow-0.0.81.dist-info}/METADATA +1 -1
- {ddeutil_workflow-0.0.80.dist-info → ddeutil_workflow-0.0.81.dist-info}/RECORD +13 -13
- {ddeutil_workflow-0.0.80.dist-info → ddeutil_workflow-0.0.81.dist-info}/WHEEL +0 -0
- {ddeutil_workflow-0.0.80.dist-info → ddeutil_workflow-0.0.81.dist-info}/entry_points.txt +0 -0
- {ddeutil_workflow-0.0.80.dist-info → ddeutil_workflow-0.0.81.dist-info}/licenses/LICENSE +0 -0
- {ddeutil_workflow-0.0.80.dist-info → ddeutil_workflow-0.0.81.dist-info}/top_level.txt +0 -0
ddeutil/workflow/__about__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__: str = "0.0.
|
1
|
+
__version__: str = "0.0.81"
|
ddeutil/workflow/cli.py
CHANGED
@@ -54,29 +54,30 @@ def init() -> None:
|
|
54
54
|
dedent(
|
55
55
|
"""
|
56
56
|
# Example workflow template.
|
57
|
-
wf-example:
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
57
|
+
name: wf-example:
|
58
|
+
type: Workflow
|
59
|
+
desc: |
|
60
|
+
An example workflow template that provide the demo of workflow.
|
61
|
+
params:
|
62
|
+
name:
|
63
|
+
type: str
|
64
|
+
default: "World"
|
65
|
+
jobs:
|
66
|
+
first-job:
|
67
|
+
stages:
|
68
|
+
|
69
|
+
- name: "Hello Stage"
|
70
|
+
echo: "Start say hi to the console"
|
71
|
+
|
72
|
+
- name: "Call tasks"
|
73
|
+
uses: tasks/say-hello-func@example
|
74
|
+
with:
|
75
|
+
name: ${{ params.name }}
|
76
|
+
|
77
|
+
second-job:
|
78
|
+
|
79
|
+
- name: "Hello Env"
|
80
|
+
echo: "Start say hi with ${ WORKFLOW_DEMO_HELLO }"
|
80
81
|
"""
|
81
82
|
).lstrip("\n")
|
82
83
|
)
|
@@ -89,12 +90,20 @@ def init() -> None:
|
|
89
90
|
dummy_tasks_path.write_text(
|
90
91
|
dedent(
|
91
92
|
"""
|
93
|
+
from typing import Any, Optional
|
94
|
+
|
92
95
|
from ddeutil.workflow import Result, tag
|
93
96
|
|
94
97
|
@tag(name="example", alias="say-hello-func")
|
95
|
-
def hello_world_task(name: str, rs: Result) -> dict[str, str]:
|
98
|
+
def hello_world_task(name: str, rs: Result, extras: Optional[dict[str, Any]] = None) -> dict[str, str]:
|
96
99
|
\"\"\"Logging hello task function\"\"\"
|
97
|
-
|
100
|
+
_extras = extras or {}
|
101
|
+
# NOTE: I will use custom newline logging if you pass `||`.
|
102
|
+
rs.trace.info(
|
103
|
+
f"Hello, {name}||"
|
104
|
+
f"> running ID: {rs.run_id}"
|
105
|
+
f"> extras: {_extras}"
|
106
|
+
)
|
98
107
|
return {"name": name}
|
99
108
|
"""
|
100
109
|
).lstrip("\n")
|
@@ -106,18 +115,19 @@ def init() -> None:
|
|
106
115
|
dotenv_file = Path(".env")
|
107
116
|
mode: str = "a" if dotenv_file.exists() else "w"
|
108
117
|
with dotenv_file.open(mode=mode) as f:
|
109
|
-
f.write("\n# Workflow
|
118
|
+
f.write("\n# Workflow Environment Variables\n")
|
110
119
|
f.write(
|
111
120
|
"WORKFLOW_DEMO_HELLO=foo\n"
|
112
121
|
"WORKFLOW_CORE_DEBUG_MODE=true\n"
|
113
122
|
"WORKFLOW_LOG_TIMEZONE=Asia/Bangkok\n"
|
114
|
-
"
|
123
|
+
'WORKFLOW_LOG_TRACE_HANDLERS=\'[{"type": "console"}]\'\n'
|
124
|
+
'WORKFLOW_LOG_AUDIT_CONF=\'{"type": "file", "path": "./audits"}\''
|
115
125
|
"WORKFLOW_LOG_AUDIT_ENABLE_WRITE=true\n"
|
116
126
|
)
|
117
127
|
|
118
128
|
typer.echo("Starter command:")
|
119
129
|
typer.echo(
|
120
|
-
"
|
130
|
+
">>> `source .env && workflow-cli workflows execute --name=wf-example`"
|
121
131
|
)
|
122
132
|
|
123
133
|
|
@@ -163,7 +173,7 @@ def api(
|
|
163
173
|
debug: Annotated[bool, typer.Option(help="A debug mode flag")] = True,
|
164
174
|
workers: Annotated[int, typer.Option(help="A worker number")] = None,
|
165
175
|
reload: Annotated[bool, typer.Option(help="A reload flag")] = False,
|
166
|
-
):
|
176
|
+
) -> None:
|
167
177
|
"""
|
168
178
|
Provision API application from the FastAPI.
|
169
179
|
"""
|
ddeutil/workflow/conf.py
CHANGED
@@ -22,19 +22,6 @@ Functions:
|
|
22
22
|
pass_env: Process environment variable substitution
|
23
23
|
api_config: Get API-specific configuration settings
|
24
24
|
|
25
|
-
Example:
|
26
|
-
```python
|
27
|
-
from ddeutil.workflow.conf import Config, YamlParser
|
28
|
-
|
29
|
-
# Load workflow configuration
|
30
|
-
parser = YamlParser("my-workflow")
|
31
|
-
workflow_config = parser.data
|
32
|
-
|
33
|
-
# Access dynamic configuration
|
34
|
-
from ddeutil.workflow.conf import dynamic
|
35
|
-
log_level = dynamic("log_level", default="INFO")
|
36
|
-
```
|
37
|
-
|
38
25
|
Note:
|
39
26
|
Configuration files support environment variable substitution using
|
40
27
|
${VAR_NAME} syntax and provide extensive validation capabilities.
|
@@ -155,7 +142,7 @@ class Config: # pragma: no cov
|
|
155
142
|
)
|
156
143
|
|
157
144
|
@property
|
158
|
-
def audit_conf(self) -> str:
|
145
|
+
def audit_conf(self) -> dict[str, Any]:
|
159
146
|
return json.loads(
|
160
147
|
env("LOG_AUDIT_URL", '{"type": "file", "path": "./audits"}')
|
161
148
|
)
|
@@ -288,9 +275,12 @@ class YamlParser:
|
|
288
275
|
continue
|
289
276
|
|
290
277
|
if data := cls.filter_yaml(file, name=name):
|
278
|
+
|
279
|
+
# NOTE: Start adding file metadata.
|
291
280
|
file_stat: os.stat_result = file.lstat()
|
292
281
|
data["created_at"] = file_stat.st_ctime
|
293
282
|
data["updated_at"] = file_stat.st_mtime
|
283
|
+
|
294
284
|
if not obj_type:
|
295
285
|
all_data.append((file_stat.st_mtime, data))
|
296
286
|
elif (t := data.get("type")) and t == obj_type:
|
@@ -324,9 +314,8 @@ class YamlParser:
|
|
324
314
|
extras: (DictData) An extra parameter that use to override core
|
325
315
|
config values.
|
326
316
|
ignore_filename: (str) An ignore filename. Default is
|
327
|
-
|
328
|
-
tags
|
329
|
-
A list of tag that want to filter.
|
317
|
+
``.confignore`` filename.
|
318
|
+
tags (list[str]): A list of tag that want to filter.
|
330
319
|
|
331
320
|
:rtype: Iterator[tuple[str, DictData]]
|
332
321
|
"""
|
@@ -365,6 +354,8 @@ class YamlParser:
|
|
365
354
|
continue
|
366
355
|
|
367
356
|
if (t := data.get("type")) and t == obj_type:
|
357
|
+
|
358
|
+
# NOTE: Start adding file metadata.
|
368
359
|
file_stat: os.stat_result = file.lstat()
|
369
360
|
data["created_at"] = file_stat.st_ctime
|
370
361
|
data["updated_at"] = file_stat.st_mtime
|
@@ -372,6 +363,7 @@ class YamlParser:
|
|
372
363
|
file.lstat().st_mtime,
|
373
364
|
data,
|
374
365
|
)
|
366
|
+
|
375
367
|
if key in all_data:
|
376
368
|
all_data[key].append(marking)
|
377
369
|
else:
|
@@ -405,15 +397,23 @@ class YamlParser:
|
|
405
397
|
def filter_yaml(cls, file: Path, name: Optional[str] = None) -> DictData:
|
406
398
|
"""Read a YAML file context from an input file path and specific name.
|
407
399
|
|
408
|
-
:
|
409
|
-
|
400
|
+
Args:
|
401
|
+
file (Path): A file path that want to extract YAML context.
|
402
|
+
name (str): A key name that search on a YAML context.
|
410
403
|
|
411
|
-
:
|
404
|
+
Returns:
|
405
|
+
DictData: A data that read from this file if it is YAML format.
|
412
406
|
"""
|
413
407
|
if any(file.suffix.endswith(s) for s in (".yml", ".yaml")):
|
414
408
|
values: DictData = YamlFlResolve(file).read()
|
415
409
|
if values is not None:
|
416
|
-
|
410
|
+
if name:
|
411
|
+
if "name" in values and values.get("name") == name:
|
412
|
+
return values
|
413
|
+
return (
|
414
|
+
values[name] | {"name": name} if name in values else {}
|
415
|
+
)
|
416
|
+
return values
|
417
417
|
return {}
|
418
418
|
|
419
419
|
@cached_property
|
ddeutil/workflow/errors.py
CHANGED
@@ -135,11 +135,12 @@ class BaseError(Exception):
|
|
135
135
|
|
136
136
|
Example:
|
137
137
|
>>> error = BaseError("Something failed", refs="stage-1")
|
138
|
-
|
138
|
+
|
139
|
+
Simple format
|
139
140
|
>>> error.to_dict()
|
140
141
|
>>> # Returns: {"name": "BaseError", "message": "Something failed"}
|
141
142
|
|
142
|
-
|
143
|
+
With reference mapping
|
143
144
|
>>> error.to_dict(with_refs=True)
|
144
145
|
>>> # Returns: {"stage-1": {"name": "BaseError", "message": "Something failed"}}
|
145
146
|
```
|
ddeutil/workflow/reusables.py
CHANGED
@@ -25,22 +25,20 @@ Functions:
|
|
25
25
|
create_model_from_caller: Generate Pydantic models from function signatures
|
26
26
|
|
27
27
|
Example:
|
28
|
-
|
29
|
-
from ddeutil.workflow.reusables import tag
|
30
|
-
|
31
|
-
@tag("data-processing", alias="process-csv")
|
32
|
-
def process_csv_file(input_path: str, output_path: str) -> dict:
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
# output_path: "/data/output.csv"
|
43
|
-
```
|
28
|
+
|
29
|
+
>>> from ddeutil.workflow.reusables import tag
|
30
|
+
>>>
|
31
|
+
>>> @tag("data-processing", alias="process-csv")
|
32
|
+
>>> def process_csv_file(input_path: str, output_path: str) -> dict:
|
33
|
+
>>> return {"status": "completed", "rows_processed": 1000}
|
34
|
+
|
35
|
+
>>> # Use in workflow YAML:
|
36
|
+
>>> # stages:
|
37
|
+
>>> # - name: "Process data"
|
38
|
+
>>> # uses: "data-processing/process-csv@latest"
|
39
|
+
>>> # args:
|
40
|
+
>>> # input_path: "/data/input.csv"
|
41
|
+
>>> # output_path: "/data/output.csv"
|
44
42
|
|
45
43
|
Note:
|
46
44
|
The registry system supports versioning and aliasing for better function
|
@@ -64,6 +62,7 @@ from typing import (
|
|
64
62
|
Protocol,
|
65
63
|
TypeVar,
|
66
64
|
Union,
|
65
|
+
cast,
|
67
66
|
get_type_hints,
|
68
67
|
)
|
69
68
|
|
@@ -201,7 +200,7 @@ def get_args_const(
|
|
201
200
|
f"Post-filter: {expr} does not valid because it raise syntax error."
|
202
201
|
) from None
|
203
202
|
|
204
|
-
body: list[Expr] = mod.body
|
203
|
+
body: list[Expr] = cast(list[Expr], mod.body)
|
205
204
|
if len(body) > 1:
|
206
205
|
raise UtilError(
|
207
206
|
"Post-filter function should be only one calling per workflow."
|
ddeutil/workflow/utils.py
CHANGED
@@ -28,17 +28,6 @@ Functions:
|
|
28
28
|
cut_id: Cut running ID to specified length
|
29
29
|
dump_all: Serialize nested BaseModel objects to dictionaries
|
30
30
|
obj_name: Get object name or class name
|
31
|
-
|
32
|
-
Example:
|
33
|
-
```python
|
34
|
-
from ddeutil.workflow.utils import gen_id, get_dt_now
|
35
|
-
|
36
|
-
# Generate unique ID
|
37
|
-
run_id = gen_id("workflow")
|
38
|
-
|
39
|
-
# Get current datetime
|
40
|
-
now = get_dt_now()
|
41
|
-
```
|
42
31
|
"""
|
43
32
|
from __future__ import annotations
|
44
33
|
|
ddeutil/workflow/workflow.py
CHANGED
@@ -198,17 +198,15 @@ class Workflow(BaseModel):
|
|
198
198
|
FileNotFoundError: If workflow configuration file not found
|
199
199
|
|
200
200
|
Example:
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
)
|
211
|
-
```
|
201
|
+
>>> # Load from default config path
|
202
|
+
>>> workflow = Workflow.from_conf('data-pipeline')
|
203
|
+
|
204
|
+
>>> # Load with custom path and extras
|
205
|
+
>>> workflow = Workflow.from_conf(
|
206
|
+
... 'data-pipeline',
|
207
|
+
... path=Path('./custom-configs'),
|
208
|
+
... extras={'env': 'prod'}
|
209
|
+
... )
|
212
210
|
"""
|
213
211
|
load: YamlParser = YamlParser(name, path=path, extras=extras, obj=cls)
|
214
212
|
data: DictData = copy.deepcopy(load.data)
|
@@ -1,21 +1,21 @@
|
|
1
|
-
ddeutil/workflow/__about__.py,sha256=
|
1
|
+
ddeutil/workflow/__about__.py,sha256=OgSQucXlwEL1i2ZuW-rDgctTF1ybVDYwWSf4fhzgOx4,28
|
2
2
|
ddeutil/workflow/__cron.py,sha256=avOagaHl9xXOmizeRWm13cOrty9Tw0vRjFq-xoEgpAY,29167
|
3
3
|
ddeutil/workflow/__init__.py,sha256=pRnUNCrwnKGrEQNSmdU9Ybf1tEQKg4LfsCpyj1Y3mhg,3241
|
4
4
|
ddeutil/workflow/__main__.py,sha256=Qd-f8z2Q2vpiEP2x6PBFsJrpACWDVxFKQk820MhFmHo,59
|
5
5
|
ddeutil/workflow/__types.py,sha256=tA2vsr6mzTSzbWB1sb62c5GgxODlfVRz6FvgLNJtQao,4788
|
6
6
|
ddeutil/workflow/audits.py,sha256=h7WVHe3z_FqtmLRvAxhnSqae8fFtZPN0tICrVr39wP4,25456
|
7
|
-
ddeutil/workflow/cli.py,sha256=
|
8
|
-
ddeutil/workflow/conf.py,sha256=
|
9
|
-
ddeutil/workflow/errors.py,sha256=
|
7
|
+
ddeutil/workflow/cli.py,sha256=qEfqno3RoHLCKBdaA3IGYXcH40EUk9sSSOxN7iocWNQ,8573
|
8
|
+
ddeutil/workflow/conf.py,sha256=mvouKL6MroSCBNiRgPMb9T7_OI26JKHMGiGfYchLkF0,16756
|
9
|
+
ddeutil/workflow/errors.py,sha256=U2OHGY46zgcr6POmdJKryTVnCkjrswA7cNqjWiV_kMI,5530
|
10
10
|
ddeutil/workflow/event.py,sha256=qm7QHw-Pozm6oIUzAIxpDkPzzVZVtHgJIUlIle0vEfQ,13943
|
11
11
|
ddeutil/workflow/job.py,sha256=lSmOgh4l3_gBJXrTEWULhSSol648h6zPe6zKzz8jDHQ,46597
|
12
12
|
ddeutil/workflow/params.py,sha256=y9f6DEIyae1j4awbj3Kbeq75-U2UPFlKv9K57Hdo_Go,17188
|
13
13
|
ddeutil/workflow/result.py,sha256=BOk3DZMtmdE7xzQYeEYTGFlIkzJQ4Ed3fYzf0zF8Jo8,8963
|
14
|
-
ddeutil/workflow/reusables.py,sha256=
|
14
|
+
ddeutil/workflow/reusables.py,sha256=SBLJSxR8ELoWJErBfSMZS3Rr1O_93T-fFBpfn2AvxuA,25007
|
15
15
|
ddeutil/workflow/stages.py,sha256=QufIa2b7A_ngOndVoGzyxKm_o5ZrauNeqxAC4vBkKFM,122678
|
16
16
|
ddeutil/workflow/traces.py,sha256=e12_rDnwVo-XR6Ca1KLwCjo3tAwbJP7yXc1YU62YOt8,73415
|
17
|
-
ddeutil/workflow/utils.py,sha256
|
18
|
-
ddeutil/workflow/workflow.py,sha256=
|
17
|
+
ddeutil/workflow/utils.py,sha256=zj4o10Iq3qgNobTQAkTaQzQ5EBBf3uhbqhcOyCo1suo,11923
|
18
|
+
ddeutil/workflow/workflow.py,sha256=9oxbJBOtr2jvv1k2BVwAs4xh20yTSqd3EpaKhC9mLlU,42904
|
19
19
|
ddeutil/workflow/api/__init__.py,sha256=5DzYL3ngceoRshh5HYCSVWChqNJSiP01E1bEd8XxPi0,4799
|
20
20
|
ddeutil/workflow/api/log_conf.py,sha256=WfS3udDLSyrP-C80lWOvxxmhd_XWKvQPkwDqKblcH3E,1834
|
21
21
|
ddeutil/workflow/api/routes/__init__.py,sha256=JRaJZB0D6mgR17MbZo8yLtdYDtD62AA8MdKlFqhG84M,420
|
@@ -28,9 +28,9 @@ ddeutil/workflow/plugins/providers/aws.py,sha256=61uIFBEWt-_D5Sui24qUPier1Hiqlw_
|
|
28
28
|
ddeutil/workflow/plugins/providers/az.py,sha256=o3dh011lEtmr7-d7FPZJPgXdT0ytFzKfc5xnVxSyXGU,34867
|
29
29
|
ddeutil/workflow/plugins/providers/container.py,sha256=DSN0RWxMjTJN5ANheeMauDaPa3X6Z2E1eGUcctYkENw,22134
|
30
30
|
ddeutil/workflow/plugins/providers/gcs.py,sha256=KgAOdMBvdbMLTH_z_FwVriBFtZfKEYx8_34jzUOVjTY,27460
|
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.
|
36
|
-
ddeutil_workflow-0.0.
|
31
|
+
ddeutil_workflow-0.0.81.dist-info/licenses/LICENSE,sha256=nGFZ1QEhhhWeMHf9n99_fdt4vQaXS29xWKxt-OcLywk,1085
|
32
|
+
ddeutil_workflow-0.0.81.dist-info/METADATA,sha256=KfQDz9_yjBxdfDMUhbO1kBrwNWS2C6cEiMyWn5b0wws,16087
|
33
|
+
ddeutil_workflow-0.0.81.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
34
|
+
ddeutil_workflow-0.0.81.dist-info/entry_points.txt,sha256=qDTpPSauL0ciO6T4iSVt8bJeYrVEkkoEEw_RlGx6Kgk,63
|
35
|
+
ddeutil_workflow-0.0.81.dist-info/top_level.txt,sha256=m9M6XeSWDwt_yMsmH6gcOjHZVK5O0-vgtNBuncHjzW4,8
|
36
|
+
ddeutil_workflow-0.0.81.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|