Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9e9f712
initial vendoring of Click into _click subdir
svlandeg Feb 13, 2026
136821e
Merge branch 'master' into drop-click
svlandeg Feb 23, 2026
8bde5b8
Merge branch 'master' into drop-click
svlandeg Feb 23, 2026
b5a53ef
Remove unused Click code (#1601)
svlandeg Mar 24, 2026
7783f0a
Merge branch 'master' into drop-click
svlandeg Mar 24, 2026
83c6f86
Merge Click and Typer functionality (#1680)
svlandeg Apr 13, 2026
1855beb
Merge branch 'master' into upstream_drop_click
svlandeg Apr 13, 2026
2c3394e
Merge branch 'drop-click' of https://github.com/tiangolo/typer into u…
svlandeg Apr 13, 2026
98d7e40
Increase coverage (#1690)
svlandeg May 19, 2026
8b5e25a
Merge branch 'master' into drop-click
svlandeg May 20, 2026
d410917
fix
svlandeg May 20, 2026
fcc2a2d
fix uv lock
svlandeg May 20, 2026
34a0731
Bring feature branch up-to-date with `master` (#1773)
svlandeg May 20, 2026
50d713a
Enable `ty` and resolve typing issues (#1770)
svlandeg May 20, 2026
9c84486
fix "typo"
svlandeg May 20, 2026
62d21fc
Merge branch 'master' into drop-click
svlandeg May 20, 2026
1c936a9
update docs
svlandeg May 20, 2026
431586c
🎨 Auto format
github-actions[bot] May 20, 2026
bb50511
📝 Tweak additional wording around Click vendoring
tiangolo May 21, 2026
b46de71
📝 Add docs section to explain Click vendoring and thanks to the Click…
tiangolo May 21, 2026
958ccc5
Merge branch 'master' into drop-click
svlandeg May 22, 2026
5ef6e42
📝 Tweak docs about dependencies
tiangolo May 22, 2026
efe0f22
Merge branch 'drop-click' of https://github.com/tiangolo/typer into d…
svlandeg May 22, 2026
d839ebc
Merge branch 'master' into drop-click
svlandeg May 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,12 +352,17 @@ For a more complete example including more features, see the <a href="https://ty

## Dependencies

**Typer** stands on the shoulders of giants. It has three required dependencies:
**Typer** stands on the shoulders of giants. It has two required dependencies:

* [Click](https://click.palletsprojects.com/): a popular tool for building CLIs in Python. Typer is based on it.
* [`rich`](https://rich.readthedocs.io/en/stable/index.html): to show nicely formatted errors automatically.
* [`shellingham`](https://github.com/sarugaku/shellingham): to automatically detect the current shell when installing completion.

Typer used to depend on [Click](https://click.palletsprojects.com/) as well, a popular tool for building CLIs in Python.
Since v0.26.0, Typer has vendored Click and has unified the two code bases for easier maintainability in the future.
Note that some Click functionality will not be available anymore in the future, as we continue to improve and extend Typer's codebase.

For Windows users, Typer also installs [`colorama`](https://github.com/tartley/colorama) for producing colored terminal text.

### `typer-slim`

There used to be a slimmed-down version of Typer called `typer-slim`, which didn't include the dependencies `rich` and `shellingham`, nor the `typer` command.
Expand Down
6 changes: 2 additions & 4 deletions docs/alternatives.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,9 @@ It uses decorators on top of functions to modify the actual value of those funct

It was built with some great ideas and design using the features available in the language at the time (Python 2.x).

/// tip | **Typer** uses it for
/// tip | **Typer** builds on top of Click functionality. It has vendored Click v8.3.1 and adds a layer on top of it.

Everything. 🚀

**Typer** mainly adds a layer on top of Click, making the code simpler and easier to use, with autocompletion everywhere, etc, but providing all the powerful features of Click underneath.
Typer aims to make the code simpler and easier to use, with autocompletion everywhere, etc, while still providing many of the powerful features of Click underneath.

As someone pointed out: <em>["Nice to see it is built on Click but adds the type stuff. Me gusta!"](https://twitter.com/fishnets88/status/1210126833745838080)</em>

Expand Down
8 changes: 0 additions & 8 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,6 @@ Auto completion works when you create a package (installable with `pip`). Or whe

///

/// tip

**Typer**'s completion is implemented internally, it uses ideas and components from Click and ideas from `click-completion`, but it doesn't use `click-completion` and re-implements some of the relevant parts of Click.

Then it extends those ideas with features and bug fixes. For example, **Typer** programs also support modern versions of PowerShell (e.g. in Windows 10) among all the other shells.

///

## Tested

* 100% <abbr title="The amount of code that is automatically tested">test coverage</abbr>.
Expand Down
2 changes: 1 addition & 1 deletion docs/help-typer.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ There you could buy me a coffee ☕️ to say thanks. 😄

## Sponsor the tools that power Typer

As you have seen in the documentation, Typer is built on top of Click.
As you have seen in the documentation, Typer has been built on top of Click.

You can also sponsor:

Expand Down
9 changes: 7 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,12 +360,17 @@ For a more complete example including more features, see the <a href="https://ty

## Dependencies

**Typer** stands on the shoulders of giants. It has three required dependencies:
**Typer** stands on the shoulders of giants. It has two required dependencies:
Comment thread
svlandeg marked this conversation as resolved.
Outdated

* [Click](https://click.palletsprojects.com/): a popular tool for building CLIs in Python. Typer is based on it.
* [`rich`](https://rich.readthedocs.io/en/stable/index.html): to show nicely formatted errors automatically.
* [`shellingham`](https://github.com/sarugaku/shellingham): to automatically detect the current shell when installing completion.
Comment thread
svlandeg marked this conversation as resolved.

Typer used to depend on [Click](https://click.palletsprojects.com/) as well, a popular tool for building CLIs in Python.
Since v0.26.0, Typer has vendored Click and has unified the two code bases for easier maintainability in the future.
Comment thread
tiangolo marked this conversation as resolved.
Outdated
Note that some Click functionality will not be available anymore in the future, as we continue to improve and extend Typer's codebase.

For Windows users, Typer also installs [`colorama`](https://github.com/tartley/colorama) for producing colored terminal text.

### `typer-slim`

There used to be a slimmed-down version of Typer called `typer-slim`, which didn't include the dependencies `rich` and `shellingham`, nor the `typer` command.
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorial/printing.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ And for the cases where you want to display data more beautifully, or more advan

### Why `typer.echo`

`typer.echo()` (which is actually just `click.echo()`) applies some checks to try and convert binary data to strings, and other similar things.
`typer.echo()` applies some checks to try and convert binary data to strings, and other similar things.

But in most of the cases you wouldn't need it, as in modern Python strings (`str`) already support and use Unicode, and you would rarely deal with pure `bytes` that you want to print on the screen.

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ classifiers = [
"Programming Language :: Python :: 3.14",
]
dependencies = [
"click >= 8.2.1, < 8.4",
"shellingham >=1.3.0",
"rich >=13.8.0",
"annotated-doc >=0.0.2",
"colorama; platform_system == 'Windows'",
]
readme = "README.md"

Expand Down Expand Up @@ -189,7 +189,7 @@ ignore = [
"docs_src/*" = ["TID"]

[tool.ruff.lint.isort]
known-third-party = ["typer", "click"]
known-third-party = ["typer"]
# For docs_src/subcommands/tutorial003/
known-first-party = ["reigns", "towns", "lands", "items", "users"]

Expand Down
4 changes: 2 additions & 2 deletions tests/assets/completion_argument.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import click
import typer
from typer import _click

app = typer.Typer()


def shell_complete(ctx: click.Context, param: click.Parameter, incomplete: str):
def shell_complete(ctx: _click.Context, param: _click.Parameter, incomplete: str):
typer.echo(f"ctx: {ctx.info_name}", err=True)
typer.echo(f"arg is: {param.name}", err=True)
typer.echo(f"incomplete is: {incomplete}", err=True)
Expand Down
63 changes: 63 additions & 0 deletions tests/atomic_write_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import time

import typer

app = typer.Typer()


@app.command()
def write_atomic(
config: typer.FileText = typer.Option(..., mode="w", atomic=True),
pause: float = typer.Option(0.3),
) -> None:
config.write("atomic-content-1\n")
config.flush()
typer.echo("halfway")
time.sleep(pause)
config.write("atomic-content-2\n")
config.flush()
typer.echo("written atomically")


@app.command()
def write_atomic_binary(
config: typer.FileBinaryWrite = typer.Option(..., atomic=True, lazy=False),
) -> None:
config.write(b"\x00\x01binary-atomic\n")
typer.echo("written binary atomically")


@app.command()
def api_atomic(
config: typer.FileText = typer.Option(..., mode="w", atomic=True, lazy=False),
) -> None:
typer.echo(f"name={config.name}")
typer.echo(f"repr={repr(config)}")
with config as entered:
typer.echo(f"entered={entered is config}")
entered.write("atomic-api-done\n")


@app.command()
def invalid_atomic_append(
config: typer.FileText = typer.Option(..., mode="a", atomic=True, lazy=False),
) -> None:
typer.echo(config.name) # pragma: no cover


@app.command()
def invalid_atomic_exclusive(
config: typer.FileText = typer.Option(..., mode="x", atomic=True, lazy=False),
) -> None:
typer.echo(config.name) # pragma: no cover


@app.command()
def invalid_atomic_read(
config: typer.FileText = typer.Option(..., mode="r", atomic=True, lazy=False),
) -> None:
typer.echo(config.name) # pragma: no cover


if __name__ == "__main__":
app()
12 changes: 12 additions & 0 deletions tests/test_annotated.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from pathlib import Path
from typing import Annotated

import pytest
import typer
from typer.testing import CliRunner

Expand Down Expand Up @@ -95,3 +96,14 @@ def custom_parser(

result = runner.invoke(app, "/some/quirky/path/implementation")
assert result.exit_code == 0


def test_annotated_option_invalid():
app = typer.Typer()

@app.command()
def cmd(value: Annotated[str, typer.Option(..., "foo-bar")]):
print(value) # pragma: no cover

with pytest.raises(ValueError, match="Invalid start character for option"):
runner.invoke(app, ["--help"], catch_exceptions=False)
122 changes: 122 additions & 0 deletions tests/test_atomic_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import subprocess
import sys
from pathlib import Path

import pytest

from . import atomic_write_example as mod


def test_atomic_write(tmp_path: Path) -> None:
original_content = "existing-content\n"
output_file = tmp_path / "atomic-write-target.txt"
output_file.write_text(original_content, encoding="utf-8")

process = subprocess.Popen(
[
sys.executable,
"-m",
"coverage",
"run",
mod.__file__,
"write-atomic",
f"--config={output_file}",
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
assert process.stdout is not None

# Halfway of writing the file, check that the original content is still there
halfway_line = process.stdout.readline().strip()
assert halfway_line == "halfway"
assert output_file.read_text(encoding="utf-8") == original_content

# Only at the end, the full new content is visible
stdout, stderr = process.communicate(timeout=5)
assert process.returncode == 0, stderr
assert "written atomically" in stdout
assert (
output_file.read_text(encoding="utf-8")
== "atomic-content-1\natomic-content-2\n"
)


def test_atomic_binary_write(tmp_path: Path) -> None:
output_file = tmp_path / "atomic-binary.bin"

result = subprocess.run(
[
sys.executable,
"-m",
"coverage",
"run",
mod.__file__,
"write-atomic-binary",
f"--config={output_file}",
],
capture_output=True,
encoding="utf-8",
)

assert result.returncode == 0, result.stderr
assert "written binary atomically" in result.stdout
assert output_file.read_bytes() == b"\x00\x01binary-atomic\n"


def test_atomic_api(tmp_path: Path) -> None:
output_file = tmp_path / "atomic-api.txt"

result = subprocess.run(
[
sys.executable,
"-m",
"coverage",
"run",
mod.__file__,
"api-atomic",
f"--config={output_file}",
],
capture_output=True,
encoding="utf-8",
)

assert result.returncode == 0, result.stderr
assert f"name={output_file}" in result.stdout
assert "repr=<_io.TextIOWrapper" in result.stdout
assert "entered=True" in result.stdout
assert output_file.read_text(encoding="utf-8") == "atomic-api-done\n"


@pytest.mark.parametrize(
("command_name", "expected_message"),
[
("invalid-atomic-append", "Appending to an existing file is not supported"),
("invalid-atomic-exclusive", "Use the `overwrite`-parameter instead."),
("invalid-atomic-read", "Atomic writes only make sense with `w`-mode."),
],
)
def test_atomic_mode_invalid_options(
tmp_path: Path, command_name: str, expected_message: str
) -> None:
output_file = tmp_path / "atomic-invalid-mode.txt"
output_file.write_text("existing-content\n", encoding="utf-8")

result = subprocess.run(
[
sys.executable,
"-m",
"coverage",
"run",
mod.__file__,
command_name,
f"--config={output_file}",
],
capture_output=True,
encoding="utf-8",
)

assert result.returncode != 0
combined_output = f"{result.stdout}\n{result.stderr}"
assert expected_message in combined_output
Loading
Loading