From 56957e3275db97a185d27121c652beed6b8b052b Mon Sep 17 00:00:00 2001 From: Bouzouki Polyxeni Date: Sat, 10 Jan 2026 17:29:22 +0200 Subject: [PATCH 1/7] Handle Abort/Exit during shell completion --- typer/completion.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/typer/completion.py b/typer/completion.py index db87f83e3f..1ce7cdc51a 100644 --- a/typer/completion.py +++ b/typer/completion.py @@ -134,12 +134,22 @@ def shell_complete( comp = comp_cls(cli, ctx_args, prog_name, complete_var) if instruction == "source": - click.echo(comp.source()) + try: + click.echo(comp.source()) + except (click.exceptions.Abort, click.exceptions.Exit): + # During shell completion we should never show a traceback. + # If completion callbacks abort/exit, just return no output. + return 0 return 0 # Typer override to print the completion help msg with Rich if instruction == "complete": - click.echo(comp.complete()) + try: + click.echo(comp.complete()) + except (click.exceptions.Abort, click.exceptions.Exit): + # During shell completion we should never show a traceback. + # If completion callbacks abort/exit, just return no output. + return 0 return 0 # Typer override end From 76ff7723dd7debbd8167f65414d117a9a574f799 Mon Sep 17 00:00:00 2001 From: Bouzouki Polyxeni Date: Mon, 12 Jan 2026 23:38:17 +0200 Subject: [PATCH 2/7] Add regression tests for Abort/Exit during shell completion --- tests/test_shell_completion_abort_exit.py | 58 +++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tests/test_shell_completion_abort_exit.py diff --git a/tests/test_shell_completion_abort_exit.py b/tests/test_shell_completion_abort_exit.py new file mode 100644 index 0000000000..c919f6f9c5 --- /dev/null +++ b/tests/test_shell_completion_abort_exit.py @@ -0,0 +1,58 @@ +import click +import pytest + +from typer.completion import shell_complete + + +class _FakeCompletion: + def __init__(self, cli, ctx_args, prog_name, complete_var): + self.cli = cli + self.ctx_args = ctx_args + self.prog_name = prog_name + self.complete_var = complete_var + + def source(self): + # This will be overridden per-test via monkeypatch on the class + return "" + + def complete(self): + # This will be overridden per-test via monkeypatch on the class + return "" + + +@pytest.mark.parametrize( + "instruction", + ["complete_zsh", "source_zsh"], +) +@pytest.mark.parametrize( + "exc", + [click.exceptions.Abort, click.exceptions.Exit], +) +def test_shell_complete_handles_abort_and_exit(monkeypatch, capsys, instruction, exc): + # Make Click return our fake completion class + monkeypatch.setattr( + click.shell_completion, + "get_completion_class", + lambda shell: _FakeCompletion, + ) + + # Patch the specific method used by the instruction to raise + if instruction.startswith("complete"): + monkeypatch.setattr(_FakeCompletion, "complete", lambda self: (_ for _ in ()).throw(exc())) + else: + monkeypatch.setattr(_FakeCompletion, "source", lambda self: (_ for _ in ()).throw(exc())) + + cli = click.Command("demo") + + # Should not raise, should just exit 0 with no output + code = shell_complete( + cli=cli, + ctx_args={}, + prog_name="demo", + complete_var="_DEMO_COMPLETE", + instruction=instruction, + ) + + out = capsys.readouterr() + assert code == 0 + assert out.out == "" \ No newline at end of file From 4dbf25bf89aed43f25bf43921ed0c4fddc2d3d38 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Mon, 12 Jan 2026 21:39:58 +0000 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=8E=A8=20Auto=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_shell_completion_abort_exit.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/test_shell_completion_abort_exit.py b/tests/test_shell_completion_abort_exit.py index c919f6f9c5..175bd90dd9 100644 --- a/tests/test_shell_completion_abort_exit.py +++ b/tests/test_shell_completion_abort_exit.py @@ -1,6 +1,5 @@ import click import pytest - from typer.completion import shell_complete @@ -38,9 +37,13 @@ def test_shell_complete_handles_abort_and_exit(monkeypatch, capsys, instruction, # Patch the specific method used by the instruction to raise if instruction.startswith("complete"): - monkeypatch.setattr(_FakeCompletion, "complete", lambda self: (_ for _ in ()).throw(exc())) + monkeypatch.setattr( + _FakeCompletion, "complete", lambda self: (_ for _ in ()).throw(exc()) + ) else: - monkeypatch.setattr(_FakeCompletion, "source", lambda self: (_ for _ in ()).throw(exc())) + monkeypatch.setattr( + _FakeCompletion, "source", lambda self: (_ for _ in ()).throw(exc()) + ) cli = click.Command("demo") @@ -55,4 +58,4 @@ def test_shell_complete_handles_abort_and_exit(monkeypatch, capsys, instruction, out = capsys.readouterr() assert code == 0 - assert out.out == "" \ No newline at end of file + assert out.out == "" From a54d6606436a4d007f4e15ab009c9cffc6bbb367 Mon Sep 17 00:00:00 2001 From: Bouzouki Polyxeni Date: Tue, 13 Jan 2026 00:03:08 +0200 Subject: [PATCH 4/7] Add small test to cover default completion methods --- tests/test_shell_completion_abort_exit.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_shell_completion_abort_exit.py b/tests/test_shell_completion_abort_exit.py index 175bd90dd9..0ead37f2b4 100644 --- a/tests/test_shell_completion_abort_exit.py +++ b/tests/test_shell_completion_abort_exit.py @@ -59,3 +59,11 @@ def test_shell_complete_handles_abort_and_exit(monkeypatch, capsys, instruction, out = capsys.readouterr() assert code == 0 assert out.out == "" +<<<<<<< HEAD +======= + +def test_fake_completion_default_methods_are_covered(): + fc = _FakeCompletion(click.Command("demo"), {}, "demo", "_DEMO_COMPLETE") + assert fc.complete() == "" + assert fc.source() == "" +>>>>>>> 5db882f (Add small test to cover default completion methods) From 259a92864be42c7a53820a0a41c003be99ea5a5a Mon Sep 17 00:00:00 2001 From: Bouzouki Polyxeni Date: Tue, 13 Jan 2026 00:38:56 +0200 Subject: [PATCH 5/7] Fix regression test for shell completion Abort/Exit --- tests/test_shell_completion_abort_exit.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/test_shell_completion_abort_exit.py b/tests/test_shell_completion_abort_exit.py index 0ead37f2b4..175bd90dd9 100644 --- a/tests/test_shell_completion_abort_exit.py +++ b/tests/test_shell_completion_abort_exit.py @@ -59,11 +59,3 @@ def test_shell_complete_handles_abort_and_exit(monkeypatch, capsys, instruction, out = capsys.readouterr() assert code == 0 assert out.out == "" -<<<<<<< HEAD -======= - -def test_fake_completion_default_methods_are_covered(): - fc = _FakeCompletion(click.Command("demo"), {}, "demo", "_DEMO_COMPLETE") - assert fc.complete() == "" - assert fc.source() == "" ->>>>>>> 5db882f (Add small test to cover default completion methods) From 15d8cf4a71c7faae07ac54fd032e2ba0d3a315cd Mon Sep 17 00:00:00 2001 From: Bouzouki Polyxeni Date: Tue, 13 Jan 2026 01:00:42 +0200 Subject: [PATCH 6/7] Cover default FakeCompletion methods for coverage --- tests/test_shell_completion_abort_exit.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/test_shell_completion_abort_exit.py b/tests/test_shell_completion_abort_exit.py index 175bd90dd9..7adc1ed70f 100644 --- a/tests/test_shell_completion_abort_exit.py +++ b/tests/test_shell_completion_abort_exit.py @@ -28,14 +28,12 @@ def complete(self): [click.exceptions.Abort, click.exceptions.Exit], ) def test_shell_complete_handles_abort_and_exit(monkeypatch, capsys, instruction, exc): - # Make Click return our fake completion class monkeypatch.setattr( click.shell_completion, "get_completion_class", lambda shell: _FakeCompletion, ) - # Patch the specific method used by the instruction to raise if instruction.startswith("complete"): monkeypatch.setattr( _FakeCompletion, "complete", lambda self: (_ for _ in ()).throw(exc()) @@ -47,7 +45,6 @@ def test_shell_complete_handles_abort_and_exit(monkeypatch, capsys, instruction, cli = click.Command("demo") - # Should not raise, should just exit 0 with no output code = shell_complete( cli=cli, ctx_args={}, @@ -59,3 +56,10 @@ def test_shell_complete_handles_abort_and_exit(monkeypatch, capsys, instruction, out = capsys.readouterr() assert code == 0 assert out.out == "" + assert out.err == "" + + +def test_fake_completion_default_methods_are_covered(): + fc = _FakeCompletion(click.Command("demo"), {}, "demo", "_DEMO_COMPLETE") + assert fc.complete() == "" + assert fc.source() == "" \ No newline at end of file From 257f788f024148c90d105998c5c926a8ac40ed2e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Mon, 12 Jan 2026 23:01:14 +0000 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=8E=A8=20Auto=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_shell_completion_abort_exit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_shell_completion_abort_exit.py b/tests/test_shell_completion_abort_exit.py index 7adc1ed70f..646c10e011 100644 --- a/tests/test_shell_completion_abort_exit.py +++ b/tests/test_shell_completion_abort_exit.py @@ -62,4 +62,4 @@ def test_shell_complete_handles_abort_and_exit(monkeypatch, capsys, instruction, def test_fake_completion_default_methods_are_covered(): fc = _FakeCompletion(click.Command("demo"), {}, "demo", "_DEMO_COMPLETE") assert fc.complete() == "" - assert fc.source() == "" \ No newline at end of file + assert fc.source() == ""