Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
25a5c37
[FEATURE] Support for common pydantic types
lachaib Dec 21, 2023
57f0cb7
Update docs to refer to Python 3.7+ instead of 3.6+
svlandeg Jul 25, 2024
6039b4f
chore: remove email-validator from examples/testing dependencies
lachaib Jul 25, 2024
c38fefc
fix: correct is_pydantic_type for python 3.12
lachaib Jul 25, 2024
e0ad01b
Fix default value to URL instead of email
svlandeg Jul 26, 2024
0ff28ba
Clarify pydantic as additional extra dependency throughout the docs
svlandeg Jul 26, 2024
6dccd26
ignore type error for now
svlandeg Aug 26, 2024
10e7986
remove unnecessary ignore statement
svlandeg Sep 3, 2024
893d86e
simplify pydantic type conversion using pydantic.TypeAdapter
lachaib Sep 4, 2024
ac41a0c
Merge branch 'master' into lachaib/issue181
svlandeg Apr 4, 2025
7608cb4
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] Apr 4, 2025
24daca1
Merge branch 'master' into lachaib/issue181
svlandeg Aug 28, 2025
3d967ca
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] Aug 28, 2025
b1025d2
update docs to new format
svlandeg Aug 28, 2025
464e60f
Merge branch 'master' into lachaib/issue181
svlandeg Sep 1, 2025
a5636c6
Merge branch 'master' into lachaib/issue181
svlandeg Sep 22, 2025
f3fc8d7
Merge branch 'lachaib/issue181' of https://github.com/lachaib/typer i…
svlandeg Sep 22, 2025
ac92b0f
Merge branch 'master' into lachaib/issue181
svlandeg Sep 23, 2025
eff275d
ignore statement from coverage (as discussed)
svlandeg Oct 3, 2025
b573667
Merge branch 'master' into lachaib/issue181
svlandeg Oct 3, 2025
3ee2868
Merge branch 'master' into lachaib/issue181
svlandeg Nov 25, 2025
9d1ef99
update tutorial files to use explicit Typer()
svlandeg Nov 25, 2025
6fa8211
rename test files to be compatible with current master
svlandeg Jan 13, 2026
8550962
also fix renaming in markdown file
svlandeg Jan 13, 2026
8e0fb62
use new format to generalize test files with a fixture
svlandeg Jan 13, 2026
5282cad
Merge branch 'master' into lachaib/issue181
svlandeg Jan 13, 2026
4717696
sync uv.lock
svlandeg Jan 13, 2026
d59a36f
🎨 Auto format
pre-commit-ci-lite[bot] Jan 13, 2026
168a79e
use builtin tuple and list
svlandeg Jan 13, 2026
640957e
configure importlib to avoid conflicts
svlandeg Jan 13, 2026
cf3ab72
add pydantic also to tests requirements in pyproject.toml
svlandeg Jan 13, 2026
f7881f4
more fixes
svlandeg Jan 13, 2026
8a44202
Merge branch 'master' into lachaib/issue181
svlandeg Jan 22, 2026
15e99a9
Merge branch 'master' into lachaib/issue181
lachaib Jan 26, 2026
8364451
remove duplicated install sections
svlandeg Jan 27, 2026
459fa0b
we shouldn't be updating release notes
svlandeg Jan 27, 2026
ac5d5d6
Merge branch 'master' into lachaib/issue181
svlandeg Feb 4, 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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Create and activate a <a href="https://typer.tiangolo.com/virtual-environments/"
```console
$ pip install typer
---> 100%
Successfully installed typer rich shellingham
Successfully installed typer rich shellingham pydantic
```

</div>
Expand Down Expand Up @@ -361,6 +361,7 @@ For a more complete example including more features, see the <a href="https://ty
By default it also comes with extra standard dependencies:

