Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This is the sole use of `valimp`. It's a single short module with no depenencies
Works like this:
```python
from valimp import parse, Parser, Coerce
from collections.abc import Callable
from typing import Annotated, Union, Optional, Any

@parse # add the `valimp.parse`` decorator to a public function or method
Expand Down Expand Up @@ -43,6 +44,9 @@ def public_function(
h: type[int],
# ... or of specific classes...
i: type[Union[int, str]], # type[int | str]
# validate input is a callable whose signature conforms with the
# subscripted argument types and return type...
l: Callable[[str, int], bool],
# support for packing extra arguments if required, can be optionally typed...
*args: Annotated[
Union[int, float, str], # int | float | str
Expand All @@ -58,10 +62,14 @@ def public_function(
# support for packing excess kwargs if required, can be optionally typed...
# **kwargs: Union[int, float]
) -> dict[str, Any]:
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}
return {"a":a, "b":b, "c":c, "d":d, "e":e, "f":f, "g":g, "h":h, "i":i, "l":l, "args":args, "j":j, "k":k}

def conforming_callable(spam: str, foo: int) -> bool:
# conforms with the `Callable[[str, int], bool]` annotation of 'l'
return True

public_function(
# NB parameters 'a' through 'i' can be passed positionally
# NB parameters 'a' through 'l' can be passed positionally
"zero", # a
1.0, # b
{"two": 2}, # c
Expand All @@ -71,6 +79,7 @@ public_function(
str, # g
bool, # h, a subclass of int
int, # i, one of the subscripted classes
conforming_callable, # l
"10", # extra arg, will be coerced to int and packed
20, # extra arg, will be packed
j="keyword_arg_j",
Expand All @@ -88,6 +97,7 @@ returns:
'g': <class 'str'>,
'h': <class 'bool'>,
'i': <class 'int'>,
'l': <function conforming_callable at 0x...>,
'args': (10, 20),
'j': 'keyword_arg_j',
'k': 1.0}
Expand All @@ -104,6 +114,7 @@ public_function(
g=str, # valid input
h=str, # INVALID, str is not int or a subclass of int
i=bool, # valid input
l=lambda spam: True, # INVALID, accepts 1 positional arg, not 2
j="valid input",
)
```
Expand All @@ -125,6 +136,9 @@ f

h
Takes a subclass of <class 'int'> although received '<class 'str'>'.

l
Takes a callable that accepts 2 positional arguments, although the received callable '<function <lambda> at 0x...>' does not.
```
And if the inputs do not match the signature...
```python
Expand Down Expand Up @@ -214,7 +228,6 @@ In short, if you only want to validate the type of function inputs then Pydantic
positional-only arguments) will be ignored. Consequently valimp DOES
allow intended positional-only arguments to be passed as keyword
arguments.
- Validation of subscripted types in `collections.abc.Callable` (although Valimp will verify that the passed value is callable).

`valimp` currently supports:
* use of the following type annotations:
Expand All @@ -226,7 +239,7 @@ In short, if you only want to validate the type of function inputs then Pydantic
* typing.Literal
* typing.Union ( `|` from 3.10 )
* typing.Optional ( `<cls> | None` from 3.10)
* collections.abc.Callable, although validation of subscripted types is **not** supported
* collections.abc.Callable, including subscripted types, for example `collections.abc.Callable[[str, int], bool]`, to validate that an input is a callable whose signature conforms with the subscripted argument types and return type
* `type`, including subscripted types, for example `type[int]`, to validate that an input is a subclass of the subscripted type
* validation of container items for the following generic classes:
* `list`
Expand Down
102 changes: 84 additions & 18 deletions docs/tutorials/tutorial.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -1093,20 +1093,26 @@
},
{
"cell_type": "markdown",
"id": "fcd08875-402c-4e74-8890-c343b6cd5e0f",
"id": "fdb7b2fb",
"metadata": {},
"source": [
"### `collections.abc.Callable`\n",
"\n",
"Valimp validates that inputs to parameters annotated with `collections.abc.Callable` are indeed callable. However, it will **not** validate any subscriptions.\n",
"Valimp validates that inputs to parameters annotated with `collections.abc.Callable` are indeed callable. If the annotation is subscripted then the input's signature is additionally validated against the subscripted argument types and return type.\n",
"\n",
"Notice how the inputs in the following example do not conform with the subscriptions although the input is validated regardless. (The first argument to `collections.abc.Callable` takes a sequence of annotations describing the callable's arguments, the second argument takes the callable's return type.)"
"(The first argument to `collections.abc.Callable` takes a sequence of annotations describing the callable's arguments, the second argument takes the callable's return type.)\n",
"\n",
"Validation of subscriptions is undertaken as follows:\n",
"- The number of positional arguments accepted by the input is validated as accommodating the number of subscripted argument types (unless the arguments are subscripted as `...`).\n",
"- Where the input annotates a parameter or its return, that annotation is validated as matching the corresponding subscripted type. Any parameter or the return that the input does not annotate is not validated (it is treated as `typing.Any`).\n",
"\n",
"Validation of subscriptions is skipped (and validation passes) if the input's signature cannot be introspected, as can be the case for some built-in callables."
]
},
{
"cell_type": "code",
"execution_count": 31,
"id": "44232c3f-b399-492b-a66c-1ab4ce983cc2",
"id": "32ffc90e",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -1121,42 +1127,102 @@
"):\n",
" return\n",
"\n",
"def some_func(a: float, b: float) -> float:\n",
" return a + b\n",
"def func_str(a: str) -> str:\n",
" return a\n",
"\n",
"def func_str_int(a: str, b: int) -> str:\n",
" return a\n",
"\n",
"# inputs are validated even though they do not conform with the subscriptions\n",
"pf(some_func, some_func, some_func, some_func)"
"# all inputs conform with the subscriptions\n",
"pf(func_str, func_str, func_str_int, func_str_int)"
]
},
{
"cell_type": "markdown",
"id": "f306c6fd-4007-46c9-82c9-19d59e7b053f",
"id": "3cbc94f4",
"metadata": {},
"source": [
"Although an error will be raised if the input is not callable..."
"Unannotated callables, for example lambda functions, conform on the basis of arity alone (their parameters and return are treated as `typing.Any`)."
]
},
{
"cell_type": "code",
"id": "fb806256",
"execution_count": null,
"id": "1b93e18e-7121-4fce-a958-d2048f7c280c",
"metadata": {},
"outputs": [],
"source": [
"pf(3, \"can't call me\", some_func, some_func)"
"# the inputs conform as each accepts the required number of positional arguments\n",
"pf(lambda x: x, lambda x: x, lambda x, y: x, lambda: None)"
]
},
{
"cell_type": "markdown",
"id": "81377cab-1d9c-47ce-accb-6e56b4db09ca",
"id": "bfd2ca11",
"metadata": {},
"source": [
"An error is raised if an input's signature does not conform with the subscriptions. In the following example the input to `b` annotates its parameter as `int` rather than `str`, the input to `c` does not accept the required number of arguments and the input to `d` annotates its return as `int` rather than `str`."
]
},
{
"cell_type": "code",
"id": "79b37922",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def func_int(a: int) -> str:\n",
" return str(a)\n",
"\n",
"def returns_int(a: str, b: int) -> int:\n",
" return b\n",
"\n",
"pf(func_str_int, func_int, func_str, returns_int)"
]
},
{
"cell_type": "markdown",
"id": "be247603",
"metadata": {},
"source": [
"```\n",
"---------------------------------------------------------------------------\n",
"InputsError Traceback (most recent call last)\n",
"Cell In[32], line 1\n",
"----> 1 pf(3, \"can't call me\", some_func, some_func)\n",
"InputsError: The following inputs to 'pf' do not conform with the corresponding type annotation:\n",
"\n",
"b\n",
"\tTakes a callable with parameter 0 annotated as <class 'str'>, although the received callable '<function func_int at 0x...>' annotates the corresponding parameter 'a' as <class 'int'>.\n",
"\n",
"c\n",
"\tTakes a callable that accepts 2 positional arguments, although the received callable '<function func_str at 0x...>' does not.\n",
"\n",
"d\n",
"\tTakes a callable with return annotated as <class 'str'>, although the received callable '<function returns_int at 0x...>' annotates its return as <class 'int'>.\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "f8509306",
"metadata": {},
"source": [
"An error is also raised if an input is not callable..."
]
},
{
"cell_type": "code",
"id": "66422a03",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"pf(3, \"can't call me\", func_str_int, func_str_int)"
]
},
{
"cell_type": "markdown",
"id": "63f0e05f",
"metadata": {},
"source": [
"```\n",
"InputsError: The following inputs to 'pf' do not conform with the corresponding type annotation:\n",
"\n",
"a\n",
Expand Down
Loading
Loading