Skip to content

Commit 0725455

Browse files
authored
Show all files that are not produced by a task. (#287)
1 parent 74ad361 commit 0725455

File tree

3 files changed

+38
-7
lines changed

3 files changed

+38
-7
lines changed

docs/source/changes.md

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ releases are available on [PyPI](https://pypi.org/project/pytask) and
1212
- {pull}`283` fixes an issue with coverage and tests using pexpect + `pdb.set_trace()`.
1313
- {pull}`285` implements that pytask does not show the traceback of tasks which are
1414
skipped because its previous task failed. Fixes {issue}`284`.
15+
- {pull}`287` changes that all files that are not produced by a task are displayed in
16+
the error message. Fixes {issue}`262`.
1517

1618
## 0.2.3 - 2022-05-30
1719

src/_pytask/execute.py

+17-7
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from _pytask.console import console
1212
from _pytask.console import create_summary_panel
1313
from _pytask.console import create_url_style_for_task
14+
from _pytask.console import format_strings_as_flat_tree
1415
from _pytask.console import format_task_id
1516
from _pytask.console import unify_styles
1617
from _pytask.dag import descending_tasks
@@ -27,6 +28,7 @@
2728
from _pytask.report import ExecutionReport
2829
from _pytask.session import Session
2930
from _pytask.shared import get_first_non_none_value
31+
from _pytask.shared import reduce_node_name
3032
from _pytask.traceback import format_exception_without_traceback
3133
from _pytask.traceback import remove_traceback_from_exc_info
3234
from _pytask.traceback import render_exc_info
@@ -167,15 +169,23 @@ def pytask_execute_task(task: Task) -> bool:
167169

168170
@hookimpl
169171
def pytask_execute_task_teardown(session: Session, task: Task) -> None:
170-
"""Check if each produced node was indeed produced."""
172+
"""Check if :class:`_pytask.nodes.FilePathNode` are produced by a task."""
173+
missing_nodes = []
171174
for product in session.dag.successors(task.name):
172175
node = session.dag.nodes[product]["node"]
173-
try:
174-
node.state()
175-
except NodeNotFoundError as e:
176-
raise NodeNotFoundError(
177-
f"{node.name} was not produced by {task.name}."
178-
) from e
176+
if isinstance(node, FilePathNode):
177+
178+
try:
179+
node.state()
180+
except NodeNotFoundError:
181+
missing_nodes.append(node)
182+
183+
if missing_nodes:
184+
paths = [reduce_node_name(i, session.config["paths"]) for i in missing_nodes]
185+
formatted = format_strings_as_flat_tree(
186+
paths, "The task did not produce the following files:\n", ""
187+
)
188+
raise NodeNotFoundError(formatted)
179189

180190

181191
@hookimpl(trylast=True)

tests/test_execute.py

+19
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,25 @@ def task_example():
3636
assert isinstance(session.execution_reports[0].exc_info[1], NodeNotFoundError)
3737

3838

39+
@pytest.mark.end_to_end
40+
def test_task_did_not_produce_multiple_nodes_and_all_are_shown(runner, tmp_path):
41+
source = """
42+
import pytask
43+
44+
@pytask.mark.produces(["1.txt", "2.txt"])
45+
def task_example():
46+
pass
47+
"""
48+
tmp_path.joinpath("task_module.py").write_text(textwrap.dedent(source))
49+
50+
result = runner.invoke(cli, [tmp_path.as_posix()])
51+
52+
assert result.exit_code == ExitCode.FAILED
53+
assert "NodeNotFoundError" in result.output
54+
assert "1.txt" in result.output
55+
assert "2.txt" in result.output
56+
57+
3958
@pytest.mark.end_to_end
4059
def test_node_not_found_in_task_setup(tmp_path):
4160
"""Test for :class:`_pytask.exceptions.NodeNotFoundError` in task setup.

0 commit comments

Comments
 (0)