-
-
Notifications
You must be signed in to change notification settings - Fork 743
✨ Support common pydantic types #723
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
25a5c37
57f0cb7
6039b4f
c38fefc
e0ad01b
0ff28ba
6dccd26
10e7986
893d86e
ac41a0c
7608cb4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
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. | ||
|
||
=== "Python 3.7+ Argument" | ||
|
||
```Python hl_lines="5" | ||
{!> ../docs_src/parameter_types/pydantic_types/tutorial001_an.py!} | ||
``` | ||
|
||
=== "Python 3.7+ Argument non-Annotated" | ||
|
||
!!! tip | ||
Prefer to use the `Annotated` version if possible. | ||
|
||
```Python hl_lines="4" | ||
{!> ../docs_src/parameter_types/pydantic_types/tutorial001.py!} | ||
``` | ||
|
||
=== "Python 3.7+ Option" | ||
|
||
```Python hl_lines="5" | ||
{!> ../docs_src/parameter_types/pydantic_types/tutorial002_an.py!} | ||
``` | ||
|
||
=== "Python 3.7+ Option non-Annotated" | ||
|
||
!!! tip | ||
Prefer to use the `Annotated` version if possible. | ||
|
||
```Python hl_lines="4" | ||
{!> ../docs_src/parameter_types/pydantic_types/tutorial002.py!} | ||
``` | ||
|
||
These types are also supported in lists or tuples | ||
|
||
=== "Python 3.7+ list" | ||
|
||
```Python hl_lines="6" | ||
{!> ../docs_src/parameter_types/pydantic_types/tutorial003_an.py!} | ||
``` | ||
|
||
=== "Python 3.7+ list non-Annotated" | ||
|
||
!!! tip | ||
Prefer to use the `Annotated` version if possible. | ||
|
||
```Python hl_lines="5" | ||
{!> ../docs_src/parameter_types/pydantic_types/tutorial003.py!} | ||
``` | ||
|
||
=== "Python 3.7+ tuple" | ||
|
||
```Python hl_lines="6" | ||
{!> ../docs_src/parameter_types/pydantic_types/tutorial004_an.py!} | ||
``` | ||
|
||
=== "Python 3.7+ tuple non-Annotated" | ||
|
||
!!! tip | ||
Prefer to use the `Annotated` version if possible. | ||
|
||
```Python hl_lines="5" | ||
{!> ../docs_src/parameter_types/pydantic_types/tutorial004.py!} | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import typer | ||
from pydantic import AnyHttpUrl | ||
|
||
|
||
def main(url_arg: AnyHttpUrl): | ||
typer.echo(f"url_arg: {url_arg}") | ||
|
||
|
||
if __name__ == "__main__": | ||
typer.run(main) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import typer | ||
from pydantic import AnyHttpUrl | ||
from typing_extensions import Annotated | ||
|
||
|
||
def main(url_arg: Annotated[AnyHttpUrl, typer.Argument()]): | ||
typer.echo(f"url_arg: {url_arg}") | ||
|
||
|
||
if __name__ == "__main__": | ||
typer.run(main) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import typer | ||
from pydantic import AnyHttpUrl | ||
|
||
|
||
def main(url_opt: AnyHttpUrl = typer.Option("https://typer.tiangolo.com")): | ||
typer.echo(f"url_opt: {url_opt}") | ||
|
||
|
||
if __name__ == "__main__": | ||
typer.run(main) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import typer | ||
from pydantic import AnyHttpUrl | ||
from typing_extensions import Annotated | ||
|
||
|
||
def main(url_opt: Annotated[AnyHttpUrl, typer.Option()] = "https://typer.tiangolo.com"): | ||
typer.echo(f"url_opt: {url_opt}") | ||
|
||
|
||
if __name__ == "__main__": | ||
typer.run(main) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from typing import List | ||
|
||
import typer | ||
from pydantic import AnyHttpUrl | ||
|
||
|
||
def main(urls: List[AnyHttpUrl] = typer.Option([], "--url")): | ||
typer.echo(f"urls: {urls}") | ||
|
||
|
||
if __name__ == "__main__": | ||
typer.run(main) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
from typing import List | ||
|
||
import typer | ||
from pydantic import AnyHttpUrl | ||
from typing_extensions import Annotated | ||
|
||
|
||
def main( | ||
urls: Annotated[List[AnyHttpUrl], typer.Option("--url", default_factory=list)], | ||
): | ||
typer.echo(f"urls: {urls}") | ||
|
||
|
||
if __name__ == "__main__": | ||
typer.run(main) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
from typing import Tuple | ||
|
||
import typer | ||
from pydantic import AnyHttpUrl, IPvAnyAddress | ||
|
||
|
||
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__": | ||
typer.run(main) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from typing import Tuple | ||
|
||
import typer | ||
from pydantic import AnyHttpUrl, IPvAnyAddress | ||
from typing_extensions import Annotated | ||
|
||
|
||
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__": | ||
typer.run(main) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,3 +10,4 @@ ruff ==0.11.2 | |
# Needed explicitly by typer-slim | ||
rich >=10.11.0 | ||
shellingham >=1.3.0 | ||
pydantic >=2.0.0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import subprocess | ||
import sys | ||
|
||
import typer | ||
from typer.testing import CliRunner | ||
|
||
from docs_src.parameter_types.pydantic_types import tutorial001 as mod | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to make sure to skip these tests when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually I made necessary changes to have everything installed for tests, I don't get the point of skipping the tests, except if we want to ensure that typer still works without pydantic, but running both flavours would be cumbersome There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I personally think it's a valid use-case for someone to be developing on Typer and to be running the test suite locally, without wanting to install There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In my experience, it's better to not skip tests on such conditions because it's "easy" to silently break If a developer tests in local and doesn't have |
||
|
||
runner = CliRunner() | ||
|
||
app = typer.Typer() | ||
app.command()(mod.main) | ||
|
||
|
||
def test_help(): | ||
result = runner.invoke(app, ["--help"]) | ||
assert result.exit_code == 0 | ||
|
||
|
||
def test_url_arg(): | ||
result = runner.invoke(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(): | ||
result = runner.invoke(app, ["invalid"]) | ||
assert result.exit_code != 0 | ||
assert "Input should be a valid URL" in result.output | ||
|
||
|
||
def test_script(): | ||
result = subprocess.run( | ||
[sys.executable, "-m", "coverage", "run", mod.__file__, "--help"], | ||
capture_output=True, | ||
encoding="utf-8", | ||
) | ||
assert "Usage" in result.stdout |
Uh oh!
There was an error while loading. Please reload this page.