# typedparser
<p align="center">
<a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py38.yml">
<img alt="build 3.8 status" title="build 3.8 status" src="https://img.shields.io/github/actions/workflow/status/simon-ging/typedparser/build-py38.yml?branch=main&label=python%203.8" />
</a>
<a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py39.yml">
<img alt="build 3.9 status" title="build 3.9 status" src="https://img.shields.io/github/actions/workflow/status/simon-ging/typedparser/build-py39.yml?branch=main&label=python%203.9" />
</a>
<a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py310.yml">
<img alt="build 3.10 status" title="build 3.10 status" src="https://img.shields.io/github/actions/workflow/status/simon-ging/typedparser/build-py310.yml?branch=main&label=python%203.10" />
</a>
<a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py311.yml">
<img alt="build 3.11 status" title="build 3.11 status" src="https://img.shields.io/github/actions/workflow/status/simon-ging/typedparser/build-py311.yml?branch=main&label=python%203.11" />
</a>
<a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py312.yml">
<img alt="build 3.12 status" title="build 3.12 status" src="https://img.shields.io/github/actions/workflow/status/simon-ging/typedparser/build-py312.yml?branch=main&label=python%203.12" />
</a>
<a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py312-full.yml">
<img alt="build 3.12 full status" title="build 3.12 full status" src="https://img.shields.io/github/actions/workflow/status/simon-ging/typedparser/build-py312-full.yml?branch=main&label=python%203.12%20full" />
</a>
<img alt="coverage" title="coverage" src="https://raw.githubusercontent.com/simon-ging/typedparser/main/docs/coverage.svg" />
<a href="https://pypi.org/project/typedparser/">
<img alt="version" title="version" src="https://img.shields.io/pypi/v/typedparser?color=success" />
</a>
</p>
Typing extension for python argparse using [attrs](https://www.attrs.org/en/stable/).
Includes typechecking and conversion utilities to parse a dictionary into an attrs instance.
## Install
Requires `python>=3.8`
```bash
pip install typedparser
```
## Basic usage
1. Create an attrs class (decorate with `@attr.define`). Note that optional arguments must also be typed as optional.
2. Define and type the fields with `typedparser.add_argument` - the syntax extends [add_argument from argparse](https://docs.python.org/3/library/argparse.html#the-add-argument-method).
3. Parse the args with `TypedParser` and enjoy args with type hints. Disable typechecking by setting `strict=False`.
```python
from typing import Optional
from attrs import define
from typedparser import add_argument, TypedParser
@define
class Args:
# omit the argument name to have it inferred from the field name
foo: str = add_argument(positional=True)
bar: int = add_argument(shortcut="-b", type=int, default=0)
opt: Optional[str] = add_argument()
# # in case you prefer the regular argparse syntax:
# foo: str = add_argument("foo")
# bar: int = add_argument("-b", "--bar", type=int, default=0)
# opt: Optional[str] = add_argument("--opt")
def main():
parser = TypedParser.create_parser(Args, strict=True)
args: Args = parser.parse_args()
print(args)
if __name__ == "__main__":
main()
```
## Features
* Create commandline arguments with type hints and checks while
staying close to the syntax of the standard library's argparse.
* Utilities for typechecking and converting nested objects:
* Nested checking and conversion of python standard types
* Supports old and new style typing (e.g. `typing.List` and `list`)
* Supports positional and keyword arguments in classes
* Can also typecheck existing attrs instances
* Allows custom conversions, by default converts source type `str` to target type `Path` and
`int` to `float`
* Allows to redefine which objects will be recursed into, by default recurses into standard
containers (list, dict, etc.)
* `@definenumpy` decorator for equality check if the instances contains numpy arrays
* Some object utilities in `typedparser.objects` required for everything else
## Advanced usage
* Use `TypedParser.from_parser(parser, Args)` to add typing to an existing parser. This is useful
to cover usecases like subparsers or argument groups.
* Snippet for argument lists `xarg: List[int] = add_argument(shortcut="-x", type=int, action="append", help="Xarg", default=[])`,
use as `-x 1 -x 2` to get `[1, 2]` in the args instance.
### Usage of attr utilities
Define the class hierarchy and parse the input using `attrs_from_dict`.
Use `@define(slots=False)` to allow multiple inheritance and setting attributes later.
```python
from attrs import define
from typing import Optional
from typedparser import attrs_from_dict
@define
class Cfg:
foo: int = 12
bar: Optional[int] = None
print(attrs_from_dict(Cfg, {"foo": 1, "bar": 2}))
# Cfg(foo=1, bar=2)
@define
class CfgNested:
sub_cfg: Cfg = None
print(attrs_from_dict(CfgNested, {"sub_cfg": {"foo": 1, "bar": 2}}))
# CfgNested(sub_cfg=Cfg(foo=1, bar=2))
```
### Strict mode (default)
* Convert everything to the target type, e.g. if the input is a list and the annotation is a tuple,
the output will be a tuple
* Raise errors if types cannot be matched, there are unknown fields in the input or
abstract annotation types are used (e.g. Sequence)
* Set `_allow_extra_keys = True` in the class definition to allow unknown fields in the input
### Non-strict mode
Enabled by calling `attrs_from_dict` with `strict=False`
* No conversion except for creating the attrs instance from the dict
* Ignore silently if types cannot be matched or abstract annotation types are used
* Unknown fields in the input will be added to the attrs instance if possible
(see the hint below about slots)
### Skip unknowns
Set `skip_unknowns=True` to ignore all unknown input fields.
### Hints
The following behaviour stems from the `attrs` package:
* New attributes cannot to be added after class definition to an attrs instance,
unless it is created with `@define(slots=False)`
[Explanation](https://www.attrs.org/en/21.2.0/glossary.html#term-slotted-classes)
* Untyped fields or "ClassVar" typed fields will be ignored by @attrs.define
and therefore also by this library.
## Install locally and run tests
Clone repository and cd into. Setup python 3.7 or higher.
Note: Some tests are skipped for python 3.7.
```bash
pip install -e .
pip install pytest pytest-cov pylint
pylint typedparser
# run tests
python -m pytest --cov
pylint tests
```
Raw data
{
"_id": null,
"home_page": null,
"name": "typedparser",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "attrs, typing, dict, attr",
"author": "simon-ging",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/cd/19/ba5d90ec5e0aa2060937db6f2693c77975d7c2e3fc8b677fda95cf8ebd88/typedparser-0.22.2.tar.gz",
"platform": "any",
"description": "# typedparser\n\n<p align=\"center\">\n<a href=\"https://github.com/simon-ging/typedparser/actions/workflows/build-py38.yml\">\n <img alt=\"build 3.8 status\" title=\"build 3.8 status\" src=\"https://img.shields.io/github/actions/workflow/status/simon-ging/typedparser/build-py38.yml?branch=main&label=python%203.8\" />\n</a>\n<a href=\"https://github.com/simon-ging/typedparser/actions/workflows/build-py39.yml\">\n <img alt=\"build 3.9 status\" title=\"build 3.9 status\" src=\"https://img.shields.io/github/actions/workflow/status/simon-ging/typedparser/build-py39.yml?branch=main&label=python%203.9\" />\n</a>\n<a href=\"https://github.com/simon-ging/typedparser/actions/workflows/build-py310.yml\">\n <img alt=\"build 3.10 status\" title=\"build 3.10 status\" src=\"https://img.shields.io/github/actions/workflow/status/simon-ging/typedparser/build-py310.yml?branch=main&label=python%203.10\" />\n</a>\n<a href=\"https://github.com/simon-ging/typedparser/actions/workflows/build-py311.yml\">\n <img alt=\"build 3.11 status\" title=\"build 3.11 status\" src=\"https://img.shields.io/github/actions/workflow/status/simon-ging/typedparser/build-py311.yml?branch=main&label=python%203.11\" />\n</a>\n<a href=\"https://github.com/simon-ging/typedparser/actions/workflows/build-py312.yml\">\n <img alt=\"build 3.12 status\" title=\"build 3.12 status\" src=\"https://img.shields.io/github/actions/workflow/status/simon-ging/typedparser/build-py312.yml?branch=main&label=python%203.12\" />\n</a>\n<a href=\"https://github.com/simon-ging/typedparser/actions/workflows/build-py312-full.yml\">\n <img alt=\"build 3.12 full status\" title=\"build 3.12 full status\" src=\"https://img.shields.io/github/actions/workflow/status/simon-ging/typedparser/build-py312-full.yml?branch=main&label=python%203.12%20full\" />\n</a>\n<img alt=\"coverage\" title=\"coverage\" src=\"https://raw.githubusercontent.com/simon-ging/typedparser/main/docs/coverage.svg\" />\n<a href=\"https://pypi.org/project/typedparser/\">\n <img alt=\"version\" title=\"version\" src=\"https://img.shields.io/pypi/v/typedparser?color=success\" />\n</a>\n</p>\n\nTyping extension for python argparse using [attrs](https://www.attrs.org/en/stable/).\n\nIncludes typechecking and conversion utilities to parse a dictionary into an attrs instance. \n\n## Install\n\nRequires `python>=3.8`\n\n```bash\npip install typedparser\n```\n\n## Basic usage\n\n1. Create an attrs class (decorate with `@attr.define`). Note that optional arguments must also be typed as optional.\n2. Define and type the fields with `typedparser.add_argument` - the syntax extends [add_argument from argparse](https://docs.python.org/3/library/argparse.html#the-add-argument-method).\n3. Parse the args with `TypedParser` and enjoy args with type hints. Disable typechecking by setting `strict=False`.\n\n```python\nfrom typing import Optional\nfrom attrs import define\nfrom typedparser import add_argument, TypedParser\n\n\n@define\nclass Args: \n # omit the argument name to have it inferred from the field name\n foo: str = add_argument(positional=True)\n bar: int = add_argument(shortcut=\"-b\", type=int, default=0)\n opt: Optional[str] = add_argument()\n\n # # in case you prefer the regular argparse syntax:\n # foo: str = add_argument(\"foo\")\n # bar: int = add_argument(\"-b\", \"--bar\", type=int, default=0)\n # opt: Optional[str] = add_argument(\"--opt\")\n \n \n\ndef main():\n parser = TypedParser.create_parser(Args, strict=True)\n args: Args = parser.parse_args()\n print(args)\n\n\nif __name__ == \"__main__\":\n main()\n\n```\n\n## Features\n\n* Create commandline arguments with type hints and checks while\nstaying close to the syntax of the standard library's argparse.\n* Utilities for typechecking and converting nested objects:\n * Nested checking and conversion of python standard types\n * Supports old and new style typing (e.g. `typing.List` and `list`)\n * Supports positional and keyword arguments in classes\n * Can also typecheck existing attrs instances\n * Allows custom conversions, by default converts source type `str` to target type `Path` and\n `int` to `float`\n * Allows to redefine which objects will be recursed into, by default recurses into standard\n containers (list, dict, etc.)\n * `@definenumpy` decorator for equality check if the instances contains numpy arrays\n* Some object utilities in `typedparser.objects` required for everything else\n\n## Advanced usage\n\n* Use `TypedParser.from_parser(parser, Args)` to add typing to an existing parser. This is useful\nto cover usecases like subparsers or argument groups.\n* Snippet for argument lists `xarg: List[int] = add_argument(shortcut=\"-x\", type=int, action=\"append\", help=\"Xarg\", default=[])`,\nuse as `-x 1 -x 2` to get `[1, 2]` in the args instance.\n\n### Usage of attr utilities\n\nDefine the class hierarchy and parse the input using `attrs_from_dict`.\nUse `@define(slots=False)` to allow multiple inheritance and setting attributes later.\n\n```python\nfrom attrs import define\nfrom typing import Optional\nfrom typedparser import attrs_from_dict\n\n@define\nclass Cfg:\n foo: int = 12\n bar: Optional[int] = None\n\nprint(attrs_from_dict(Cfg, {\"foo\": 1, \"bar\": 2}))\n# Cfg(foo=1, bar=2)\n\n\n@define\nclass CfgNested:\n sub_cfg: Cfg = None\n\nprint(attrs_from_dict(CfgNested, {\"sub_cfg\": {\"foo\": 1, \"bar\": 2}}))\n# CfgNested(sub_cfg=Cfg(foo=1, bar=2))\n```\n\n### Strict mode (default)\n\n* Convert everything to the target type, e.g. if the input is a list and the annotation is a tuple,\n the output will be a tuple\n* Raise errors if types cannot be matched, there are unknown fields in the input or\n abstract annotation types are used (e.g. Sequence)\n* Set `_allow_extra_keys = True` in the class definition to allow unknown fields in the input\n\n### Non-strict mode\n\nEnabled by calling `attrs_from_dict` with `strict=False`\n\n* No conversion except for creating the attrs instance from the dict\n* Ignore silently if types cannot be matched or abstract annotation types are used\n* Unknown fields in the input will be added to the attrs instance if possible\n (see the hint below about slots)\n\n### Skip unknowns\n\nSet `skip_unknowns=True` to ignore all unknown input fields.\n\n### Hints\n\nThe following behaviour stems from the `attrs` package:\n\n* New attributes cannot to be added after class definition to an attrs instance,\n unless it is created with `@define(slots=False)`\n [Explanation](https://www.attrs.org/en/21.2.0/glossary.html#term-slotted-classes)\n* Untyped fields or \"ClassVar\" typed fields will be ignored by @attrs.define\n and therefore also by this library.\n\n## Install locally and run tests\n\nClone repository and cd into. Setup python 3.7 or higher. \nNote: Some tests are skipped for python 3.7.\n\n```bash\npip install -e .\npip install pytest pytest-cov pylint\npylint typedparser\n\n# run tests\npython -m pytest --cov\npylint tests\n```\n",
"bugtrack_url": null,
"license": "Apache-2.0",
"summary": "Extension for python argparse with typehints and typechecks.",
"version": "0.22.2",
"project_urls": {
"Project-URL": "https://github.com/simon-ging/typedparser"
},
"split_keywords": [
"attrs",
" typing",
" dict",
" attr"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "eaad6ad3525ecf75652e4850b2b6b1782c0db814fef8c57a9f92cb312cefa37d",
"md5": "a4d0e4a80d96753283e625a0b7ad58a7",
"sha256": "60aabf821e070c45f1ed647c4ce90bec5a8e4edd0f391041e7c873eebfa59c3e"
},
"downloads": -1,
"filename": "typedparser-0.22.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "a4d0e4a80d96753283e625a0b7ad58a7",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 21045,
"upload_time": "2024-12-18T17:41:32",
"upload_time_iso_8601": "2024-12-18T17:41:32.899208Z",
"url": "https://files.pythonhosted.org/packages/ea/ad/6ad3525ecf75652e4850b2b6b1782c0db814fef8c57a9f92cb312cefa37d/typedparser-0.22.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "cd19ba5d90ec5e0aa2060937db6f2693c77975d7c2e3fc8b677fda95cf8ebd88",
"md5": "17a228d8ffa9014bb904be9ab40e6dca",
"sha256": "07517de48d402aac5c4cd403469f7bcd823c42d3ad2168452510c3a052409e41"
},
"downloads": -1,
"filename": "typedparser-0.22.2.tar.gz",
"has_sig": false,
"md5_digest": "17a228d8ffa9014bb904be9ab40e6dca",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 21968,
"upload_time": "2024-12-18T17:41:35",
"upload_time_iso_8601": "2024-12-18T17:41:35.365271Z",
"url": "https://files.pythonhosted.org/packages/cd/19/ba5d90ec5e0aa2060937db6f2693c77975d7c2e3fc8b677fda95cf8ebd88/typedparser-0.22.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-12-18 17:41:35",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "simon-ging",
"github_project": "typedparser",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [
{
"name": "attrs",
"specs": []
}
],
"lcname": "typedparser"
}