Skip to content

Commit 262d6e7

Browse files
authored
Simplify and fix code in dag.py. (#418)
1 parent 60201c4 commit 262d6e7

File tree

5 files changed

+43
-57
lines changed

5 files changed

+43
-57
lines changed

docs/source/changes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ releases are available on [PyPI](https://pypi.org/project/pytask) and
3535
- {pull}`414` allow more ruff rules.
3636
- {pull}`416` removes `.from_annot` again.
3737
- {pull}`417` deprecates {func}`pytask.mark.task` in favor of {func}`pytask.task`.
38+
- {pull}`418` fixes and error and simplifies code in `dag.py`.
3839

3940
## 0.3.2 - 2023-06-07
4041

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ extend-ignore = [
6464
"src/_pytask/capture.py" = ["PGH003"]
6565
"src/_pytask/hookspecs.py" = ["ARG001"]
6666
"src/_pytask/outcomes.py" = ["N818"]
67+
"src/_pytask/dag.py" = ["B023"]
6768
"tests/test_capture.py" = ["T201", "PT011"]
6869
"tests/*" = ["D", "ANN", "PLR2004", "S101"]
6970
"scripts/*" = ["D", "INP001"]

src/_pytask/dag.py

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import hashlib
55
import itertools
66
import sys
7+
from typing import Sequence
78
from typing import TYPE_CHECKING
89

910
import networkx as nx
@@ -38,6 +39,7 @@
3839
from rich.tree import Tree
3940

4041
if TYPE_CHECKING:
42+
from pathlib import Path
4143
from _pytask.session import Session
4244

4345

@@ -79,12 +81,10 @@ def pytask_dag_create_dag(tasks: list[PTask]) -> nx.DiGraph:
7981
dag.add_node(task.name, task=task)
8082

8183
tree_map(lambda x: dag.add_node(x.name, node=x), task.depends_on)
82-
tree_map(
83-
lambda x: dag.add_edge(x.name, task.name), task.depends_on # noqa: B023
84-
)
84+
tree_map(lambda x: dag.add_edge(x.name, task.name), task.depends_on)
8585

8686
tree_map(lambda x: dag.add_node(x.name, node=x), task.produces)
87-
tree_map(lambda x: dag.add_edge(task.name, x.name), task.produces) # noqa: B023
87+
tree_map(lambda x: dag.add_edge(task.name, x.name), task.produces)
8888

8989
_check_if_dag_has_cycles(dag)
9090

@@ -110,9 +110,9 @@ def pytask_dag_select_execution_dag(session: Session, dag: nx.DiGraph) -> None:
110110

111111

112112
@hookimpl
113-
def pytask_dag_validate_dag(dag: nx.DiGraph) -> None:
113+
def pytask_dag_validate_dag(session: Session, dag: nx.DiGraph) -> None:
114114
"""Validate the DAG."""
115-
_check_if_root_nodes_are_available(dag)
115+
_check_if_root_nodes_are_available(dag, session.config["paths"])
116116
_check_if_tasks_have_the_same_products(dag)
117117

118118

@@ -200,7 +200,7 @@ def _format_cycles(cycles: list[tuple[str, ...]]) -> str:
200200
)
201201

202202

203-
def _check_if_root_nodes_are_available(dag: nx.DiGraph) -> None:
203+
def _check_if_root_nodes_are_available(dag: nx.DiGraph, paths: Sequence[Path]) -> None:
204204
missing_root_nodes = []
205205
is_task_skipped: dict[str, bool] = {}
206206

@@ -217,23 +217,14 @@ def _check_if_root_nodes_are_available(dag: nx.DiGraph) -> None:
217217
missing_root_nodes.append(node)
218218

219219
if missing_root_nodes:
220-
all_names = missing_root_nodes + [
221-
successor
222-
for node in missing_root_nodes
223-
for successor in dag.successors(node)
224-
if not is_task_skipped[successor]
225-
]
226-
common_ancestor = find_common_ancestor_of_nodes(*all_names)
227220
dictionary = {}
228221
for node in missing_root_nodes:
229-
short_node_name = reduce_node_name(
230-
dag.nodes[node]["node"], [common_ancestor]
231-
)
222+
short_node_name = reduce_node_name(dag.nodes[node]["node"], paths)
232223
not_skipped_successors = [
233224
task for task in dag.successors(node) if not is_task_skipped[task]
234225
]
235226
short_successors = reduce_names_of_multiple_nodes(
236-
not_skipped_successors, dag, [common_ancestor]
227+
not_skipped_successors, dag, paths
237228
)
238229
dictionary[short_node_name] = short_successors
239230

src/_pytask/shared.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
from _pytask.node_protocols import MetaNode
1414
from _pytask.node_protocols import PPathNode
1515
from _pytask.node_protocols import PTask
16-
from _pytask.node_protocols import PTaskWithPath
1716
from _pytask.path import find_closest_ancestor
1817
from _pytask.path import find_common_ancestor
1918
from _pytask.path import relative_to
@@ -73,7 +72,7 @@ def reduce_node_name(node: MetaNode, paths: Sequence[Path]) -> str:
7372
path from one path in ``session.config["paths"]`` to the node.
7473
7574
"""
76-
if isinstance(node, (PPathNode, PTaskWithPath)):
75+
if isinstance(node, PPathNode):
7776
ancestor = find_closest_ancestor(node.path, paths)
7877
if ancestor is None:
7978
try:

tests/test_dag.py

Lines changed: 31 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
11
from __future__ import annotations
22

33
import textwrap
4-
from contextlib import ExitStack as does_not_raise # noqa: N813
54
from pathlib import Path
65

7-
import networkx as nx
86
import pytest
9-
from _pytask.dag import _check_if_root_nodes_are_available
107
from _pytask.dag import pytask_dag_create_dag
11-
from _pytask.exceptions import NodeNotFoundError
12-
from _pytask.exceptions import ResolvingDependenciesError
138
from attrs import define
149
from pytask import cli
1510
from pytask import ExitCode
11+
from pytask import NodeNotFoundError
1612
from pytask import PathNode
1713
from pytask import Task
1814

@@ -46,36 +42,8 @@ def test_pytask_dag_create_dag():
4642
)
4743

4844

49-
@pytest.mark.unit()
50-
@pytest.mark.xfail(reason="session object is missing.")
51-
def test_check_if_root_nodes_are_available():
52-
dag = nx.DiGraph()
53-
54-
root = Path.cwd() / "src"
55-
56-
path = root.joinpath("task_dummy")
57-
task = Task(base_name="task", path=path, function=None)
58-
task.path = path
59-
task.base_name = "task_dummy"
60-
dag.add_node(task.name, task=task)
61-
62-
available_node = Node.from_path(root.joinpath("available_node"))
63-
dag.add_node(available_node.name, node=available_node)
64-
dag.add_edge(available_node.name, task.name)
65-
66-
with does_not_raise():
67-
_check_if_root_nodes_are_available(dag)
68-
69-
missing_node = Node.from_path(root.joinpath("missing_node"))
70-
dag.add_node(missing_node.name, node=missing_node)
71-
dag.add_edge(missing_node.name, task.name)
72-
73-
with pytest.raises(ResolvingDependenciesError):
74-
_check_if_root_nodes_are_available(dag)
75-
76-
7745
@pytest.mark.end_to_end()
78-
def test_check_if_root_nodes_are_available_end_to_end(tmp_path, runner):
46+
def test_check_if_root_nodes_are_available(tmp_path, runner):
7947
source = """
8048
import pytask
8149
@@ -101,9 +69,35 @@ def task_d(produces):
10169

10270

10371
@pytest.mark.end_to_end()
104-
def test_check_if_root_nodes_are_available_with_separate_build_folder_end_to_end(
105-
tmp_path, runner
106-
):
72+
def test_check_if_root_nodes_are_available_w_name(tmp_path, runner):
73+
source = """
74+
from pathlib import Path
75+
from typing_extensions import Annotated, Any
76+
from pytask import PathNode, PythonNode
77+
78+
node1 = PathNode(name="input1", path=Path(__file__).parent / "in.txt")
79+
node2 = PythonNode(name="input2")
80+
81+
def task_e(in1_: Annotated[Path, node1], in2_: Annotated[Any, node2]): ...
82+
"""
83+
tmp_path.joinpath("task_e.py").write_text(textwrap.dedent(source))
84+
85+
result = runner.invoke(cli, [tmp_path.as_posix()])
86+
87+
assert result.exit_code == ExitCode.DAG_FAILED
88+
assert "Failures during resolving dependencies" in result.output
89+
90+
# Ensure that node names are reduced.
91+
assert "Failures during resolving dependencies" in result.output
92+
assert "Some dependencies do not exist or are" in result.output
93+
assert tmp_path.joinpath("task_e.py").as_posix() + "::task_e" not in result.output
94+
assert "task_e.py::task_e" in result.output
95+
assert tmp_path.joinpath("in.txt").as_posix() not in result.output
96+
assert tmp_path.name + "/in.txt" in result.output
97+
98+
99+
@pytest.mark.end_to_end()
100+
def test_check_if_root_nodes_are_available_with_separate_build_folder(tmp_path, runner):
107101
tmp_path.joinpath("src").mkdir()
108102
tmp_path.joinpath("bld").mkdir()
109103
source = """

0 commit comments

Comments
 (0)