typedparser 0.6.4__tar.gz → 0.20.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -186,7 +186,7 @@
186
186
  same "printed page" as the copyright notice for easier
187
187
  identification within third-party archives.
188
188
 
189
- Copyright 2023 gingsi
189
+ Copyright 2023 simon-ging
190
190
 
191
191
  Licensed under the Apache License, Version 2.0 (the "License");
192
192
  you may not use this file except in compliance with the License.
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: typedparser
3
- Version: 0.6.4
3
+ Version: 0.20.3
4
4
  Summary: Extension for python argparse with typehints and typechecks.
5
- Author: gingsi
5
+ Author: simon-ging
6
6
  License: Apache-2.0
7
- Project-URL: Project-URL, https://github.com/gingsi/typedparser
7
+ Project-URL: Project-URL, https://github.com/simon-ging/typedparser
8
8
  Keywords: attrs,typing,dict,attr
9
9
  Platform: any
10
10
  Classifier: Development Status :: 3 - Alpha
@@ -14,24 +14,48 @@ Classifier: Natural Language :: English
14
14
  Classifier: Programming Language :: Python :: 3.7
15
15
  Classifier: Programming Language :: Python :: 3.8
16
16
  Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
17
20
  Classifier: Operating System :: OS Independent
18
21
  Classifier: Topic :: Software Development :: Version Control :: Git
19
22
  Requires-Python: >=3.7
20
23
  Description-Content-Type: text/markdown
21
24
  License-File: LICENSE
22
25
  Requires-Dist: attrs
23
- Requires-Dist: numpy
26
+ Requires-Dist: importlib_metadata; python_version < "3.8"
27
+ Requires-Dist: typing_extensions; python_version < "3.8"
28
+ Provides-Extra: full
29
+ Requires-Dist: numpy; extra == "full"
24
30
 
25
31
  # typedparser
26
32
 
27
33
  <p align="center">
28
- <a href="https://github.com/gingsi/typedparser/actions/workflows/build-py37.yml">
29
- <img alt="build 3.7 status" title="build 3.7 status" src="https://img.shields.io/github/actions/workflow/status/gingsi/typedparser/build-py37.yml?branch=main&label=build%203.7" />
34
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py37.yml">
35
+ <img alt="build 3.7 status" title="build 3.7 status" src="https://img.shields.io/github/actions/workflow/status/simon-ging/typedparser/build-py37.yml?branch=main&label=python%203.7" />
30
36
  </a>
31
- <a href="https://github.com/gingsi/typedparser/actions/workflows/build-py310.yml">
32
- <img alt="build 3.10 status" title="build 3.10 status" src="https://img.shields.io/github/actions/workflow/status/gingsi/typedparser/build-py310.yml?branch=main&label=build%203.10" />
37
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py38.yml">
38
+ <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" />
33
39
  </a>
34
- <img alt="coverage" title="coverage" src="https://raw.githubusercontent.com/gingsi/typedparser/main/docs/coverage.svg" />
40
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py39.yml">
41
+ <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" />
42
+ </a>
43
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py310.yml">
44
+ <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" />
45
+ </a>
46
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py311.yml">
47
+ <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" />
48
+ </a>
49
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py312.yml">
50
+ <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" />
51
+ </a>
52
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py37-full.yml">
53
+ <img alt="build 3.7 full status" title="build 3.7 full status" src="https://img.shields.io/github/actions/workflow/status/simon-ging/typedparser/build-py37-full.yml?branch=main&label=python%203.7%20full" />
54
+ </a>
55
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py312-full.yml">
56
+ <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" />
57
+ </a>
58
+ <img alt="coverage" title="coverage" src="https://raw.githubusercontent.com/simon-ging/typedparser/main/docs/coverage.svg" />
35
59
  <a href="https://pypi.org/project/typedparser/">
36
60
  <img alt="version" title="version" src="https://img.shields.io/pypi/v/typedparser?color=success" />
37
61
  </a>
