Skip to content

Commit 2992fe8

Browse files
authored
Support positional-only arguments (#30)
Enforces positional-only arguments which can now only be passed positionally. Passing a positional-only argument by keyword now raises an `InputsError`, **unless** the signature provides for `**kwargs`, in which case the keyword input is absorbed by `**kwargs` — consistent with standard Python behaviour.
1 parent 4583d63 commit 2992fe8

4 files changed

Lines changed: 421 additions & 54 deletions

File tree

README.md

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ from typing import Annotated, Union, Optional, Any
1717
def public_function(
1818
# validate against built-in or custom types
1919
a: str,
20+
/, # support for positional-only arguments
2021
# support for type unions
2122
b: Union[int, float], # or from Python 3.10 `int | float`
2223
# validate type of container items
@@ -61,7 +62,7 @@ def public_function(
6162
return {"a":a, "b":b, "c":c, "d":d, "e":e, "f":f, "g":g, "h":h, "i":i, "args":args, "j":j, "k":k}
6263

6364
public_function(
64-
# NB parameters 'a' through 'i' can be passed positionally
65+
# NB 'a' must be passed positionally, 'b' through 'i' can be passed positionally
6566
"zero", # a
6667
1.0, # b
6768
{"two": 2}, # c
@@ -95,7 +96,7 @@ returns:
9596
And if there are invalid inputs...
9697
```python
9798
public_function(
98-
a=["not a string"], # INVALID
99+
["not a string"], # INVALID
99100
b="not an int or a float", # INVALID
100101
c={2: "two"}, # INVALID, key not a str and value not an int or float
101102
d=3.2, # valid input
@@ -134,19 +135,22 @@ public_function(
134135
{"two": 2},
135136
3.2,
136137
# no argument passed for required positional args 'e', 'f', 'g', 'h' and 'i'
137-
a="a again", # passing multiple values for parameter 'a'
138-
# no argument passed for required keyword arg 'j'
138+
a="a again", # 'a' is positional-only: cannot be passed as a kwarg unless sig has **kwargs
139+
c={"three": 3}, # passing multiple values for 'c'
139140
not_a_kwarg="not a kwarg", # including an unexpected kwarg
141+
# no argument passed for required keyword arg 'j'
140142
)
141143
```
142144
raises:
143145
```
144146
InputsError: Inputs to 'public_function' do not conform with the function signature:
145147
146-
Got multiple values for argument: 'a'.
148+
Got multiple values for argument: 'c'.
147149
148150
Got unexpected keyword argument: 'not_a_kwarg'.
149151
152+
Got positional-only argument as keyword argument (and signature makes no provision for **kwargs that would otherwise receive it): 'a'.
153+
150154
Missing 5 positional arguments: 'e', 'f', 'g', 'h' and 'i'.
151155
152156
Missing 1 keyword-only argument: 'j'.
@@ -232,6 +236,18 @@ In short, if you only want to validate the type of function inputs then Pydantic
232236
* `collections.abc.Mapping`
233237
* packing and optionally coercing, parsing and validating packed objects, i.e. objects
234238
received to, for example, *args and **kwargs.
239+
* full verification of signature compliance in accordance with standard Python, i.e.
240+
verifies:
241+
- no excess positional arguments
242+
- no unexpected keyword arguments (if the signature does not provide for **kwargs)
243+
- no missing 'required' arguments (i.e. arguments that do not otherwise have a default value)
244+
- no duplicate arguments
245+
- postional-only arguments are passed positionally. (If a keyword argument is passed
246+
with the same name as a positional-only argument then it will be considered valid if
247+
the signature provides for **kwargs (and in this case it will be received by
248+
**kwargs), whilst if the signature does not provide for **kwargs then the argument
249+
will be considered invalid.)
250+
- positional arguments are passed either positionally or by keyword argument
235251

236252
`valimp` does NOT support:
237253
- Validation of subscripted types in `collections.abc.Callable`. Any subscriptions to
@@ -241,12 +257,6 @@ In short, if you only want to validate the type of function inputs then Pydantic
241257
verify that an object passed to a parameter annotated as `Callable` is in fact
242258
callable).
243259

244-
`valimp` does NOT currently support:
245-
- Positional-only arguments. Any '/' in the signature (to define
246-
positional-only arguments) will be ignored. Consequently valimp DOES
247-
allow intended positional-only arguments to be passed as keyword
248-
arguments.
249-
250260
The library has been built with development in mind and PRs are very much welcome!
251261

252262
## License

docs/tutorials/tutorial.ipynb

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1714,6 +1714,7 @@
17141714
"* A keyword argument is passed that is not represented in the signature (unexpected keyword argument).\n",
17151715
"* More arguments are passed positionally than accommodated for by the signature (excess positional arguments).\n",
17161716
"* A parameter is passed both positionally and as a keyword argument (got multiple values).\n",
1717+
"* A positional-only argument is passed as a keyword argument. (Note that if a keyword argument is passed with the same name as a positional-only argument then it will be considered _valid_ if the signature provides for **kwargs (and in this case it will be received by those **kwargs), whilst if the signature does not provide for **kwargs then the error will be included in the raised `InputsError`.)\n",
17171718
"\n",
17181719
"All signature errors are advised in the error message, together with any errors relating to invalid types."
17191720
]
@@ -1727,8 +1728,10 @@
17271728
"source": [
17281729
"@parse\n",
17291730
"def pf(\n",
1730-
" a: int,\n",
1731+
" a: int, # positional_only\n",
1732+
" /,\n",
17311733
" b: int,\n",
1734+
" c: int,\n",
17321735
" *,\n",
17331736
" kw_a: int,\n",
17341737
"):\n",
@@ -1742,7 +1745,7 @@
17421745
"metadata": {},
17431746
"outputs": [],
17441747
"source": [
1745-
"pf(3, \"not an int\", 5, a=3, not_a_kwarg=3)"
1748+
"pf(3, \"not an int\", 4, 5, a=3, c=2, not_a_kwarg=3)"
17461749
]
17471750
},
17481751
{
@@ -1754,17 +1757,19 @@
17541757
"---------------------------------------------------------------------------\n",
17551758
"InputsError Traceback (most recent call last)\n",
17561759
"Cell In[47], line 1\n",
1757-
"----> 1 pf(3, \"not an int\", 5, a=3, not_a_kwarg=3)\n",
1760+
"----> 1 pf(3, \"not an int\", 4, 5, a=3, c=2, not_a_kwarg=3)\n",
17581761
"\n",
17591762
"InputsError: Inputs to 'pf' do not conform with the function signature:\n",
17601763
"\n",
1761-
"Got multiple values for argument: 'a'.\n",
1764+
"Got multiple values for argument: 'c'.\n",
17621765
"\n",
17631766
"Received 1 excess positional argument as:\n",
17641767
"\t'5' of type <class 'int'>.\n",
17651768
"\n",
17661769
"Got unexpected keyword argument: 'not_a_kwarg'.\n",
17671770
"\n",
1771+
"Got positional-only argument as keyword argument (and signature makes no provision for **kwargs that would otherwise receive it): 'a'.\n",
1772+
"\n",
17681773
"Missing 1 keyword-only argument: 'kw_a'.\n",
17691774
"\n",
17701775
"The following inputs to 'pf' do not conform with the corresponding type annotation:\n",
@@ -1797,7 +1802,7 @@
17971802
"\n",
17981803
"InputsError: Inputs to 'pf' do not conform with the function signature:\n",
17991804
"\n",
1800-
"Missing 1 positional argument: 'b'.\n",
1805+
"Missing 2 positional arguments: 'b' and 'c'.\n",
18011806
"```"
18021807
]
18031808
},

0 commit comments

Comments
 (0)