Skip to content
Open
Show file tree
Hide file tree
Changes from 20 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
2 changes: 0 additions & 2 deletions .github/DISCUSSION_TEMPLATE/questions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ body:
required: true
- label: I already read and followed all the tutorials in the docs and didn't find an answer.
required: true
- label: I already checked if it is not related to Typer but to [Click](https://github.com/pallets/click).
required: true
- type: checkboxes
id: help
attributes:
Expand Down
3 changes: 2 additions & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ abstract: >-
Typer, build great CLIs. Easy to code. Based on Python type hints.
keywords:
- typer
- click
- cli
- python
license: MIT
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,11 +352,19 @@ 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.
* [`colorama`](https://github.com/tartley/colorama) (only on Windows): for producing colored terminal text on Windows.

### Click code

Typer used to depend on [Click](https://click.palletsprojects.com/) as well, a popular tool for building CLIs in Python.

Since version 0.26.0, Typer has vendored Click (included Click's source code internally, instead of installing it as a third party package) and has unified the code interactions between Typer and the embedded Click source code 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.

### `typer-slim`

Expand Down
6 changes: 3 additions & 3 deletions docs/alternatives.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,11 @@ 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

Everything. 🚀
It has <abbr title="included Click's source code internally, instead of installing it as a third party package">vendored</abbr> Click version 8.3.1 and adds a layer on top of it.

**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
12 changes: 10 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,11 +360,19 @@ 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.
* [`colorama`](https://github.com/tartley/colorama) (only on Windows): for producing colored terminal text on Windows.

### Click code

Typer used to depend on [Click](https://click.palletsprojects.com/) as well, a popular tool for building CLIs in Python.

Since version 0.26.0, Typer has vendored Click (included Click's source code internally, instead of installing it as a third party package) and has unified the code interactions between Typer and the embedded Click source code 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.

### `typer-slim`

Expand Down
69 changes: 69 additions & 0 deletions docs/tutorial/click.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Vendored Click

/// note

This is historical information, if you are just learning Typer from scratch, you don't need to read it. ☕️

///

Typer used to depend on [Click](https://click.palletsprojects.com/), a popular tool for building CLIs in Python, as an external dependency.

Since version 0.26.0, Typer has vendored Click (included Click's source code internally, instead of installing it as a third party package) and has unified the code interactions between Typer and the embedded Click source code 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.

## Breaking Changes

Typer used to support extracting the internal Click app from a Typer app to use and modify it with any Click functionality. For example, to add Click-specific plug-ins.

The same way, it supported adding Click-specific types to override the default Typer ones.

Using Click directly was an edge case feature that was not commonly used, and it is no longer supported. If your app depended specifically on this, you will need to either migrate it to use plain Typer, or migrate it to use Click directly instead of Typer.

## Codebase Compatibility Improvements

Because Typer used to depend on Click, any new features or changes in newer Click versions could break compatibility in Typer.

The Click team has always been very helpful and supportive with Typer. But still, this dependency interaction would cause extra effort and burden for both the Typer team and the Click team.

Now that Typer continues evolving, starting from a fixed copy of Click's source code, any changes in Click's codebase will not affect Typer.

The Typer team will not need to make sure there are workarounds for changes in new versions of Click, and the Click team will not need to consider additional edge cases caused by Typer.

## Compatibility Improvements for Your Apps

The fact that Typer depended on Click caused an additional ongoing issue that could happen from time to time to user projects.

Many packages come with a CLI, some of them could use Click, some could use Typer.

Some of these packages could require a recent version of Click that Typer still didn't support, but as all these package dependencies would belong to the same project, there would be conflicts.

In these cases, the package that depended on the newer version of Click would require installing it, but the other package that used Typer would break. Or if there were version pins, some combinations of packages would not be installable together.

Across different versions of Click through time, there were many changes needed in Typer to make it all compatible with multiple versions of Click at the same time.

Now that Typer and Click are decoupled, a package could depend on a newer version of Click, while another package that uses Typer would continue working as normally, as that Typer version would bundle anything necessary from Click's source code to work.

## Future Improvements

Both the Click team and the Typer team have future improvements planned, not having to coordinate with each other for compatibility will simplify the work of both teams.

In some cases feature ideas could overlap, and could have caused incompatibilities. Now this won't be a problem as each team can focus on each project independently.

## Typer Changes

After vendoring Click, Typer will reduce, simplify and refactor parts of the vendored Click code that are not necessary for Typer, or that could be done in a different way to facilitate future improvements in Typer.

Then Typer will gradually introduce new features and improvements that have been planned for a while but were too difficult to implement before this.

## User Focus

These decisions are all carefully planned and based on real world use cases extracted from the official Typer developer survey, including the tradeoff between the potential breaking changes for some use cases and the planned future features and improvements that will be enabled.

## Thank You Click

Click has been the foundation building block of Typer and most CLI's in Python (through Typer or directly with Click).

We wouldn't be here without Click, and we are very grateful for all the work that the Click team has done.

Thank you Click! 🙇
2 changes: 1 addition & 1 deletion docs/tutorial/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ First, make sure you create your [virtual environment](../virtual-environments.m
```console
$ pip install typer
---> 100%
Successfully installed typer click shellingham rich
Successfully installed typer shellingham rich
```

</div>
Expand Down
6 changes: 2 additions & 4 deletions docs/tutorial/package.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,10 @@ $ uv add typer
Using CPython 3.14.0 interpreter at: /location/of/python/
Creating virtual environment at: .venv

Resolved 10 packages in 21ms
Resolved 9 packages in 21ms
Built rick-portal-gun @ file:/home/rick-portal-gun
Prepared 1 package in 19ms
Installed 10 packages in 34ms
+ click==8.3.1
Installed 9 packages in 34ms
+ colorama==0.4.6
+ markdown-it-py==4.0.0
+ mdurl==0.1.2
Expand Down Expand Up @@ -568,7 +567,6 @@ Collecting rick-portal-gun
Downloading rick_portal_gun-0.1.0-py3-none-any.whl.metadata (435 bytes)
Requirement already satisfied: typer<0.13.0,>=0.12.3 in ./.local/lib/python3.10/site-packages (from rick-portal-gun==0.1.0) (0.12.3)
Requirement already satisfied: typing-extensions>=3.7.4.3 in ./.local/lib/python3.10/site-packages (from typer<0.13.0,>=0.12.3->rick-portal-gun==0.1.0) (4.11.0)
Requirement already satisfied: click>=8.0.0 in ./.local/lib/python3.10/site-packages (from typer<0.13.0,>=0.12.3->rick-portal-gun==0.1.0) (8.1.7)
Requirement already satisfied: shellingham>=1.3.0 in ./.local/lib/python3.10/site-packages (from typer<0.13.0,>=0.12.3->rick-portal-gun==0.1.0) (1.5.4)
Requirement already satisfied: rich>=10.11.0 in ./.local/lib/python3.10/site-packages (from typer<0.13.0,>=0.12.3->rick-portal-gun==0.1.0) (13.7.1)
Requirement already satisfied: pygments<3.0.0,>=2.13.0 in ./.local/lib/python3.10/site-packages (from rich>=10.11.0->typer<0.13.0,>=0.12.3->rick-portal-gun==0.1.0) (2.17.2)
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
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ nav:
- tutorial/exceptions.md
- tutorial/one-file-per-command.md
- tutorial/typer-command.md
- tutorial/click.md
- "":
- reference/index.md
- reference/typer.md
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)
Loading
Loading