Examples · a real benchmark sync
Asyncio support carried onto typer 0.9.1
borissmidt/typer, a fork of fastapi/typer with 5 changes on commit f83c66d, between releases, synced onto upstream's 0.9.1. Measured 2026-10-28.
Moved onto 0.9.1, tests pass
5 kept. Read by hand afterwards: correct. Five commits (asyncio support) carried unchanged over 46 upstream commits; identical content, and the fork's async tests pass with upstream's 881.
Each change
| Change | Outcome | Why |
|---|---|---|
| #88: Add asyncio support | kept | applies to the new release as is |
| #88: Fix unrelated failing test of powershell autocomplete | kept | applies to the new release as is |
| #88: Fix the mypy linter | kept | applies to the new release as is |
| #88: Remove the loop factory to simplify the PR and make mypy happy | kept | applies to the new release as is |
| [pre-commit.ci] Auto format from pre-commit.com hooks | kept | applies to the new release as is |
Tests
pass The project's own tests ran on the carried copy (119.6 s) and on 0.9.1 as shipped (108.9 s), and the changes added no failure.
Agents
No agent was needed: every change applied as it was or was already upstream. The free plan does this on its own.
The carried diff against 0.9.1
What the pull request would change: the fork's work on top of the new release.
tests/test_annotated.py | 16 ++++++++++++++++ tests/test_completion/test_completion_install.py | 9 +-------- typer/main.py | 21 +++++++++++++++++++-- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/tests/test_annotated.py b/tests/test_annotated.pyindex 6436ad6..5c6d91b 100644--- a/tests/test_annotated.py+++ b/tests/test_annotated.py@@ -57,3 +57,19 @@ def test_annotated_option_with_argname_doesnt_mutate_multiple_calls(): result = runner.invoke(app, ["--force"]) assert result.exit_code == 0, result.output assert "Forcing operation" in result.output+++def test_runner_can_use_an_async_method():+ app = typer.Typer()++ @app.command()+ async def cmd(val: Annotated[int, typer.Argument()] = 0):+ print(f"hello {val}")++ result = runner.invoke(app)+ assert result.exit_code == 0, result.output+ assert "hello 0" in result.output++ result = runner.invoke(app, ["42"])+ assert result.exit_code == 0, result.output+ assert "hello 42" in result.outputdiff --git a/tests/test_completion/test_completion_install.py b/tests/test_completion/test_completion_install.pyindex 0856b9b..1a5b5f3 100644--- a/tests/test_completion/test_completion_install.py+++ b/tests/test_completion/test_completion_install.py@@ -5,14 +5,12 @@ from pathlib import Path from unittest import mock import shellingham-import typer from typer.testing import CliRunner from docs_src.commands.index import tutorial001 as mod runner = CliRunner()-app = typer.Typer()-app.command()(mod.main)+app = mod.app def test_completion_install_no_shell():@@ -145,11 +143,6 @@ def test_completion_install_fish(): assert "Completion will take effect once you restart the terminal" in result.stdout -runner = CliRunner()-app = typer.Typer()-app.command()(mod.main)-- def test_completion_install_powershell(): completion_path: Path = ( Path.home() / f".config/powershell/Microsoft.PowerShell_profile.ps1"diff --git a/typer/main.py b/typer/main.pyindex c739a29..c662001 100644--- a/typer/main.py+++ b/typer/main.py@@ -1,10 +1,11 @@+import asyncio import inspect import os import sys import traceback from datetime import datetime from enum import Enum-from functools import update_wrapper+from functools import update_wrapper, wraps from pathlib import Path from traceback import FrameSummary, StackSummary from types import TracebackType@@ -46,6 +47,7 @@ try: except ImportError: # pragma: nocover rich = None # type: ignore + _original_except_hook = sys.excepthook _typer_developer_exception_attr_name = "__typer_developer_exception__" @@ -235,12 +237,27 @@ class Typer: cls = TyperCommand def decorator(f: CommandFunctionType) -> CommandFunctionType:+ def add_runner(f: CommandFunctionType) -> CommandFunctionType:+ @wraps(f)+ def run_wrapper(*args: Any, **kwargs: Any) -> Any:+ if sys.version_info >= (3, 7):+ return asyncio.run(f(*args, **kwargs))+ else:+ asyncio.get_event_loop().run_until_complete(f(*args, **kwargs))++ return run_wrapper # type: ignore++ if inspect.iscoroutinefunction(f):+ callback = add_runner(f)+ else:+ callback = f+ self.registered_commands.append( CommandInfo( name=name, cls=cls, context_settings=context_settings,- callback=f,+ callback=callback, help=help, epilog=epilog, short_help=short_help,
From data/bench/sync-real.json, run with scripts/bench-sync-real.ts on 2026-10-28. Every result on the results page, moved or stayed. Try your own fork.