@@ -41,22 +65,6 @@ Typing extension for python argparse using [attrs](https://www.attrs.org/en/stab
41
65
 
42
66
  Includes typechecking and conversion utilities to parse a dictionary into an attrs instance.
43
67
 
44
- ## Features
45
-
46
- * Create commandline arguments with type hints and checks while
47
- staying very close to the syntax of the standard library's argparse.
48
- * Utilities for typechecking and converting nested objects:
49
- * Nested checking and conversion of python standard types
50
- * Supports old and new style typing (e.g. `typing.List` and `list`)
51
- * Supports positional and keyword arguments in classes
52
- * Can also typecheck existing attrs instances
53
- * Allows custom conversions, by default converts source type `str` to target type `Path` and
54
- `int` to `float`
55
- * Allows to redefine which objects will be recursed into, by default recurses into standard
56
- containers (list, dict, etc.)
57
- * `@definenumpy` decorator for equality check if the instances contains numpy arrays
58
- * Some object utilities in `typedparser.objects` required for everything else
59
-
60
68
  ## Install
61
69
 
62
70
  Requires `python>=3.7`
@@ -65,11 +73,11 @@ Requires `python>=3.7`
65
73
  pip install typedparser
66
74
  ```
67
75
 
68
- ## Usage of the parser
76
+ ## Basic usage
69
77
 
70
- 1. Create an attrs class (decorate with `@attr.define`)
71
- 2. Define 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).
72
- 3. Parse the args with `TypedParser`, now the args are typechecked and there are typehints available.
78
+ 1. Create an attrs class (decorate with `@attr.define`). Note that optional arguments must also be typed as optional.
79
+ 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).
80
+ 3. Parse the args with `TypedParser` and enjoy args with type hints. Disable typechecking by setting `strict=False`.
73
81
 
74
82
  ~~~python
75
83
  from typing import Optional
@@ -78,21 +86,21 @@ from typedparser import add_argument, TypedParser
78
86
 
79
87
 
80
88
  @define
81
- class Args:
82
- foo: int = add_argument("foo", type=int)
83
- bar: int = add_argument("-b", "--bar", type=int, default=0)
84
-
85
- # Syntax extensions:
89
+ class Args:
90
+ # omit the argument name to have it inferred from the field name
91
+ foo: str = add_argument(positional=True)
92
+ bar: int = add_argument(shortcut="-b", type=int, default=0)
93
+ opt: Optional[str] = add_argument()
94
+
95
+ # # in case you prefer the regular argparse syntax:
96
+ # foo: str = add_argument("foo")
97
+ # bar: int = add_argument("-b", "--bar", type=int, default=0)
98
+ # opt: Optional[str] = add_argument("--opt")
86
99
 
87
- # Omit argument name to create an optional argument --opt
88
- opt: Optional[int] = add_argument(type=int)
89
100
 
90
- # Use shortcut to create an optional argument -s / --short
91
- short: Optional[int] = add_argument(shortcut="-s", type=int)
92
-
93
101
 
94
102
  def main():
95
- parser = TypedParser.create_parser(Args)
103
+ parser = TypedParser.create_parser(Args, strict=True)
96
104
  args: Args = parser.parse_args()
97
105
  print(args)
98
106
 
@@ -102,14 +110,31 @@ if __name__ == "__main__":
102
110
 
103
111
  ~~~
104
112
 
105
- ### Advanced usage
113
+
114
+ ## Features
115
+
116
+ * Create commandline arguments with type hints and checks while
117
+ staying close to the syntax of the standard library's argparse.
118
+ * Utilities for typechecking and converting nested objects:
119
+ * Nested checking and conversion of python standard types
120
+ * Supports old and new style typing (e.g. `typing.List` and `list`)
121
+ * Supports positional and keyword arguments in classes
122
+ * Can also typecheck existing attrs instances
123
+ * Allows custom conversions, by default converts source type `str` to target type `Path` and
124
+ `int` to `float`
125
+ * Allows to redefine which objects will be recursed into, by default recurses into standard
126
+ containers (list, dict, etc.)
127
+ * `@definenumpy` decorator for equality check if the instances contains numpy arrays
128
+ * Some object utilities in `typedparser.objects` required for everything else
129
+
130
+ ## Advanced usage
106
131
 
107
132
  * Use `TypedParser.from_parser(parser, Args)` to add typing to an existing parser. This is useful
108
133
  to cover usecases like subparsers or argument groups.
109
134
  * Snippet for argument lists `xarg: List[int] = add_argument(shortcut="-x", type=int, action="append", help="Xarg", default=[])`,
110
135
  use as `-x 1 -x 2` to get `[1, 2]` in the args instance.
111
136
 
112
- ## Usage of attr utilities
137
+ ### Usage of attr utilities
113
138
 
114
139
  Define the class hierarchy and parse the input using `attrs_from_dict`.
115
140
  Use `@define(slots=False)` to allow multiple inheritance and setting attributes later.
@@ -143,6 +168,7 @@ print(attrs_from_dict(CfgNested, {"sub_cfg": {"foo": 1, "bar": 2}}))
143
168
  the output will be a tuple
144
169
  * Raise errors if types cannot be matched, there are unknown fields in the input or
145
170
  abstract annotation types are used (e.g. Sequence)
171
+ * Set `_allow_extra_keys = True` in the class definition to allow unknown fields in the input
146
172
 
147
173
  ### Non-strict mode
148
174
 
@@ -174,7 +200,7 @@ Note: Some tests are skipped for python 3.7.
174
200
 
175
201
  ~~~bash
176
202
  pip install -e .
177
- pip install pytest pytest-cov pylint pytest-lazy-fixture
203
+ pip install pytest pytest-cov pylint
178
204
  pylint typedparser
179
205
 
180
206
  # run tests
@@ -1,13 +1,31 @@
1
1
  # typedparser
2
2
 
3
3
  <p align="center">
4
- <a href="https://github.com/gingsi/typedparser/actions/workflows/build-py37.yml">
5
- <img alt="build 3.7 status" title="build 3.7 status" src="https://img.shields.io/github/actions/workflow/status/gingsi/typedparser/build-py37.yml?branch=main&label=build%203.7" />
4
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py37.yml">
5
+ <img alt="build 3.7 status" title="build 3.7 status" src="https://img.shields.io/github/actions/workflow/status/simon-ging/typedparser/build-py37.yml?branch=main&label=python%203.7" />
6
6
  </a>
7
- <a href="https://github.com/gingsi/typedparser/actions/workflows/build-py310.yml">
8
- <img alt="build 3.10 status" title="build 3.10 status" src="https://img.shields.io/github/actions/workflow/status/gingsi/typedparser/build-py310.yml?branch=main&label=build%203.10" />
7
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py38.yml">
8
+ <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" />
9
9
  </a>
10
- <img alt="coverage" title="coverage" src="https://raw.githubusercontent.com/gingsi/typedparser/main/docs/coverage.svg" />
10
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py39.yml">
11
+ <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" />
12
+ </a>
13
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py310.yml">
14
+ <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" />
15
+ </a>
16
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py311.yml">
17
+ <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" />
18
+ </a>
19
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py312.yml">
20
+ <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" />
21
+ </a>
22
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py37-full.yml">
23
+ <img alt="build 3.7 full status" title="build 3.7 full status" src="https://img.shields.io/github/actions/workflow/status/simon-ging/typedparser/build-py37-full.yml?branch=main&label=python%203.7%20full" />
24
+ </a>
25
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py312-full.yml">
26
+ <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" />
27
+ </a>
28
+ <img alt="coverage" title="coverage" src="https://raw.githubusercontent.com/simon-ging/typedparser/main/docs/coverage.svg" />
11
29
  <a href="https://pypi.org/project/typedparser/">
12
30
  <img alt="version" title="version" src="https://img.shields.io/pypi/v/typedparser?color=success" />
13
31
  </a>
@@ -17,22 +35,6 @@ Typing extension for python argparse using [attrs](https://www.attrs.org/en/stab
17
35
 
18
36
  Includes typechecking and conversion utilities to parse a dictionary into an attrs instance.
19
37
 
20
- ## Features
21
-
22
- * Create commandline arguments with type hints and checks while
23
- staying very close to the syntax of the standard library's argparse.
24
- * Utilities for typechecking and converting nested objects:
25
- * Nested checking and conversion of python standard types
26
- * Supports old and new style typing (e.g. `typing.List` and `list`)
27
- * Supports positional and keyword arguments in classes
28
- * Can also typecheck existing attrs instances
29
- * Allows custom conversions, by default converts source type `str` to target type `Path` and
30
- `int` to `float`
31
- * Allows to redefine which objects will be recursed into, by default recurses into standard
32
- containers (list, dict, etc.)
33
- * `@definenumpy` decorator for equality check if the instances contains numpy arrays
34
- * Some object utilities in `typedparser.objects` required for everything else
35
-
36
38
  ## Install
37
39
 
38
40
  Requires `python>=3.7`
@@ -41,11 +43,11 @@ Requires `python>=3.7`
41
43
  pip install typedparser
42
44
  ```
43
45
 
44
- ## Usage of the parser
46
+ ## Basic usage
45
47
 
46
- 1. Create an attrs class (decorate with `@attr.define`)
47
- 2. Define 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).
48
- 3. Parse the args with `TypedParser`, now the args are typechecked and there are typehints available.
48
+ 1. Create an attrs class (decorate with `@attr.define`). Note that optional arguments must also be typed as optional.
49
+ 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).
50
+ 3. Parse the args with `TypedParser` and enjoy args with type hints. Disable typechecking by setting `strict=False`.
49
51
 
