├── .github └── workflows │ └── unittest.yml ├── .gitignore ├── LICENSE ├── README.md ├── classopt ├── __init__.py ├── config.py ├── decorator.py ├── inheritance.py └── utils.py ├── examples ├── head_decorator.py ├── head_inheritance.py ├── poetry.lock ├── print_args.py └── pyproject.toml ├── poetry.lock ├── pyproject.toml └── tests ├── __init__.py ├── conftest.py ├── test_decorator.py └── test_inheritance.py /.github/workflows/unittest.yml: -------------------------------------------------------------------------------- 1 | name: Unit test 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | build: 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | os: ["ubuntu-20.04", "macos-12", "windows-2022"] 13 | python-version: ["3.7", "3.8", "3.9", "3.10"] 14 | defaults: 15 | run: 16 | shell: bash 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Set up Python 20 | uses: actions/setup-python@v4 21 | with: 22 | python-version: ${{ matrix.python-version }} 23 | 24 | - name: Install Poetry 25 | uses: snok/install-poetry@v1 26 | with: 27 | virtualenvs-create: true 28 | virtualenvs-in-project: true 29 | 30 | - name: Install library 31 | run: poetry install --no-interaction 32 | 33 | - name: Run test 34 | run: poetry run pytest 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 moisutsu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Welcome to ClassOpt 👋

2 |

3 | Version 4 | 5 | License: MIT 6 | 7 | 8 | Twitter: moisutsu 9 | 10 |

