From a3ee10a76c6b67f41cb5e656ec7fd203a7a5f2cf Mon Sep 17 00:00:00 2001 From: Muhammed Hussein Karimi Date: Tue, 10 Sep 2024 00:29:10 +0330 Subject: [PATCH 01/23] =?UTF-8?q?=E2=9C=A8=20feat:=20json=20and=20bytes=20?= =?UTF-8?q?field=20suppport=20in=20options?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Muhammed Hussein Karimi --- typer/main.py | 5 ++++- typer/models.py | 20 +++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/typer/main.py b/typer/main.py index a621bda6ad..4d32948a09 100644 --- a/typer/main.py +++ b/typer/main.py @@ -42,6 +42,7 @@ ParamMeta, Required, TyperInfo, + DictParamType, ) from .utils import get_params_from_function @@ -716,8 +717,10 @@ def get_click_type( elif parameter_info.parser is not None: return click.types.FuncParamType(parameter_info.parser) - elif annotation is str: + elif annotation in [str, bytes]: return click.STRING + elif annotation is dict: + return DictParamType() elif annotation is int: if parameter_info.min is not None or parameter_info.max is not None: min_ = None diff --git a/typer/models.py b/typer/models.py index 9bbe2a36d2..63a0d909b2 100644 --- a/typer/models.py +++ b/typer/models.py @@ -1,5 +1,6 @@ import inspect import io +import json from typing import ( TYPE_CHECKING, Any, @@ -19,7 +20,7 @@ if TYPE_CHECKING: # pragma: no cover from .core import TyperCommand, TyperGroup from .main import Typer - + from click.core import Parameter NoneType = type(None) @@ -52,6 +53,23 @@ class CallbackParam(click.Parameter): pass +class DictParamType(click.ParamType): + name = "dict" + + def convert( + self, + value: Any, + param: Optional["Parameter"], + ctx: Optional["Context"] + ) -> Any: + if isinstance(value, dict): + return value + return json.loads(value) + + def __repr__(self) -> str: + return "DICT" + + class DefaultPlaceholder: """ You shouldn't use this class directly. From 1dd8d719b8983fcbeb803d8a290a0727dc831f3f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 21:03:52 +0000 Subject: [PATCH 02/23] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20f?= =?UTF-8?q?ormat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- typer/main.py | 2 +- typer/models.py | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/typer/main.py b/typer/main.py index 4d32948a09..7a8ca0966d 100644 --- a/typer/main.py +++ b/typer/main.py @@ -32,6 +32,7 @@ Default, DefaultPlaceholder, DeveloperExceptionConfig, + DictParamType, FileBinaryRead, FileBinaryWrite, FileText, @@ -42,7 +43,6 @@ ParamMeta, Required, TyperInfo, - DictParamType, ) from .utils import get_params_from_function diff --git a/typer/models.py b/typer/models.py index 63a0d909b2..067026ac56 100644 --- a/typer/models.py +++ b/typer/models.py @@ -18,9 +18,10 @@ import click.shell_completion if TYPE_CHECKING: # pragma: no cover + from click.core import Parameter + from .core import TyperCommand, TyperGroup from .main import Typer - from click.core import Parameter NoneType = type(None) @@ -57,10 +58,7 @@ class DictParamType(click.ParamType): name = "dict" def convert( - self, - value: Any, - param: Optional["Parameter"], - ctx: Optional["Context"] + self, value: Any, param: Optional["Parameter"], ctx: Optional["Context"] ) -> Any: if isinstance(value, dict): return value From 1f3d1cf9ad8a533bf29c2fba9d2a787a57866b60 Mon Sep 17 00:00:00 2001 From: Muhammed Hussein Karimi Date: Tue, 10 Sep 2024 00:39:42 +0330 Subject: [PATCH 03/23] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20=20typo:=20fix=20lin?= =?UTF-8?q?ter=20issues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Muhammed Hussein Karimi --- typer/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/typer/models.py b/typer/models.py index 067026ac56..07feaa01df 100644 --- a/typer/models.py +++ b/typer/models.py @@ -58,7 +58,10 @@ class DictParamType(click.ParamType): name = "dict" def convert( - self, value: Any, param: Optional["Parameter"], ctx: Optional["Context"] + self, + value: Any, + param: Optional["click.Parameter"], + ctx: Optional["click.Context"], ) -> Any: if isinstance(value, dict): return value From c24f1a7288e769240f4bffae6cba855da4edac41 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 21:11:23 +0000 Subject: [PATCH 04/23] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20f?= =?UTF-8?q?ormat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- typer/models.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/typer/models.py b/typer/models.py index 07feaa01df..bd614582a8 100644 --- a/typer/models.py +++ b/typer/models.py @@ -18,8 +18,6 @@ import click.shell_completion if TYPE_CHECKING: # pragma: no cover - from click.core import Parameter - from .core import TyperCommand, TyperGroup from .main import Typer From 11292ff03ff46a9c7de9af941475db0d1b590e5b Mon Sep 17 00:00:00 2001 From: Muhammed Hussein Karimi Date: Tue, 10 Sep 2024 13:11:19 +0330 Subject: [PATCH 05/23] =?UTF-8?q?=F0=9F=93=9D=20docs:=20added=20json=20arg?= =?UTF-8?q?=20type=20in=20docs=5Fsrc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Muhammed Hussein Karimi --- docs_src/parameter_types/json/__init__.py | 0 docs_src/parameter_types/json/tutorial001.py | 11 +++++ .../test_json/__init__.py | 0 .../test_json/test_tutorial001.py | 48 +++++++++++++++++++ 4 files changed, 59 insertions(+) create mode 100644 docs_src/parameter_types/json/__init__.py create mode 100644 docs_src/parameter_types/json/tutorial001.py create mode 100644 tests/test_tutorial/test_parameter_types/test_json/__init__.py create mode 100644 tests/test_tutorial/test_parameter_types/test_json/test_tutorial001.py diff --git a/docs_src/parameter_types/json/__init__.py b/docs_src/parameter_types/json/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/parameter_types/json/tutorial001.py b/docs_src/parameter_types/json/tutorial001.py new file mode 100644 index 0000000000..ae4c798743 --- /dev/null +++ b/docs_src/parameter_types/json/tutorial001.py @@ -0,0 +1,11 @@ +import json +import typer +from typing import Annotated + + +def main(user_info: Annotated[dict, typer.Option()]): + print(f"User Info: {json.dumps(user_info)}") + + +if __name__ == "__main__": + typer.run(main) diff --git a/tests/test_tutorial/test_parameter_types/test_json/__init__.py b/tests/test_tutorial/test_parameter_types/test_json/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/test_tutorial/test_parameter_types/test_json/test_tutorial001.py b/tests/test_tutorial/test_parameter_types/test_json/test_tutorial001.py new file mode 100644 index 0000000000..798c560da1 --- /dev/null +++ b/tests/test_tutorial/test_parameter_types/test_json/test_tutorial001.py @@ -0,0 +1,48 @@ +import subprocess +import sys +import json + +import typer +from typer.testing import CliRunner + +from docs_src.parameter_types.json import tutorial001 as mod + +runner = CliRunner() + +app = typer.Typer() +app.command()(mod.main) + + +def test_help(): + result = runner.invoke(app, ["--help"]) + assert result.exit_code == 0 + assert "--user-info" in result.output + assert "DICT" in result.output + + +def test_params(): + data = {"name": "Camila", "age": 15, "height_meters": 1.7, "female": True} + result = runner.invoke( + app, + [ + "--user-info", + json.dumps(data), + ], + ) + assert result.exit_code == 0 + assert result.output.strip() == (f"User Info: {json.dumps(data)}") + + +def test_invalid(): + result = runner.invoke(app, ["--user-info", "Camila"]) + assert result.exit_code != 0 + assert "Expecting value: line 1 column 1 (char 0)" in result.exc_info[1].args[0] + + +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 From 137e1bc9435170ec5dfbabb3fc22add7a9871a08 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 09:41:32 +0000 Subject: [PATCH 06/23] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20f?= =?UTF-8?q?ormat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs_src/parameter_types/json/tutorial001.py | 3 ++- .../test_parameter_types/test_json/test_tutorial001.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs_src/parameter_types/json/tutorial001.py b/docs_src/parameter_types/json/tutorial001.py index ae4c798743..49851daabb 100644 --- a/docs_src/parameter_types/json/tutorial001.py +++ b/docs_src/parameter_types/json/tutorial001.py @@ -1,7 +1,8 @@ import json -import typer from typing import Annotated +import typer + def main(user_info: Annotated[dict, typer.Option()]): print(f"User Info: {json.dumps(user_info)}") diff --git a/tests/test_tutorial/test_parameter_types/test_json/test_tutorial001.py b/tests/test_tutorial/test_parameter_types/test_json/test_tutorial001.py index 798c560da1..ded0daf44b 100644 --- a/tests/test_tutorial/test_parameter_types/test_json/test_tutorial001.py +++ b/tests/test_tutorial/test_parameter_types/test_json/test_tutorial001.py @@ -1,6 +1,6 @@ +import json import subprocess import sys -import json import typer from typer.testing import CliRunner From 0f5a1b3bdc13c51ccede341cf90df67e9a9d7412 Mon Sep 17 00:00:00 2001 From: Muhammed Hussein Karimi Date: Tue, 10 Sep 2024 13:36:18 +0330 Subject: [PATCH 07/23] =?UTF-8?q?=F0=9F=90=9B=20fix:=20docs=5Fsrc=20python?= =?UTF-8?q?=20version=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Muhammed Hussein Karimi --- docs_src/parameter_types/json/tutorial001.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs_src/parameter_types/json/tutorial001.py b/docs_src/parameter_types/json/tutorial001.py index 49851daabb..caad51065d 100644 --- a/docs_src/parameter_types/json/tutorial001.py +++ b/docs_src/parameter_types/json/tutorial001.py @@ -1,5 +1,5 @@ import json -from typing import Annotated +from typing_extensions import Annotated import typer From 432a48cdbac747f3764af1551086b742cdf96498 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 10:10:46 +0000 Subject: [PATCH 08/23] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20f?= =?UTF-8?q?ormat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs_src/parameter_types/json/tutorial001.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs_src/parameter_types/json/tutorial001.py b/docs_src/parameter_types/json/tutorial001.py index caad51065d..b1adc1d026 100644 --- a/docs_src/parameter_types/json/tutorial001.py +++ b/docs_src/parameter_types/json/tutorial001.py @@ -1,7 +1,7 @@ import json -from typing_extensions import Annotated import typer +from typing_extensions import Annotated def main(user_info: Annotated[dict, typer.Option()]): From 31e7623f5ae19da10db3417c7415a50f88370c0d Mon Sep 17 00:00:00 2001 From: Muhammed Hussein Karimi Date: Tue, 10 Sep 2024 14:06:01 +0330 Subject: [PATCH 09/23] =?UTF-8?q?=F0=9F=93=9D=20docs:=20added=20json=20arg?= =?UTF-8?q?=20type=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Muhammed Hussein Karimi --- docs/tutorial/parameter-types/json.md | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 docs/tutorial/parameter-types/json.md diff --git a/docs/tutorial/parameter-types/json.md b/docs/tutorial/parameter-types/json.md new file mode 100644 index 0000000000..4b09b09b0c --- /dev/null +++ b/docs/tutorial/parameter-types/json.md @@ -0,0 +1,37 @@ +# JSON + +To use JSON inputs use `dict` as Argument type + +it will do something like + +```python +import json + +data = json.loads(user_input) +``` + +## Usage + +You will get all the correct editor support, attributes, methods, etc for the dict object:` + +//// tab | Python 3.7+ + +```Python hl_lines="5" +{!> ../docs_src/parameter_types/json/tutorial001.py!} +``` + +//// + +Check it: + +
+ +```console +// Run your program +$ python main.py --user-info '{"name": "Camila", "age": 15, "height_meters": 1.7, "female": true}' + +User Info: {"name": "Camila", "age": 15, "height_meters": 1.7, "female": true} + +``` + +
From f83e7986590fced5a412894b3691c01d2f827d48 Mon Sep 17 00:00:00 2001 From: Muhammed Hussein Karimi Date: Tue, 10 Sep 2024 14:13:24 +0330 Subject: [PATCH 10/23] =?UTF-8?q?=F0=9F=93=9D=20docs:=20just=20a=20typo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Muhammed Hussein Karimi --- docs/tutorial/parameter-types/json.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/parameter-types/json.md b/docs/tutorial/parameter-types/json.md index 4b09b09b0c..6f8d022ae9 100644 --- a/docs/tutorial/parameter-types/json.md +++ b/docs/tutorial/parameter-types/json.md @@ -12,7 +12,7 @@ data = json.loads(user_input) ## Usage -You will get all the correct editor support, attributes, methods, etc for the dict object:` +You will get all the correct editor support, attributes, methods, etc for the dict object: //// tab | Python 3.7+ From 2b480ad1aa82eb6082e4c52d56f13d096045a0dd Mon Sep 17 00:00:00 2001 From: Muhammed Hussein Karimi Date: Tue, 17 Sep 2024 11:36:22 +0330 Subject: [PATCH 11/23] :white_check_mark: DictParamType tests added --- tests/test_others.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/test_others.py b/tests/test_others.py index a577369b16..40ed04b368 100644 --- a/tests/test_others.py +++ b/tests/test_others.py @@ -1,3 +1,4 @@ +import json import os import subprocess import sys @@ -12,7 +13,7 @@ import typer.completion from typer.core import _split_opt from typer.main import solve_typer_info_defaults, solve_typer_info_help -from typer.models import ParameterInfo, TyperInfo +from typer.models import ParameterInfo, TyperInfo, DictParamType from typer.testing import CliRunner runner = CliRunner() @@ -275,3 +276,19 @@ def test_split_opt(): prefix, opt = _split_opt("verbose") assert prefix == "" assert opt == "verbose" + + +def test_json_param_type_convert(): + data = {"name": "Camila", "age": 15, "height_meters": 1.7, "female": True} + converted = DictParamType().convert(json.dumps(data), None, None) + assert data == converted + + +def test_json_param_type_convert_dict_input(): + data = {"name": "Camila", "age": 15, "height_meters": 1.7, "female": True} + converted = DictParamType().convert(data, None, None) + assert data == converted + + +def test_dict_param_tyoe_name(): + assert repr(DictParamType) == "DICT" From fdb7bb468e7f736183e34e9ecebc2130b9a9567a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 17 Sep 2024 08:06:48 +0000 Subject: [PATCH 12/23] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20f?= =?UTF-8?q?ormat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_others.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_others.py b/tests/test_others.py index 40ed04b368..7f19372c26 100644 --- a/tests/test_others.py +++ b/tests/test_others.py @@ -13,7 +13,7 @@ import typer.completion from typer.core import _split_opt from typer.main import solve_typer_info_defaults, solve_typer_info_help -from typer.models import ParameterInfo, TyperInfo, DictParamType +from typer.models import DictParamType, ParameterInfo, TyperInfo from typer.testing import CliRunner runner = CliRunner() From 114b76112030131b1c058ba98d6f09646a23db82 Mon Sep 17 00:00:00 2001 From: Muhammed Hussein Karimi Date: Tue, 17 Sep 2024 11:39:55 +0330 Subject: [PATCH 13/23] :bug: DictParamType repl test fix --- tests/test_others.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_others.py b/tests/test_others.py index 7f19372c26..a2e60c74f5 100644 --- a/tests/test_others.py +++ b/tests/test_others.py @@ -291,4 +291,4 @@ def test_json_param_type_convert_dict_input(): def test_dict_param_tyoe_name(): - assert repr(DictParamType) == "DICT" + assert repr(DictParamType()) == "DICT" From 38163f0c8f1b266ae016d8bcd9102ba52415b404 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 10 Jan 2025 16:37:25 +0100 Subject: [PATCH 14/23] rewrite example code to focus on dict functionality --- docs/tutorial/parameter-types/json.md | 35 +++++++------------ docs_src/parameter_types/json/tutorial001.py | 5 ++- .../test_json/test_tutorial001.py | 3 +- 3 files changed, 17 insertions(+), 26 deletions(-) diff --git a/docs/tutorial/parameter-types/json.md b/docs/tutorial/parameter-types/json.md index 6f8d022ae9..1a6086d79a 100644 --- a/docs/tutorial/parameter-types/json.md +++ b/docs/tutorial/parameter-types/json.md @@ -1,26 +1,8 @@ -# JSON +# Dict -To use JSON inputs use `dict` as Argument type +You can declare a *CLI parameter* to be a standard Python `dict`: -it will do something like - -```python -import json - -data = json.loads(user_input) -``` - -## Usage - -You will get all the correct editor support, attributes, methods, etc for the dict object: - -//// tab | Python 3.7+ - -```Python hl_lines="5" -{!> ../docs_src/parameter_types/json/tutorial001.py!} -``` - -//// +{* docs_src/parameter_types/json/tutorial001.py hl[5] *} Check it: @@ -30,8 +12,17 @@ Check it: // Run your program $ python main.py --user-info '{"name": "Camila", "age": 15, "height_meters": 1.7, "female": true}' -User Info: {"name": "Camila", "age": 15, "height_meters": 1.7, "female": true} +Name: Camila +User attributes: ['age', 'female', 'height_meters', 'name'] ``` + +This can be particularly useful when you want to include JSON input: + +```python +import json + +data = json.loads(user_input) +``` diff --git a/docs_src/parameter_types/json/tutorial001.py b/docs_src/parameter_types/json/tutorial001.py index b1adc1d026..92b507a322 100644 --- a/docs_src/parameter_types/json/tutorial001.py +++ b/docs_src/parameter_types/json/tutorial001.py @@ -1,11 +1,10 @@ -import json - import typer from typing_extensions import Annotated def main(user_info: Annotated[dict, typer.Option()]): - print(f"User Info: {json.dumps(user_info)}") + print(f"Name: {user_info.get('name', 'Unknown')}") + print(f"User attributes: {sorted(user_info.keys())}") if __name__ == "__main__": diff --git a/tests/test_tutorial/test_parameter_types/test_json/test_tutorial001.py b/tests/test_tutorial/test_parameter_types/test_json/test_tutorial001.py index ded0daf44b..1e71919fc6 100644 --- a/tests/test_tutorial/test_parameter_types/test_json/test_tutorial001.py +++ b/tests/test_tutorial/test_parameter_types/test_json/test_tutorial001.py @@ -30,7 +30,8 @@ def test_params(): ], ) assert result.exit_code == 0 - assert result.output.strip() == (f"User Info: {json.dumps(data)}") + assert f"Name: Camila" in result.output.strip() + assert f"User attributes: {['age', 'female', 'height_meters', 'name']}" in result.output.strip() def test_invalid(): From 71ca8e3bc8cea348bb40cf3496bb8d55068afa34 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 10 Jan 2025 15:37:35 +0000 Subject: [PATCH 15/23] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20f?= =?UTF-8?q?ormat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test_parameter_types/test_json/test_tutorial001.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/test_tutorial/test_parameter_types/test_json/test_tutorial001.py b/tests/test_tutorial/test_parameter_types/test_json/test_tutorial001.py index 1e71919fc6..3e34572ca6 100644 --- a/tests/test_tutorial/test_parameter_types/test_json/test_tutorial001.py +++ b/tests/test_tutorial/test_parameter_types/test_json/test_tutorial001.py @@ -30,8 +30,11 @@ def test_params(): ], ) assert result.exit_code == 0 - assert f"Name: Camila" in result.output.strip() - assert f"User attributes: {['age', 'female', 'height_meters', 'name']}" in result.output.strip() + assert "Name: Camila" in result.output.strip() + assert ( + f"User attributes: {['age', 'female', 'height_meters', 'name']}" + in result.output.strip() + ) def test_invalid(): From 240d6145b31fafe7e0e0af8a2656e1f529bdd43e Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 10 Jan 2025 16:51:26 +0100 Subject: [PATCH 16/23] rename to dict instead of json --- docs/tutorial/parameter-types/{json.md => dict.md} | 6 +++--- docs_src/parameter_types/{json => dict}/__init__.py | 0 .../parameter_types/{json => dict}/tutorial001.py | 0 .../{test_json => test_dict}/__init__.py | 0 .../{test_json => test_dict}/test_tutorial001.py | 11 ++++------- 5 files changed, 7 insertions(+), 10 deletions(-) rename docs/tutorial/parameter-types/{json.md => dict.md} (70%) rename docs_src/parameter_types/{json => dict}/__init__.py (100%) rename docs_src/parameter_types/{json => dict}/tutorial001.py (100%) rename tests/test_tutorial/test_parameter_types/{test_json => test_dict}/__init__.py (100%) rename tests/test_tutorial/test_parameter_types/{test_json => test_dict}/test_tutorial001.py (74%) diff --git a/docs/tutorial/parameter-types/json.md b/docs/tutorial/parameter-types/dict.md similarity index 70% rename from docs/tutorial/parameter-types/json.md rename to docs/tutorial/parameter-types/dict.md index 1a6086d79a..8d2c4d3c17 100644 --- a/docs/tutorial/parameter-types/json.md +++ b/docs/tutorial/parameter-types/dict.md @@ -2,7 +2,7 @@ You can declare a *CLI parameter* to be a standard Python `dict`: -{* docs_src/parameter_types/json/tutorial001.py hl[5] *} +{* docs_src/parameter_types/dict/tutorial001.py hl[5] *} Check it: @@ -10,10 +10,10 @@ Check it: ```console // Run your program -$ python main.py --user-info '{"name": "Camila", "age": 15, "height_meters": 1.7, "female": true}' +$ python main.py --user-info '{"name": "Camila", "age": 15, "height": 1.7, "female": true}' Name: Camila -User attributes: ['age', 'female', 'height_meters', 'name'] +User attributes: ['age', 'female', 'height', 'name'] ``` diff --git a/docs_src/parameter_types/json/__init__.py b/docs_src/parameter_types/dict/__init__.py similarity index 100% rename from docs_src/parameter_types/json/__init__.py rename to docs_src/parameter_types/dict/__init__.py diff --git a/docs_src/parameter_types/json/tutorial001.py b/docs_src/parameter_types/dict/tutorial001.py similarity index 100% rename from docs_src/parameter_types/json/tutorial001.py rename to docs_src/parameter_types/dict/tutorial001.py diff --git a/tests/test_tutorial/test_parameter_types/test_json/__init__.py b/tests/test_tutorial/test_parameter_types/test_dict/__init__.py similarity index 100% rename from tests/test_tutorial/test_parameter_types/test_json/__init__.py rename to tests/test_tutorial/test_parameter_types/test_dict/__init__.py diff --git a/tests/test_tutorial/test_parameter_types/test_json/test_tutorial001.py b/tests/test_tutorial/test_parameter_types/test_dict/test_tutorial001.py similarity index 74% rename from tests/test_tutorial/test_parameter_types/test_json/test_tutorial001.py rename to tests/test_tutorial/test_parameter_types/test_dict/test_tutorial001.py index 3e34572ca6..e91dc73d19 100644 --- a/tests/test_tutorial/test_parameter_types/test_json/test_tutorial001.py +++ b/tests/test_tutorial/test_parameter_types/test_dict/test_tutorial001.py @@ -5,7 +5,7 @@ import typer from typer.testing import CliRunner -from docs_src.parameter_types.json import tutorial001 as mod +from docs_src.parameter_types.dict import tutorial001 as mod runner = CliRunner() @@ -21,7 +21,7 @@ def test_help(): def test_params(): - data = {"name": "Camila", "age": 15, "height_meters": 1.7, "female": True} + data = {"name": "Camila", "age": 15, "height": 1.7, "female": True} result = runner.invoke( app, [ @@ -30,11 +30,8 @@ def test_params(): ], ) assert result.exit_code == 0 - assert "Name: Camila" in result.output.strip() - assert ( - f"User attributes: {['age', 'female', 'height_meters', 'name']}" - in result.output.strip() - ) + assert "Name: Camila" in result.output + assert "User attributes: ['age', 'female', 'height', 'name']" in result.output def test_invalid(): From 966206d02e1574444e551360fb2d9798a86900da Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 10 Jan 2025 17:11:34 +0100 Subject: [PATCH 17/23] specific annotated and non-annotated version of the tutorial and test files --- docs/tutorial/parameter-types/dict.md | 2 +- docs_src/parameter_types/dict/tutorial001.py | 3 +- .../parameter_types/dict/tutorial001_an.py | 11 +++++ .../test_dict/test_tutorial001_an.py | 49 +++++++++++++++++++ 4 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 docs_src/parameter_types/dict/tutorial001_an.py create mode 100644 tests/test_tutorial/test_parameter_types/test_dict/test_tutorial001_an.py diff --git a/docs/tutorial/parameter-types/dict.md b/docs/tutorial/parameter-types/dict.md index 8d2c4d3c17..0fae3db467 100644 --- a/docs/tutorial/parameter-types/dict.md +++ b/docs/tutorial/parameter-types/dict.md @@ -2,7 +2,7 @@ You can declare a *CLI parameter* to be a standard Python `dict`: -{* docs_src/parameter_types/dict/tutorial001.py hl[5] *} +{* docs_src/parameter_types/dict/tutorial001_an.py hl[5] *} Check it: diff --git a/docs_src/parameter_types/dict/tutorial001.py b/docs_src/parameter_types/dict/tutorial001.py index 92b507a322..e4027719eb 100644 --- a/docs_src/parameter_types/dict/tutorial001.py +++ b/docs_src/parameter_types/dict/tutorial001.py @@ -1,8 +1,7 @@ import typer -from typing_extensions import Annotated -def main(user_info: Annotated[dict, typer.Option()]): +def main(user_info: dict = typer.Option({})): print(f"Name: {user_info.get('name', 'Unknown')}") print(f"User attributes: {sorted(user_info.keys())}") diff --git a/docs_src/parameter_types/dict/tutorial001_an.py b/docs_src/parameter_types/dict/tutorial001_an.py new file mode 100644 index 0000000000..f876c255d1 --- /dev/null +++ b/docs_src/parameter_types/dict/tutorial001_an.py @@ -0,0 +1,11 @@ +import typer +from typing_extensions import Annotated + + +def main(user_info: Annotated[dict, typer.Option()] = {}): + print(f"Name: {user_info.get('name', 'Unknown')}") + print(f"User attributes: {sorted(user_info.keys())}") + + +if __name__ == "__main__": + typer.run(main) diff --git a/tests/test_tutorial/test_parameter_types/test_dict/test_tutorial001_an.py b/tests/test_tutorial/test_parameter_types/test_dict/test_tutorial001_an.py new file mode 100644 index 0000000000..8e7e06a719 --- /dev/null +++ b/tests/test_tutorial/test_parameter_types/test_dict/test_tutorial001_an.py @@ -0,0 +1,49 @@ +import json +import subprocess +import sys + +import typer +from typer.testing import CliRunner + +from docs_src.parameter_types.dict 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 + assert "--user-info" in result.output + assert "DICT" in result.output + + +def test_params(): + data = {"name": "Camila", "age": 15, "height": 1.7, "female": True} + result = runner.invoke( + app, + [ + "--user-info", + json.dumps(data), + ], + ) + assert result.exit_code == 0 + assert "Name: Camila" in result.output + assert "User attributes: ['age', 'female', 'height', 'name']" in result.output + + +def test_invalid(): + result = runner.invoke(app, ["--user-info", "Camila"]) + assert result.exit_code != 0 + assert "Expecting value: line 1 column 1 (char 0)" in result.exc_info[1].args[0] + + +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 From 7caf522862d67931371649b774cf224818aec9d4 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 10 Jan 2025 17:19:00 +0100 Subject: [PATCH 18/23] remove default --- docs_src/parameter_types/dict/tutorial001.py | 2 +- docs_src/parameter_types/dict/tutorial001_an.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs_src/parameter_types/dict/tutorial001.py b/docs_src/parameter_types/dict/tutorial001.py index e4027719eb..2f0cd61559 100644 --- a/docs_src/parameter_types/dict/tutorial001.py +++ b/docs_src/parameter_types/dict/tutorial001.py @@ -1,7 +1,7 @@ import typer -def main(user_info: dict = typer.Option({})): +def main(user_info: dict = typer.Option()): print(f"Name: {user_info.get('name', 'Unknown')}") print(f"User attributes: {sorted(user_info.keys())}") diff --git a/docs_src/parameter_types/dict/tutorial001_an.py b/docs_src/parameter_types/dict/tutorial001_an.py index f876c255d1..92b507a322 100644 --- a/docs_src/parameter_types/dict/tutorial001_an.py +++ b/docs_src/parameter_types/dict/tutorial001_an.py @@ -2,7 +2,7 @@ from typing_extensions import Annotated -def main(user_info: Annotated[dict, typer.Option()] = {}): +def main(user_info: Annotated[dict, typer.Option()]): print(f"Name: {user_info.get('name', 'Unknown')}") print(f"User attributes: {sorted(user_info.keys())}") From d0cb4ab520aaa930bd3365e8c72ed669d54da16e Mon Sep 17 00:00:00 2001 From: svlandeg Date: Tue, 25 Nov 2025 12:22:30 +0100 Subject: [PATCH 19/23] update tutorial files to use Typer() instance --- docs/tutorial/parameter-types/dict.md | 2 +- docs_src/parameter_types/dict/tutorial001.py | 5 ++++- docs_src/parameter_types/dict/tutorial001_an.py | 5 ++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/tutorial/parameter-types/dict.md b/docs/tutorial/parameter-types/dict.md index 0fae3db467..25d2066819 100644 --- a/docs/tutorial/parameter-types/dict.md +++ b/docs/tutorial/parameter-types/dict.md @@ -2,7 +2,7 @@ You can declare a *CLI parameter* to be a standard Python `dict`: -{* docs_src/parameter_types/dict/tutorial001_an.py hl[5] *} +{* docs_src/parameter_types/dict/tutorial001_an.py hl[8] *} Check it: diff --git a/docs_src/parameter_types/dict/tutorial001.py b/docs_src/parameter_types/dict/tutorial001.py index 2f0cd61559..cb2c59fc5d 100644 --- a/docs_src/parameter_types/dict/tutorial001.py +++ b/docs_src/parameter_types/dict/tutorial001.py @@ -1,10 +1,13 @@ import typer +app = typer.Typer() + +@app.command() def main(user_info: dict = typer.Option()): print(f"Name: {user_info.get('name', 'Unknown')}") print(f"User attributes: {sorted(user_info.keys())}") if __name__ == "__main__": - typer.run(main) + app() diff --git a/docs_src/parameter_types/dict/tutorial001_an.py b/docs_src/parameter_types/dict/tutorial001_an.py index 92b507a322..d782a229d2 100644 --- a/docs_src/parameter_types/dict/tutorial001_an.py +++ b/docs_src/parameter_types/dict/tutorial001_an.py @@ -1,11 +1,14 @@ import typer from typing_extensions import Annotated +app = typer.Typer() + +@app.command() def main(user_info: Annotated[dict, typer.Option()]): print(f"Name: {user_info.get('name', 'Unknown')}") print(f"User attributes: {sorted(user_info.keys())}") if __name__ == "__main__": - typer.run(main) + app() From 2b38061106e8bdde530a9a6578126f9171715590 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Tue, 25 Nov 2025 12:23:34 +0100 Subject: [PATCH 20/23] add new page to index --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index a9a661098b..f949969b2e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -113,6 +113,7 @@ nav: - tutorial/parameter-types/enum.md - tutorial/parameter-types/path.md - tutorial/parameter-types/file.md + - tutorial/parameter-types/dict.md - tutorial/parameter-types/custom-types.md - SubCommands - Command Groups: - tutorial/subcommands/index.md From 8c621c76ee2e6d55e5d88b8710bd5e21ecf77af4 Mon Sep 17 00:00:00 2001 From: Sofie Van Landeghem Date: Tue, 25 Nov 2025 12:26:52 +0100 Subject: [PATCH 21/23] revert edit for bytes --- typer/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typer/main.py b/typer/main.py index 88c5c91bff..f3dec46293 100644 --- a/typer/main.py +++ b/typer/main.py @@ -712,7 +712,7 @@ def get_click_type( elif parameter_info.parser is not None: return click.types.FuncParamType(parameter_info.parser) - elif annotation in [str, bytes]: + elif annotation is str: return click.STRING elif annotation is dict: return DictParamType() From bb946fbfdb93f408872eecd9464a8761e572a433 Mon Sep 17 00:00:00 2001 From: Sofie Van Landeghem Date: Tue, 25 Nov 2025 16:15:29 +0100 Subject: [PATCH 22/23] update tests to use mod.app --- .../test_parameter_types/test_dict/test_tutorial001.py | 4 +--- .../test_parameter_types/test_dict/test_tutorial001_an.py | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/test_tutorial/test_parameter_types/test_dict/test_tutorial001.py b/tests/test_tutorial/test_parameter_types/test_dict/test_tutorial001.py index e91dc73d19..20257fa98e 100644 --- a/tests/test_tutorial/test_parameter_types/test_dict/test_tutorial001.py +++ b/tests/test_tutorial/test_parameter_types/test_dict/test_tutorial001.py @@ -8,9 +8,7 @@ from docs_src.parameter_types.dict import tutorial001 as mod runner = CliRunner() - -app = typer.Typer() -app.command()(mod.main) +app = mod.app def test_help(): diff --git a/tests/test_tutorial/test_parameter_types/test_dict/test_tutorial001_an.py b/tests/test_tutorial/test_parameter_types/test_dict/test_tutorial001_an.py index 8e7e06a719..0d3517755c 100644 --- a/tests/test_tutorial/test_parameter_types/test_dict/test_tutorial001_an.py +++ b/tests/test_tutorial/test_parameter_types/test_dict/test_tutorial001_an.py @@ -8,9 +8,7 @@ from docs_src.parameter_types.dict import tutorial001_an as mod runner = CliRunner() - -app = typer.Typer() -app.command()(mod.main) +app = mod.app def test_help(): From 739ebd0034fdd4c489362b0c97091e8c6de467a6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 15:15:52 +0000 Subject: [PATCH 23/23] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20f?= =?UTF-8?q?ormat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test_parameter_types/test_dict/test_tutorial001.py | 1 - .../test_parameter_types/test_dict/test_tutorial001_an.py | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/test_tutorial/test_parameter_types/test_dict/test_tutorial001.py b/tests/test_tutorial/test_parameter_types/test_dict/test_tutorial001.py index 20257fa98e..46d66a0284 100644 --- a/tests/test_tutorial/test_parameter_types/test_dict/test_tutorial001.py +++ b/tests/test_tutorial/test_parameter_types/test_dict/test_tutorial001.py @@ -2,7 +2,6 @@ import subprocess import sys -import typer from typer.testing import CliRunner from docs_src.parameter_types.dict import tutorial001 as mod diff --git a/tests/test_tutorial/test_parameter_types/test_dict/test_tutorial001_an.py b/tests/test_tutorial/test_parameter_types/test_dict/test_tutorial001_an.py index 0d3517755c..edf3a6af02 100644 --- a/tests/test_tutorial/test_parameter_types/test_dict/test_tutorial001_an.py +++ b/tests/test_tutorial/test_parameter_types/test_dict/test_tutorial001_an.py @@ -2,7 +2,6 @@ import subprocess import sys -import typer from typer.testing import CliRunner from docs_src.parameter_types.dict import tutorial001_an as mod