50
52
  ~~~python
51
53
  from typing import Optional
@@ -54,21 +56,21 @@ from typedparser import add_argument, TypedParser
54
56
 
55
57
 
56
58
  @define
57
- class Args:
58
- foo: int = add_argument("foo", type=int)
59
- bar: int = add_argument("-b", "--bar", type=int, default=0)
60
-
61
- # Syntax extensions:
59
+ class Args:
60
+ # omit the argument name to have it inferred from the field name
61
+ foo: str = add_argument(positional=True)
62
+ bar: int = add_argument(shortcut="-b", type=int, default=0)
63
+ opt: Optional[str] = add_argument()
64
+
65
+ # # in case you prefer the regular argparse syntax:
66
+ # foo: str = add_argument("foo")
67
+ # bar: int = add_argument("-b", "--bar", type=int, default=0)
68
+ # opt: Optional[str] = add_argument("--opt")
62
69
 
63
- # Omit argument name to create an optional argument --opt
64
- opt: Optional[int] = add_argument(type=int)
65
70
 
66
- # Use shortcut to create an optional argument -s / --short
67
- short: Optional[int] = add_argument(shortcut="-s", type=int)
68
-
69
71
 
70
72
  def main():
71
- parser = TypedParser.create_parser(Args)
73
+ parser = TypedParser.create_parser(Args, strict=True)
72
74
  args: Args = parser.parse_args()
73
75
  print(args)
74
76
 
@@ -78,14 +80,31 @@ if __name__ == "__main__":
78
80
 
79
81
  ~~~
80
82
 
81
- ### Advanced usage
83
+
84
+ ## Features
85
+
86
+ * Create commandline arguments with type hints and checks while
87
+ staying close to the syntax of the standard library's argparse.
88
+ * Utilities for typechecking and converting nested objects:
89
+ * Nested checking and conversion of python standard types
90
+ * Supports old and new style typing (e.g. `typing.List` and `list`)
91
+ * Supports positional and keyword arguments in classes
92
+ * Can also typecheck existing attrs instances
93
+ * Allows custom conversions, by default converts source type `str` to target type `Path` and
94
+ `int` to `float`
95
+ * Allows to redefine which objects will be recursed into, by default recurses into standard
96
+ containers (list, dict, etc.)
97
+ * `@definenumpy` decorator for equality check if the instances contains numpy arrays
98
+ * Some object utilities in `typedparser.objects` required for everything else
99
+
100
+ ## Advanced usage
82
101
 
83
102
  * Use `TypedParser.from_parser(parser, Args)` to add typing to an existing parser. This is useful
84
103
  to cover usecases like subparsers or argument groups.
85
104
  * Snippet for argument lists `xarg: List[int] = add_argument(shortcut="-x", type=int, action="append", help="Xarg", default=[])`,
86
105
  use as `-x 1 -x 2` to get `[1, 2]` in the args instance.
87
106
 
88
- ## Usage of attr utilities
107
+ ### Usage of attr utilities
89
108
 
90
109
  Define the class hierarchy and parse the input using `attrs_from_dict`.
91
110
  Use `@define(slots=False)` to allow multiple inheritance and setting attributes later.
@@ -119,6 +138,7 @@ print(attrs_from_dict(CfgNested, {"sub_cfg": {"foo": 1, "bar": 2}}))
119
138
  the output will be a tuple
120
139
  * Raise errors if types cannot be matched, there are unknown fields in the input or
121
140
  abstract annotation types are used (e.g. Sequence)
141
+ * Set `_allow_extra_keys = True` in the class definition to allow unknown fields in the input
122
142
 
123
143
  ### Non-strict mode
124
144
 
@@ -150,7 +170,7 @@ Note: Some tests are skipped for python 3.7.
150
170
 
151
171
  ~~~bash
152
172
  pip install -e .
153
- pip install pytest pytest-cov pylint pytest-lazy-fixture
173
+ pip install pytest pytest-cov pylint
154
174
  pylint typedparser
155
175
 
156
176
  # run tests
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
  name = "typedparser"
7
7
  description = "Extension for python argparse with typehints and typechecks."
8
8
  readme = "README.md"
9
- authors = [{ "name" = "gingsi" }]
9
+ authors = [{ "name" = "simon-ging" }]
10
10
  requires-python = ">=3.7"
11
11
  license = { text = "Apache-2.0" }
12
12
  keywords = ["attrs", "typing", "dict", "attr"]
@@ -18,13 +18,19 @@ classifiers = [
18
18
  "Programming Language :: Python :: 3.7",
19
19
  "Programming Language :: Python :: 3.8",
20
20
  "Programming Language :: Python :: 3.9",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
21
24
  "Operating System :: OS Independent",
22
25
  "Topic :: Software Development :: Version Control :: Git"
23
26
  ]
24
27
  dynamic = ["version", "dependencies"]
25
28
 
29
+ [project.optional-dependencies]
30
+ full = ["numpy"]
31
+
26
32
  [project.urls]
27
- Project-URL = "https://github.com/gingsi/typedparser"
33
+ Project-URL = "https://github.com/simon-ging/typedparser"
28
34
 
29
35
  [tool.setuptools]
30
36
  zip-safe = true
@@ -0,0 +1,4 @@
1
+ attrs
2
+ importlib_metadata;python_version<'3.8'
3
+ typing_extensions;python_version<'3.8'
4
+
@@ -1,5 +1,12 @@
1
1
  from ._typedattr import definenumpy, attrs_from_dict, NamedTupleMixin
