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/13] =?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/13] =?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/13] =?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/13] =?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/13] =?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/13] =?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/13] =?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/13] =?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/13] =?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/13] =?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/13] :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/13] =?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/13] :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"