Merge pull request #14 from p2p-ld/bugfix-revalidation

[bugfix] Revalidate with already-validated/proxied array
This commit is contained in:
Jonny Saunders 2024-09-03 13:20:33 -07:00 committed by GitHub
commit c46015d306
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 68 additions and 23 deletions

View file

@ -2,7 +2,17 @@
## 1.*
### 1.5.0 - 24-09-02 - `str` support for HDF5
### 1.5.*
#### 1.5.1 - 24-09-03 - Fix revalidation with proxy classes
Bugfix:
- [#14](https://github.com/p2p-ld/numpydantic/pull/14): Allow revalidation of proxied arrays
Tests:
- Add test module for tests against all interfaces, test for above bug
#### 1.5.0 - 24-09-02 - `str` support for HDF5
Strings in hdf5 are tricky! HDF5 doesn't have native support for unicode,
but it can be persuaded to store data in ASCII or virtualized utf-8 under somewhat obscure conditions.
@ -36,7 +46,9 @@ instance[0,0] = 'hey'
assert instance[0,0] == 'hey'
```
### 1.4.1 - 24-09-02 - `len()` support and dunder method testing
### 1.4.*
#### 1.4.1 - 24-09-02 - `len()` support and dunder method testing
It's pretty natural to want to do `len(array)` as a shorthand for `array.shape[0]`,
but since some of the numpydantic classes are passthrough proxy objects,
@ -52,7 +64,7 @@ There is a certain combinatoric explosion when we start testing across all inter
for all input types, for all dtype and all shape cases,
but for now numpydantic is fast enough that this doesn't matter <3.
### 1.4.0 - 24-09-02 - HDF5 Compound Dtype Support
#### 1.4.0 - 24-09-02 - HDF5 Compound Dtype Support
HDF5 can have compound dtypes like:
@ -107,7 +119,9 @@ my_model = MyModel(
np.dtype('int64')
```
### 1.3.3 - 24-08-13 - Callable type annotations
### 1.3.*
#### 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.
@ -162,11 +176,11 @@ ShapeError: Invalid shape! expected shape ['3'], got shape (4,)
using it. This shaves ~600ms off import time.
### 1.3.2 - 24-08-12 - Allow subclasses of dtypes
#### 1.3.2 - 24-08-12 - Allow subclasses of dtypes
(also when using objects for dtypes, subclasses of that object are allowed to validate)
### 1.3.1 - 24-08-12 - Allow arbitrary dtypes, pydantic models as dtypes
#### 1.3.1 - 24-08-12 - Allow arbitrary dtypes, pydantic models as dtypes
Previously we would only allow dtypes if we knew for sure that there was some
python base type to generate a schema with.
@ -183,7 +197,7 @@ Only one substantial change, and that is a `get_object_dtype` method which
interfaces can override if there is some fancy way they have of getting
types/items from an object array.
### 1.3.0 - 24-08-05 - Better string dtype handling
#### 1.3.0 - 24-08-05 - Better string dtype handling
API Changes:
- Split apart the validation methods into smaller chunks to better support
@ -194,7 +208,9 @@ Bugfix:
- [#4](https://github.com/p2p-ld/numpydantic/issues/4) - Support dtype checking
for strings in zarr and numpy arrays
### 1.2.3 - 24-07-31 - Vendor `nptyping`
### 1.2.*
#### 1.2.3 - 24-07-31 - Vendor `nptyping`
`nptyping` vendored into `numpydantic.vendor.nptyping` -
`nptyping` is no longer maintained, and pins `numpy<2`.
@ -221,22 +237,24 @@ Tidying:
- Remove `monkeypatch` module! we don't need it anymore!
everything has either been upstreamed or vendored.
### 1.2.2 - 24-07-31
#### 1.2.2 - 24-07-31
Add `datetime` map to numpy's :class:`numpy.datetime64` type
### 1.2.1 - 24-06-27
#### 1.2.1 - 24-06-27
Fix a minor bug where {class}`~numpydantic.exceptions.DtypeError` would not cause
pydantic to throw a {class}`pydantic.ValidationError` because custom validator functions
need to raise either `AssertionError` or `ValueError` - made `DtypeError` also
inherit from `ValueError` because that is also technically true.
### 1.2.0 - 24-06-13 - Shape ranges
#### 1.2.0 - 24-06-13 - Shape ranges
- Add ability to specify shapes as ranges - see [shape ranges](shape-ranges)
### 1.1.0 - 24-05-24 - Instance Checking
### 1.1.*
#### 1.1.0 - 24-05-24 - Instance Checking
https://github.com/p2p-ld/numpydantic/pull/1

View file

@ -1,6 +1,6 @@
[project]
name = "numpydantic"
version = "1.5.0"
version = "1.5.1"
description = "Type and shape validation and serialization for arbitrary array types in pydantic models"
authors = [
{name = "sneakers-the-rat", email = "sneakers-the-rat@protonmail.com"},

View file

@ -194,10 +194,7 @@ class H5Interface(Interface):
passthrough numpy-like interface to the dataset.
"""
input_types = (
H5ArrayPath,
H5Arraylike,
)
input_types = (H5ArrayPath, H5Arraylike, H5Proxy)
return_type = H5Proxy
@classmethod
@ -211,7 +208,7 @@ class H5Interface(Interface):
Check that the given array is a :class:`.H5ArrayPath` or something that
resembles one.
"""
if isinstance(array, H5ArrayPath):
if isinstance(array, (H5ArrayPath, H5Proxy)):
return True
if isinstance(array, (tuple, list)) and len(array) in (2, 3):
@ -242,6 +239,9 @@ class H5Interface(Interface):
"""Create an :class:`.H5Proxy` to use throughout validation"""
if isinstance(array, H5ArrayPath):
array = H5Proxy.from_h5array(h5array=array)
elif isinstance(array, H5Proxy):
# nothing to do, already proxied
pass
elif isinstance(array, (tuple, list)) and len(array) == 2: # pragma: no cover
array = H5Proxy(file=array[0], path=array[1])
elif isinstance(array, (tuple, list)) and len(array) == 3:

View file

@ -190,7 +190,7 @@ class VideoInterface(Interface):
OpenCV interface to treat videos as arrays.
"""
input_types = (str, Path, VideoCapture)
input_types = (str, Path, VideoCapture, VideoProxy)
return_type = VideoProxy
@classmethod
@ -204,7 +204,9 @@ class VideoInterface(Interface):
Check if array is a string or Path with a supported video extension,
or an opened VideoCapture object
"""
if VideoCapture is not None and isinstance(array, VideoCapture):
if (VideoCapture is not None and isinstance(array, VideoCapture)) or isinstance(
array, VideoProxy
):
return True
if isinstance(array, str):
@ -220,6 +222,8 @@ class VideoInterface(Interface):
"""Get a :class:`.VideoProxy` object for this video"""
if isinstance(array, VideoCapture):
proxy = VideoProxy(video=array)
elif isinstance(array, VideoProxy):
proxy = array
else:
proxy = VideoProxy(path=array)
return proxy

View file

@ -1,5 +1,11 @@
import pytest
"""
Tests for the interface base model,
for tests that should apply to all interfaces, use ``test_interfaces.py``
"""
import gc
import pytest
import numpy as np
from numpydantic.interface import Interface
@ -8,6 +14,7 @@ from numpydantic.interface import Interface
@pytest.fixture(scope="module")
def interfaces():
"""Define test interfaces in this module, and delete afterwards"""
interfaces_enabled = True
class Interface1(Interface):
input_types = (list,)
@ -24,7 +31,7 @@ def interfaces():
@classmethod
def enabled(cls) -> bool:
return True
return interfaces_enabled
Interface2 = type("Interface2", Interface1.__bases__, dict(Interface1.__dict__))
Interface2.checked = False
@ -44,7 +51,7 @@ def interfaces():
@classmethod
def enabled(cls) -> bool:
return True
return interfaces_enabled
class Interfaces:
interface1 = Interface1
@ -55,9 +62,13 @@ def interfaces():
yield Interfaces
# Interface.__subclasses__().remove(Interface1)
# Interface.__subclasses__().remove(Interface2)
del Interfaces
del Interface1
del Interface2
del Interface3
del Interface4
interfaces_enabled = False
gc.collect()
def test_interface_match_error(interfaces):

View file

@ -0,0 +1,12 @@
"""
Tests that should be applied to all interfaces
"""
def test_interface_revalidate(all_interfaces):
"""
An interface should revalidate with the output of its initial validation
See: https://github.com/p2p-ld/numpydantic/pull/14
"""
_ = type(all_interfaces)(array=all_interfaces.array)