highway-dsl 0.0.2__py3-none-any.whl → 0.0.3__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.
Potentially problematic release.
This version of highway-dsl might be problematic. Click here for more details.
- highway_dsl/__init__.py +3 -0
- highway_dsl/workflow_dsl.py +100 -8
- highway_dsl-0.0.3.dist-info/METADATA +160 -0
- highway_dsl-0.0.3.dist-info/RECORD +7 -0
- highway_dsl-0.0.2.dist-info/METADATA +0 -227
- highway_dsl-0.0.2.dist-info/RECORD +0 -7
- {highway_dsl-0.0.2.dist-info → highway_dsl-0.0.3.dist-info}/WHEEL +0 -0
- {highway_dsl-0.0.2.dist-info → highway_dsl-0.0.3.dist-info}/licenses/LICENSE +0 -0
- {highway_dsl-0.0.2.dist-info → highway_dsl-0.0.3.dist-info}/top_level.txt +0 -0
highway_dsl/__init__.py
CHANGED
|
@@ -6,6 +6,7 @@ from .workflow_dsl import (
|
|
|
6
6
|
ParallelOperator,
|
|
7
7
|
WaitOperator,
|
|
8
8
|
ForEachOperator,
|
|
9
|
+
WhileOperator,
|
|
9
10
|
RetryPolicy,
|
|
10
11
|
TimeoutPolicy,
|
|
11
12
|
OperatorType,
|
|
@@ -14,11 +15,13 @@ from .workflow_dsl import (
|
|
|
14
15
|
__all__ = [
|
|
15
16
|
"Workflow",
|
|
16
17
|
"WorkflowBuilder",
|
|
18
|
+
"BaseOperator",
|
|
17
19
|
"TaskOperator",
|
|
18
20
|
"ConditionOperator",
|
|
19
21
|
"ParallelOperator",
|
|
20
22
|
"WaitOperator",
|
|
21
23
|
"ForEachOperator",
|
|
24
|
+
"WhileOperator",
|
|
22
25
|
"RetryPolicy",
|
|
23
26
|
"TimeoutPolicy",
|
|
24
27
|
"OperatorType",
|
highway_dsl/workflow_dsl.py
CHANGED
|
@@ -16,6 +16,7 @@ class OperatorType(Enum):
|
|
|
16
16
|
FOREACH = "foreach"
|
|
17
17
|
SWITCH = "switch"
|
|
18
18
|
TRY_CATCH = "try_catch"
|
|
19
|
+
WHILE = "while"
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class RetryPolicy(BaseModel):
|
|
@@ -52,8 +53,8 @@ class TaskOperator(BaseOperator):
|
|
|
52
53
|
|
|
53
54
|
class ConditionOperator(BaseOperator):
|
|
54
55
|
condition: str
|
|
55
|
-
if_true: str
|
|
56
|
-
if_false: str
|
|
56
|
+
if_true: Optional[str]
|
|
57
|
+
if_false: Optional[str]
|
|
57
58
|
operator_type: OperatorType = Field(OperatorType.CONDITION, frozen=True)
|
|
58
59
|
|
|
59
60
|
|
|
@@ -94,6 +95,21 @@ class ForEachOperator(BaseOperator):
|
|
|
94
95
|
operator_type: OperatorType = Field(OperatorType.FOREACH, frozen=True)
|
|
95
96
|
|
|
96
97
|
|
|
98
|
+
class WhileOperator(BaseOperator):
|
|
99
|
+
condition: str
|
|
100
|
+
loop_body: List[
|
|
101
|
+
Union[
|
|
102
|
+
TaskOperator,
|
|
103
|
+
ConditionOperator,
|
|
104
|
+
WaitOperator,
|
|
105
|
+
ParallelOperator,
|
|
106
|
+
ForEachOperator,
|
|
107
|
+
"WhileOperator",
|
|
108
|
+
]
|
|
109
|
+
] = Field(default_factory=list)
|
|
110
|
+
operator_type: OperatorType = Field(OperatorType.WHILE, frozen=True)
|
|
111
|
+
|
|
112
|
+
|
|
97
113
|
class Workflow(BaseModel):
|
|
98
114
|
name: str
|
|
99
115
|
version: str = "1.0.0"
|
|
@@ -106,6 +122,7 @@ class Workflow(BaseModel):
|
|
|
106
122
|
WaitOperator,
|
|
107
123
|
ParallelOperator,
|
|
108
124
|
ForEachOperator,
|
|
125
|
+
WhileOperator,
|
|
109
126
|
],
|
|
110
127
|
] = Field(default_factory=dict)
|
|
111
128
|
variables: Dict[str, Any] = Field(default_factory=dict)
|
|
@@ -122,6 +139,7 @@ class Workflow(BaseModel):
|
|
|
122
139
|
OperatorType.WAIT.value: WaitOperator,
|
|
123
140
|
OperatorType.PARALLEL.value: ParallelOperator,
|
|
124
141
|
OperatorType.FOREACH.value: ForEachOperator,
|
|
142
|
+
OperatorType.WHILE.value: WhileOperator,
|
|
125
143
|
}
|
|
126
144
|
for task_id, task_data in data["tasks"].items():
|
|
127
145
|
operator_type = task_data.get("operator_type")
|
|
@@ -141,6 +159,7 @@ class Workflow(BaseModel):
|
|
|
141
159
|
WaitOperator,
|
|
142
160
|
ParallelOperator,
|
|
143
161
|
ForEachOperator,
|
|
162
|
+
WhileOperator,
|
|
144
163
|
],
|
|
145
164
|
) -> "Workflow":
|
|
146
165
|
self.tasks[task.task_id] = task
|
|
@@ -172,12 +191,18 @@ class Workflow(BaseModel):
|
|
|
172
191
|
|
|
173
192
|
|
|
174
193
|
class WorkflowBuilder:
|
|
175
|
-
def __init__(
|
|
194
|
+
def __init__(
|
|
195
|
+
self,
|
|
196
|
+
name: str,
|
|
197
|
+
existing_workflow: Optional[Workflow] = None,
|
|
198
|
+
parent: Optional["WorkflowBuilder"] = None,
|
|
199
|
+
):
|
|
176
200
|
if existing_workflow:
|
|
177
201
|
self.workflow = existing_workflow
|
|
178
202
|
else:
|
|
179
203
|
self.workflow = Workflow(name=name)
|
|
180
204
|
self._current_task: Optional[str] = None
|
|
205
|
+
self.parent = parent
|
|
181
206
|
|
|
182
207
|
def task(self, task_id: str, function: str, **kwargs) -> "WorkflowBuilder":
|
|
183
208
|
task = TaskOperator(task_id=task_id, function=function, **kwargs)
|
|
@@ -188,18 +213,37 @@ class WorkflowBuilder:
|
|
|
188
213
|
return self
|
|
189
214
|
|
|
190
215
|
def condition(
|
|
191
|
-
self,
|
|
216
|
+
self,
|
|
217
|
+
task_id: str,
|
|
218
|
+
condition: str,
|
|
219
|
+
if_true: Callable[["WorkflowBuilder"], "WorkflowBuilder"],
|
|
220
|
+
if_false: Callable[["WorkflowBuilder"], "WorkflowBuilder"],
|
|
221
|
+
**kwargs,
|
|
192
222
|
) -> "WorkflowBuilder":
|
|
223
|
+
true_builder = if_true(WorkflowBuilder(f"{{task_id}}_true", parent=self))
|
|
224
|
+
false_builder = if_false(WorkflowBuilder(f"{{task_id}}_false", parent=self))
|
|
225
|
+
|
|
226
|
+
true_tasks = list(true_builder.workflow.tasks.keys())
|
|
227
|
+
false_tasks = list(false_builder.workflow.tasks.keys())
|
|
228
|
+
|
|
193
229
|
task = ConditionOperator(
|
|
194
230
|
task_id=task_id,
|
|
195
231
|
condition=condition,
|
|
196
|
-
if_true=
|
|
197
|
-
if_false=
|
|
232
|
+
if_true=true_tasks[0] if true_tasks else None,
|
|
233
|
+
if_false=false_tasks[0] if false_tasks else None,
|
|
198
234
|
**kwargs,
|
|
199
235
|
)
|
|
236
|
+
|
|
200
237
|
if self._current_task:
|
|
201
238
|
task.dependencies.append(self._current_task)
|
|
239
|
+
|
|
202
240
|
self.workflow.add_task(task)
|
|
241
|
+
|
|
242
|
+
for task_obj in true_builder.workflow.tasks.values():
|
|
243
|
+
self.workflow.add_task(task_obj)
|
|
244
|
+
for task_obj in false_builder.workflow.tasks.values():
|
|
245
|
+
self.workflow.add_task(task_obj)
|
|
246
|
+
|
|
203
247
|
self._current_task = task_id
|
|
204
248
|
return self
|
|
205
249
|
|
|
@@ -214,12 +258,32 @@ class WorkflowBuilder:
|
|
|
214
258
|
return self
|
|
215
259
|
|
|
216
260
|
def parallel(
|
|
217
|
-
self,
|
|
261
|
+
self,
|
|
262
|
+
task_id: str,
|
|
263
|
+
branches: Dict[str, Callable[["WorkflowBuilder"], "WorkflowBuilder"]],
|
|
264
|
+
**kwargs,
|
|
218
265
|
) -> "WorkflowBuilder":
|
|
219
|
-
|
|
266
|
+
branch_builders = {
|
|
267
|
+
name: branch_func(WorkflowBuilder(f"{{task_id}}_{{name}}", parent=self))
|
|
268
|
+
for name, branch_func in branches.items()
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
branch_tasks = {
|
|
272
|
+
name: list(builder.workflow.tasks.keys())
|
|
273
|
+
for name, builder in branch_builders.items()
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
task = ParallelOperator(task_id=task_id, branches=branch_tasks, **kwargs)
|
|
277
|
+
|
|
220
278
|
if self._current_task:
|
|
221
279
|
task.dependencies.append(self._current_task)
|
|
280
|
+
|
|
222
281
|
self.workflow.add_task(task)
|
|
282
|
+
|
|
283
|
+
for builder in branch_builders.values():
|
|
284
|
+
for task_obj in builder.workflow.tasks.values():
|
|
285
|
+
self.workflow.add_task(task_obj)
|
|
286
|
+
|
|
223
287
|
self._current_task = task_id
|
|
224
288
|
return self
|
|
225
289
|
|
|
@@ -235,6 +299,34 @@ class WorkflowBuilder:
|
|
|
235
299
|
self._current_task = task_id
|
|
236
300
|
return self
|
|
237
301
|
|
|
302
|
+
def while_loop(
|
|
303
|
+
self,
|
|
304
|
+
task_id: str,
|
|
305
|
+
condition: str,
|
|
306
|
+
loop_body: Callable[["WorkflowBuilder"], "WorkflowBuilder"],
|
|
307
|
+
**kwargs,
|
|
308
|
+
) -> "WorkflowBuilder":
|
|
309
|
+
loop_builder = loop_body(WorkflowBuilder(f"{{task_id}}_loop", parent=self))
|
|
310
|
+
loop_tasks = list(loop_builder.workflow.tasks.values())
|
|
311
|
+
|
|
312
|
+
task = WhileOperator(
|
|
313
|
+
task_id=task_id,
|
|
314
|
+
condition=condition,
|
|
315
|
+
loop_body=loop_tasks,
|
|
316
|
+
**kwargs,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
if self._current_task:
|
|
320
|
+
task.dependencies.append(self._current_task)
|
|
321
|
+
|
|
322
|
+
self.workflow.add_task(task)
|
|
323
|
+
|
|
324
|
+
for task_obj in loop_tasks:
|
|
325
|
+
self.workflow.add_task(task_obj)
|
|
326
|
+
|
|
327
|
+
self._current_task = task_id
|
|
328
|
+
return self
|
|
329
|
+
|
|
238
330
|
def retry(
|
|
239
331
|
self,
|
|
240
332
|
max_retries: int = 3,
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: highway_dsl
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: A domain specific language (DSL) for defining and managing data processing pipelines.
|
|
5
|
+
Author-email: Farseed Ashouri <farseed.ashouri@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/rodmena-limited/highway_dsl
|
|
8
|
+
Project-URL: Issues, https://github.com/rodmena-limited/highway_dsl/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: pydantic>=2.12.3
|
|
16
|
+
Requires-Dist: pyyaml>=6.0
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
19
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
20
|
+
Requires-Dist: types-PyYAML>=6.0.0; extra == "dev"
|
|
21
|
+
Requires-Dist: pytest-cov>=2.12.1; extra == "dev"
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# Highway DSL
|
|
25
|
+
|
|
26
|
+
[](https://badge.fury.io/py/highway-dsl)
|
|
27
|
+
[](https://opensource.org/licenses/MIT)
|
|
28
|
+
|
|
29
|
+
**Highway DSL** is a Python-based domain-specific language for defining complex workflows in a clear, concise, and fluent manner. It is part of the larger **Highway** project, an advanced workflow engine capable of running complex DAG-based workflows.
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
* **Fluent API:** A powerful and intuitive `WorkflowBuilder` for defining workflows programmatically.
|
|
34
|
+
* **Pydantic-based:** All models are built on Pydantic, providing robust data validation, serialization, and documentation.
|
|
35
|
+
* **Rich Operators:** A comprehensive set of operators for handling various workflow scenarios:
|
|
36
|
+
* `Task`
|
|
37
|
+
* `Condition` (if/else)
|
|
38
|
+
* `Parallel`
|
|
39
|
+
* `ForEach`
|
|
40
|
+
* `Wait`
|
|
41
|
+
* `While`
|
|
42
|
+
* **YAML/JSON Interoperability:** Workflows can be defined in Python and exported to YAML or JSON, and vice-versa.
|
|
43
|
+
* **Extensible:** The DSL is designed to be extensible with custom operators and policies.
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install highway-dsl
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Quick Start
|
|
52
|
+
|
|
53
|
+
Here's a simple example of how to define a workflow using the `WorkflowBuilder`:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from datetime import timedelta
|
|
57
|
+
from highway_dsl import WorkflowBuilder
|
|
58
|
+
|
|
59
|
+
workflow = (
|
|
60
|
+
WorkflowBuilder("simple_etl")
|
|
61
|
+
.task("extract", "etl.extract_data", result_key="raw_data")
|
|
62
|
+
.task(
|
|
63
|
+
"transform",
|
|
64
|
+
"etl.transform_data",
|
|
65
|
+
args=["{{raw_data}}"],
|
|
66
|
+
result_key="transformed_data",
|
|
67
|
+
)
|
|
68
|
+
.retry(max_retries=3, delay=timedelta(seconds=10))
|
|
69
|
+
.task("load", "etl.load_data", args=["{{transformed_data}}"])
|
|
70
|
+
.timeout(timeout=timedelta(minutes=30))
|
|
71
|
+
.wait("wait_next", timedelta(hours=24))
|
|
72
|
+
.task("cleanup", "etl.cleanup")
|
|
73
|
+
.build()
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
print(workflow.to_yaml())
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Advanced Usage
|
|
80
|
+
|
|
81
|
+
### Conditional Logic
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from highway_dsl import WorkflowBuilder, RetryPolicy
|
|
85
|
+
from datetime import timedelta
|
|
86
|
+
|
|
87
|
+
builder = WorkflowBuilder("data_processing_pipeline")
|
|
88
|
+
|
|
89
|
+
builder.task("start", "workflows.tasks.initialize", result_key="init_data")
|
|
90
|
+
builder.task(
|
|
91
|
+
"validate",
|
|
92
|
+
"workflows.tasks.validate_data",
|
|
93
|
+
args=["{{init_data}}"],
|
|
94
|
+
result_key="validated_data",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
builder.condition(
|
|
98
|
+
"check_quality",
|
|
99
|
+
condition="{{validated_data.quality_score}} > 0.8",
|
|
100
|
+
if_true=lambda b: b.task(
|
|
101
|
+
"high_quality_processing",
|
|
102
|
+
"workflows.tasks.advanced_processing",
|
|
103
|
+
args=["{{validated_data}}"],
|
|
104
|
+
retry_policy=RetryPolicy(max_retries=5, delay=timedelta(seconds=10), backoff_factor=2.0),
|
|
105
|
+
),
|
|
106
|
+
if_false=lambda b: b.task(
|
|
107
|
+
"standard_processing",
|
|
108
|
+
"workflows.tasks.basic_processing",
|
|
109
|
+
args=["{{validated_data}}"],
|
|
110
|
+
),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
workflow = builder.build()
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### While Loops
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from highway_dsl import WorkflowBuilder
|
|
120
|
+
|
|
121
|
+
builder = WorkflowBuilder("qa_rework_workflow")
|
|
122
|
+
|
|
123
|
+
builder.task("start_qa", "workflows.tasks.start_qa", result_key="qa_results")
|
|
124
|
+
|
|
125
|
+
builder.while_loop(
|
|
126
|
+
"qa_rework_loop",
|
|
127
|
+
condition="{{qa_results.status}} == 'failed'",
|
|
128
|
+
loop_body=lambda b: b.task("perform_rework", "workflows.tasks.perform_rework").task(
|
|
129
|
+
"re_run_qa", "workflows.tasks.run_qa", result_key="qa_results"
|
|
130
|
+
),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
builder.task("finalize_product", "workflows.tasks.finalize_product", dependencies=["qa_rework_loop"])
|
|
134
|
+
|
|
135
|
+
workflow = builder.build()
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Development
|
|
139
|
+
|
|
140
|
+
To set up the development environment:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
git clone https://github.com/your-username/highway.git
|
|
144
|
+
cd highway
|
|
145
|
+
python -m venv .venv
|
|
146
|
+
source .venv/bin/activate
|
|
147
|
+
pip install -e .[dev]
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Running Tests
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
pytest
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Type Checking
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
mypy .
|
|
160
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
highway_dsl/__init__.py,sha256=mr1oMylxliFwu2VO2qpyM3sVQwYIoPL2P6JE-6ZuF7M,507
|
|
2
|
+
highway_dsl/workflow_dsl.py,sha256=yMTmFr5bbjxfVTleCvSsDZ__n9C7qH39RdzajkUEmiI,11882
|
|
3
|
+
highway_dsl-0.0.3.dist-info/licenses/LICENSE,sha256=qdFq1H66BvKg67mf4-WGpFwtG2u_dNknxuJDQ1_ubaY,1072
|
|
4
|
+
highway_dsl-0.0.3.dist-info/METADATA,sha256=--TFErjeBDZ1mAyNHk30CQavLuKWAaoxgHK7xFpT-Ok,4612
|
|
5
|
+
highway_dsl-0.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
6
|
+
highway_dsl-0.0.3.dist-info/top_level.txt,sha256=_5uX-bbBsQ2rsi1XMr7WRyKbr6ack5GqVBcy-QjF1C8,12
|
|
7
|
+
highway_dsl-0.0.3.dist-info/RECORD,,
|
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: highway_dsl
|
|
3
|
-
Version: 0.0.2
|
|
4
|
-
Summary: A domain specific language (DSL) for defining and managing data processing pipelines.
|
|
5
|
-
Author-email: Farseed Ashouri <farseed.ashouri@gmail.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/rodmena-limited/highway_dsl
|
|
8
|
-
Project-URL: Issues, https://github.com/rodmena-limited/highway_dsl/issues
|
|
9
|
-
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
-
Classifier: Operating System :: OS Independent
|
|
12
|
-
Requires-Python: >=3.9
|
|
13
|
-
Description-Content-Type: text/markdown
|
|
14
|
-
License-File: LICENSE
|
|
15
|
-
Requires-Dist: pydantic>=2.12.3
|
|
16
|
-
Requires-Dist: pyyaml>=6.0
|
|
17
|
-
Provides-Extra: dev
|
|
18
|
-
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
19
|
-
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
20
|
-
Requires-Dist: types-PyYAML>=6.0.0; extra == "dev"
|
|
21
|
-
Requires-Dist: pytest-cov>=2.12.1; extra == "dev"
|
|
22
|
-
Dynamic: license-file
|
|
23
|
-
|
|
24
|
-
# Highway DSL
|
|
25
|
-
|
|
26
|
-
Highway DSL is a Python-based Domain Specific Language (DSL) for defining and managing complex workflows. It allows users to declaratively specify tasks, dependencies, and execution parameters, supporting various control flow mechanisms like conditions, parallel execution, and retries.
|
|
27
|
-
|
|
28
|
-
## Features
|
|
29
|
-
|
|
30
|
-
* **Declarative Workflow Definition:** Define workflows using a clear and concise Python API or through YAML/JSON configurations.
|
|
31
|
-
* **Pydantic Models:** Leverages Pydantic for robust data validation and serialization/deserialization of workflow definitions.
|
|
32
|
-
* **Rich Task Types:** Supports various operators including:
|
|
33
|
-
* `TaskOperator`: Executes a Python function.
|
|
34
|
-
* `ConditionOperator`: Enables conditional branching based on expressions.
|
|
35
|
-
* `WaitOperator`: Pauses workflow execution for a specified duration or until a specific datetime.
|
|
36
|
-
* `ParallelOperator`: Executes multiple branches of tasks concurrently.
|
|
37
|
-
* `ForEachOperator`: Iterates over a collection, executing a chain of tasks for each item.
|
|
38
|
-
* **Retry and Timeout Policies:** Define retry strategies and timeout limits for individual tasks.
|
|
39
|
-
* **Serialization/Deserialization:** Seamless conversion of workflow definitions between Python objects, YAML, and JSON formats.
|
|
40
|
-
* **Workflow Builder:** A fluent API for constructing workflows programmatically.
|
|
41
|
-
|
|
42
|
-
### Feature Overview
|
|
43
|
-
|
|
44
|
-
```mermaid
|
|
45
|
-
graph TD
|
|
46
|
-
A[Workflow] --> B{TaskOperator};
|
|
47
|
-
A --> C{ConditionOperator};
|
|
48
|
-
A --> D{WaitOperator};
|
|
49
|
-
A --> E{ParallelOperator};
|
|
50
|
-
A --> F{ForEachOperator};
|
|
51
|
-
|
|
52
|
-
B --> G[Executes Python Function];
|
|
53
|
-
C --> H{If/Else Branching};
|
|
54
|
-
D --> I[Pauses Execution];
|
|
55
|
-
E --> J[Concurrent Branches];
|
|
56
|
-
F --> K[Iterates Over Items];
|
|
57
|
-
|
|
58
|
-
subgraph Policies
|
|
59
|
-
B --> L[RetryPolicy];
|
|
60
|
-
B --> M[TimeoutPolicy];
|
|
61
|
-
end
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
## Installation
|
|
65
|
-
|
|
66
|
-
To install Highway DSL, you can use pip:
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
pip install highway-dsl
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
If you want to install it for development, including testing dependencies:
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
pip install "highway-dsl[dev]"
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
## Usage
|
|
79
|
-
|
|
80
|
-
### Defining a Simple Workflow
|
|
81
|
-
|
|
82
|
-
```python
|
|
83
|
-
from datetime import timedelta
|
|
84
|
-
from highway_dsl import WorkflowBuilder
|
|
85
|
-
|
|
86
|
-
def demonstrate_basic_workflow():
|
|
87
|
-
"""Show a simple complete workflow using just the builder"""
|
|
88
|
-
|
|
89
|
-
workflow = (
|
|
90
|
-
WorkflowBuilder("simple_etl")
|
|
91
|
-
.task("extract", "etl.extract_data", result_key="raw_data")
|
|
92
|
-
.task(
|
|
93
|
-
"transform",
|
|
94
|
-
"etl.transform_data",
|
|
95
|
-
args=["{{raw_data}}"],
|
|
96
|
-
result_key="transformed_data",
|
|
97
|
-
)
|
|
98
|
-
.retry(max_retries=3, delay=timedelta(seconds=10))
|
|
99
|
-
.task("load", "etl.load_data", args=["{{transformed_data}}"])
|
|
100
|
-
.timeout(timeout=timedelta(minutes=30))
|
|
101
|
-
.wait("wait_next", timedelta(hours=24))
|
|
102
|
-
.task("cleanup", "etl.cleanup")
|
|
103
|
-
.build()
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
workflow.set_variables(
|
|
107
|
-
{"database_url": "postgresql://localhost/mydb", "chunk_size": 1000}
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
return workflow
|
|
111
|
-
|
|
112
|
-
if __name__ == "__main__":
|
|
113
|
-
basic_workflow = demonstrate_basic_workflow()
|
|
114
|
-
print(basic_workflow.to_yaml())
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### Defining a Complex Workflow
|
|
118
|
-
|
|
119
|
-
Refer to `example_usage.py` for a more complex example demonstrating conditional logic, parallel execution, and iteration.
|
|
120
|
-
|
|
121
|
-
### YAML Configuration
|
|
122
|
-
|
|
123
|
-
You can also define workflows directly in YAML:
|
|
124
|
-
|
|
125
|
-
```yaml
|
|
126
|
-
name: simple_etl
|
|
127
|
-
version: 1.0.0
|
|
128
|
-
description: Simple ETL workflow with retry and timeout
|
|
129
|
-
variables:
|
|
130
|
-
database_url: postgresql://localhost/mydb
|
|
131
|
-
chunk_size: 1000
|
|
132
|
-
start_task: extract
|
|
133
|
-
tasks:
|
|
134
|
-
extract:
|
|
135
|
-
task_id: extract
|
|
136
|
-
operator_type: task
|
|
137
|
-
function: etl.extract_data
|
|
138
|
-
result_key: raw_data
|
|
139
|
-
dependencies: []
|
|
140
|
-
metadata: {}
|
|
141
|
-
|
|
142
|
-
transform:
|
|
143
|
-
task_id: transform
|
|
144
|
-
operator_type: task
|
|
145
|
-
function: etl.transform_data
|
|
146
|
-
args: ["{{raw_data}}"]
|
|
147
|
-
result_key: transformed_data
|
|
148
|
-
dependencies: ["extract"]
|
|
149
|
-
retry_policy:
|
|
150
|
-
max_retries: 3
|
|
151
|
-
delay: PT10S
|
|
152
|
-
backoff_factor: 2.0
|
|
153
|
-
metadata: {}
|
|
154
|
-
|
|
155
|
-
load:
|
|
156
|
-
task_id: load
|
|
157
|
-
operator_type: task
|
|
158
|
-
function: etl.load_data
|
|
159
|
-
args: ["{{transformed_data}}"]
|
|
160
|
-
dependencies: ["transform"]
|
|
161
|
-
timeout_policy:
|
|
162
|
-
timeout: PT30M
|
|
163
|
-
kill_on_timeout: true
|
|
164
|
-
metadata: {}
|
|
165
|
-
|
|
166
|
-
wait_next:
|
|
167
|
-
task_id: wait_next
|
|
168
|
-
operator_type: wait
|
|
169
|
-
wait_for: "P1D"
|
|
170
|
-
dependencies: ["load"]
|
|
171
|
-
metadata: {}
|
|
172
|
-
|
|
173
|
-
cleanup:
|
|
174
|
-
task_id: cleanup
|
|
175
|
-
operator_type: task
|
|
176
|
-
function: etl.cleanup
|
|
177
|
-
dependencies: ["wait_next"]
|
|
178
|
-
metadata: {}
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
To load this YAML:
|
|
182
|
-
|
|
183
|
-
```python
|
|
184
|
-
from highway_dsl import Workflow
|
|
185
|
-
|
|
186
|
-
yaml_content = """
|
|
187
|
-
# ... (yaml content from above)
|
|
188
|
-
"""
|
|
189
|
-
|
|
190
|
-
workflow = Workflow.from_yaml(yaml_content)
|
|
191
|
-
print(workflow.name)
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
## Development
|
|
195
|
-
|
|
196
|
-
### Running Tests
|
|
197
|
-
|
|
198
|
-
To run the unit tests, navigate to the project root and execute:
|
|
199
|
-
|
|
200
|
-
```bash
|
|
201
|
-
pytest
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
### Type Checking
|
|
205
|
-
|
|
206
|
-
To perform static type checking with MyPy:
|
|
207
|
-
|
|
208
|
-
```bash
|
|
209
|
-
mypy .
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
## Project Structure
|
|
213
|
-
|
|
214
|
-
```
|
|
215
|
-
.highway/
|
|
216
|
-
├── highway_dsl/
|
|
217
|
-
│ ├── __init__.py # Exposes the public API
|
|
218
|
-
│ └── workflow_dsl.py # Core DSL definitions (Pydantic models)
|
|
219
|
-
├── example_usage.py # Examples of how to use the DSL
|
|
220
|
-
├── tests/
|
|
221
|
-
│ ├── __init__.py
|
|
222
|
-
│ ├── conftest.py # Pytest configuration
|
|
223
|
-
│ └── test_workflow_dsl.py # Unit and integration tests
|
|
224
|
-
├── pyproject.toml # Project metadata and dependencies
|
|
225
|
-
├── README.md # This file
|
|
226
|
-
└── SUMMARY.md # Summary of changes and future instructions
|
|
227
|
-
```
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
highway_dsl/__init__.py,sha256=8qmPd9ZZNgwPGZuWwPYvMOljg73BJIT2SSM7iIRycmw,447
|
|
2
|
-
highway_dsl/workflow_dsl.py,sha256=2QWDhbXLPulq_kTZk_Yjs6L3BNwws_H6EDV0S1CjOXs,9205
|
|
3
|
-
highway_dsl-0.0.2.dist-info/licenses/LICENSE,sha256=qdFq1H66BvKg67mf4-WGpFwtG2u_dNknxuJDQ1_ubaY,1072
|
|
4
|
-
highway_dsl-0.0.2.dist-info/METADATA,sha256=uLLXSVlLWM8H6F5wR1huiAtgXfkIVdmLV-XsYwZkW6s,6390
|
|
5
|
-
highway_dsl-0.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
6
|
-
highway_dsl-0.0.2.dist-info/top_level.txt,sha256=_5uX-bbBsQ2rsi1XMr7WRyKbr6ack5GqVBcy-QjF1C8,12
|
|
7
|
-
highway_dsl-0.0.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|