Merge pull request #8 from p2p-ld/constructable

Make NDArray callable as a functional validator
This commit is contained in:
Jonny Saunders 2024-08-13 23:29:20 -07:00 committed by GitHub
commit 6a397a9aba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 78 additions and 5 deletions

View file

@ -2,6 +2,61 @@
## 1.*
### 1.3.3 - 24-08-13 - Callable type annotations
Problem, when you use a numpydantic `"wrap"` validator, it gives the annotation as a `handler` function.
So this is effectively what happens
```python
@field_validator("*", mode="wrap")
@classmethod
def cast_specified_columns(
cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
) -> Any:
# where handler is the callable here
# so
# return handler(val)
return NDArray[Any, Any](val)
```
where `Any, Any` is whatever you had put in there.
So this makes it so you can use an annotation as a functional validator. it looks a little bit whacky but idk it makes sense as a PARAMETERIZED TYPE
```python
>>> from numpydantic import NDArray, Shape
>>> import numpy as np
>>> array = np.array([1,2,3], dtype=int)
>>> validated = NDArray[Shape["3"], int](array)
>>> assert validated is array
True
>>> bad_array = np.array([1,2,3,4], dtype=int)
>>> _ = NDArray[Shape["3"], int](bad_array)
175 """
176 Raise a ShapeError if the shape is invalid.
177
178 Raises:
179 :class:`~numpydantic.exceptions.ShapeError`
180 """
181 if not valid:
--> 182 raise ShapeError(
183 f"Invalid shape! expected shape {self.shape.prepared_args}, "
184 f"got shape {shape}"
185 )
ShapeError: Invalid shape! expected shape ['3'], got shape (4,)
```
**Performance:**
- Don't import the pandas module if we don't have to, since we are not
using it. This shaves ~600ms off import time.
### 1.3.2 - 24-08-12 - Allow subclasses of dtypes
(also when using objects for dtypes, subclasses of that object are allowed to validate)

View file

@ -1,6 +1,6 @@
[project]
name = "numpydantic"
version = "1.3.2"
version = "1.3.3"
description = "Type and shape validation and serialization for numpy arrays in pydantic models"
authors = [
{name = "sneakers-the-rat", email = "sneakers-the-rat@protonmail.com"},

View file

@ -29,7 +29,7 @@ from numpydantic.schema import (
get_validate_interface,
make_json_schema,
)
from numpydantic.types import DtypeType, ShapeType
from numpydantic.types import DtypeType, NDArrayType, ShapeType
from numpydantic.vendor.nptyping.error import InvalidArgumentsError
from numpydantic.vendor.nptyping.ndarray import NDArrayMeta as _NDArrayMeta
from numpydantic.vendor.nptyping.nptyping_type import NPTypingType
@ -54,6 +54,10 @@ class NDArrayMeta(_NDArrayMeta, implementation="NDArray"):
if TYPE_CHECKING: # pragma: no cover
__getitem__ = SubscriptableMeta.__getitem__
def __call__(cls, val: NDArrayType) -> NDArrayType:
"""Call ndarray as a validator function"""
return get_validate_interface(cls.__args__[0], cls.__args__[1])(val)
def __instancecheck__(self, instance: Any):
"""
Extended type checking that determines whether

View file

@ -32,7 +32,9 @@ from numpydantic.vendor.nptyping.error import (
)
from numpydantic.vendor.nptyping.ndarray import NDArray
from numpydantic.vendor.nptyping.package_info import __version__
from numpydantic.vendor.nptyping.pandas_.dataframe import DataFrame
# don't import unnecessarily since we don't use it
# from numpydantic.vendor.nptyping.pandas_.dataframe import DataFrame
from numpydantic.vendor.nptyping.recarray import RecArray
from numpydantic.vendor.nptyping.shape import Shape
from numpydantic.vendor.nptyping.shape_expression import (

View file

@ -31,7 +31,6 @@ import numpy as np
from numpydantic.vendor.nptyping.base_meta_classes import (
FinalMeta,
ImmutableMeta,
InconstructableMeta,
MaybeCheckableMeta,
PrintableMeta,
SubscriptableMeta,
@ -54,7 +53,6 @@ from numpydantic.vendor.nptyping.typing_ import (
class NDArrayMeta(
SubscriptableMeta,
InconstructableMeta,
ImmutableMeta,
FinalMeta,
MaybeCheckableMeta,

View file

@ -350,3 +350,17 @@ def test_instancecheck():
return array
my_function(np.zeros((1, 2, 3), int))
def test_callable():
"""
NDArray objects are callable to validate and cast
Don't test validation here, just that we can be called
"""
annotation = NDArray[Shape["3"], int]
array = np.array([1, 2, 3], dtype=int)
validated = annotation(array)
assert validated is array
with pytest.raises(DtypeError):
_ = annotation(np.zeros((1, 2, 3)))