* <a href="https://rich.readthedocs.io/en/stable/index.html" class="external-link" target="_blank"><code>rich</code></a>: to show nicely formatted errors automatically.
* <a href="https://docs.pydantic.dev/latest/" class="external-link" target="_blank"><code>pydantic</code></a>: to support the usage of Pydantic types.
* <a href="https://github.com/sarugaku/shellingham" class="external-link" target="_blank"><code>shellingham</code></a>: to automatically detect the current shell when installing completion.
* With `shellingham` you can just use `--install-completion`.
* Without `shellingham`, you have to pass the name of the shell to install completion for, e.g. `--install-completion bash`.
Expand All @@ -381,7 +382,7 @@ pip install typer
pip install "typer-slim[standard]"
```

The `standard` extra dependencies are `rich` and `shellingham`.
The `standard` extra dependencies are `rich`, `shellingham` and `pydantic`.

**Note**: The `typer` command is only included in the `typer` package.

Expand Down
5 changes: 3 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Create and activate a <a href="https://typer.tiangolo.com/virtual-environments/"
```console
$ pip install typer
---> 100%
Successfully installed typer rich shellingham
Successfully installed typer rich shellingham pydantic
```

</div>
Expand Down Expand Up @@ -367,6 +367,7 @@ For a more complete example including more features, see the <a href="https://ty
By default it also comes with extra standard dependencies:

* <a href="https://rich.readthedocs.io/en/stable/index.html" class="external-link" target="_blank"><code>rich</code></a>: to show nicely formatted errors automatically.
* <a href="https://docs.pydantic.dev/latest/" class="external-link" target="_blank"><code>pydantic</code></a>: to support the usage of Pydantic types.
* <a href="https://github.com/sarugaku/shellingham" class="external-link" target="_blank"><code>shellingham</code></a>: to automatically detect the current shell when installing completion.
* With `shellingham` you can just use `--install-completion`.
* Without `shellingham`, you have to pass the name of the shell to install completion for, e.g. `--install-completion bash`.
Expand All @@ -387,7 +388,7 @@ pip install typer
pip install "typer-slim[standard]"
```

The `standard` extra dependencies are `rich` and `shellingham`.
The `standard` extra dependencies are `rich`, `shellingham` and `pydantic`.

**Note**: The `typer` command is only included in the `typer` package.

