From a4b567e7987934046cf468d4c23395b90aee9fbe Mon Sep 17 00:00:00 2001 From: moisutsu Date: Sat, 5 Nov 2022 17:45:06 +0900 Subject: [PATCH 01/11] Add interconversion with json --- classopt/decorator.py | 118 +++++++++++++++++++++++++++++++++++++++- tests/test_decorator.py | 64 +++++++++++++++++++++- 2 files changed, 179 insertions(+), 3 deletions(-) diff --git a/classopt/decorator.py b/classopt/decorator.py index c6c64e3..19adb61 100644 --- a/classopt/decorator.py +++ b/classopt/decorator.py @@ -1,7 +1,21 @@ +import json +import os import typing from argparse import ArgumentParser -from dataclasses import MISSING, Field, dataclass, asdict -from typing import TYPE_CHECKING, List, Optional, overload +from dataclasses import MISSING, Field, asdict, dataclass +from json import JSONDecoder, JSONEncoder +from pathlib import Path +from typing import ( + TYPE_CHECKING, + Any, + Callable, + List, + Optional, + Tuple, + Type, + Union, + overload, +) from classopt import config @@ -23,6 +37,36 @@ def to_dict(self) -> dict: def from_dict(cls, data: dict) -> _T: ... + def to_json( + self, + save_path: Union[str, os.PathLike, None], + skipkeys: bool = False, + ensure_ascii: bool = True, + check_circular: bool = True, + allow_nan: bool = True, + cls: Optional[Type[JSONEncoder]] = None, + indent: Union[int, str, None] = None, + separators: Optional[Tuple[str, str]] = None, + default: Optional[Callable[[Any], Any]] = None, + sort_keys: bool = False, + **kwargs, + ) -> str: + ... + + @classmethod + def from_json( + cls, + json_content_or_path: Union[str, bytes, os.PathLike], + cls_: Optional[Type[JSONDecoder]] = None, + object_hook: Optional[Callable[[dict], Any]] = None, + parse_float: Optional[Callable[[str], Any]] = None, + parse_int: Optional[Callable[[str], Any]] = None, + parse_constant: Optional[Callable[[str], Any]] = None, + object_pairs_hook: Optional[Callable[[List[tuple]], Any]] = None, + **kwargs, + ) -> _T: + ... + @overload def classopt( @@ -141,4 +185,74 @@ def from_dict(cls, data: dict): setattr(cls, "from_dict", from_dict) + def to_json( + self, + save_path: Union[str, os.PathLike, None] = None, + skipkeys: bool = False, + ensure_ascii: bool = True, + check_circular: bool = True, + allow_nan: bool = True, + cls: Optional[Type[JSONEncoder]] = None, + indent: Union[int, str, None] = None, + separators: Optional[Tuple[str, str]] = None, + default: Optional[Callable[[Any], Any]] = None, + sort_keys: bool = False, + **kwargs, + ) -> str: + json_content = json.dumps( + obj=self.to_dict(), + skipkeys=skipkeys, + ensure_ascii=ensure_ascii, + check_circular=check_circular, + allow_nan=allow_nan, + cls=cls, + indent=indent, + separators=separators, + default=default, + sort_keys=sort_keys, + **kwargs, + ) + + if not save_path is None: + Path(save_path).write_text(json_content) + + return json_content + + setattr(cls, "to_json", to_json) + + @classmethod + def from_json( + cls, + json_content_or_path: Union[str, bytes, os.PathLike], + cls_: Optional[Type[JSONDecoder]] = None, + object_hook: Optional[Callable[[dict], Any]] = None, + parse_float: Optional[Callable[[str], Any]] = None, + parse_int: Optional[Callable[[str], Any]] = None, + parse_constant: Optional[Callable[[str], Any]] = None, + object_pairs_hook: Optional[Callable[[List[tuple]], Any]] = None, + **kwargs, + ): + if not isinstance(json_content_or_path, bytes) and ( + isinstance(json_content_or_path, os.PathLike) + or Path(json_content_or_path).exists() + ): + json_content = Path(json_content_or_path).read_text() + else: + json_content = json_content_or_path + + return cls.from_dict( + json.loads( + s=json_content, + cls=cls_, + object_hook=object_hook, + parse_float=parse_float, + parse_int=parse_int, + parse_constant=parse_constant, + object_pairs_hook=object_pairs_hook, + **kwargs, + ) + ) + + setattr(cls, "from_json", from_json) + return dataclass(cls) diff --git a/tests/test_decorator.py b/tests/test_decorator.py index 67fbca8..a25a78f 100644 --- a/tests/test_decorator.py +++ b/tests/test_decorator.py @@ -3,6 +3,7 @@ from typing import List import pytest + from classopt import classopt, config @@ -284,11 +285,72 @@ class Opt: assert opt.arg_int == args_dict["arg_int"] assert opt.arg_float == args_dict["arg_float"] - assert opt.arg_path == args_dict["arg_path"] assert opt.arg_list == args_dict["arg_list"] del_args() + def test_to_json(self): + import tempfile + from pathlib import Path + from typing import List + + @classopt + class Opt: + arg_int: int + arg_float: float + arg_list: List[str] + + set_args("3", "3.2", "a", "b", "c") + + opt = Opt.from_args() + + correct_json = ( + """{"arg_int": 3, "arg_float": 3.2, "arg_list": ["a", "b", "c"]}""" + ) + + opt_json = opt.to_json() + + assert opt_json == correct_json + + temp_path = Path(tempfile.mkdtemp()) / "test.json" + opt.to_json(temp_path) + + assert temp_path.read_text() == correct_json + + del_args() + + def test_from_json(self): + import tempfile + from pathlib import Path + from typing import List + + @classopt + class Opt: + arg_int: int + arg_float: float + arg_list: List[str] + + content_json = ( + """{"arg_int": 3, "arg_float": 3.2, "arg_list": ["a", "b", "c"]}""" + ) + + opt = Opt.from_json(content_json) + + assert opt.arg_int == 3 + assert opt.arg_float == 3.2 + assert opt.arg_list == ["a", "b", "c"] + + temp_path = Path(tempfile.mkdtemp()) / "test.json" + temp_path.write_text(content_json) + + opt = Opt.from_json(temp_path) + + assert opt.arg_int == 3 + assert opt.arg_float == 3.2 + assert opt.arg_list == ["a", "b", "c"] + + del_args() + def set_args(*args): del_args() # otherwise tests fail with e.g. "pytest -s" From 0e2747b985fde268229d49576b95a0a2cfd218e7 Mon Sep 17 00:00:00 2001 From: moisutsu Date: Tue, 6 Dec 2022 14:22:14 +0900 Subject: [PATCH 02/11] Re-run workflows From a4a043343b22bee3093297c3896fdbb331114b52 Mon Sep 17 00:00:00 2001 From: moisutsu Date: Thu, 15 Dec 2022 18:09:06 +0900 Subject: [PATCH 03/11] Change to run github actions only on pull requests --- .github/workflows/unittest.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index dac1191..5fc36d4 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -1,7 +1,6 @@ name: Unit test on: - push: pull_request: jobs: From 4331a0d6e863e69514c5240296a062a79b5f45fd Mon Sep 17 00:00:00 2001 From: moisutsu Date: Sat, 5 Nov 2022 17:45:06 +0900 Subject: [PATCH 04/11] Add interconversion with json --- classopt/decorator.py | 117 +++++++++++++++++++++++++++++++++++++++- tests/test_decorator.py | 62 +++++++++++++++++++++ 2 files changed, 178 insertions(+), 1 deletion(-) diff --git a/classopt/decorator.py b/classopt/decorator.py index 62525df..899c35c 100644 --- a/classopt/decorator.py +++ b/classopt/decorator.py @@ -1,7 +1,22 @@ +import json +import os import typing from argparse import ArgumentParser from dataclasses import MISSING, Field, asdict, dataclass -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, overload +from json import JSONDecoder, JSONEncoder +from pathlib import Path +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + List, + Optional, + Tuple, + Type, + Union, + overload, +) from classopt import config from classopt.utils import ( @@ -28,6 +43,36 @@ def to_dict(self) -> dict: def from_dict(cls, data: dict) -> _T: ... + def to_json( + self, + save_path: Union[str, os.PathLike, None], + skipkeys: bool = False, + ensure_ascii: bool = True, + check_circular: bool = True, + allow_nan: bool = True, + cls: Optional[Type[JSONEncoder]] = None, + indent: Union[int, str, None] = None, + separators: Optional[Tuple[str, str]] = None, + default: Optional[Callable[[Any], Any]] = None, + sort_keys: bool = False, + **kwargs, + ) -> str: + ... + + @classmethod + def from_json( + cls, + json_content_or_path: Union[str, bytes, os.PathLike], + cls_: Optional[Type[JSONDecoder]] = None, + object_hook: Optional[Callable[[dict], Any]] = None, + parse_float: Optional[Callable[[str], Any]] = None, + parse_int: Optional[Callable[[str], Any]] = None, + parse_constant: Optional[Callable[[str], Any]] = None, + object_pairs_hook: Optional[Callable[[List[tuple]], Any]] = None, + **kwargs, + ) -> _T: + ... + @overload def classopt( @@ -154,4 +199,74 @@ def from_dict(cls, data: dict): setattr(cls, "from_dict", from_dict) + def to_json( + self, + save_path: Union[str, os.PathLike, None] = None, + skipkeys: bool = False, + ensure_ascii: bool = True, + check_circular: bool = True, + allow_nan: bool = True, + cls: Optional[Type[JSONEncoder]] = None, + indent: Union[int, str, None] = None, + separators: Optional[Tuple[str, str]] = None, + default: Optional[Callable[[Any], Any]] = None, + sort_keys: bool = False, + **kwargs, + ) -> str: + json_content = json.dumps( + obj=self.to_dict(), + skipkeys=skipkeys, + ensure_ascii=ensure_ascii, + check_circular=check_circular, + allow_nan=allow_nan, + cls=cls, + indent=indent, + separators=separators, + default=default, + sort_keys=sort_keys, + **kwargs, + ) + + if not save_path is None: + Path(save_path).write_text(json_content) + + return json_content + + setattr(cls, "to_json", to_json) + + @classmethod + def from_json( + cls, + json_content_or_path: Union[str, bytes, os.PathLike], + cls_: Optional[Type[JSONDecoder]] = None, + object_hook: Optional[Callable[[dict], Any]] = None, + parse_float: Optional[Callable[[str], Any]] = None, + parse_int: Optional[Callable[[str], Any]] = None, + parse_constant: Optional[Callable[[str], Any]] = None, + object_pairs_hook: Optional[Callable[[List[tuple]], Any]] = None, + **kwargs, + ): + if not isinstance(json_content_or_path, bytes) and ( + isinstance(json_content_or_path, os.PathLike) + or Path(json_content_or_path).exists() + ): + json_content = Path(json_content_or_path).read_text() + else: + json_content = json_content_or_path + + return cls.from_dict( + json.loads( + s=json_content, + cls=cls_, + object_hook=object_hook, + parse_float=parse_float, + parse_int=parse_int, + parse_constant=parse_constant, + object_pairs_hook=object_pairs_hook, + **kwargs, + ) + ) + + setattr(cls, "from_json", from_json) + return dataclass(cls) diff --git a/tests/test_decorator.py b/tests/test_decorator.py index 516567f..5c7be5f 100644 --- a/tests/test_decorator.py +++ b/tests/test_decorator.py @@ -290,6 +290,68 @@ class Opt: del_args() + def test_to_json(self): + import tempfile + from pathlib import Path + from typing import List + + @classopt + class Opt: + arg_int: int + arg_float: float + arg_list: List[str] + + set_args("3", "3.2", "a", "b", "c") + + opt = Opt.from_args() + + correct_json = ( + """{"arg_int": 3, "arg_float": 3.2, "arg_list": ["a", "b", "c"]}""" + ) + + opt_json = opt.to_json() + + assert opt_json == correct_json + + temp_path = Path(tempfile.mkdtemp()) / "test.json" + opt.to_json(temp_path) + + assert temp_path.read_text() == correct_json + + del_args() + + def test_from_json(self): + import tempfile + from pathlib import Path + from typing import List + + @classopt + class Opt: + arg_int: int + arg_float: float + arg_list: List[str] + + content_json = ( + """{"arg_int": 3, "arg_float": 3.2, "arg_list": ["a", "b", "c"]}""" + ) + + opt = Opt.from_json(content_json) + + assert opt.arg_int == 3 + assert opt.arg_float == 3.2 + assert opt.arg_list == ["a", "b", "c"] + + temp_path = Path(tempfile.mkdtemp()) / "test.json" + temp_path.write_text(content_json) + + opt = Opt.from_json(temp_path) + + assert opt.arg_int == 3 + assert opt.arg_float == 3.2 + assert opt.arg_list == ["a", "b", "c"] + + del_args() + def set_args(*args): del_args() # otherwise tests fail with e.g. "pytest -s" From 50e51fff308c121cf98142e27b3e31d891a3a996 Mon Sep 17 00:00:00 2001 From: moisutsu Date: Tue, 6 Dec 2022 14:22:14 +0900 Subject: [PATCH 05/11] Re-run workflows From cfb99800647cbc37910c4115703ebd55e2783fdf Mon Sep 17 00:00:00 2001 From: moisutsu Date: Thu, 15 Dec 2022 18:09:06 +0900 Subject: [PATCH 06/11] Change to run github actions only on pull requests --- .github/workflows/unittest.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index dac1191..5fc36d4 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -1,7 +1,6 @@ name: Unit test on: - push: pull_request: jobs: From 4f98a7ca369f61599d15877baecf3a28402f179f Mon Sep 17 00:00:00 2001 From: moisutsu Date: Fri, 30 Dec 2022 15:36:35 +0900 Subject: [PATCH 07/11] Add non-primitives to test interconversion with json --- tests/test_decorator.py | 14 +++++++------- tests/test_inheritance.py | 5 ++--- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/tests/test_decorator.py b/tests/test_decorator.py index 5c7be5f..cbe7e31 100644 --- a/tests/test_decorator.py +++ b/tests/test_decorator.py @@ -300,14 +300,13 @@ class Opt: arg_int: int arg_float: float arg_list: List[str] + arg_path: Path - set_args("3", "3.2", "a", "b", "c") + set_args("3", "3.2", "a", "b", "c", "test.txt") opt = Opt.from_args() - correct_json = ( - """{"arg_int": 3, "arg_float": 3.2, "arg_list": ["a", "b", "c"]}""" - ) + correct_json = """{"arg_int": 3, "arg_float": 3.2, "arg_list": ["a", "b", "c"], "arg_path": "test.txt"}""" opt_json = opt.to_json() @@ -330,16 +329,16 @@ class Opt: arg_int: int arg_float: float arg_list: List[str] + arg_path: Path - content_json = ( - """{"arg_int": 3, "arg_float": 3.2, "arg_list": ["a", "b", "c"]}""" - ) + content_json = """{"arg_int": 3, "arg_float": 3.2, "arg_list": ["a", "b", "c"], "arg_path": "test.txt"}""" opt = Opt.from_json(content_json) assert opt.arg_int == 3 assert opt.arg_float == 3.2 assert opt.arg_list == ["a", "b", "c"] + assert opt.arg_path == Path("test.txt") temp_path = Path(tempfile.mkdtemp()) / "test.json" temp_path.write_text(content_json) @@ -349,6 +348,7 @@ class Opt: assert opt.arg_int == 3 assert opt.arg_float == 3.2 assert opt.arg_list == ["a", "b", "c"] + assert opt.arg_path == Path("test.txt") del_args() diff --git a/tests/test_inheritance.py b/tests/test_inheritance.py index 644f687..b3a5d2a 100644 --- a/tests/test_inheritance.py +++ b/tests/test_inheritance.py @@ -121,7 +121,7 @@ def _parser_factory(cls) -> ArgumentParser: opt = Opt.from_args() del_args() - + def test_args_from_script(self): class Opt(ClassOpt): arg_int: int @@ -134,14 +134,13 @@ class Opt(ClassOpt): del_args() - opt2 = Opt.from_args(["5","hello","3.2"]) + opt2 = Opt.from_args(["5", "hello", "3.2"]) assert opt1.arg_int == opt2.arg_int assert opt1.arg_str == opt2.arg_str assert opt1.arg_float == opt2.arg_float - def set_args(*args): for arg in args: sys.argv.append(arg) From a3b6e00e9465186aacf787de9ed331098ddbd8f6 Mon Sep 17 00:00:00 2001 From: moisutsu Date: Sun, 17 Sep 2023 16:05:23 +0900 Subject: [PATCH 08/11] Bump pytest version to 6.2.5 --- poetry.lock | 244 +++++++++++++++++++++++-------------------------- pyproject.toml | 2 +- 2 files changed, 113 insertions(+), 133 deletions(-) diff --git a/poetry.lock b/poetry.lock index c87b6bc..de2bfdf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,40 +1,54 @@ +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. + [[package]] name = "appdirs" version = "1.4.4" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false python-versions = "*" +files = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] [[package]] name = "atomicwrites" version = "1.4.0" description = "Atomic file writes." -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] [[package]] name = "attrs" version = "21.2.0" description = "Classes Without Boilerplate" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, + {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, +] [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] -docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] +dev = ["coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "sphinx", "sphinx-notfound-page", "zope.interface"] +docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "zope.interface"] +tests-no-zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six"] [[package]] name = "black" version = "21.6b0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.6.2" +files = [ + {file = "black-21.6b0-py3-none-any.whl", hash = "sha256:dfb8c5a069012b2ab1e972e7b908f5fb42b6bbabcba0a788b86dc05067c7d9c7"}, + {file = "black-21.6b0.tar.gz", hash = "sha256:dc132348a88d103016726fe360cb9ede02cecf99b76e3660ce6c596be132ce04"}, +] [package.dependencies] appdirs = "*" @@ -56,9 +70,12 @@ uvloop = ["uvloop (>=0.15.2)"] name = "click" version = "8.0.1" description = "Composable command line interface toolkit" -category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, + {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, +] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -68,50 +85,65 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} name = "colorama" version = "0.4.4" description = "Cross-platform colored terminal text." -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] [[package]] name = "importlib-metadata" version = "4.6.0" description = "Read metadata from Python packages" -category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "importlib_metadata-4.6.0-py3-none-any.whl", hash = "sha256:c6513572926a96458f8c8f725bf0e00108fba0c9583ade9bd15b869c9d726e33"}, + {file = "importlib_metadata-4.6.0.tar.gz", hash = "sha256:4a5611fea3768d3d967c447ab4e93f567d95db92225b43b7b238dbfb855d70bb"}, +] [package.dependencies] typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] perf = ["ipython"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517", "pyfakefs", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy", "pytest-perf (>=0.9.2)"] [[package]] name = "iniconfig" version = "1.1.1" description = "iniconfig: brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = "*" +files = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] [[package]] name = "mypy-extensions" version = "0.4.3" description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "dev" optional = false python-versions = "*" +files = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] [[package]] name = "packaging" version = "21.0" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, + {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, +] [package.dependencies] pyparsing = ">=2.0.2" @@ -120,17 +152,23 @@ pyparsing = ">=2.0.2" name = "pathspec" version = "0.8.1" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, + {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, +] [[package]] name = "pluggy" version = "0.13.1" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} @@ -142,25 +180,34 @@ dev = ["pre-commit", "tox"] name = "py" version = "1.10.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, + {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, +] [[package]] name = "pyparsing" version = "2.4.7" description = "Python parsing module" -category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] [[package]] name = "pytest" -version = "6.2.4" +version = "6.2.5" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, +] [package.dependencies] atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} @@ -169,7 +216,7 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<1.0.0a1" +pluggy = ">=0.12,<2.0" py = ">=1.8.2" toml = "*" @@ -180,113 +227,9 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm name = "regex" version = "2021.7.1" description = "Alternative regular expression module, to replace re." -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "typed-ast" -version = "1.4.3" -description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "dev" optional = false python-versions = "*" - -[[package]] -name = "typing-extensions" -version = "3.10.0.0" -description = "Backported and Experimental Type Hints for Python 3.5+" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "zipp" -version = "3.5.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] - -[metadata] -lock-version = "1.1" -python-versions = "^3.7" -content-hash = "db95c83fbfb5628534161185f94874dd07a84ab8db130198902859790231d6f5" - -[metadata.files] -appdirs = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, -] -atomicwrites = [ - {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, - {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, -] -attrs = [ - {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, - {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, -] -black = [ - {file = "black-21.6b0-py3-none-any.whl", hash = "sha256:dfb8c5a069012b2ab1e972e7b908f5fb42b6bbabcba0a788b86dc05067c7d9c7"}, - {file = "black-21.6b0.tar.gz", hash = "sha256:dc132348a88d103016726fe360cb9ede02cecf99b76e3660ce6c596be132ce04"}, -] -click = [ - {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, - {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, -] -colorama = [ - {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, - {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, -] -importlib-metadata = [ - {file = "importlib_metadata-4.6.0-py3-none-any.whl", hash = "sha256:c6513572926a96458f8c8f725bf0e00108fba0c9583ade9bd15b869c9d726e33"}, - {file = "importlib_metadata-4.6.0.tar.gz", hash = "sha256:4a5611fea3768d3d967c447ab4e93f567d95db92225b43b7b238dbfb855d70bb"}, -] -iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, -] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] -packaging = [ - {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, - {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, -] -pathspec = [ - {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, - {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, -] -pluggy = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, -] -py = [ - {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, - {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, -] -pyparsing = [ - {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, - {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, -] -pytest = [ - {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, - {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, -] -regex = [ +files = [ {file = "regex-2021.7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:494d0172774dc0beeea984b94c95389143db029575f7ca908edd74469321ea99"}, {file = "regex-2021.7.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:8cf6728f89b071bd3ab37cb8a0e306f4de897553a0ed07442015ee65fbf53d62"}, {file = "regex-2021.7.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1806370b2bef4d4193eebe8ee59a9fd7547836a34917b7badbe6561a8594d9cb"}, @@ -325,11 +268,25 @@ regex = [ {file = "regex-2021.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:18040755606b0c21281493ec309214bd61e41a170509e5014f41d6a5a586e161"}, {file = "regex-2021.7.1.tar.gz", hash = "sha256:849802379a660206277675aa5a5c327f5c910c690649535863ddf329b0ba8c87"}, ] -toml = [ + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] -typed-ast = [ + +[[package]] +name = "typed-ast" +version = "1.4.3" +description = "a fork of Python 2 and 3 ast modules with type comment support" +optional = false +python-versions = "*" +files = [ {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, @@ -361,12 +318,35 @@ typed-ast = [ {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, ] -typing-extensions = [ + +[[package]] +name = "typing-extensions" +version = "3.10.0.0" +description = "Backported and Experimental Type Hints for Python 3.5+" +optional = false +python-versions = "*" +files = [ {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, ] -zipp = [ + +[[package]] +name = "zipp" +version = "3.5.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.6" +files = [ {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"}, {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"}, ] + +[package.extras] +docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] +testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.7" +content-hash = "db95c83fbfb5628534161185f94874dd07a84ab8db130198902859790231d6f5" diff --git a/pyproject.toml b/pyproject.toml index 81e3159..49cbb2b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ python = "^3.7" [tool.poetry.dev-dependencies] black = "^21.6b0" -pytest = "^6.2.4" +pytest = "^6.2.5" [build-system] requires = ["poetry-core>=1.0.0"] From b33fe121f33689f4019ccf47ec3333d7c31c6857 Mon Sep 17 00:00:00 2001 From: moisutsu Date: Sun, 17 Sep 2023 16:06:50 +0900 Subject: [PATCH 09/11] Use asdf for managing Python versions --- .tool-versions | 1 + 1 file changed, 1 insertion(+) create mode 100644 .tool-versions diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..47cd22e --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +python 3.10.13 From 7381f0809704b3fd71078c60c50cd4ce1d0adefb Mon Sep 17 00:00:00 2001 From: moisutsu Date: Sun, 17 Sep 2023 16:19:22 +0900 Subject: [PATCH 10/11] Format --- classopt/decorator.py | 17 +++++++++++++---- tests/test_decorator.py | 1 - tests/test_inheritance.py | 1 - 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/classopt/decorator.py b/classopt/decorator.py index 6ae7dd9..0d470e4 100644 --- a/classopt/decorator.py +++ b/classopt/decorator.py @@ -102,7 +102,9 @@ def wrap(cls): return wrap(cls) -def _process_class(cls, default_long: bool, default_short: bool, external_parser: ArgumentParser): +def _process_class( + cls, default_long: bool, default_short: bool, external_parser: ArgumentParser +): @classmethod def from_args(cls, args: Optional[List[str]] = None): parser = external_parser if external_parser is not None else ArgumentParser() @@ -147,7 +149,10 @@ def from_args(cls, args: Optional[List[str]] = None): elif arg_field.default_factory != MISSING: kwargs["default"] = arg_field.type(arg_field.default_factory()) - if type(arg_field.type) in GENERIC_ALIASES and arg_field.type.__origin__ == list: + if ( + type(arg_field.type) in GENERIC_ALIASES + and arg_field.type.__origin__ == list + ): kwargs["type"] = arg_field.type.__args__[0] if not "nargs" in arg_field.metadata: kwargs["nargs"] = "*" @@ -170,7 +175,9 @@ def from_args(cls, args: Optional[List[str]] = None): def to_dict(self): def classopt_dict_factory(items: List[Tuple[str, Any]]) -> Dict[str, Any]: - converted_dict = {key: convert_non_primitives_to_string(value) for key, value in items} + converted_dict = { + key: convert_non_primitives_to_string(value) for key, value in items + } return converted_dict @@ -181,7 +188,9 @@ def classopt_dict_factory(items: List[Tuple[str, Any]]) -> Dict[str, Any]: @classmethod def from_dict(cls, data: dict): reverted_data = { - key: revert_non_primitives_from_string(value, original_type=cls.__annotations__[key]) + key: revert_non_primitives_from_string( + value, original_type=cls.__annotations__[key] + ) for key, value in data.items() if key in cls.__annotations__ } diff --git a/tests/test_decorator.py b/tests/test_decorator.py index e1db276..e773119 100644 --- a/tests/test_decorator.py +++ b/tests/test_decorator.py @@ -330,7 +330,6 @@ class Opt: del_args() - args_dict = { "arg_int": 3, "arg_float": 3.2, diff --git a/tests/test_inheritance.py b/tests/test_inheritance.py index aeb3b54..e4d4e76 100644 --- a/tests/test_inheritance.py +++ b/tests/test_inheritance.py @@ -109,7 +109,6 @@ def _parser_factory(cls) -> ArgumentParser: with self.assertRaises(userArgumentParserException): opt = Opt.from_args() - def test_args_from_script(self): class Opt(ClassOpt): arg_int: int From b88db62f477d50f3cfe840f9646b84cce66a6289 Mon Sep 17 00:00:00 2001 From: moisutsu Date: Sun, 17 Sep 2023 16:20:17 +0900 Subject: [PATCH 11/11] Fix unit test for test_from_json --- tests/test_decorator.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_decorator.py b/tests/test_decorator.py index e773119..ce4c308 100644 --- a/tests/test_decorator.py +++ b/tests/test_decorator.py @@ -339,7 +339,5 @@ class Opt: assert opt.arg_int == args_dict["arg_int"] assert opt.arg_float == args_dict["arg_float"] - assert opt.arg_str == "test" - assert opt.arg_path == Path("test.txt") - assert opt.arg_list == ["a", "b"] - assert opt.arg_set == {"a", "b"} + assert opt.arg_list == None + assert opt.arg_path == None