2
- from ._typedparser import add_argument, TypedParser, define, VerboseQuietArgs
2
+ from ._typedparser import (
3
+ add_argument,
4
+ TypedParser,
5
+ VerboseQuietArgs,
6
+ TaskSplitterArgs,
7
+ split_list_given_task_splitter_args,
8
+ split_list_for_processing,
9
+ )
3
10
  from .custom_format import CustomArgparseFmt
4
11
  from .objects import get_attr_names
5
12
 
@@ -9,9 +16,13 @@ __all__ = [
9
16
  "NamedTupleMixin",
10
17
  "add_argument",
11
18
  "TypedParser",
12
- "define",
13
19
  "VerboseQuietArgs",
14
20
  "CustomArgparseFmt",
15
21
  "get_attr_names",
22
+ "TaskSplitterArgs",
23
+ "split_list_given_task_splitter_args",
24
+ "split_list_for_processing",
16
25
  ]
17
- __version__ = "0.6.4"
26
+
27
+
28
+ __version__ = "0.20.3"
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import collections
2
4
  from functools import partial
3
5
  from inspect import isclass
@@ -15,6 +17,7 @@ from typing import (
15
17
  List,
16
18
  Optional,
17
19
  get_type_hints,
20
+ Callable,
18
21
  )
19
22
 
20
23
  import attrs
@@ -23,19 +26,28 @@ from attrs import define, fields_dict, fields, Attribute
23
26
 
24
27
  from .objects import check_object_equality, RecursorInterface, StrictRecursor, AttrsClass
25
28
 
29
+ try:
30
+ from types import UnionType
31
+ except ImportError:
32
+ UnionType = None
33
+
26
34
  try:
27
35
  # python>=3.8
28
36
  from typing import get_origin, get_args
29
37
  except ImportError:
30
38
  # python<3.8
39
+ # note that attrs automatically installs this and importlib-metadata for old python versions
31
40
  from typing_extensions import get_origin, get_args
32
41
 
33
42
  # default conversions allow to convert instead of raising errors in case of matching types
34
43
  # e.g. create Path given str, or create float given int
35
- conversion_type = List[Tuple[Tuple[Type, ...], Type]]
44
+ # syntax: ((source_type1, source_type2, ...), target_type, conversion_function)
45
+ # if conversion_function is none, use target_type as conversion function
46
+ conversion_type = List[Tuple[Tuple[Type, ...], Type, Optional[Callable]]]
36
47
  default_conversions: conversion_type = [
37
- ((str,), Path),
38
- ((int,), float),
48
+ ((str,), Path, None),
49
+ ((int,), float, None),
50
+ ((Path,), str, Path.as_posix),
39
51
  ]
40
52
 
41
53
 
@@ -129,7 +141,16 @@ def _attrs_from_dict(
129
141
  skip_unknowns: bool = False,
130
142
  conversions: Optional[conversion_type] = None,
131
143
  more_error_info: str = "",
144
+ current_position: Optional[List] = None,
132
145
  ):
146
+ if current_position is None:
147
+ current_position = []
148
+ if isinstance(input_dict_or_attrs, str):
149
+ raise TypeError(
150
+ f"Expected a dictionary or attrs instance, got type '{type(input_dict_or_attrs)}' "
151
+ f"value '{input_dict_or_attrs}' at position '{'.'.join(current_position)}' (root "
152
+ f"if empty).\n\nError context: {more_error_info}."
153
+ )
133
154
  _print_fn(f"Parsing {cls} from {input_dict_or_attrs}")
134
155
  input_cls = type(input_dict_or_attrs)
135
156
  if has(input_cls):
