From 8903a21b22fc023d275ee01946763b3a2d143a6b Mon Sep 17 00:00:00 2001 From: Dilan Date: Thu, 12 Mar 2026 20:58:32 +0530 Subject: [PATCH 1/5] Fix: trigger result_callback for single command apps (Issue #445) --- typer/main.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/typer/main.py b/typer/main.py index 0bea4c2315..9fe252d114 100644 --- a/typer/main.py +++ b/typer/main.py @@ -1200,6 +1200,25 @@ def get_command(typer_instance: Typer) -> click.Command: if typer_instance._add_completion: click_command.params.append(click_install_param) click_command.params.append(click_show_param) + + click_callback = click_command.callback + use_result_callback = None + if typer_instance.info.result_callback: + if isinstance(typer_instance.info.result_callback, DefaultPlaceholder): + use_result_callback = typer_instance.info.result_callback.value + else: + use_result_callback = typer_instance.info.result_callback + + if click_callback and use_result_callback: + + def callback_wrapper(*args: Any, **kwargs: Any) -> Any: + result = click_callback(*args, **kwargs) + ctx = click.get_current_context() + return ctx.invoke(use_result_callback, result) + + update_wrapper(callback_wrapper, click_callback) + click_command.callback = callback_wrapper # type: ignore + return click_command raise RuntimeError( "Could not get a command for this Typer instance" From 69c587ea314d43420d5bd81a6fc873460f0e8026 Mon Sep 17 00:00:00 2001 From: Dilan Date: Thu, 12 Mar 2026 21:39:30 +0530 Subject: [PATCH 2/5] Fix: resolve lint and coverage issues for PR #1631 --- tests/test_single_command_callback.py | 27 +++++++++++++++++++++++++++ typer/main.py | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 tests/test_single_command_callback.py diff --git a/tests/test_single_command_callback.py b/tests/test_single_command_callback.py new file mode 100644 index 0000000000..b76acc216f --- /dev/null +++ b/tests/test_single_command_callback.py @@ -0,0 +1,27 @@ +import typer +from typer.testing import CliRunner + +runner = CliRunner() + +def test_result_callback_single_command(): + # A list to capture the result from the callback + captured_results = [] + + def my_callback(value: str): + captured_results.append(value) + + # Create app with a result_callback + app = typer.Typer(result_callback=my_callback) + + @app.command() + def main(): + return "single_command_result" + + # Invoke the app (using the single command fast-path) + result = runner.invoke(app, []) + + # Verify the command ran successfully + assert result.exit_code == 0 + + # CRITICAL: Verify the callback was actually executed + assert "single_command_result" in captured_results, "Result callback was not triggered for single command!" diff --git a/typer/main.py b/typer/main.py index 9fe252d114..824b402e6a 100644 --- a/typer/main.py +++ b/typer/main.py @@ -1217,7 +1217,7 @@ def callback_wrapper(*args: Any, **kwargs: Any) -> Any: return ctx.invoke(use_result_callback, result) update_wrapper(callback_wrapper, click_callback) - click_command.callback = callback_wrapper # type: ignore + click_command.callback = callback_wrapper return click_command raise RuntimeError( From 39fdeeb4e5544032d4fb6665cdbc02fbc3b969f0 Mon Sep 17 00:00:00 2001 From: Dilan Date: Thu, 12 Mar 2026 22:12:59 +0530 Subject: [PATCH 3/5] Fix: resolve lint and coverage issues for PR Issue #445) #473 --- mypy_output.txt | Bin 0 -> 326 bytes pre_commit_output.txt | Bin 0 -> 5176 bytes tests/test_single_command_callback.py | 15 +++++++++------ 3 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 mypy_output.txt create mode 100644 pre_commit_output.txt diff --git a/mypy_output.txt b/mypy_output.txt new file mode 100644 index 0000000000000000000000000000000000000000..d5dbdf97ffed257aaad8315f87198a44f76e5971 GIT binary patch literal 326 zcmYk1O$x$5429oX@D8)oMFrgm9>7azDYczyv35#-7G7TcGE$WACdo_kl6k*Ic?}xD znfDuKWVKpd)xcyb6!gGyxH9HBl|~nsLU(P{>8XSEz%kKd0a$j_#8iQH4cEfds5{im z&wCt7jtNiSgtxvC%?`$DEf!$%KOLu2WRtjI?k<&ZqgSO=OD)(RV!cFX3x3zxCAP=d eirD_@wBptqk^Re~NKlN48gKE%7${|18Ve=e*4h_8y88dk^sq~9 z6_gTlQg+W}&dmJhy8QUwvVF^~WuGmxw)JhGXVdmHa{V&vX*RHqX6$!0&#kb=631(C zEHvPM*)B=8Qo^AmJdg~_3cMHW$AUT^fYhjWY6kCHgu?0bRfo{%;h1VjDCI5=1zUfV~{gXwNIuP=}gmhJriWM zqxl#8&19>-QYtk*^C zoE^KQ!}8G@{#Qt0jkBYL4$DW1ihigcxt7eD*;_^UhCQ*DdN!1ass3&4Q*kq81}X2%MCrDMJ6)#NK$cAnelrb4`Y{d z-}Cv7pU#!>PijDp=H9vnwSsC<_Io>1$={0)Jmc*AyQl?paGBTYf!BoSH#$?a#=2#o z8r615$F9f7^{1Z9M;NUO-4(JWc{W_qS(K~z?>Yj>K7}yA0%TMW?z_%Z$+2!8Xcj#O zD}ghoISH;w?l=>5GIDe1cTFe52a&X}n?C1#TeF~|f8IXE9?ys_m$lGsS5Nx!hD&+P z(Rraf0)P7Np5BmZ*JTR()1_m3Vm371R6GY&RRbdatK`AI=(f=6nci55bJXtWZ?GUc ztZ(>r&ArgX!fIvy<(#rvW3CH~T=Qlz!a zqx_Z54egze_&Yp3*TZ@Ur7=`fsNP2Ke~FdA6fCE zeR6MptK0C5k^|D_-eKa!N1AnY`{9&(oCA|BaU=pJ_C{~SOX!9^PvrwiUMR?k1EEGt zpzABgv#V}37q*eTIhQbrSmc(VjCKAduZ!%MvRJbc-=~lDMR88tmigk}?+YuWn$7L^ z?Vb>5Y20>O-qkF(HO=qHKQcw}n#Mi5s{u8U$9KW`>dDQkK9-^JV|y%#>(b?cJnezr zS1a$aOI=1^JT7uG`KfTvB2&-i;os-(nmjvdO6f`AuUEx`nEfJO`z#Jo(|I=K;YWJb z#52`}ayP9|DWb;EXM}gskx^TR(vdG=D%1624-URnZ8CePJ0j0Ux7Sd_lrq((vd~Eo zopBddj}g}5Z(7Ohs@^|HHon)}UR_gtOdUm%mEMw`0&MZS=wsTBG%fv*HuivJs*$dAIi?lddhod*38WKH2Ngw-slOb37 zCQe`BNs{P-_&-)?v~q0gc|E|kPL*D`qPwvZ_QzY`%Ko1GvwE)Ze7YjtO4^_yOQueu f)F6GMM_%rDM;Z5~Z*!~#=Z@%q^}j2&;&}fAiX!~e literal 0 HcmV?d00001 diff --git a/tests/test_single_command_callback.py b/tests/test_single_command_callback.py index b76acc216f..5ae31b4698 100644 --- a/tests/test_single_command_callback.py +++ b/tests/test_single_command_callback.py @@ -3,25 +3,28 @@ runner = CliRunner() + def test_result_callback_single_command(): # A list to capture the result from the callback - captured_results = [] + captured_results: list[str] = [] - def my_callback(value: str): + def my_callback(value: str) -> None: captured_results.append(value) # Create app with a result_callback app = typer.Typer(result_callback=my_callback) @app.command() - def main(): + def main() -> str: return "single_command_result" # Invoke the app (using the single command fast-path) result = runner.invoke(app, []) - + # Verify the command ran successfully assert result.exit_code == 0 - + # CRITICAL: Verify the callback was actually executed - assert "single_command_result" in captured_results, "Result callback was not triggered for single command!" + assert "single_command_result" in captured_results, ( + "Result callback was not triggered for single command!" + ) From 5ecdb9b7dd826361742b585aab335b3ec732620f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Thu, 12 Mar 2026 16:43:56 +0000 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=8E=A8=20Auto=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mypy_output.txt | Bin 326 -> 327 bytes pre_commit_output.txt | Bin 5176 -> 5177 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/mypy_output.txt b/mypy_output.txt index d5dbdf97ffed257aaad8315f87198a44f76e5971..7e32ce4a090d682d8b6ed3e03aa1f8e92ddaa4af 100644 GIT binary patch delta 9 QcmX@cbew5}8zUnZ01+bslmGw# delta 7 OcmX@kbc|_(8zTS=2Lg!z diff --git a/pre_commit_output.txt b/pre_commit_output.txt index f695b19c4ddf33a643ea3bc78ed62dd83319a2b9..21352b7a70f64f8a904575bd831a35ace49e7cff 100644 GIT binary patch delta 9 Qcmdm?u~TD%g$N@T01{XN$p8QV delta 7 Ocmdm~u|s2ng$Mu)(gMB! From 762c99544b427e578bd24674c7fd5f30cc961dab Mon Sep 17 00:00:00 2001 From: Dilan Date: Thu, 12 Mar 2026 22:57:34 +0530 Subject: [PATCH 5/5] Fix: Build Error --- mypy_output.txt | Bin 327 -> 0 bytes pre_commit_output.txt | Bin 5177 -> 0 bytes tests/test_single_command_callback.py | 35 +++++++++++++++++++++++--- 3 files changed, 32 insertions(+), 3 deletions(-) delete mode 100644 mypy_output.txt delete mode 100644 pre_commit_output.txt diff --git a/mypy_output.txt b/mypy_output.txt deleted file mode 100644 index 7e32ce4a090d682d8b6ed3e03aa1f8e92ddaa4af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 327 zcmYk1O$x$5429pi;2mbEiwe3CJb;%_DYczyv35#-7G7TcGE$WACdo_kl6k*Ic?}xD ziT4|4WVKpd)xcyb6!gGyxH9HBl|~nsLU*mz>8ZVTz%kKd0a$j>#8iQH4cEfds5{im z&wCt7jtNiSgtxvC%?`$DEf!$%KOLuIWYf4|?k<&bqgSP~7E0J3V!cFX1Af=pCAP=d firD_@wB*(sk^Re~NKlN48gKE%7${|18Ve=e*4h_8y88dk^sq~9 z6_gTlQg+W}&dmJhy8QUwvVF^~WuGmxw)JhGXVdmHa{V&vX*RHqX6$!0&#kb=631(C zEHvPM*)B=8Qo^AmJdg~_3cMHW$AUT^fYhjWY6kCHgu?0bRfo{%;h1VjDCI5=1zUfV~{gXwNIuP=}gmhJriWM zqxl#8&19>-QYtk*^C zoE^KQ!}8G@{#Qt0jkBYL4$DW1ihigcxt7eD*;_^UhCQ*DdN!1ass3&4Q*kq81}X2%MCrDMJ6)#NK$cAnelrb4`Y{d z-}Cv7pU#!>PijDp=H9vnwSsC<_Io>1$={0)Jmc*AyQl?paGBTYf!BoSH#$?a#=2#o z8r615$F9f7^{1Z9M;NUO-4(JWc{W_qS(K~z?>Yj>K7}yA0%TMW?z_%Z$+2!8Xcj#O zD}ghoISH;w?l=>5GIDe1cTFe52a&X}n?C1#TeF~|f8IXE9?ys_m$lGsS5Nx!hD&+P z(Rraf0)P7Np5BmZ*JTR()1_m3Vm371R6GY&RRbdatK`AI=(f=6nci55bJXtWZ?GUc ztZ(>r&ArgX!fIvy<(#rvW3CH~T=Qlz!a zqx_Z54egze_&Yp3*TZ@Ur7=`fsNP2Ke~FdA6fCE zeR6MptK0C5k^|D_-eKa!N1AnY`{9&(oCA|BaU=pJ_C{~SOX!9^PvrwiUMR?k1EEGt zpzABgv#V}37q*eTIhQbrSmc(VjCKAduZ!%MvRJbc-=~lDMR88tmigk}?+YuWn$7L^ z?Vb>5Y20>O-qkF(HO=qHKQcw}n#Mi5s{u8U$9KW`>dDQkK9-^JV|y%#>(b?cJnezr zS1a$aOI=1^JT7uG`KfTvB2&-i;os-(nmjvdO6f`AuUEx`nEfJO`z#Jo(|I=K;YWJb z#52`}ayP9|DWb;EXM}gskx^TR(vdG=D%1624-URnZ8CePJ0j0Ux7Sd_lrq((vd~Eo zopBddj}g}5Z(7Ohs@^|HHon)}UR_gtOdUm%mEMw`0&MZS=wsTBG%fv*HuivJs*$dAIi?lddhod*38WKH2Ngw-slOb37 zCQe`BNs{P-_&-)?v~q0gc|E|kPL*D`qPwvZ_QzY`%Ko1GvwE)Ze7YjtO4^_yOQueu g)F6GMM_%rDM;Z5~Z*!~#=Z@%q^}j2&;&@;B35O2+-2eap diff --git a/tests/test_single_command_callback.py b/tests/test_single_command_callback.py index 5ae31b4698..f848cf6660 100644 --- a/tests/test_single_command_callback.py +++ b/tests/test_single_command_callback.py @@ -1,10 +1,10 @@ import typer from typer.testing import CliRunner -runner = CliRunner() +runner: CliRunner = CliRunner() -def test_result_callback_single_command(): +def test_result_callback_single_command() -> None: # A list to capture the result from the callback captured_results: list[str] = [] @@ -12,7 +12,7 @@ def my_callback(value: str) -> None: captured_results.append(value) # Create app with a result_callback - app = typer.Typer(result_callback=my_callback) + app: typer.Typer = typer.Typer(result_callback=my_callback) @app.command() def main() -> str: @@ -28,3 +28,32 @@ def main() -> str: assert "single_command_result" in captured_results, ( "Result callback was not triggered for single command!" ) + + +def test_result_callback_single_command_placeholder() -> None: + from typer.models import DefaultPlaceholder + + # A list to capture the result from the callback + captured_results: list[str] = [] + + def my_callback(value: str) -> None: + captured_results.append(value) + + # Create app and manually inject a DefaultPlaceholder for the result_callback + app = typer.Typer() + app.info.result_callback = DefaultPlaceholder(my_callback) + + @app.command() + def main() -> str: + return "placeholder_result" + + # Invoke the app + result = runner.invoke(app, []) + + # Verify the command ran successfully + assert result.exit_code == 0 + + # Verify the callback was actually executed via the placeholder path + assert "placeholder_result" in captured_results, ( + "Result callback was not triggered via placeholder!" + )