📝 Update docs examples for custom param types using Annotated, fix overloads for typer.Argument (#594)

* 📝 Add source examples for custom parameter types with Annotated

*  Add tests for custom parameters with Annotated

* 📝 Update docs for custom parameters with Annotated

* ♻️ Fix overloads default in Argument after Annotated

*  Fix test for custom param types
This commit is contained in:
Sebastián Ramírez 2023-05-01 22:02:57 -07:00 committed by GitHub
parent 8c59b68c62
commit 58522bf0fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 169 additions and 12 deletions

View file

@ -11,10 +11,20 @@ There are two ways to achieve this:
`typer.Argument` and `typer.Option` can create custom parameter types with a `parser` <abbr title="something that can be called like a function">callable</abbr>.
=== "Python 3.6+"
```Python hl_lines="12-13 17 18"
{!../docs_src/parameter_types/custom_types/tutorial001.py!}
```
```Python hl_lines="13-14 18-19"
{!> ../docs_src/parameter_types/custom_types/tutorial001_an.py!}
```
=== "Python 3.6+ non-Annotated"
!!! tip
Prefer to use the `Annotated` version if possible.
```Python hl_lines="12-13 17-18"
{!> ../docs_src/parameter_types/custom_types/tutorial001.py!}
```
The function (or callable) that you pass to the parameter `parser` will receive the input value as a string and should return the parsed value with your own custom type.
@ -22,6 +32,17 @@ The function (or callable) that you pass to the parameter `parser` will receive
If you already have a <a href="https://click.palletsprojects.com/en/8.1.x/parameters/#implementing-custom-types" class="external-link" target="_blank">Click Custom Type</a>, you can use it in `typer.Argument()` and `typer.Option()` with the `click_type` parameter.
```Python hl_lines="13-17 21 22"
{!../docs_src/parameter_types/custom_types/tutorial002.py!}
```
=== "Python 3.6+"
```Python hl_lines="14-18 22-25"
{!> ../docs_src/parameter_types/custom_types/tutorial002_an.py!}
```
=== "Python 3.6+ non-Annotated"
!!! tip
Prefer to use the `Annotated` version if possible.
```Python hl_lines="13-17 21-22"
{!> ../docs_src/parameter_types/custom_types/tutorial002.py!}
```

View file

@ -14,7 +14,7 @@ def parse_custom_class(value: str):
def main(
custom_arg: CustomClass = typer.Argument("X", parser=parse_custom_class),
custom_arg: CustomClass = typer.Argument(parser=parse_custom_class),
custom_opt: CustomClass = typer.Option("Y", parser=parse_custom_class),
):
print(f"custom_arg is {custom_arg}")

View file

@ -0,0 +1,26 @@
import typer
from typing_extensions import Annotated
class CustomClass:
def __init__(self, value: str):
self.value = value
def __str__(self):
return f"<CustomClass: value={self.value}>"
def parse_custom_class(value: str):
return CustomClass(value * 2)
def main(
custom_arg: Annotated[CustomClass, typer.Argument(parser=parse_custom_class)],
custom_opt: Annotated[CustomClass, typer.Option(parser=parse_custom_class)] = "Foo",
):
print(f"custom_arg is {custom_arg}")
print(f"--custom-opt is {custom_opt}")
if __name__ == "__main__":
typer.run(main)

View file

@ -18,8 +18,8 @@ class CustomClassParser(click.ParamType):
def main(
custom_arg: CustomClass = typer.Argument("X", click_type=CustomClassParser()),
custom_opt: CustomClass = typer.Option("Y", click_type=CustomClassParser()),
custom_arg: CustomClass = typer.Argument(click_type=CustomClassParser()),
custom_opt: CustomClass = typer.Option("Foo", click_type=CustomClassParser()),
):
print(f"custom_arg is {custom_arg}")
print(f"--custom-opt is {custom_opt}")

View file

@ -0,0 +1,32 @@
import click
import typer
from typing_extensions import Annotated
class CustomClass:
def __init__(self, value: str):
self.value = value
def __repr__(self):
return f"<CustomClass: value={self.value}>"
class CustomClassParser(click.ParamType):
name = "CustomClass"
def convert(self, value, param, ctx):
return CustomClass(value * 3)
def main(
custom_arg: Annotated[CustomClass, typer.Argument(click_type=CustomClassParser())],
custom_opt: Annotated[
CustomClass, typer.Option(click_type=CustomClassParser())
] = "Foo",
):
print(f"custom_arg is {custom_arg}")
print(f"--custom-opt is {custom_opt}")
if __name__ == "__main__":
typer.run(main)

View file

@ -0,0 +1,39 @@
import subprocess
import sys
import typer
from typer.testing import CliRunner
from docs_src.parameter_types.custom_types import tutorial001_an as mod
runner = CliRunner()
app = typer.Typer()
app.command()(mod.main)
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
def test_parse_custom_type():
result = runner.invoke(app, ["0", "--custom-opt", "1"])
assert "custom_arg is <CustomClass: value=00>" in result.output
assert "custom-opt is <CustomClass: value=11>" in result.output
def test_parse_custom_type_with_default():
result = runner.invoke(app, ["0"])
assert "custom_arg is <CustomClass: value=00>" in result.output
assert "custom-opt is <CustomClass: value=FooFoo>" in result.output
def test_script():
result = subprocess.run(
[sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
)
assert "Usage" in result.stdout

View file

@ -26,7 +26,7 @@ def test_parse_custom_type():
def test_parse_custom_type_with_default():
result = runner.invoke(app, ["0"])
assert "custom_arg is <CustomClass: value=000>" in result.output
assert "custom-opt is <CustomClass: value=YYY>" in result.output
assert "custom-opt is <CustomClass: value=FooFooFoo>" in result.output
def test_script():

View file

@ -0,0 +1,39 @@
import subprocess
import sys
import typer
from typer.testing import CliRunner
from docs_src.parameter_types.custom_types import tutorial002_an as mod
runner = CliRunner()
app = typer.Typer()
app.command()(mod.main)
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
def test_parse_custom_type():
result = runner.invoke(app, ["0", "--custom-opt", "1"])
assert "custom_arg is <CustomClass: value=000>" in result.output
assert "custom-opt is <CustomClass: value=111>" in result.output
def test_parse_custom_type_with_default():
result = runner.invoke(app, ["0"])
assert "custom_arg is <CustomClass: value=000>" in result.output
assert "custom-opt is <CustomClass: value=FooFooFoo>" in result.output
def test_script():
result = subprocess.run(
[sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
)
assert "Usage" in result.stdout

View file

@ -254,7 +254,7 @@ def Option(
@overload
def Argument(
# Parameter
default: Optional[Any],
default: Optional[Any] = ...,
*,
callback: Optional[Callable[..., Any]] = None,
metavar: Optional[str] = None,
@ -308,7 +308,7 @@ def Argument(
@overload
def Argument(
# Parameter
default: Optional[Any],
default: Optional[Any] = ...,
*,
callback: Optional[Callable[..., Any]] = None,
metavar: Optional[str] = None,