@@ -169,7 +190,7 @@ def _attrs_from_dict(
169
190
  for field_name, field_value in matching_input.items():
170
191
  field_att = cls_fields_dict[field_name]
171
192
  try:
172
- is_positional = bool(field_att.default == attrs.NOTHING)
193
+ is_positional = bool(field_att.default == attrs.NOTHING) and not field_att.kw_only
173
194
  except ValueError:
174
195
  # some field defaults, e.g. numpy arrays do not have comparison defined here
175
196
  # but that means that the default is set and the field is not positional
@@ -181,10 +202,30 @@ def _attrs_from_dict(
181
202
 
182
203
  # create an attrs instance from the dict
183
204
  # the instance will be flat (nested dicts are not resolved yet) and not typechecked.
184
- attrs_inst = attrs.evolve(cls(*in_args), **in_kwargs) # noqa
205
+ try:
206
+ cls_inst = cls(*in_args, **in_kwargs) # noqa
207
+ attrs_inst = cls_inst
208
+ # # not sure why evolve was used before
209
+ # attrs_inst = attrs.evolve(cls_inst, **in_kwargs) # noqa
210
+ except TypeError as e:
211
+ raise TypeError(
212
+ f"Failed creating instance of {cls} from input {input_dict} "
213
+ f"with args {in_args} and kwargs {in_kwargs}. Error occurred at: "
214
+ f"{'.'.join(current_position)}. {e}\n\nError context: {more_error_info}."
215
+ ) from e
185
216
 
186
217
  # typecheck and unfold nested values in the attrs instance
187
- type_hints = get_type_hints(cls)
218
+ try:
219
+ type_hints = get_type_hints(cls)
220
+ except TypeError as e:
221
+ raise TypeError(
222
+ f"Failed getting type hints for {cls} with input '{input_dict}'. Error occurred at: "
223
+ f"{'.'.join(current_position)}. {e}\n\nError context: {more_error_info}."
224
+ "Potential reasons: You used an old python version <=3.8, used from __future__ "
225
+ "import annotations, and annotated something as dict[str, int]. "
226
+ "This does not work together with get_type_hints. Solution: "
227
+ "Annotate with typing.Dict[str, int] instead of dict[str, int], or upgrade python."
228
+ ) from e
188
229
  for att in all_atts:
189
230
  name = att.name
190
231
  value = getattr(attrs_inst, name)
@@ -201,12 +242,13 @@ def _attrs_from_dict(
201
242
  skip_unknowns=skip_unknowns,
202
243
  conversions=conversions,
203
244
  more_error_info=more_error_info,
245
+ current_position=current_position + [name],
204
246
  )
205
247
  setattr(attrs_inst, name, new_value)
206
248
 
207
249
  # handle unknown fields
208
250
  if len(nonmatching_input) > 0 and not skip_unknowns:
209
- if strict:
251
+ if strict and not getattr(cls, "_allow_extra_keys", False):
210
252
  raise TypeError(
211
253
  f"Keys in input {list(nonmatching_input.keys())} not defined "
212
254
  f"for class {cls} with attributes {sorted(all_att_names)}"
@@ -238,14 +280,17 @@ def _parse_nested(
238
280
  conversions: conversion_type = None,
239
281
  depth: int = 0,
240
282
  more_error_info: str = "",
283
+ current_position: Optional[List] = None,
241
284
  ):
242
285
  conversions = default_conversions if conversions is None else conversions
243
286
  parse_recursive = partial(
244
287
  _parse_nested,
245
288
  recursor,
246
- depth=depth + 1,
247
289
  skip_unknowns=skip_unknowns,
290
+ conversions=conversions,
291
+ depth=depth + 1,
248
292
  more_error_info=more_error_info,
293
+ current_position=current_position,
249
294
  )
250
295
 
251
296
  if isinstance(typ, str):
@@ -265,11 +310,11 @@ def _parse_nested(
265
310
  value_type_name = value_type.__name__ if hasattr(value_type, "__name__") else str(value_type)
266
311
  err_msg = (
267
312
  f"Could not parse {name}={value} (type {value_type_name}) as type "
268
- f"{target_type_name} with strict={strict} "
269
- # f"depth={depth} type annotation origin={origin} args={args}"
313
+ f"{target_type_name} with strict={strict}"
314
+ # f" depth={depth} type annotation origin={origin} args={args}"
270
315
  )
271
316
 
272
- def maybe_raise_typeerorr(full_err_msg):
317
+ def maybe_raise_typeerror(full_err_msg):
273
318
  if strict:
274
319
  raise TypeError(full_err_msg)
275
320
  _print_fn(f"Caught: {full_err_msg}. Returning as is.")
@@ -284,22 +329,29 @@ def _parse_nested(
284
329
  strict=strict,
285
330
  skip_unknowns=skip_unknowns,
286
331
  conversions=conversions,
332
+ more_error_info=more_error_info,
333
+ current_position=current_position,
287
334
  )
288
335
 
289
336
  # resolve any
290
- if typ == Any:
337
+ if typ in [Any, any]:
291
338
  return value
292
339
 
293
340
  # resolve unions (mostly "optional")
294
- if origin == Union:
341
+ if origin == Union or (UnionType is not None and isinstance(typ, UnionType)):
342
+ collected_errors = []
295
343
  for new_typ in args:
296
344
  try:
297
345
  # force strict parsing and catch errors to try all different types
298
346
  return parse_recursive(name, value, new_typ, strict=True)
299
- except TypeError:
300
- pass
347
+ except TypeError as e:
348
+ collected_errors.append(f"{e}")
301
349
  # if we get here, none of the types worked
302
- return maybe_raise_typeerorr(f"{err_msg}. No type in Union matches.")
350
+ collected_error_str = "\n".join(collected_errors)
351
+ return maybe_raise_typeerror(
352
+ f"No type in Union matches. Errors per type:\n{collected_error_str}\n"
353
+ f"Final error: {err_msg}"
354
+ )
303
355
 
304
356
  final_list_type = None
305
357
 
@@ -308,9 +360,9 @@ def _parse_nested(
308
360
  if not (len(args) == 2 and args[1] == Ellipsis):
309
361
  # resolve fixed tuple
310
362
  if not recursor.is_iterable_fn(value):
311
- return maybe_raise_typeerorr(f"{err_msg}. Expect a sequence, got {type(value)}")
363
+ return maybe_raise_typeerror(f"{err_msg}. Expect a sequence, got {type(value)}")
312
364
  if len(value) != len(args):
313
- return maybe_raise_typeerorr(
365
+ return maybe_raise_typeerror(
314
366
  f"{err_msg}. Expect a sequence with length {len(args)}, got length {len(value)}"
315
367
  )
316
368
  new_tuple = []
@@ -324,7 +376,7 @@ def _parse_nested(
324
376
  # check dict
325
377
  if origin in (dict, collections.defaultdict):
326
378
  if not recursor.is_mapping_fn(value):
327
- return maybe_raise_typeerorr(f"{err_msg}. Expect a mapping, got {type(value)}")
379
+ return maybe_raise_typeerror(f"{err_msg}. Expect a mapping, got {type(value)}")
328
380
  if len(args) == 0:
329
381
  args = [Any, Any]
330
382
  dict_keytype, dict_valuetype = args
@@ -336,9 +388,9 @@ def _parse_nested(
336
388
  dict_output = {}
337
389
 
338
390
  for key, val in value.items():
339
- dict_output[parse_recursive(name, key, dict_keytype, strict=strict)] = parse_recursive(
340
- name, val, dict_valuetype, strict=strict
341
- )
391
+ parsed_key = parse_recursive(f"{name}/dict_key_type", key, dict_keytype, strict=strict)
392
+ parsed_value = parse_recursive(f"{name}/{key}", val, dict_valuetype, strict=strict)
393
+ dict_output[parsed_key] = parsed_value
342
394
 
343
395
  return dict_output
344
396
 
@@ -352,7 +404,7 @@ def _parse_nested(
352
404
  # resolve list
353
405
  if origin == list:
354
406
  if not recursor.is_iterable_fn(value):
355
- return maybe_raise_typeerorr(f"{err_msg}. Expect iterable, got {type(value)}")
407
+ return maybe_raise_typeerror(f"{err_msg}. Expect iterable, got {type(value)}")
356
408
  list_arg_type = args[0]
357
409
  list_output = []
358
410
  for item in value:
@@ -381,19 +433,25 @@ def _parse_nested(
381
433
  except TypeError:
382
434
  is_sc = False
383
435
  if is_sc:
384
- return maybe_raise_typeerorr(f"{err_msg}. Abstract collections are not supported.")
436
+ return maybe_raise_typeerror(f"{err_msg}. Abstract collections are not supported.")
385
437
 
386
438
  # add explicit conversions
387
- for convert_source, convert_target in conversions:
439
+ for convert_source, convert_target, convert_callable in conversions:
388
440
  if not isclass(typ):
389
441
  continue
390
442
  if issubclass(typ, convert_target) and isinstance(value, convert_source):
391
- return convert_target(value)
443
+ if convert_callable is None:
444
+ convert_callable = convert_target
445
+ return convert_callable(value)
392
446
 
393
447
  if strict:
394
448
  add_info = ""
395
449
  if typ is None:
396
- add_info = "Untyped fields not allowed in strict mode. "
450
+ add_info = (
451
+ "Untyped fields not allowed in strict mode. "
452
+ "Add type annotations or set strict=False. "
453
+ )
454
+
397
455
  raise TypeError(f"{add_info}{err_msg}. Wrong type or type not supported.")
398
456
  _print_fn(f"{err_msg}. Returning value {value} as-is")
399
457
  return value
@@ -1,11 +1,10 @@
1
1
  # from __future__ import annotations # do not use here, it breaks everything
2
2
 
3
3
  import argparse
4
+ from attr import field, define
4
5
  from dataclasses import dataclass
5
6
  from typing import Optional, Type, Any
6
7
 
7
- from attr import field, define
8
-
9
8
  from ._typedattr import AttrsClass
10
9
  from .custom_format import CustomArgparseFmt
11
10
  from .funcs import parse_typed_args, add_typed_args
@@ -55,7 +54,9 @@ class TypedParser:
55
54
  return typed_args
56
55
 
57
56
 
58
- def add_argument(*name_or_flags: str, shortcut: Optional[str] = None, **kwargs):
57
+ def add_argument(
58
+ *name_or_flags: str, shortcut: Optional[str] = None, positional: bool = False, **kwargs
59
+ ):
59
60
  """
60
61
  Interface matches ArgumentParser.add_argument:
61
62
  https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.add_argument
@@ -66,6 +67,7 @@ def add_argument(*name_or_flags: str, shortcut: Optional[str] = None, **kwargs):
66
67
  shortcut: Shortcut for the argument including leading single dash.
67
68
  If given, will be added as a shortcut to the argument.
68
69
  Cannot be used if name_or_flags are given.
70
+ positional: If True, will be added as a positional argument.
69
71
  **kwargs: will be passed to argparse parser.add_argument
70
72
  """
71
73
  assert "name_or_flags" not in kwargs, "Pass name_or_flags as positional arguments"
@@ -86,7 +88,12 @@ def add_argument(*name_or_flags: str, shortcut: Optional[str] = None, **kwargs):
86
88
  # other actions are assumed to have default None output in argparse
87
89
 
88
90
  return field(
89
- metadata={"name_or_flags": name_or_flags, "shortcut": shortcut, **kwargs},
91
+ metadata={
92
+ "name_or_flags": name_or_flags,
93
+ "shortcut": shortcut,
94
+ "positional": positional,
95
+ **kwargs,
96
+ },
90
97
  kw_only=True,
91
98
  default=default,
92
99
  )
@@ -96,3 +103,30 @@ def add_argument(*name_or_flags: str, shortcut: Optional[str] = None, **kwargs):
96
103
  class VerboseQuietArgs:
97
104
  verbose: bool = add_argument(shortcut="-v", help="Increase verbosity", action="store_true")
98
105
  quiet: bool = add_argument(shortcut="-q", help="Reduce verbosity", action="store_true")
106
+
107
+
108
+ @define(slots=False) # slots false to allow multi inheritance
109
+ class TaskSplitterArgs:
110
+ start: int = add_argument(shortcut="-s", type=int, default=0, help="Where to start processing")
111
+ num: Optional[int] = add_argument(shortcut="-n", type=int, help="Number of tasks to process.")
112
+
113
+
114
+ def split_list_given_task_splitter_args(
115
+ in_list, task_splitter_args: TaskSplitterArgs, print_fn=None
116
+ ):
117
+ start, num = task_splitter_args.start, task_splitter_args.num
118
+ return split_list_for_processing(in_list, start, num, print_fn=print_fn)
119
+
120
+
121
+ def split_list_for_processing(in_list, start: int = 0, num: Optional[int] = None, print_fn=None):
122
+ len_in_list = len(in_list)
123
+ if start > 0:
124
+ in_list = in_list[start:]
125
+ if num is not None:
126
+ in_list = in_list[:num]
127
+ if print_fn is not None:
128
+ print_fn(
129
+ f"Split list for processing, input length {len_in_list}, starting at {start}, "
130
+ f"processing max {num} reduced to {len(in_list)}"
131
+ )
132
+ return in_list
@@ -2,7 +2,7 @@ import argparse
2
2
  from typing import Any, Dict
3
3
 
4
4
  from attr import AttrsInstance
5
- from attrs import has, fields_dict
5
+ from attrs import has, fields_dict, asdict
6
6
 
7
7
  from ._typedattr import attrs_from_dict
8
8
  from .objects import get_attr_names
@@ -47,11 +47,36 @@ def add_typed_args(parser: argparse.ArgumentParser, typed_args_class) -> None:
47
47
  continue
48
48
 
49
49
  shortcut = field_metadata.pop("shortcut", None)
50
+ if shortcut is not None and len(name_or_flags) > 0:
51
+ raise ValueError(
52
+ f"Argument '{field_name}' invalid. With shortcut='{shortcut}' "
53
+ f"the argument name will be inferred from the field name and therefore the "
54
+ f"positional arguments must be empty, but got {name_or_flags}."
55
+ )
56
+
57
+ positional = field_metadata.pop("positional", False)
58
+ if positional and len(name_or_flags) > 0:
59
+ raise ValueError(
60
+ f"Argument '{field_name}' invalid. With positional='{positional} "
61
+ f"the argument name will be inferred from the field name and therefore the "
62
+ f"positional arguments must be empty, but got {name_or_flags}."
63
+ )
64
+
65
+ if shortcut is not None and positional:
66
+ raise ValueError(
67
+ f"Argument '{field_name}' invalid. Cannot use positional and shortcut at the same "
68
+ f"time but got positional='{positional}' and shortcut='{shortcut}'."
69
+ )
70
+
50
71
  if len(name_or_flags) == 0:
51
72
  name_or_flags = []
52
- if shortcut is not None:
53
- name_or_flags.append(shortcut)
54
- name_or_flags.append(f"--{field_name}")
73
+ if positional:
74
+ name_or_flags.append(field_name)
75
+ else:
76
+ if shortcut is not None:
77
+ name_or_flags.append(shortcut)
78
+ name_or_flags.append(f"--{field_name}")
79
+
55
80
  try:
56
81
  parser.add_argument(*name_or_flags, **field_metadata)
57
82
  except TypeError as e:
@@ -92,8 +117,11 @@ def parse_typed_args(
92
117
  if len(missing_args) > 0:
93
118
  args_desc = {k: args_dict[k] for k in sorted(missing_args)}
94
119
  missing_err = (
95
- f"Argument(s) {args_desc} missing from configuration "
96
- f"'{typed_args_class.__name__}' with keys {fields_keys}."
120
+ f"Arguments received from argparse {args_desc} "
121
+ f"are missing from argument definition '{typed_args_class.__name__}'. "
122
+ f"This probably means there is a mismatch between the call to add_argument() "
123
+ f"and the attribute name. "
124
+ f"Available keys in '{typed_args_class.__name__}': {fields_keys}."
97
125
  )
98
126
  if strict:
99
127
  raise KeyError(missing_err)
@@ -113,7 +141,7 @@ def parse_typed_args(
113
141
  raise AttributeError(
114
142
  f"Arguments are missing from configuration and cannot be added. "
115
143
  f"Either add it to the configuration or decorate with @attrs.define(slots=False) "
116
- f"to allow adding unknown fields. Missing: {missing_err}"
144
+ f"to allow adding unknown fields. Missing: {missing_args}"
117
145
  ) from e # noqa
118
146
  return out_args
119
147
 
@@ -4,17 +4,20 @@ Generic utilities for python objects
4
4
  from __future__ import annotations
5
5
 
6
6
  from abc import ABCMeta, abstractmethod
7
+ from attr import has, AttrsInstance
8
+ from attrs import fields
7
9
  from collections.abc import Mapping, Iterable
8
10
  from copy import deepcopy
9
11
  from functools import partial
10
12
  from typing import Any, Callable, Type, List
11
13
 
12
- import numpy as np
13
- from attr import has, AttrsInstance
14
- from attrs import fields
15
-
16
14
  AttrsClass = Type[AttrsInstance]
17
15
 
16
+ try:
17
+ import numpy as np
18
+ except ImportError:
19
+ np = None
20
+
18
21
 
19
22
  def get_attr_names(cls: AttrsClass) -> List[str]:
20
23
  """Get all attribute names of an attrs class."""
@@ -270,7 +273,7 @@ def _compare_leaf(
270
273
  d1: Any, d2: Any, _depth: int, rtol=1.0e-5, atol=1.0e-8, equal_nan=False, prefix=""
271
274
  ) -> List[str]:
272
275
  # at this point the 2 leaves are guaranteed to be the same type
273
- if isinstance(d1, np.ndarray):
276
+ if np is not None and isinstance(d1, np.ndarray):
274
277
  comp = np.allclose(d1, d2, rtol=rtol, atol=atol, equal_nan=equal_nan)
275
278
  else:
276
279
  comp = d1 == d2
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: typedparser
3
- Version: 0.6.4
3
+ Version: 0.20.3
4
4
  Summary: Extension for python argparse with typehints and typechecks.
5
- Author: gingsi
5
+ Author: simon-ging
6
6
  License: Apache-2.0
7
- Project-URL: Project-URL, https://github.com/gingsi/typedparser
7
+ Project-URL: Project-URL, https://github.com/simon-ging/typedparser
8
8
  Keywords: attrs,typing,dict,attr
9
9
  Platform: any
10
10
  Classifier: Development Status :: 3 - Alpha
@@ -14,24 +14,48 @@ Classifier: Natural Language :: English
14
14
  Classifier: Programming Language :: Python :: 3.7
15
15
  Classifier: Programming Language :: Python :: 3.8
16
16
  Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
17
20
  Classifier: Operating System :: OS Independent
18
21
  Classifier: Topic :: Software Development :: Version Control :: Git
19
22
  Requires-Python: >=3.7
20
23
  Description-Content-Type: text/markdown
21
24
  License-File: LICENSE
22
25
  Requires-Dist: attrs
23
- Requires-Dist: numpy
26
+ Requires-Dist: importlib_metadata; python_version < "3.8"
27
+ Requires-Dist: typing_extensions; python_version < "3.8"
28
+ Provides-Extra: full
29
+ Requires-Dist: numpy; extra == "full"
24
30
 
25
31
  # typedparser
26
32
 
27
33
  <p align="center">
28
- <a href="https://github.com/gingsi/typedparser/actions/workflows/build-py37.yml">
29
- <img alt="build 3.7 status" title="build 3.7 status" src="https://img.shields.io/github/actions/workflow/status/gingsi/typedparser/build-py37.yml?branch=main&label=build%203.7" />
34
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py37.yml">
35
+ <img alt="build 3.7 status" title="build 3.7 status" src="https://img.shields.io/github/actions/workflow/status/simon-ging/typedparser/build-py37.yml?branch=main&label=python%203.7" />
30
36
  </a>
31
- <a href="https://github.com/gingsi/typedparser/actions/workflows/build-py310.yml">
32
- <img alt="build 3.10 status" title="build 3.10 status" src="https://img.shields.io/github/actions/workflow/status/gingsi/typedparser/build-py310.yml?branch=main&label=build%203.10" />
37
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py38.yml">
38
+ <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" />
33
39
  </a>
34
- <img alt="coverage" title="coverage" src="https://raw.githubusercontent.com/gingsi/typedparser/main/docs/coverage.svg" />
40
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py39.yml">
41
+ <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" />
42
+ </a>
43
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py310.yml">
44
+ <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" />
45
+ </a>
46
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py311.yml">
47
+ <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" />
48
+ </a>
49
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py312.yml">
50
+ <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" />
51
+ </a>
52
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py37-full.yml">
53
+ <img alt="build 3.7 full status" title="build 3.7 full status" src="https://img.shields.io/github/actions/workflow/status/simon-ging/typedparser/build-py37-full.yml?branch=main&label=python%203.7%20full" />
54
+ </a>
55
+ <a href="https://github.com/simon-ging/typedparser/actions/workflows/build-py312-full.yml">
56
+ <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" />
57
+ </a>
58
+ <img alt="coverage" title="coverage" src="https://raw.githubusercontent.com/simon-ging/typedparser/main/docs/coverage.svg" />
35
59
  <a href="https://pypi.org/project/typedparser/">
36
60
  <img alt="version" title="version" src="https://img.shields.io/pypi/v/typedparser?color=success" />
37
61
  </a>
@@ -41,22 +65,6 @@ Typing extension for python argparse using [attrs](https://www.attrs.org/en/stab
41
65
 
42
66
  Includes typechecking and conversion utilities to parse a dictionary into an attrs instance.
43
67
 
44
- ## Features
45
-
46
- * Create commandline arguments with type hints and checks while
47
- staying very close to the syntax of the standard library's argparse.
48
- * Utilities for typechecking and converting nested objects:
49
- * Nested checking and conversion of python standard types
50
- * Supports old and new style typing (e.g. `typing.List` and `list`)
51
- * Supports positional and keyword arguments in classes
52
- * Can also typecheck existing attrs instances
53
- * Allows custom conversions, by default converts source type `str` to target type `Path` and
54
- `int` to `float`
55
- * Allows to redefine which objects will be recursed into, by default recurses into standard
56
- containers (list, dict, etc.)
57
- * `@definenumpy` decorator for equality check if the instances contains numpy arrays
58
- * Some object utilities in `typedparser.objects` required for everything else
59
-
60
68
  ## Install
61
69
 
62
70
  Requires `python>=3.7`
@@ -65,11 +73,11 @@ Requires `python>=3.7`
65
73
  pip install typedparser
66
74
  ```
67
75
 
68
- ## Usage of the parser
76
+ ## Basic usage
69
77
 
70
- 1. Create an attrs class (decorate with `@attr.define`)
71
- 2. Define 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).
72
- 3. Parse the args with `TypedParser`, now the args are typechecked and there are typehints available.
78
+ 1. Create an attrs class (decorate with `@attr.define`). Note that optional arguments must also be typed as optional.
79
+ 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).
80
+ 3. Parse the args with `TypedParser` and enjoy args with type hints. Disable typechecking by setting `strict=False`.
73
81
 
74
82
  ~~~python
75
83
  from typing import Optional
@@ -78,21 +86,21 @@ from typedparser import add_argument, TypedParser
78
86
 
79
87
 
80
88
  @define
81
- class Args:
82
- foo: int = add_argument("foo", type=int)
83
- bar: int = add_argument("-b", "--bar", type=int, default=0)
84
-
85
- # Syntax extensions:
89
+ class Args:
90
+ # omit the argument name to have it inferred from the field name
91
+ foo: str = add_argument(positional=True)
92
+ bar: int = add_argument(shortcut="-b", type=int, default=0)
93
+ opt: Optional[str] = add_argument()
94
+
95
+ # # in case you prefer the regular argparse syntax:
96
+ # foo: str = add_argument("foo")
97
+ # bar: int = add_argument("-b", "--bar", type=int, default=0)
98
+ # opt: Optional[str] = add_argument("--opt")
86
99
 
87
- # Omit argument name to create an optional argument --opt
88
- opt: Optional[int] = add_argument(type=int)
89
100
 
90
- # Use shortcut to create an optional argument -s / --short
91
- short: Optional[int] = add_argument(shortcut="-s", type=int)
92
-
93
101
 
94
102
  def main():
95
- parser = TypedParser.create_parser(Args)
103
+ parser = TypedParser.create_parser(Args, strict=True)
96
104
  args: Args = parser.parse_args()
97
105
  print(args)
98
106
 
@@ -102,14 +110,31 @@ if __name__ == "__main__":
102
110
 
103
111
  ~~~
104
112
 
105
- ### Advanced usage
113
+
114
+ ## Features
115
+
116
+ * Create commandline arguments with type hints and checks while
117
+ staying close to the syntax of the standard library's argparse.
118
+ * Utilities for typechecking and converting nested objects:
119
+ * Nested checking and conversion of python standard types
120
+ * Supports old and new style typing (e.g. `typing.List` and `list`)
121
+ * Supports positional and keyword arguments in classes
122
+ * Can also typecheck existing attrs instances
123
+ * Allows custom conversions, by default converts source type `str` to target type `Path` and
124
+ `int` to `float`
125
+ * Allows to redefine which objects will be recursed into, by default recurses into standard
126
+ containers (list, dict, etc.)
127
+ * `@definenumpy` decorator for equality check if the instances contains numpy arrays
128
+ * Some object utilities in `typedparser.objects` required for everything else
129
+
130
+ ## Advanced usage
106
131
 
107
132
  * Use `TypedParser.from_parser(parser, Args)` to add typing to an existing parser. This is useful
108
133
  to cover usecases like subparsers or argument groups.
109
134
  * Snippet for argument lists `xarg: List[int] = add_argument(shortcut="-x", type=int, action="append", help="Xarg", default=[])`,
110
135
  use as `-x 1 -x 2` to get `[1, 2]` in the args instance.
111
136
 
112
- ## Usage of attr utilities
137
+ ### Usage of attr utilities
113
138
 
114
139
  Define the class hierarchy and parse the input using `attrs_from_dict`.
115
140
  Use `@define(slots=False)` to allow multiple inheritance and setting attributes later.
@@ -143,6 +168,7 @@ print(attrs_from_dict(CfgNested, {"sub_cfg": {"foo": 1, "bar": 2}}))
143
168
  the output will be a tuple
144
169
  * Raise errors if types cannot be matched, there are unknown fields in the input or
145
170
  abstract annotation types are used (e.g. Sequence)
171
+ * Set `_allow_extra_keys = True` in the class definition to allow unknown fields in the input
146
172
 
147
173
  ### Non-strict mode
148
174
 
@@ -174,7 +200,7 @@ Note: Some tests are skipped for python 3.7.
174
200
 
175
201
  ~~~bash
176
202
  pip install -e .
177
- pip install pytest pytest-cov pylint pytest-lazy-fixture
203
+ pip install pytest pytest-cov pylint
178
204
  pylint typedparser
179
205
 
180
206
  # run tests
@@ -0,0 +1,8 @@
1
+ attrs
2
+
3
+ [:python_version < "3.8"]
4
+ importlib_metadata
5
+ typing_extensions
6
+
7
+ [full]
8
+ numpy
@@ -1,2 +0,0 @@
1
- attrs
2
- numpy
@@ -1,2 +0,0 @@
1
- attrs
2
- numpy
File without changes