Skip to content

Commit a3aa5e8

Browse files
committed
feat(compiler): implicit_deps_by_order
- allow automatic implicit deps injection specified through subtasks
1 parent 3e56b97 commit a3aa5e8

2 files changed

Lines changed: 46 additions & 0 deletions

File tree

src/cascade/compiler/ast.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ class Goal(BaseTask):
183183
type: ClassVar[str] = "goal"
184184

185185
subtasks: list[str]
186+
implicit_deps_by_order: Optional[bool] = False
186187

187188
@model_validator(mode="after")
188189
def validate_goal(self) -> Self:
@@ -230,6 +231,16 @@ def propogate_priority(self, ast: TaskAST):
230231
for t in self.subtasks:
231232
tasks_dict[t].priority *= self.priority
232233

234+
def inject_implicit_deps(self, ast: TaskAST):
235+
"""
236+
Inject implicit dependencies specified by subtask ordering. This should be done before dependency graph checking
237+
"""
238+
if self.implicit_deps_by_order:
239+
for i in range(1, len(self.subtasks)):
240+
ast.get_tasks_in_dict()[self.subtasks[i]].deps.after.add(
241+
self.subtasks[i - 1]
242+
)
243+
233244

234245
def get_task_type(v: Any) -> str:
235246
if isinstance(v, dict):
@@ -376,6 +387,9 @@ def normalize_dependencies(self) -> list[Step]:
376387
def check(self) -> Self:
377388
self.check_refs()
378389
self.check_deadlines()
390+
# NOTE: Implicit dependency injection should be done before deps checking
391+
for goal in self.get_goals():
392+
goal.inject_implicit_deps(self)
379393
self.check_deps_graph()
380394
return self
381395

tests/compiler/test_deps.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from cascade.compiler import *
2+
from cascade.compiler.ast import CascadeConfig, Dependencies, Status
3+
from typing import List
4+
from zoneinfo import ZoneInfo
5+
6+
DEFAULT_TZ = ZoneInfo("Europe/London")
7+
8+
9+
def test_implicit_deps():
10+
tasks: List[Step | Goal] = [
11+
Step(name="Task A", status=Status.todo, duration=DURATION_UNIT),
12+
Step(
13+
name="Task B",
14+
status=Status.todo,
15+
deps=Dependencies(after={"task-c"}),
16+
duration=DURATION_UNIT,
17+
),
18+
Step(name="Task C", status=Status.todo, duration=DURATION_UNIT),
19+
Goal(
20+
name="Goal A",
21+
subtasks=["goal-b", "task-a"],
22+
implicit_deps_by_order=True,
23+
),
24+
Goal(name="Goal B", subtasks=["task-b"]),
25+
]
26+
ast = TaskAST(config=CascadeConfig(default_tz=DEFAULT_TZ), tasks=tasks)
27+
processed = ProcessedAST.from_raw_ast(ast)
28+
29+
assert len(processed.nodes) == 3
30+
assert processed.nodes["task-a"].deps == {"task-b"}
31+
assert processed.nodes["task-b"].deps == {"task-c"}
32+
assert processed.nodes["task-c"].deps == set()

0 commit comments

Comments
 (0)