From 58522bf0fc363bead311b0bd71247f678ac5635c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Mon, 1 May 2023 22:02:57 -0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20Update=20docs=20examples=20for?= =?UTF-8?q?=20custom=20param=20types=20using=20`Annotated`,=20fix=20overlo?= =?UTF-8?q?ads=20for=20`typer.Argument`=20(#594)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 📝 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 --- docs/tutorial/parameter-types/custom-types.md | 33 +++++++++++++--- .../custom_types/tutorial001.py | 2 +- .../custom_types/tutorial001_an.py | 26 +++++++++++++ .../custom_types/tutorial002.py | 4 +- .../custom_types/tutorial002_an.py | 32 +++++++++++++++ .../test_custom_types/test_tutorial001_an.py | 39 +++++++++++++++++++ .../test_custom_types/test_tutorial002.py | 2 +- .../test_custom_types/test_tutorial002_an.py | 39 +++++++++++++++++++ typer/params.py | 4 +- 9 files changed, 169 insertions(+), 12 deletions(-) create mode 100644 docs_src/parameter_types/custom_types/tutorial001_an.py create mode 100644 docs_src/parameter_types/custom_types/tutorial002_an.py create mode 100644 tests/test_tutorial/test_parameter_types/test_custom_types/test_tutorial001_an.py create mode 100644 tests/test_tutorial/test_parameter_types/test_custom_types/test_tutorial002_an.py diff --git a/docs/tutorial/parameter-types/custom-types.md b/docs/tutorial/parameter-types/custom-types.md index 0412ef0..56acbe4 100644 --- a/docs/tutorial/parameter-types/custom-types.md +++ b/docs/tutorial/parameter-types/custom-types.md @@ -11,10 +11,20 @@ There are two ways to achieve this: `typer.Argument` and `typer.Option` can create custom parameter types with a `parser` callable. +=== "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 Click Custom Type, 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!} + ``` diff --git a/docs_src/parameter_types/custom_types/tutorial001.py b/docs_src/parameter_types/custom_types/tutorial001.py index 902fef1..456c46b 100644 --- a/docs_src/parameter_types/custom_types/tutorial001.py +++ b/docs_src/parameter_types/custom_types/tutorial001.py @@ -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}") diff --git a/docs_src/parameter_types/custom_types/tutorial001_an.py b/docs_src/parameter_types/custom_types/tutorial001_an.py new file mode 100644 index 0000000..f43ae63 --- /dev/null +++ b/docs_src/parameter_types/custom_types/tutorial001_an.py @@ -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"" + + +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) diff --git a/docs_src/parameter_types/custom_types/tutorial002.py b/docs_src/parameter_types/custom_types/tutorial002.py index 9726230..bb8a108 100644 --- a/docs_src/parameter_types/custom_types/tutorial002.py +++ b/docs_src/parameter_types/custom_types/tutorial002.py @@ -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}") diff --git a/docs_src/parameter_types/custom_types/tutorial002_an.py b/docs_src/parameter_types/custom_types/tutorial002_an.py new file mode 100644 index 0000000..f762c17 --- /dev/null +++ b/docs_src/parameter_types/custom_types/tutorial002_an.py @@ -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"" + + +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) diff --git a/tests/test_tutorial/test_parameter_types/test_custom_types/test_tutorial001_an.py b/tests/test_tutorial/test_parameter_types/test_custom_types/test_tutorial001_an.py new file mode 100644 index 0000000..779d86f --- /dev/null +++ b/tests/test_tutorial/test_parameter_types/test_custom_types/test_tutorial001_an.py @@ -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 " in result.output + assert "custom-opt is " in result.output + + +def test_parse_custom_type_with_default(): + result = runner.invoke(app, ["0"]) + assert "custom_arg is " in result.output + assert "custom-opt is " 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 diff --git a/tests/test_tutorial/test_parameter_types/test_custom_types/test_tutorial002.py b/tests/test_tutorial/test_parameter_types/test_custom_types/test_tutorial002.py index 47ff20d..f9e7da0 100644 --- a/tests/test_tutorial/test_parameter_types/test_custom_types/test_tutorial002.py +++ b/tests/test_tutorial/test_parameter_types/test_custom_types/test_tutorial002.py @@ -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 " in result.output - assert "custom-opt is " in result.output + assert "custom-opt is " in result.output def test_script(): diff --git a/tests/test_tutorial/test_parameter_types/test_custom_types/test_tutorial002_an.py b/tests/test_tutorial/test_parameter_types/test_custom_types/test_tutorial002_an.py new file mode 100644 index 0000000..fe3ce78 --- /dev/null +++ b/tests/test_tutorial/test_parameter_types/test_custom_types/test_tutorial002_an.py @@ -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 " in result.output + assert "custom-opt is " in result.output + + +def test_parse_custom_type_with_default(): + result = runner.invoke(app, ["0"]) + assert "custom_arg is " in result.output + assert "custom-opt is " 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 diff --git a/typer/params.py b/typer/params.py index d0ef231..a143f3f 100644 --- a/typer/params.py +++ b/typer/params.py @@ -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,