From 004aefa1597d1bb778b048ae5ddc865786c4963b Mon Sep 17 00:00:00 2001 From: Tobias Raabe Date: Sun, 2 Aug 2026 18:48:07 +0200 Subject: [PATCH 1/2] Catch BaseException from task code --- src/_pytask/execute.py | 2 +- tests/test_execute.py | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/_pytask/execute.py b/src/_pytask/execute.py index 6cf2f2e1..51a25db7 100644 --- a/src/_pytask/execute.py +++ b/src/_pytask/execute.py @@ -122,7 +122,7 @@ def pytask_execute_task_protocol(session: Session, task: PTask) -> ExecutionRepo short_exc_info = remove_traceback_from_exc_info(sys.exc_info()) report = ExecutionReport.from_task_and_exception(task, short_exc_info) session.should_stop = True - except Exception: # noqa: BLE001 + except BaseException: # noqa: BLE001 report = ExecutionReport.from_task_and_exception(task, sys.exc_info()) else: report = ExecutionReport.from_task(task) diff --git a/tests/test_execute.py b/tests/test_execute.py index c3315ab5..c8795684 100644 --- a/tests/test_execute.py +++ b/tests/test_execute.py @@ -11,6 +11,7 @@ import pytest import pytask +from _pytask.mark import MARK_GEN from pytask import CaptureMethod from pytask import ExitCode from pytask import NodeNotFoundError @@ -123,6 +124,45 @@ def task_3(paths = [Path("deleted.txt"), Path("out_2.txt")]): assert isinstance(exc_info[1], NodeNotFoundError) +@pytest.mark.parametrize("exit_code", [0, 7]) +def test_system_exit_is_a_normal_task_failure(tmp_path, exit_code): + source = f""" + from pathlib import Path + + def task_exits(produces=Path("upstream.txt")): + raise SystemExit({exit_code}) + + def task_descendant( + path=Path("upstream.txt"), produces=Path("descendant.txt") + ): + produces.write_text(path.read_text()) + + def task_unrelated(produces=Path("unrelated.txt")): + produces.write_text("unrelated") + """ + tmp_path.joinpath("task_module.py").write_text(textwrap.dedent(source)) + + session = build(paths=tmp_path) + + assert session.exit_code == ExitCode.FAILED + assert tmp_path.joinpath("unrelated.txt").read_text() == "unrelated" + assert not tmp_path.joinpath("descendant.txt").exists() + assert {report.outcome for report in session.execution_reports} == { + TaskOutcome.FAIL, + TaskOutcome.SKIP_PREVIOUS_FAILED, + TaskOutcome.SUCCESS, + } + failed_report = next( + report + for report in session.execution_reports + if report.outcome == TaskOutcome.FAIL + ) + assert failed_report.exc_info is not None + assert isinstance(failed_report.exc_info[1], SystemExit) + assert failed_report.exc_info[1].code == exit_code + assert MARK_GEN.config is None + + def test_depends_on_and_produces_can_be_used_in_task(tmp_path): source = """ from pathlib import Path From ee560510d091a5ebb0449e3e637a8592b2be0494 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 2 Aug 2026 16:49:47 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_mark.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_mark.py b/tests/test_mark.py index 1fb7713e..62218508 100644 --- a/tests/test_mark.py +++ b/tests/test_mark.py @@ -191,8 +191,10 @@ def task_func(arg=arg): [ ( "foo or", - ("at column 7: expected not OR left parenthesis OR identifier; got end of " - "input"), + ( + "at column 7: expected not OR left parenthesis OR identifier; got end of " + "input" + ), ), ( "foo or or",