11 | 12 | > Arguments parser with class for Python, inspired by [StructOpt](https://github.com/TeXitoi/structopt) 13 | 14 | ## Install 15 | 16 | ```sh 17 | pip install classopt 18 | ``` 19 | 20 | ## Usage 21 | 22 | 23 | Import `classopt` and define the Opt class with decorator. 24 | 25 | ```python 26 | from classopt import classopt 27 | 28 | @classopt(default_long=True) 29 | class Opt: 30 | file: str 31 | count: int = 3 32 | numbers: list[int] 33 | flag: bool 34 | 35 | if __name__ == "__main__": 36 | opt = Opt.from_args() 37 | print(opt) 38 | print(opt.file) 39 | ``` 40 | 41 | Run with command line arguments. 42 | 43 | ```bash 44 | $ python example.py --file example.txt --numbers 1 2 3 --flag 45 | Opt(file='example.txt', count=3, numbers=[1, 2, 3], flag=True) 46 | example.txt 47 | ``` 48 | You can specify most of the arguments to [argparse.ArgumentParser.add_argument](https://docs.python.org/ja/3/library/argparse.html#argparse.ArgumentParser.add_argument) in `config` (except name_or_flags). 49 | 50 | 51 | ```python 52 | from classopt import classopt, config 53 | 54 | @classopt 55 | class Opt: 56 | file: str 57 | count: int = config(long=True) 58 | numbers: list = config(long=True, short=True, nargs="+", type=int) 59 | flag: bool = config(long=True, action="store_false") 60 | 61 | if __name__ == "__main__": 62 | opt = Opt.from_args() 63 | print(opt) 64 | ``` 65 | 66 | ```bash 67 | $ python example.py example.txt --count 5 -n 1 2 3 --flag 68 | Opt(file='example.txt', count=5, numbers=[1, 2, 3], flag=False) 69 | ``` 70 | 71 | Some details 72 | ```python 73 | # `default_long=True` is equivalent to `config(long=True)' for all members 74 | # Similarly, you can do `default_short=True` 75 | @classopt(default_long=True) 76 | class Opt: 77 | # `long=False` overrides `default_long=True` 78 | file: str = config(long=False) 79 | 80 | # equivalent to `numbers: list = config(nargs="*", type=int)` 81 | # and `numbers: typing.List[int]` 82 | numbers: list[int] 83 | 84 | # equivalent to `flag: bool = config(action="store_true")` 85 | flag: bool 86 | ``` 87 | 88 | ### Other Way 89 | 90 | You can also define an argument parser by inheriting from `ClassOpt`. 91 | 92 | ```python 93 | from classopt import ClassOpt, config 94 | 95 | class Opt(ClassOpt): 96 | file: str 97 | count: int = config(long=True) 98 | numbers: list[int] = config(long=True, short="-c") 99 | flag: bool = config(long=True) 100 | 101 | if __name__ == "__main__": 102 | opt = Opt.from_args() 103 | print(opt) 104 | print(opt.file) 105 | ``` 106 | 107 | Run with command line arguments. 108 | 109 | ```bash 110 | $ python example.py example.txt --count 5 -c 1 2 3 --flag 111 | Opt(file='example.txt', count=5, numbers=[1, 2, 3], flag=True) 112 | example.txt 113 | ``` 114 | 115 | The inherited method does not support some features and may disappear in the future. 116 | So we recommend the decorator method. 117 | 118 | ## Run tests 119 | 120 | ```sh 121 | poetry run pytest 122 | ``` 123 | 124 | ## Author 125 | 126 | 👤 **moisutsu** 127 | 128 | * Twitter: [@moisutsu](https://twitter.com/moisutsu) 129 | * Github: [@moisutsu](https://github.com/moisutsu) 130 | 131 | ## Show your support 132 | 133 | Give a ⭐️ if this project helped you! 134 | 135 | ## 📝 License 136 | 137 | Copyright © 2021 [moisutsu](https://github.com/moisutsu).
138 | This project is [MIT](https://github.com/moisutsu/classopt/blob/main/LICENSE) licensed. 139 | 140 | *** 141 | _This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_ 142 | -------------------------------------------------------------------------------- /classopt/__init__.py: -------------------------------------------------------------------------------- 1 | from .config import config 2 | from .decorator import classopt 3 | from .inheritance import ClassOpt 4 | 5 | __all__ = ["classopt", "ClassOpt", "config"] 6 | -------------------------------------------------------------------------------- /classopt/config.py: -------------------------------------------------------------------------------- 1 | from dataclasses import Field, field 2 | from typing import Any, Iterable, Optional, Tuple, Union 3 | 4 | 5 | def config( 6 | long: Optional[bool] = None, 7 | short: Optional[Union[bool, str]] = None, 8 | action: Optional[str] = None, 9 | nargs: Optional[Union[int, str]] = None, 10 | const: Any = None, 11 | default: Any = None, 12 | type: Optional[type] = None, 13 | choices: Optional[Iterable] = None, 14 | required: Optional[bool] = None, 15 | help: Optional[str] = None, 16 | metavar: Optional[Union[str, Tuple[str]]] = None, 17 | dest: Optional[str] = None, 18 | version: Optional[str] = None, 19 | **kwargs: Any, 20 | ) -> Field: 21 | metadata = {} 22 | metadata.update(kwargs) 23 | 24 | assign_if_not_none(metadata, "long", long) 25 | assign_if_not_none(metadata, "short", short) 26 | assign_if_not_none(metadata, "action", action) 27 | assign_if_not_none(metadata, "nargs", nargs) 28 | assign_if_not_none(metadata, "const", const) 29 | assign_if_not_none(metadata, "default", default) 30 | assign_if_not_none(metadata, "type", type) 31 | assign_if_not_none(metadata, "choices", choices) 32 | assign_if_not_none(metadata, "required", required) 33 | assign_if_not_none(metadata, "help", help) 34 | assign_if_not_none(metadata, "metavar", metavar) 35 | assign_if_not_none(metadata, "dest", dest) 36 | assign_if_not_none(metadata, "version", version) 37 | 38 | # to avoid errors like below: 39 | # ValueError: mutable default for field hoge is not allowed: use default_factory 40 | if isinstance(default, (list, dict, set)): 41 | return field( 42 | default_factory=lambda: default, 43 | metadata=metadata, 44 | ) 45 | else: 46 | return field( 47 | default=default, 48 | metadata=metadata, 49 | ) 50 | 51 | 52 | def assign_if_not_none(d: dict, key: str, value: Any): 53 | if value is None: 54 | return 55 | d[key] = value 56 | -------------------------------------------------------------------------------- /classopt/decorator.py: -------------------------------------------------------------------------------- 1 | import typing 2 | from argparse import ArgumentParser 3 | from dataclasses import MISSING, Field, asdict, dataclass 4 | from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, overload 5 | 6 | from classopt import config 7 | from classopt.utils import ( 8 | GENERIC_ALIASES, 9 | convert_non_primitives_to_string, 10 | revert_non_primitives_from_string, 11 | ) 12 | 13 | if TYPE_CHECKING: 14 | from typing import Callable, Generic, Literal, Type, TypeVar, Union 15 | 16 | _C = TypeVar("_C") 17 | _T = TypeVar("_T") 18 | 19 | class _ClassOptGeneric(Generic[_T]): 20 | @classmethod 21 | def from_args(cls) -> _T: 22 | ... 23 | 24 | def to_dict(self) -> dict: 25 | ... 26 | 27 | @classmethod 28 | def from_dict(cls, data: dict) -> _T: 29 | ... 30 | 31 | 32 | @overload 33 | def classopt( 34 | cls: "Type[_C]", 35 | default_long: bool = False, 36 | default_short: bool = False, 37 | ) -> "Union[Type[_C], Type[_ClassOptGeneric[_C]]]": 38 | ... 39 | 40 | 41 | @overload 42 | def classopt( 43 | cls: "Literal[None]" = None, 44 | default_long: bool = False, 45 | default_short: bool = False, 46 | ) -> "Callable[[Type[_C]], Union[Type[_C], Type[_ClassOptGeneric[_C]]]]": 47 | ... 48 | 49 | 50 | def classopt(cls=None, default_long=False, default_short=False, parser=None): 51 | def wrap(cls): 52 | return _process_class(cls, default_long, default_short, parser) 53 | 54 | if cls is None: 55 | return wrap 56 | 57 | return wrap(cls) 58 | 59 | 60 | def _process_class(cls, default_long: bool, default_short: bool, external_parser: ArgumentParser): 61 | @classmethod 62 | def from_args(cls, args: Optional[List[str]] = None): 63 | parser = external_parser if external_parser is not None else ArgumentParser() 64 | 65 | for arg_name, arg_field in cls.__dataclass_fields__.items(): 66 | kwargs = {} 67 | kwargs.update(arg_field.metadata) 68 | kwargs["type"] = arg_field.type 69 | kwargs.pop("long", None) 70 | kwargs.pop("short", None) 71 | 72 | name_or_flags = [] 73 | if isinstance(arg_field.metadata.get("long"), bool): 74 | if arg_field.metadata.get("long"): 75 | name_or_flags.append(f"--{arg_name}") 76 | elif default_long: 77 | name_or_flags.append(f"--{arg_name}") 78 | 79 | if isinstance(arg_field.metadata.get("short"), str): 80 | name_or_flags.append(arg_field.metadata.get("short")) 81 | elif isinstance(arg_field.metadata.get("short"), bool): 82 | if arg_field.metadata.get("short"): 83 | name_or_flags.append(f"-{arg_name[0]}") 84 | elif default_short: 85 | name_or_flags.append(f"-{arg_name[0]}") 86 | 87 | if len(name_or_flags) == 0: 88 | name_or_flags.append(arg_name) 89 | 90 | if "action" in arg_field.metadata: 91 | kwargs.pop("type") 92 | elif arg_field.type == bool: 93 | kwargs.pop("type") 94 | kwargs["action"] = "store_true" 95 | 96 | if ( 97 | arg_field.default == MISSING and arg_field.default_factory == MISSING 98 | ) or arg_field.default is None: 99 | kwargs["default"] = None 100 | elif arg_field.default != MISSING: 101 | kwargs["default"] = arg_field.type(arg_field.default) 102 | elif arg_field.default_factory != MISSING: 103 | kwargs["default"] = arg_field.type(arg_field.default_factory()) 104 | 105 | if type(arg_field.type) in GENERIC_ALIASES and arg_field.type.__origin__ == list: 106 | kwargs["type"] = arg_field.type.__args__[0] 107 | if not "nargs" in arg_field.metadata: 108 | kwargs["nargs"] = "*" 109 | 110 | if "type" in arg_field.metadata: 111 | kwargs["type"] = arg_field.metadata["type"] 112 | 113 | parser.add_argument(*name_or_flags, **kwargs) 114 | 115 | ns = parser.parse_args(args=args) 116 | return cls(**vars(ns)) 117 | 118 | for arg_name in cls.__annotations__.keys(): 119 | if not hasattr(cls, arg_name): 120 | setattr(cls, arg_name, None) 121 | elif not isinstance(getattr(cls, arg_name), Field): 122 | setattr(cls, arg_name, config(default=getattr(cls, arg_name))) 123 | 124 | setattr(cls, "from_args", from_args) 125 | 126 | def to_dict(self): 127 | def classopt_dict_factory(items: List[Tuple[str, Any]]) -> Dict[str, Any]: 128 | converted_dict = {key: convert_non_primitives_to_string(value) for key, value in items} 129 | 130 | return converted_dict 131 | 132 | return asdict(self, dict_factory=classopt_dict_factory) 133 | 134 | setattr(cls, "to_dict", to_dict) 135 | 136 | @classmethod 137 | def from_dict(cls, data: dict): 138 | reverted_data = { 139 | key: revert_non_primitives_from_string(value, original_type=cls.__annotations__[key]) 140 | for key, value in data.items() 141 | if key in cls.__annotations__ 142 | } 143 | 144 | default_data = {} 145 | for arg_name, arg_field in cls.__dataclass_fields__.items(): 146 | if arg_name in reverted_data: 147 | continue 148 | if ( 149 | arg_field.default == MISSING and arg_field.default_factory == MISSING 150 | ) or arg_field.default is None: 151 | default_data[arg_name] = None 152 | elif arg_field.default != MISSING: 153 | default_data[arg_name] = arg_field.type(arg_field.default) 154 | elif arg_field.default_factory != MISSING: 155 | type_ = arg_field.type.__origin__ 156 | default_data[arg_name] = type_(arg_field.default_factory()) 157 | 158 | return cls(**default_data, **reverted_data) 159 | 160 | setattr(cls, "from_dict", from_dict) 161 | 162 | return dataclass(cls) 163 | -------------------------------------------------------------------------------- /classopt/inheritance.py: -------------------------------------------------------------------------------- 1 | import typing 2 | from argparse import ArgumentParser 3 | from dataclasses import dataclass 4 | from typing import TypeVar, Optional, List 5 | 6 | T = TypeVar("T") 7 | 8 | 9 | class ClassOpt: 10 | @classmethod 11 | def _parser_factory(cls: T) -> ArgumentParser: 12 | return ArgumentParser() 13 | 14 | @classmethod 15 | def from_args(cls: T, args: Optional[List[str]] = None) -> T: 16 | parser = cls._parser_factory() 17 | 18 | for arg_name, arg_type in cls.__annotations__.items(): 19 | kwargs = {} 20 | 21 | metadata = {} 22 | if hasattr(cls, arg_name): 23 | metadata.update(getattr(cls, arg_name).metadata) 24 | 25 | kwargs.update(metadata) 26 | kwargs["type"] = arg_type 27 | kwargs.pop("long", None) 28 | kwargs.pop("short", None) 29 | 30 | name_or_flags = [] 31 | if isinstance(metadata.get("long"), bool): 32 | if metadata.get("long"): 33 | name_or_flags.append(f"--{arg_name}") 34 | 35 | if isinstance(metadata.get("short"), str): 36 | name_or_flags.append(metadata.get("short")) 37 | elif isinstance(metadata.get("short"), bool): 38 | if metadata.get("short"): 39 | name_or_flags.append(f"-{arg_name[0]}") 40 | 41 | if len(name_or_flags) == 0: 42 | name_or_flags.append(arg_name) 43 | 44 | if "action" in metadata: 45 | kwargs.pop("type") 46 | elif arg_type == bool: 47 | kwargs.pop("type") 48 | kwargs["action"] = "store_true" 49 | 50 | generic_aliases = [typing._GenericAlias] 51 | try: 52 | from types import GenericAlias 53 | 54 | generic_aliases.append(GenericAlias) 55 | except ImportError: 56 | pass 57 | if type(arg_type) in generic_aliases and arg_type.__origin__ == list: 58 | kwargs["type"] = arg_type.__args__[0] 59 | if not "nargs" in metadata: 60 | kwargs["nargs"] = "*" 61 | 62 | if "type" in metadata: 63 | kwargs["type"] = metadata["type"] 64 | 65 | parser.add_argument(*name_or_flags, **kwargs) 66 | 67 | ns = parser.parse_args(args=args) 68 | 69 | return dataclass(cls)(**vars(ns)) 70 | -------------------------------------------------------------------------------- /classopt/utils.py: -------------------------------------------------------------------------------- 1 | import typing 2 | from typing import Any, Union 3 | 4 | GENERIC_ALIASES = {typing._GenericAlias} 5 | try: 6 | from types import GenericAlias 7 | 8 | GENERIC_ALIASES.add(GenericAlias) 9 | except ImportError: 10 | pass 11 | 12 | 13 | PRIMITIVE_TYPES = { 14 | int, 15 | float, 16 | complex, 17 | bool, 18 | str, 19 | list, 20 | tuple, 21 | set, 22 | dict, 23 | type(None), 24 | } 25 | 26 | 27 | def revert_type_from_generic(type_: type) -> type: 28 | if type(type_) in GENERIC_ALIASES: 29 | return type_.__origin__ 30 | else: 31 | return type_ 32 | 33 | 34 | def convert_non_primitives_to_string(value: Any) -> Union[Any, str]: 35 | if revert_type_from_generic(type(value)) in PRIMITIVE_TYPES: 36 | return value 37 | else: 38 | return str(value) 39 | 40 | 41 | def revert_non_primitives_from_string(value: Any, original_type: type): 42 | if revert_type_from_generic(original_type) in PRIMITIVE_TYPES: 43 | return value 44 | else: 45 | return original_type(value) 46 | -------------------------------------------------------------------------------- /examples/head_decorator.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from classopt import classopt, config 4 | 5 | 6 | @classopt(default_long=True, default_short=True) 7 | class Opt: 8 | input_file: Path = config(long=False, short=False) 9 | lines: int = config(short="-n", default=10, help="print the first LINES lines") 10 | index: bool = config(help="number all output lines") 11 | 12 | 13 | def main(opt: Opt): 14 | prefix_width = len(str(opt.lines)) 15 | with opt.input_file.open() as f: 16 | for line_number, line in enumerate(f, 1): 17 | prefix = f"{str(line_number).rjust(prefix_width)}: " if opt.index else "" 18 | print(f"{prefix}{line}", end="") 19 | if line_number >= opt.lines: 20 | break 21 | 22 | 23 | if __name__ == "__main__": 24 | opt = Opt.from_args() 25 | main(opt) 26 | -------------------------------------------------------------------------------- /examples/head_inheritance.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from classopt import ClassOpt, config 4 | 5 | 6 | class Opt(ClassOpt): 7 | input_file: Path 8 | lines: int = config( 9 | long=True, short="-n", default=10, help="print the first LINES lines" 10 | ) 11 | index: bool = config(long=True, short=True, help="number all output lines") 12 | 13 | 14 | def main(opt: Opt): 15 | prefix_width = len(str(opt.lines)) 16 | with opt.input_file.open() as f: 17 | for line_number, line in enumerate(f, 1): 18 | prefix = f"{str(line_number).rjust(prefix_width)}: " if opt.index else "" 19 | print(f"{prefix}{line}", end="") 20 | if line_number >= opt.lines: 21 | break 22 | 23 | 24 | if __name__ == "__main__": 25 | opt = Opt.from_args() 26 | main(opt) 27 | -------------------------------------------------------------------------------- /examples/poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "appdirs" 3 | version = "1.4.4" 4 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 5 | category = "dev" 6 | optional = false 7 | python-versions = "*" 8 | 9 | [[package]] 10 | name = "atomicwrites" 11 | version = "1.4.0" 12 | description = "Atomic file writes." 13 | category = "dev" 14 | optional = false 15 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 16 | 17 | [[package]] 18 | name = "attrs" 19 | version = "21.2.0" 20 | description = "Classes Without Boilerplate" 21 | category = "dev" 22 | optional = false 23 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 24 | 25 | [package.extras] 26 | 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"] 27 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] 28 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] 29 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] 30 | 31 | [[package]] 32 | name = "black" 33 | version = "21.6b0" 34 | description = "The uncompromising code formatter." 35 | category = "dev" 36 | optional = false 37 | python-versions = ">=3.6.2" 38 | 39 | [package.dependencies] 40 | appdirs = "*" 41 | click = ">=7.1.2" 42 | mypy-extensions = ">=0.4.3" 43 | pathspec = ">=0.8.1,<1" 44 | regex = ">=2020.1.8" 45 | toml = ">=0.10.1" 46 | 47 | [package.extras] 48 | colorama = ["colorama (>=0.4.3)"] 49 | d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"] 50 | python2 = ["typed-ast (>=1.4.2)"] 51 | uvloop = ["uvloop (>=0.15.2)"] 52 | 53 | [[package]] 54 | name = "classopt" 55 | version = "0.1.7" 56 | description = "Arguments parser with class for Python, inspired by StructOpt" 57 | category = "main" 58 | optional = false 59 | python-versions = ">=3.7,<4.0" 60 | 61 | [[package]] 62 | name = "click" 63 | version = "8.0.1" 64 | description = "Composable command line interface toolkit" 65 | category = "dev" 66 | optional = false 67 | python-versions = ">=3.6" 68 | 69 | [package.dependencies] 70 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 71 | 72 | [[package]] 73 | name = "colorama" 74 | version = "0.4.4" 75 | description = "Cross-platform colored terminal text." 76 | category = "dev" 77 | optional = false 78 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 79 | 80 | [[package]] 81 | name = "more-itertools" 82 | version = "8.8.0" 83 | description = "More routines for operating on iterables, beyond itertools" 84 | category = "dev" 85 | optional = false 86 | python-versions = ">=3.5" 87 | 88 | [[package]] 89 | name = "mypy-extensions" 90 | version = "0.4.3" 91 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 92 | category = "dev" 93 | optional = false 94 | python-versions = "*" 95 | 96 | [[package]] 97 | name = "packaging" 98 | version = "21.0" 99 | description = "Core utilities for Python packages" 100 | category = "dev" 101 | optional = false 102 | python-versions = ">=3.6" 103 | 104 | [package.dependencies] 105 | pyparsing = ">=2.0.2" 106 | 107 | [[package]] 108 | name = "pathspec" 109 | version = "0.8.1" 110 | description = "Utility library for gitignore style pattern matching of file paths." 111 | category = "dev" 112 | optional = false 113 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 114 | 115 | [[package]] 116 | name = "pluggy" 117 | version = "0.13.1" 118 | description = "plugin and hook calling mechanisms for python" 119 | category = "dev" 120 | optional = false 121 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 122 | 123 | [package.extras] 124 | dev = ["pre-commit", "tox"] 125 | 126 | [[package]] 127 | name = "py" 128 | version = "1.10.0" 129 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 130 | category = "dev" 131 | optional = false 132 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 133 | 134 | [[package]] 135 | name = "pyparsing" 136 | version = "2.4.7" 137 | description = "Python parsing module" 138 | category = "dev" 139 | optional = false 140 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 141 | 142 | [[package]] 143 | name = "pytest" 144 | version = "5.4.3" 145 | description = "pytest: simple powerful testing with Python" 146 | category = "dev" 147 | optional = false 148 | python-versions = ">=3.5" 149 | 150 | [package.dependencies] 151 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 152 | attrs = ">=17.4.0" 153 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 154 | more-itertools = ">=4.0.0" 155 | packaging = "*" 156 | pluggy = ">=0.12,<1.0" 157 | py = ">=1.5.0" 158 | wcwidth = "*" 159 | 160 | [package.extras] 161 | checkqa-mypy = ["mypy (==v0.761)"] 162 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 163 | 164 | [[package]] 165 | name = "regex" 166 | version = "2021.7.6" 167 | description = "Alternative regular expression module, to replace re." 168 | category = "dev" 169 | optional = false 170 | python-versions = "*" 171 | 172 | [[package]] 173 | name = "toml" 174 | version = "0.10.2" 175 | description = "Python Library for Tom's Obvious, Minimal Language" 176 | category = "dev" 177 | optional = false 178 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 179 | 180 | [[package]] 181 | name = "wcwidth" 182 | version = "0.2.5" 183 | description = "Measures the displayed width of unicode strings in a terminal" 184 | category = "dev" 185 | optional = false 186 | python-versions = "*" 187 | 188 | [metadata] 189 | lock-version = "1.1" 190 | python-versions = "^3.9" 191 | content-hash = "41d377884e10958c8a0df4c8527f82949f039ae178b45a2c930f6cbf02776a62" 192 | 193 | [metadata.files] 194 | appdirs = [ 195 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 196 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 197 | ] 198 | atomicwrites = [ 199 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 200 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 201 | ] 202 | attrs = [ 203 | {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, 204 | {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, 205 | ] 206 | black = [ 207 | {file = "black-21.6b0-py3-none-any.whl", hash = "sha256:dfb8c5a069012b2ab1e972e7b908f5fb42b6bbabcba0a788b86dc05067c7d9c7"}, 208 | {file = "black-21.6b0.tar.gz", hash = "sha256:dc132348a88d103016726fe360cb9ede02cecf99b76e3660ce6c596be132ce04"}, 209 | ] 210 | classopt = [ 211 | {file = "classopt-0.1.7-py3-none-any.whl", hash = "sha256:28b57ba45b41049640e0fe21aa6ae073627973200486c1715c573ffb88e98f7d"}, 212 | {file = "classopt-0.1.7.tar.gz", hash = "sha256:164907ce9bed2917b9c8fbc13060d21dd91c7a5d662476ad326a51f9594c5969"}, 213 | ] 214 | click = [ 215 | {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, 216 | {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, 217 | ] 218 | colorama = [ 219 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 220 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 221 | ] 222 | more-itertools = [ 223 | {file = "more-itertools-8.8.0.tar.gz", hash = "sha256:83f0308e05477c68f56ea3a888172c78ed5d5b3c282addb67508e7ba6c8f813a"}, 224 | {file = "more_itertools-8.8.0-py3-none-any.whl", hash = "sha256:2cf89ec599962f2ddc4d568a05defc40e0a587fbc10d5989713638864c36be4d"}, 225 | ] 226 | mypy-extensions = [ 227 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 228 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 229 | ] 230 | packaging = [ 231 | {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, 232 | {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, 233 | ] 234 | pathspec = [ 235 | {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, 236 | {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, 237 | ] 238 | pluggy = [ 239 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 240 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 241 | ] 242 | py = [ 243 | {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, 244 | {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, 245 | ] 246 | pyparsing = [ 247 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 248 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 249 | ] 250 | pytest = [ 251 | {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, 252 | {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, 253 | ] 254 | regex = [ 255 | {file = "regex-2021.7.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e6a1e5ca97d411a461041d057348e578dc344ecd2add3555aedba3b408c9f874"}, 256 | {file = "regex-2021.7.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:6afe6a627888c9a6cfbb603d1d017ce204cebd589d66e0703309b8048c3b0854"}, 257 | {file = "regex-2021.7.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ccb3d2190476d00414aab36cca453e4596e8f70a206e2aa8db3d495a109153d2"}, 258 | {file = "regex-2021.7.6-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:ed693137a9187052fc46eedfafdcb74e09917166362af4cc4fddc3b31560e93d"}, 259 | {file = "regex-2021.7.6-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:99d8ab206a5270c1002bfcf25c51bf329ca951e5a169f3b43214fdda1f0b5f0d"}, 260 | {file = "regex-2021.7.6-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:b85ac458354165405c8a84725de7bbd07b00d9f72c31a60ffbf96bb38d3e25fa"}, 261 | {file = "regex-2021.7.6-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:3f5716923d3d0bfb27048242a6e0f14eecdb2e2a7fac47eda1d055288595f222"}, 262 | {file = "regex-2021.7.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5983c19d0beb6af88cb4d47afb92d96751fb3fa1784d8785b1cdf14c6519407"}, 263 | {file = "regex-2021.7.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf1d2d183abc7faa101ebe0b8d04fd19cb9138820abc8589083035c9440b8ca6"}, 264 | {file = "regex-2021.7.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1947e7de155063e1c495c50590229fb98720d4c383af5031bbcb413db33fa1be"}, 265 | {file = "regex-2021.7.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17d8a3f99b18d87ac54a449b836d485cc8c195bb6f5e4379c84c8519045facc9"}, 266 | {file = "regex-2021.7.6-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d30895ec80cc80358392841add9dde81ea1d54a4949049269115e6b0555d0498"}, 267 | {file = "regex-2021.7.6-cp36-cp36m-win32.whl", hash = "sha256:c92831dac113a6e0ab28bc98f33781383fe294df1a2c3dfd1e850114da35fd5b"}, 268 | {file = "regex-2021.7.6-cp36-cp36m-win_amd64.whl", hash = "sha256:791aa1b300e5b6e5d597c37c346fb4d66422178566bbb426dd87eaae475053fb"}, 269 | {file = "regex-2021.7.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:59506c6e8bd9306cd8a41511e32d16d5d1194110b8cfe5a11d102d8b63cf945d"}, 270 | {file = "regex-2021.7.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:564a4c8a29435d1f2256ba247a0315325ea63335508ad8ed938a4f14c4116a5d"}, 271 | {file = "regex-2021.7.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:59c00bb8dd8775473cbfb967925ad2c3ecc8886b3b2d0c90a8e2707e06c743f0"}, 272 | {file = "regex-2021.7.6-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:9a854b916806c7e3b40e6616ac9e85d3cdb7649d9e6590653deb5b341a736cec"}, 273 | {file = "regex-2021.7.6-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:db2b7df831c3187a37f3bb80ec095f249fa276dbe09abd3d35297fc250385694"}, 274 | {file = "regex-2021.7.6-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:173bc44ff95bc1e96398c38f3629d86fa72e539c79900283afa895694229fe6a"}, 275 | {file = "regex-2021.7.6-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:15dddb19823f5147e7517bb12635b3c82e6f2a3a6b696cc3e321522e8b9308ad"}, 276 | {file = "regex-2021.7.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ddeabc7652024803666ea09f32dd1ed40a0579b6fbb2a213eba590683025895"}, 277 | {file = "regex-2021.7.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8244c681018423a0d1784bc6b9af33bdf55f2ab8acb1f3cd9dd83d90e0813253"}, 278 | {file = "regex-2021.7.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8a4c742089faf0e51469c6a1ad7e3d3d21afae54a16a6cead85209dfe0a1ce65"}, 279 | {file = "regex-2021.7.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:914e626dc8e75fe4fc9b7214763f141d9f40165d00dfe680b104fa1b24063bbf"}, 280 | {file = "regex-2021.7.6-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3fabb19c82ecf39832a3f5060dfea9a7ab270ef156039a1143a29a83a09a62de"}, 281 | {file = "regex-2021.7.6-cp37-cp37m-win32.whl", hash = "sha256:f080248b3e029d052bf74a897b9d74cfb7643537fbde97fe8225a6467fb559b5"}, 282 | {file = "regex-2021.7.6-cp37-cp37m-win_amd64.whl", hash = "sha256:d8bbce0c96462dbceaa7ac4a7dfbbee92745b801b24bce10a98d2f2b1ea9432f"}, 283 | {file = "regex-2021.7.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:edd1a68f79b89b0c57339bce297ad5d5ffcc6ae7e1afdb10f1947706ed066c9c"}, 284 | {file = "regex-2021.7.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:422dec1e7cbb2efbbe50e3f1de36b82906def93ed48da12d1714cabcd993d7f0"}, 285 | {file = "regex-2021.7.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cbe23b323988a04c3e5b0c387fe3f8f363bf06c0680daf775875d979e376bd26"}, 286 | {file = "regex-2021.7.6-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:0eb2c6e0fcec5e0f1d3bcc1133556563222a2ffd2211945d7b1480c1b1a42a6f"}, 287 | {file = "regex-2021.7.6-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:1c78780bf46d620ff4fff40728f98b8afd8b8e35c3efd638c7df67be2d5cddbf"}, 288 | {file = "regex-2021.7.6-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:bc84fb254a875a9f66616ed4538542fb7965db6356f3df571d783f7c8d256edd"}, 289 | {file = "regex-2021.7.6-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:598c0a79b4b851b922f504f9f39a863d83ebdfff787261a5ed061c21e67dd761"}, 290 | {file = "regex-2021.7.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:875c355360d0f8d3d827e462b29ea7682bf52327d500a4f837e934e9e4656068"}, 291 | {file = "regex-2021.7.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfc0957c4a4b91eff5ad036088769e600a25774256cd0e1154378591ce573f08"}, 292 | {file = "regex-2021.7.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:efb4af05fa4d2fc29766bf516f1f5098d6b5c3ed846fde980c18bf8646ad3979"}, 293 | {file = "regex-2021.7.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7423aca7cc30a6228ccdcf2ea76f12923d652c5c7c6dc1959a0b004e308f39fb"}, 294 | {file = "regex-2021.7.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bb9834c1e77493efd7343b8e38950dee9797d2d6f2d5fd91c008dfaef64684b9"}, 295 | {file = "regex-2021.7.6-cp38-cp38-win32.whl", hash = "sha256:e586f448df2bbc37dfadccdb7ccd125c62b4348cb90c10840d695592aa1b29e0"}, 296 | {file = "regex-2021.7.6-cp38-cp38-win_amd64.whl", hash = "sha256:2fe5e71e11a54e3355fa272137d521a40aace5d937d08b494bed4529964c19c4"}, 297 | {file = "regex-2021.7.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6110bab7eab6566492618540c70edd4d2a18f40ca1d51d704f1d81c52d245026"}, 298 | {file = "regex-2021.7.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4f64fc59fd5b10557f6cd0937e1597af022ad9b27d454e182485f1db3008f417"}, 299 | {file = "regex-2021.7.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:89e5528803566af4df368df2d6f503c84fbfb8249e6631c7b025fe23e6bd0cde"}, 300 | {file = "regex-2021.7.6-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2366fe0479ca0e9afa534174faa2beae87847d208d457d200183f28c74eaea59"}, 301 | {file = "regex-2021.7.6-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f9392a4555f3e4cb45310a65b403d86b589adc773898c25a39184b1ba4db8985"}, 302 | {file = "regex-2021.7.6-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:2bceeb491b38225b1fee4517107b8491ba54fba77cf22a12e996d96a3c55613d"}, 303 | {file = "regex-2021.7.6-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:f98dc35ab9a749276f1a4a38ab3e0e2ba1662ce710f6530f5b0a6656f1c32b58"}, 304 | {file = "regex-2021.7.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:319eb2a8d0888fa6f1d9177705f341bc9455a2c8aca130016e52c7fe8d6c37a3"}, 305 | {file = "regex-2021.7.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598ee917dbe961dcf827217bf2466bb86e4ee5a8559705af57cbabb3489dd37e"}, 306 | {file = "regex-2021.7.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:56fc7045a1999a8d9dd1896715bc5c802dfec5b9b60e883d2cbdecb42adedea4"}, 307 | {file = "regex-2021.7.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8363ac90ea63c3dd0872dfdb695f38aff3334bfa5712cffb238bd3ffef300e3"}, 308 | {file = "regex-2021.7.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:716a6db91b3641f566531ffcc03ceec00b2447f0db9942b3c6ea5d2827ad6be3"}, 309 | {file = "regex-2021.7.6-cp39-cp39-win32.whl", hash = "sha256:eaf58b9e30e0e546cdc3ac06cf9165a1ca5b3de8221e9df679416ca667972035"}, 310 | {file = "regex-2021.7.6-cp39-cp39-win_amd64.whl", hash = "sha256:4c9c3155fe74269f61e27617529b7f09552fbb12e44b1189cebbdb24294e6e1c"}, 311 | {file = "regex-2021.7.6.tar.gz", hash = "sha256:8394e266005f2d8c6f0bc6780001f7afa3ef81a7a2111fa35058ded6fce79e4d"}, 312 | ] 313 | toml = [ 314 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 315 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 316 | ] 317 | wcwidth = [ 318 | {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, 319 | {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, 320 | ] 321 | -------------------------------------------------------------------------------- /examples/print_args.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from classopt import classopt, config 4 | 5 | 6 | @classopt(default_long=True, default_short=True) 7 | class Opt: 8 | a: int = 0 9 | b: int = config(default=1) 10 | c: Path = Path("./tmp.txt") 11 | d: list = config(default=[0, 1, 2], nargs="+", type=int) 12 | 13 | 14 | if __name__ == "__main__": 15 | opt: Opt = Opt.from_args() 16 | print(opt) 17 | -------------------------------------------------------------------------------- /examples/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "examples" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["moisutsu "] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.9" 9 | classopt = "^0.1.7" 10 | 11 | [tool.poetry.dev-dependencies] 12 | pytest = "^5.2" 13 | black = "^21.6b0" 14 | 15 | [build-system] 16 | requires = ["poetry-core>=1.0.0"] 17 | build-backend = "poetry.core.masonry.api" 18 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "appdirs" 3 | version = "1.4.4" 4 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 5 | category = "dev" 6 | optional = false 7 | python-versions = "*" 8 | 9 | [[package]] 10 | name = "atomicwrites" 11 | version = "1.4.0" 12 | description = "Atomic file writes." 13 | category = "dev" 14 | optional = false 15 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 16 | 17 | [[package]] 18 | name = "attrs" 19 | version = "21.2.0" 20 | description = "Classes Without Boilerplate" 21 | category = "dev" 22 | optional = false 23 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 24 | 25 | [package.extras] 26 | 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"] 27 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] 28 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] 29 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] 30 | 31 | [[package]] 32 | name = "black" 33 | version = "21.6b0" 34 | description = "The uncompromising code formatter." 35 | category = "dev" 36 | optional = false 37 | python-versions = ">=3.6.2" 38 | 39 | [package.dependencies] 40 | appdirs = "*" 41 | click = ">=7.1.2" 42 | mypy-extensions = ">=0.4.3" 43 | pathspec = ">=0.8.1,<1" 44 | regex = ">=2020.1.8" 45 | toml = ">=0.10.1" 46 | typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\""} 47 | typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} 48 | 49 | [package.extras] 50 | colorama = ["colorama (>=0.4.3)"] 51 | d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"] 52 | python2 = ["typed-ast (>=1.4.2)"] 53 | uvloop = ["uvloop (>=0.15.2)"] 54 | 55 | [[package]] 56 | name = "click" 57 | version = "8.0.1" 58 | description = "Composable command line interface toolkit" 59 | category = "dev" 60 | optional = false 61 | python-versions = ">=3.6" 62 | 63 | [package.dependencies] 64 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 65 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 66 | 67 | [[package]] 68 | name = "colorama" 69 | version = "0.4.4" 70 | description = "Cross-platform colored terminal text." 71 | category = "dev" 72 | optional = false 73 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 74 | 75 | [[package]] 76 | name = "importlib-metadata" 77 | version = "4.6.0" 78 | description = "Read metadata from Python packages" 79 | category = "dev" 80 | optional = false 81 | python-versions = ">=3.6" 82 | 83 | [package.dependencies] 84 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 85 | zipp = ">=0.5" 86 | 87 | [package.extras] 88 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 89 | perf = ["ipython"] 90 | 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)"] 91 | 92 | [[package]] 93 | name = "iniconfig" 94 | version = "1.1.1" 95 | description = "iniconfig: brain-dead simple config-ini parsing" 96 | category = "dev" 97 | optional = false 98 | python-versions = "*" 99 | 100 | [[package]] 101 | name = "mypy-extensions" 102 | version = "0.4.3" 103 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 104 | category = "dev" 105 | optional = false 106 | python-versions = "*" 107 | 108 | [[package]] 109 | name = "packaging" 110 | version = "21.0" 111 | description = "Core utilities for Python packages" 112 | category = "dev" 113 | optional = false 114 | python-versions = ">=3.6" 115 | 116 | [package.dependencies] 117 | pyparsing = ">=2.0.2" 118 | 119 | [[package]] 120 | name = "pathspec" 121 | version = "0.8.1" 122 | description = "Utility library for gitignore style pattern matching of file paths." 123 | category = "dev" 124 | optional = false 125 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 126 | 127 | [[package]] 128 | name = "pluggy" 129 | version = "0.13.1" 130 | description = "plugin and hook calling mechanisms for python" 131 | category = "dev" 132 | optional = false 133 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 134 | 135 | [package.dependencies] 136 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 137 | 138 | [package.extras] 139 | dev = ["pre-commit", "tox"] 140 | 141 | [[package]] 142 | name = "py" 143 | version = "1.10.0" 144 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 145 | category = "dev" 146 | optional = false 147 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 148 | 149 | [[package]] 150 | name = "pyparsing" 151 | version = "2.4.7" 152 | description = "Python parsing module" 153 | category = "dev" 154 | optional = false 155 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 156 | 157 | [[package]] 158 | name = "pytest" 159 | version = "6.2.4" 160 | description = "pytest: simple powerful testing with Python" 161 | category = "dev" 162 | optional = false 163 | python-versions = ">=3.6" 164 | 165 | [package.dependencies] 166 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 167 | attrs = ">=19.2.0" 168 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 169 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 170 | iniconfig = "*" 171 | packaging = "*" 172 | pluggy = ">=0.12,<1.0.0a1" 173 | py = ">=1.8.2" 174 | toml = "*" 175 | 176 | [package.extras] 177 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 178 | 179 | [[package]] 180 | name = "regex" 181 | version = "2021.7.1" 182 | description = "Alternative regular expression module, to replace re." 183 | category = "dev" 184 | optional = false 185 | python-versions = "*" 186 | 187 | [[package]] 188 | name = "toml" 189 | version = "0.10.2" 190 | description = "Python Library for Tom's Obvious, Minimal Language" 191 | category = "dev" 192 | optional = false 193 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 194 | 195 | [[package]] 196 | name = "typed-ast" 197 | version = "1.4.3" 198 | description = "a fork of Python 2 and 3 ast modules with type comment support" 199 | category = "dev" 200 | optional = false 201 | python-versions = "*" 202 | 203 | [[package]] 204 | name = "typing-extensions" 205 | version = "3.10.0.0" 206 | description = "Backported and Experimental Type Hints for Python 3.5+" 207 | category = "dev" 208 | optional = false 209 | python-versions = "*" 210 | 211 | [[package]] 212 | name = "zipp" 213 | version = "3.5.0" 214 | description = "Backport of pathlib-compatible object wrapper for zip files" 215 | category = "dev" 216 | optional = false 217 | python-versions = ">=3.6" 218 | 219 | [package.extras] 220 | docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] 221 | 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"] 222 | 223 | [metadata] 224 | lock-version = "1.1" 225 | python-versions = "^3.7" 226 | content-hash = "db95c83fbfb5628534161185f94874dd07a84ab8db130198902859790231d6f5" 227 | 228 | [metadata.files] 229 | appdirs = [ 230 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 231 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 232 | ] 233 | atomicwrites = [ 234 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 235 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 236 | ] 237 | attrs = [ 238 | {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, 239 | {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, 240 | ] 241 | black = [ 242 | {file = "black-21.6b0-py3-none-any.whl", hash = "sha256:dfb8c5a069012b2ab1e972e7b908f5fb42b6bbabcba0a788b86dc05067c7d9c7"}, 243 | {file = "black-21.6b0.tar.gz", hash = "sha256:dc132348a88d103016726fe360cb9ede02cecf99b76e3660ce6c596be132ce04"}, 244 | ] 245 | click = [ 246 | {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, 247 | {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, 248 | ] 249 | colorama = [ 250 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 251 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 252 | ] 253 | importlib-metadata = [ 254 | {file = "importlib_metadata-4.6.0-py3-none-any.whl", hash = "sha256:c6513572926a96458f8c8f725bf0e00108fba0c9583ade9bd15b869c9d726e33"}, 255 | {file = "importlib_metadata-4.6.0.tar.gz", hash = "sha256:4a5611fea3768d3d967c447ab4e93f567d95db92225b43b7b238dbfb855d70bb"}, 256 | ] 257 | iniconfig = [ 258 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 259 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 260 | ] 261 | mypy-extensions = [ 262 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 263 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 264 | ] 265 | packaging = [ 266 | {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, 267 | {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, 268 | ] 269 | pathspec = [ 270 | {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, 271 | {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, 272 | ] 273 | pluggy = [ 274 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 275 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 276 | ] 277 | py = [ 278 | {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, 279 | {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, 280 | ] 281 | pyparsing = [ 282 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 283 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 284 | ] 285 | pytest = [ 286 | {file = "pytest-6.2.4-py3-none-any.whl", hash = "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890"}, 287 | {file = "pytest-6.2.4.tar.gz", hash = "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b"}, 288 | ] 289 | regex = [ 290 | {file = "regex-2021.7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:494d0172774dc0beeea984b94c95389143db029575f7ca908edd74469321ea99"}, 291 | {file = "regex-2021.7.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:8cf6728f89b071bd3ab37cb8a0e306f4de897553a0ed07442015ee65fbf53d62"}, 292 | {file = "regex-2021.7.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1806370b2bef4d4193eebe8ee59a9fd7547836a34917b7badbe6561a8594d9cb"}, 293 | {file = "regex-2021.7.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d0cf2651a8804f6325747c7e55e3be0f90ee2848e25d6b817aa2728d263f9abb"}, 294 | {file = "regex-2021.7.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:268fe9dd1deb4a30c8593cabd63f7a241dfdc5bd9dd0233906c718db22cdd49a"}, 295 | {file = "regex-2021.7.1-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:7743798dfb573d006f1143d745bf17efad39775a5190b347da5d83079646be56"}, 296 | {file = "regex-2021.7.1-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:0e46c1191b2eb293a6912269ed08b4512e7e241bbf591f97e527492e04c77e93"}, 297 | {file = "regex-2021.7.1-cp36-cp36m-win32.whl", hash = "sha256:b1dbeef938281f240347d50f28ae53c4b046a23389cd1fc4acec5ea0eae646a1"}, 298 | {file = "regex-2021.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6c72ebb72e64e9bd195cb35a9b9bbfb955fd953b295255b8ae3e4ad4a146b615"}, 299 | {file = "regex-2021.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf819c5b77ff44accc9a24e31f1f7ceaaf6c960816913ed3ef8443b9d20d81b6"}, 300 | {file = "regex-2021.7.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e80d2851109e56420b71f9702ad1646e2f0364528adbf6af85527bc61e49f394"}, 301 | {file = "regex-2021.7.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a1b6a3f600d6aff97e3f28c34192c9ed93fee293bd96ef327b64adb51a74b2f6"}, 302 | {file = "regex-2021.7.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:ed77b97896312bc2deafe137ca2626e8b63808f5bedb944f73665c68093688a7"}, 303 | {file = "regex-2021.7.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a548bb51c4476332ce4139df8e637386730f79a92652a907d12c696b6252b64d"}, 304 | {file = "regex-2021.7.1-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:210c359e6ee5b83f7d8c529ba3c75ba405481d50f35a420609b0db827e2e3bb5"}, 305 | {file = "regex-2021.7.1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:1d386402ae7f3c9b107ae5863f7ecccb0167762c82a687ae6526b040feaa5ac6"}, 306 | {file = "regex-2021.7.1-cp37-cp37m-win32.whl", hash = "sha256:5049d00dbb78f9d166d1c704e93934d42cce0570842bb1a61695123d6b01de09"}, 307 | {file = "regex-2021.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:361be4d311ac995a8c7ad577025a3ae3a538531b1f2cf32efd8b7e5d33a13e5a"}, 308 | {file = "regex-2021.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f32f47fb22c988c0b35756024b61d156e5c4011cb8004aa53d93b03323c45657"}, 309 | {file = "regex-2021.7.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b024ee43ee6b310fad5acaee23e6485b21468718cb792a9d1693eecacc3f0b7e"}, 310 | {file = "regex-2021.7.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b092754c06852e8a8b022004aff56c24b06310189186805800d09313c37ce1f8"}, 311 | {file = "regex-2021.7.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:a8a5826d8a1b64e2ff9af488cc179e1a4d0f144d11ce486a9f34ea38ccedf4ef"}, 312 | {file = "regex-2021.7.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:444723ebaeb7fa8125f29c01a31101a3854ac3de293e317944022ae5effa53a4"}, 313 | {file = "regex-2021.7.1-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:fdad3122b69cdabdb3da4c2a4107875913ac78dab0117fc73f988ad589c66b66"}, 314 | {file = "regex-2021.7.1-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4b1999ef60c45357598935c12508abf56edbbb9c380df6f336de38a6c3a294ae"}, 315 | {file = "regex-2021.7.1-cp38-cp38-win32.whl", hash = "sha256:e07e92935040c67f49571779d115ecb3e727016d42fb36ee0d8757db4ca12ee0"}, 316 | {file = "regex-2021.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:6b8b629f93246e507287ee07e26744beaffb4c56ed520576deac8b615bd76012"}, 317 | {file = "regex-2021.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:56bef6b414949e2c9acf96cb5d78de8b529c7b99752619494e78dc76f99fd005"}, 318 | {file = "regex-2021.7.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:78a2a885345a2d60b5e68099e877757d5ed12e46ba1e87507175f14f80892af3"}, 319 | {file = "regex-2021.7.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3f7a92e60930f8fca2623d9e326c173b7cf2c8b7e4fdcf984b75a1d2fb08114d"}, 320 | {file = "regex-2021.7.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4fc86b729ab88fe8ac3ec92287df253c64aa71560d76da5acd8a2e245839c629"}, 321 | {file = "regex-2021.7.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:59845101de68fd5d3a1145df9ea022e85ecd1b49300ea68307ad4302320f6f61"}, 322 | {file = "regex-2021.7.1-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:ce269e903b00d1ab4746793e9c50a57eec5d5388681abef074d7b9a65748fca5"}, 323 | {file = "regex-2021.7.1-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:c11f2fca544b5e30a0e813023196a63b1cb9869106ef9a26e9dae28bce3e4e26"}, 324 | {file = "regex-2021.7.1-cp39-cp39-win32.whl", hash = "sha256:1ccbd41dbee3a31e18938096510b7d4ee53aa9fce2ee3dcc8ec82ae264f6acfd"}, 325 | {file = "regex-2021.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:18040755606b0c21281493ec309214bd61e41a170509e5014f41d6a5a586e161"}, 326 | {file = "regex-2021.7.1.tar.gz", hash = "sha256:849802379a660206277675aa5a5c327f5c910c690649535863ddf329b0ba8c87"}, 327 | ] 328 | toml = [ 329 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 330 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 331 | ] 332 | typed-ast = [ 333 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, 334 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, 335 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, 336 | {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, 337 | {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, 338 | {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, 339 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, 340 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, 341 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, 342 | {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, 343 | {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, 344 | {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, 345 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, 346 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, 347 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, 348 | {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, 349 | {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, 350 | {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, 351 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, 352 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, 353 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, 354 | {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, 355 | {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, 356 | {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, 357 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, 358 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, 359 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, 360 | {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, 361 | {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, 362 | {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, 363 | ] 364 | typing-extensions = [ 365 | {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, 366 | {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, 367 | {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, 368 | ] 369 | zipp = [ 370 | {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"}, 371 | {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"}, 372 | ] 373 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "classopt" 3 | version = "0.2.1" 4 | description = "Arguments parser with class for Python, inspired by StructOpt" 5 | authors = ["moisutsu "] 6 | readme = "README.md" 7 | repository = "https://github.com/moisutsu/classopt" 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.7" 11 | 12 | [tool.poetry.dev-dependencies] 13 | black = "^21.6b0" 14 | pytest = "^6.2.4" 15 | 16 | [build-system] 17 | requires = ["poetry-core>=1.0.0"] 18 | build-backend = "poetry.core.masonry.api" 19 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moisutsu/classopt/6d0781164696bc021b856768a56eaef22a2c0cd5/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import pytest 4 | 5 | 6 | @pytest.fixture(scope="function", autouse=True) 7 | def cleanup_args(): 8 | # Cleanup args before each test 9 | # otherwise tests fail with e.g. "pytest -s" because sys.argv[1:] becomes ["-s"] 10 | # and pytest options will be parsed by `ArgumentParser` 11 | del_args() 12 | 13 | # Run a test... 14 | yield 15 | 16 | # Cleanup args after each test 17 | del_args() 18 | 19 | 20 | def set_args(*args): 21 | del_args() 22 | for arg in args: 23 | sys.argv.append(arg) 24 | 25 | 26 | def del_args(): 27 | del sys.argv[1:] 28 | -------------------------------------------------------------------------------- /tests/test_decorator.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import unittest 3 | from typing import List 4 | 5 | import pytest 6 | 7 | from classopt import classopt, config 8 | 9 | from .conftest import del_args, set_args 10 | 11 | 12 | class TestClassOpt(unittest.TestCase): 13 | def test_classopt(self): 14 | @classopt 15 | class Opt: 16 | arg_int: int 17 | arg_str: str 18 | arg_float: float 19 | 20 | set_args("5", "hello", "3.2") 21 | 22 | opt = Opt.from_args() 23 | 24 | assert opt.arg_int == 5 25 | assert opt.arg_str == "hello" 26 | assert opt.arg_float == 3.2 27 | 28 | def test_advanced_usage(self): 29 | @classopt() 30 | class Opt: 31 | long_arg: str = config(long=True) 32 | short_arg1: str = config(long=True, short=True) 33 | short_arg2: str = config(long=True, short="-x") 34 | default_int: int = config(long=True, default=3) 35 | store_true: bool = config(long=True, action="store_true") 36 | nargs: List[int] = config(long=True, nargs="+", type=int) 37 | 38 | set_args( 39 | "--long_arg", 40 | "long_arg", 41 | "-s", 42 | "short_arg1", 43 | "-x", 44 | "short_arg2", 45 | "--store_true", 46 | "--nargs", 47 | "1", 48 | "2", 49 | "3", 50 | ) 51 | 52 | opt = Opt.from_args() 53 | 54 | assert opt.long_arg == "long_arg" 55 | assert opt.short_arg1 == "short_arg1" 56 | assert opt.short_arg2 == "short_arg2" 57 | assert opt.default_int == 3 58 | assert opt.store_true 59 | assert opt.nargs == [1, 2, 3] 60 | 61 | def test_default_long(self): 62 | @classopt(default_long=True) 63 | class Opt: 64 | arg0: str = config(long=False) 65 | arg1: int 66 | arg2: str 67 | 68 | set_args("hogehoge", "--arg1", "3", "--arg2", "hello") 69 | 70 | opt = Opt.from_args() 71 | 72 | assert opt.arg0 == "hogehoge" 73 | assert opt.arg1 == 3 74 | assert opt.arg2 == "hello" 75 | 76 | def test_default_short(self): 77 | @classopt(default_long=True, default_short=True) 78 | class Opt: 79 | a_arg: int 80 | b_arg: str 81 | 82 | set_args("-a", "3", "-b", "hello") 83 | 84 | opt = Opt.from_args() 85 | 86 | assert opt.a_arg == 3 87 | assert opt.b_arg == "hello" 88 | 89 | def test_generic_alias(self): 90 | @classopt(default_long=True) 91 | class Opt: 92 | list_a: List[int] = config(nargs="+") 93 | list_b: List[str] = config(nargs="*") 94 | 95 | set_args("--list_a", "3", "2", "1", "--list_b", "hello", "world") 96 | 97 | opt = Opt.from_args() 98 | 99 | assert opt.list_a == [3, 2, 1] 100 | assert opt.list_b == ["hello", "world"] 101 | 102 | @pytest.mark.skipif( 103 | sys.version_info < (3, 9), 104 | reason="These version does not support `list` type with subscription.", 105 | ) 106 | def test_generic_alias_for_python3_9_or_later(self): 107 | @classopt(default_long=True) 108 | class Opt: 109 | list_a: list[int] = config(nargs="+") 110 | list_b: list[str] = config(nargs="*") 111 | 112 | set_args("--list_a", "3", "2", "1", "--list_b", "hello", "world") 113 | 114 | opt = Opt.from_args() 115 | 116 | assert opt.list_a == [3, 2, 1] 117 | assert opt.list_b == ["hello", "world"] 118 | 119 | def test_default_value(self): 120 | @classopt(default_long=True) 121 | class Opt: 122 | numbers: List[int] 123 | flag: bool 124 | 125 | set_args("--numbers", "1", "2", "3", "--flag") 126 | 127 | opt = Opt.from_args() 128 | 129 | assert opt.numbers == [1, 2, 3] 130 | assert opt.flag 131 | 132 | def test_external_parser(self): 133 | from argparse import ArgumentParser 134 | 135 | class userArgumentParserException(Exception): 136 | pass 137 | 138 | class userArgumentParser(ArgumentParser): 139 | def error(self, message): 140 | raise userArgumentParserException() 141 | 142 | @classopt(parser=userArgumentParser()) 143 | class Opt: 144 | arg_int: int 145 | arg_str: str 146 | arg_float: float 147 | 148 | set_args("5", "hello", "3.2") 149 | 150 | opt = Opt.from_args() 151 | 152 | assert opt.arg_int == 5 153 | assert opt.arg_str == "hello" 154 | assert opt.arg_float == 3.2 155 | 156 | set_args("5", "hello") 157 | 158 | with self.assertRaises(userArgumentParserException): 159 | opt = Opt.from_args() 160 | 161 | def test_simple_default_value_passing(self): 162 | @classopt(default_long=True) 163 | class Opt: 164 | arg0: int = 3 165 | arg1: list = ["hello", "world"] 166 | arg2: int 167 | arg3: int = config(default=5) 168 | arg4: list = config(default=[1, 2, 3]) 169 | arg5: str 170 | 171 | set_args("--arg5", "hello") 172 | 173 | opt = Opt.from_args() 174 | 175 | assert opt.arg0 == 3 176 | assert opt.arg1 == ["hello", "world"] 177 | assert opt.arg2 == None 178 | assert opt.arg3 == 5 179 | assert opt.arg4 == [1, 2, 3] 180 | assert opt.arg5 == "hello" 181 | 182 | def test_convert_default_value_type_to_specified_type(self): 183 | from pathlib import Path 184 | 185 | @classopt(default_long=True) 186 | class Opt: 187 | arg0: Path = "test.py" 188 | 189 | set_args() 190 | 191 | opt = Opt.from_args() 192 | 193 | assert opt.arg0 == Path("test.py") 194 | 195 | def test_args_from_scipt(self): 196 | @classopt 197 | class Opt: 198 | arg_int: int 199 | arg_str: str 200 | arg_float: float 201 | 202 | set_args("5", "hello", "3.2") 203 | 204 | opt1 = Opt.from_args() 205 | 206 | del_args() 207 | 208 | opt2 = Opt.from_args(["5", "hello", "3.2"]) 209 | 210 | assert opt1.arg_int == opt2.arg_int 211 | assert opt1.arg_str == opt2.arg_str 212 | assert opt1.arg_float == opt2.arg_float 213 | 214 | def test_to_dict(self): 215 | from pathlib import Path 216 | from typing import List 217 | 218 | @classopt 219 | class Opt: 220 | arg_int: int 221 | arg_float: float 222 | arg_path: Path 223 | arg_list: List[str] 224 | 225 | set_args("3", "3.2", "test.txt", "a", "b", "c") 226 | 227 | opt = Opt.from_args() 228 | 229 | opt_dict = opt.to_dict() 230 | correct_dict = { 231 | "arg_int": 3, 232 | "arg_float": 3.2, 233 | "arg_path": "test.txt", 234 | "arg_list": ["a", "b", "c"], 235 | } 236 | 237 | assert all( 238 | opt_dict[key] == correct_dict[key] 239 | for key in set(list(opt_dict.keys()) + list(correct_dict.keys())) 240 | ) 241 | 242 | def test_from_dict(self): 243 | from pathlib import Path 244 | from typing import List 245 | 246 | @classopt 247 | class Opt: 248 | arg_int: int 249 | arg_float: float 250 | arg_path: Path 251 | arg_list: List[str] 252 | 253 | args_dict = { 254 | "arg_int": 3, 255 | "arg_float": 3.2, 256 | "arg_path": "test.txt", 257 | "arg_list": ["a", "b", "c"], 258 | } 259 | 260 | opt = Opt.from_dict(args_dict) 261 | 262 | assert opt.arg_int == args_dict["arg_int"] 263 | assert opt.arg_float == args_dict["arg_float"] 264 | assert opt.arg_path == Path(args_dict["arg_path"]) 265 | assert opt.arg_list == args_dict["arg_list"] 266 | 267 | def test_from_dict_partial(self): 268 | from pathlib import Path 269 | from typing import List, Set 270 | 271 | @classopt 272 | class Opt: 273 | arg_int: int 274 | arg_float: float = 0.0 275 | arg_str: str = "test" 276 | arg_path: Path = "test.txt" 277 | arg_list: List[str] = ["a", "b"] 278 | # test for implicit type conversion 279 | arg_set: Set[str] = ["a", "b"] 280 | 281 | args_dict = { 282 | "arg_int": 3, 283 | "arg_float": 3.2, 284 | } 285 | 286 | opt = Opt.from_dict(args_dict) 287 | 288 | assert opt.arg_int == args_dict["arg_int"] 289 | assert opt.arg_float == args_dict["arg_float"] 290 | assert opt.arg_str == "test" 291 | assert opt.arg_path == Path("test.txt") 292 | assert opt.arg_list == ["a", "b"] 293 | assert opt.arg_set == {"a", "b"} 294 | -------------------------------------------------------------------------------- /tests/test_inheritance.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from typing import List 3 | 4 | from classopt import ClassOpt, config 5 | 6 | from .conftest import del_args, set_args 7 | 8 | 9 | class TestClassOpt(unittest.TestCase): 10 | def test_classopt(self): 11 | class Opt(ClassOpt): 12 | arg_int: int 13 | arg_str: str 14 | arg_float: float 15 | 16 | set_args("5", "hello", "3.2") 17 | 18 | opt = Opt.from_args() 19 | 20 | assert opt.arg_int == 5 21 | assert opt.arg_str == "hello" 22 | assert opt.arg_float == 3.2 23 | 24 | def test_advanced_usage(self): 25 | class Opt(ClassOpt): 26 | long_arg: str = config(long=True) 27 | short_arg1: str = config(long=True, short=True) 28 | short_arg2: str = config(long=True, short="-x") 29 | default_int: int = config(long=True, default=3) 30 | store_true: bool = config(long=True, action="store_true") 31 | nargs: List[int] = config(long=True, nargs="+", type=int) 32 | 33 | set_args( 34 | "--long_arg", 35 | "long_arg", 36 | "-s", 37 | "short_arg1", 38 | "-x", 39 | "short_arg2", 40 | "--store_true", 41 | "--nargs", 42 | "1", 43 | "2", 44 | "3", 45 | ) 46 | 47 | opt = Opt.from_args() 48 | 49 | assert opt.long_arg == "long_arg" 50 | assert opt.short_arg1 == "short_arg1" 51 | assert opt.short_arg2 == "short_arg2" 52 | assert opt.default_int == 3 53 | assert opt.store_true 54 | assert opt.nargs == [1, 2, 3] 55 | 56 | def test_generic_alias(self): 57 | class Opt(ClassOpt): 58 | list_a: List[int] = config(long=True, nargs="+") 59 | list_b: List[str] = config(long=True, nargs="*") 60 | 61 | set_args("--list_a", "3", "2", "1", "--list_b", "hello", "world") 62 | 63 | opt = Opt.from_args() 64 | 65 | assert opt.list_a == [3, 2, 1] 66 | assert opt.list_b == ["hello", "world"] 67 | 68 | def test_default_value(self): 69 | class Opt(ClassOpt): 70 | numbers: List[int] = config(long=True) 71 | flag: bool = config(long=True) 72 | 73 | set_args("--numbers", "1", "2", "3", "--flag") 74 | 75 | opt = Opt.from_args() 76 | 77 | assert opt.numbers == [1, 2, 3] 78 | assert opt.flag 79 | 80 | def test_external_parser(self): 81 | from argparse import ArgumentParser 82 | 83 | class userArgumentParserException(Exception): 84 | pass 85 | 86 | class userArgumentParser(ArgumentParser): 87 | def error(self, message): 88 | raise userArgumentParserException() 89 | 90 | class Opt(ClassOpt): 91 | arg_int: int 92 | arg_str: str 93 | arg_float: float 94 | 95 | @classmethod 96 | def _parser_factory(cls) -> ArgumentParser: 97 | return userArgumentParser() 98 | 99 | set_args("5", "hello", "3.2") 100 | 101 | opt = Opt.from_args() 102 | 103 | assert opt.arg_int == 5 104 | assert opt.arg_str == "hello" 105 | assert opt.arg_float == 3.2 106 | 107 | set_args("5", "hello") 108 | 109 | with self.assertRaises(userArgumentParserException): 110 | opt = Opt.from_args() 111 | 112 | def test_args_from_script(self): 113 | class Opt(ClassOpt): 114 | arg_int: int 115 | arg_str: str 116 | arg_float: float 117 | 118 | set_args("5", "hello", "3.2") 119 | 120 | opt1 = Opt.from_args() 121 | 122 | del_args() 123 | 124 | opt2 = Opt.from_args(["5", "hello", "3.2"]) 125 | 126 | assert opt1.arg_int == opt2.arg_int 127 | assert opt1.arg_str == opt2.arg_str 128 | assert opt1.arg_float == opt2.arg_float 129 | --------------------------------------------------------------------------------