Expand Down
37 changes: 37 additions & 0 deletions docs/tutorial/parameter-types/pydantic-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
Pydantic types such as [AnyUrl](https://docs.pydantic.dev/latest/api/networks/#pydantic.networks.AnyUrl) or [EmailStr](https://docs.pydantic.dev/latest/api/networks/#pydantic.networks.EmailStr) can be very convenient to describe and validate some parameters.

Pydantic is installed automatically when installing Typer with its extra standard dependencies:

<div class="termy">

```console
// Pydantic comes with typer
$ pip install typer
---> 100%
Successfully installed typer rich shellingham pydantic

// Alternatively, you can install Pydantic independently
$ pip install pydantic
---> 100%
Successfully installed pydantic

// Or if you want to use EmailStr
$ pip install "pydantic[email]"
---> 100%
Successfully installed pydantic, email-validator
```

</div>


You can then use them as parameter types.

{* docs_src/parameter_types/pydantic_types/tutorial001_an_py39.py hl[9] *}

{* docs_src/parameter_types/pydantic_types/tutorial002_an_py39.py hl[9] *}

These types are also supported in lists or tuples:

{* docs_src/parameter_types/pydantic_types/tutorial003_an_py39.py hl[9] *}

{* docs_src/parameter_types/pydantic_types/tutorial004_an_py39.py hl[9] *}
Empty file.
15 changes: 15 additions & 0 deletions docs_src/parameter_types/pydantic_types/tutorial001_an_py39.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from typing import Annotated

import typer
from pydantic import AnyHttpUrl

app = typer.Typer()


@app.command()
def main(url_arg: Annotated[AnyHttpUrl, typer.Argument()]):
typer.echo(f"url_arg: {url_arg}")


if __name__ == "__main__":
app()
13 changes: 13 additions & 0 deletions docs_src/parameter_types/pydantic_types/tutorial001_py39.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import typer
from pydantic import AnyHttpUrl

app = typer.Typer()


@app.command()
def main(url_arg: AnyHttpUrl):
typer.echo(f"url_arg: {url_arg}")


if __name__ == "__main__":
app()
15 changes: 15 additions & 0 deletions docs_src/parameter_types/pydantic_types/tutorial002_an_py39.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from typing import Annotated

import typer
from pydantic import AnyHttpUrl

app = typer.Typer()


@app.command()
def main(url_opt: Annotated[AnyHttpUrl, typer.Option()] = "https://typer.tiangolo.com"):
typer.echo(f"url_opt: {url_opt}")


if __name__ == "__main__":
app()
13 changes: 13 additions & 0 deletions docs_src/parameter_types/pydantic_types/tutorial002_py39.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import typer
from pydantic import AnyHttpUrl

app = typer.Typer()


@app.command()
def main(url_opt: AnyHttpUrl = typer.Option("https://typer.tiangolo.com")):
typer.echo(f"url_opt: {url_opt}")


if __name__ == "__main__":
app()
17 changes: 17 additions & 0 deletions docs_src/parameter_types/pydantic_types/tutorial003_an_py39.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from typing import Annotated

import typer
from pydantic import AnyHttpUrl

app = typer.Typer()


@app.command()
def main(
urls: Annotated[list[AnyHttpUrl], typer.Option("--url", default_factory=list)],
):
typer.echo(f"urls: {urls}")


if __name__ == "__main__":
app()
13 changes: 13 additions & 0 deletions docs_src/parameter_types/pydantic_types/tutorial003_py39.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import typer
from pydantic import AnyHttpUrl

app = typer.Typer()


@app.command()
def main(urls: list[AnyHttpUrl] = typer.Option([], "--url")):
typer.echo(f"urls: {urls}")


if __name__ == "__main__":
app()
23 changes: 23 additions & 0 deletions docs_src/parameter_types/pydantic_types/tutorial004_an_py39.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from typing import Annotated

import typer
from pydantic import AnyHttpUrl, IPvAnyAddress

app = typer.Typer()


@app.command()
def main(
server: Annotated[
tuple[str, IPvAnyAddress, AnyHttpUrl],
typer.Option(help="User name, age, email and social media URL"),
],
):
name, address, url = server
typer.echo(f"name: {name}")
typer.echo(f"address: {address}")
typer.echo(f"url: {url}")


if __name__ == "__main__":
app()
20 changes: 20 additions & 0 deletions docs_src/parameter_types/pydantic_types/tutorial004_py39.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import typer
from pydantic import AnyHttpUrl, IPvAnyAddress

app = typer.Typer()


@app.command()
def main(
server: tuple[str, IPvAnyAddress, AnyHttpUrl] = typer.Option(
..., help="Server name, IP address and public URL"
),
):
name, address, url = server
typer.echo(f"name: {name}")
typer.echo(f"address: {address}")
typer.echo(f"url: {url}")


if __name__ == "__main__":
app()
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ nav:
- tutorial/parameter-types/path.md
- tutorial/parameter-types/file.md
- tutorial/parameter-types/custom-types.md
- tutorial/parameter-types/pydantic-types.md
- SubCommands - Command Groups:
- tutorial/subcommands/index.md
- tutorial/subcommands/add-typer.md
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Changelog = "https://typer.tiangolo.com/release-notes/"
standard = [
"shellingham >=1.3.0",
"rich >=10.11.0",
"pydantic >=2.0.0",
]

[dependency-groups]
Expand Down Expand Up @@ -81,6 +82,7 @@ github-actions = [
tests = [
"coverage[toml]>=6.2,<8.0",
"mypy==1.19.1",
"pydantic >=2.0.0",
"pytest>=4.4.0,<9.0.0",
"pytest-cov>=2.10.0,<8.0.0",
"pytest-sugar>=0.9.4,<1.2.0",
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import importlib
import subprocess
import sys
from types import ModuleType

import pytest
from typer.testing import CliRunner

runner = CliRunner()


@pytest.fixture(
name="mod",
params=[
pytest.param("tutorial001_py39"),
pytest.param("tutorial001_an_py39"),
],
)
def get_mod(request: pytest.FixtureRequest) -> ModuleType:
module_name = f"docs_src.parameter_types.pydantic_types.{request.param}"
mod = importlib.import_module(module_name)
return mod


def test_help(mod: ModuleType):
result = runner.invoke(mod.app, ["--help"])
assert result.exit_code == 0


def test_url_arg(mod: ModuleType):
result = runner.invoke(mod.app, ["https://typer.tiangolo.com"])
assert result.exit_code == 0
assert "url_arg: https://typer.tiangolo.com" in result.output


def test_url_arg_invalid(mod: ModuleType):
result = runner.invoke(mod.app, ["invalid"])
assert result.exit_code != 0
assert "Input should be a valid URL" in result.output


def test_script(mod: ModuleType):
result = subprocess.run(
[sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
capture_output=True,
encoding="utf-8",
)
assert "Usage" in result.stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import importlib
import subprocess
import sys
from types import ModuleType

import pytest
from typer.testing import CliRunner

runner = CliRunner()


@pytest.fixture(
name="mod",
params=[
pytest.param("tutorial002_py39"),
pytest.param("tutorial002_an_py39"),
],
)
def get_mod(request: pytest.FixtureRequest) -> ModuleType:
module_name = f"docs_src.parameter_types.pydantic_types.{request.param}"
mod = importlib.import_module(module_name)
return mod


def test_help(mod: ModuleType):
result = runner.invoke(mod.app, ["--help"])
assert result.exit_code == 0


def test_url_opt(mod: ModuleType):
result = runner.invoke(mod.app, ["--url-opt", "https://typer.tiangolo.com"])
assert result.exit_code == 0
assert "url_opt: https://typer.tiangolo.com" in result.output


def test_url_opt_invalid(mod: ModuleType):
result = runner.invoke(mod.app, ["--url-opt", "invalid"])
assert result.exit_code != 0
assert "Input should be a valid URL" in result.output


def test_script(mod: ModuleType):
result = subprocess.run(
[sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
capture_output=True,
encoding="utf-8",
)
assert "Usage" in result.stdout
Loading