mirror of
https://github.com/p2p-ld/numpydantic.git
synced 2024-11-12 17:54:29 +00:00
tests and no covers continue
This commit is contained in:
parent
0ee371ad05
commit
9b13226164
10 changed files with 198 additions and 9 deletions
|
@ -10,7 +10,7 @@ from numpydantic.interface.interface import Interface
|
|||
|
||||
try:
|
||||
from dask.array.core import Array as DaskArray
|
||||
except ImportError:
|
||||
except ImportError: # pragma: no cover
|
||||
DaskArray = None
|
||||
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ from numpydantic.types import NDArrayType
|
|||
|
||||
try:
|
||||
import h5py
|
||||
except ImportError:
|
||||
except ImportError: # pragma: no cover
|
||||
h5py = None
|
||||
|
||||
if sys.version_info.minor >= 10:
|
||||
|
@ -160,9 +160,11 @@ class H5Interface(Interface):
|
|||
"""Create an :class:`.H5Proxy` to use throughout validation"""
|
||||
if isinstance(array, H5ArrayPath):
|
||||
array = H5Proxy.from_h5array(h5array=array)
|
||||
elif isinstance(array, (tuple, list)) and len(array) == 2:
|
||||
elif isinstance(array, (tuple, list)) and len(array) == 2: # pragma: no cover
|
||||
array = H5Proxy(file=array[0], path=array[1])
|
||||
else:
|
||||
else: # pragma: no cover
|
||||
# this should never happen really since `check` confirms this before
|
||||
# we'd reach here, but just to complete the if else...
|
||||
raise ValueError(
|
||||
"Need to specify a file and a path within an HDF5 file to use the HDF5 "
|
||||
"Interface"
|
||||
|
|
|
@ -141,7 +141,7 @@ class Interface(ABC, Generic[T]):
|
|||
for iface in cls.interfaces():
|
||||
if isinstance(iface.input_types, Union[tuple, list]):
|
||||
in_types.extend(iface.input_types)
|
||||
else:
|
||||
else: # pragma: no cover
|
||||
in_types.append(iface.input_types)
|
||||
|
||||
return tuple(in_types)
|
||||
|
|
|
@ -11,7 +11,7 @@ try:
|
|||
|
||||
ENABLED = True
|
||||
|
||||
except ImportError:
|
||||
except ImportError: # pragma: no cover
|
||||
ENABLED = False
|
||||
ndarray = None
|
||||
|
||||
|
@ -23,6 +23,13 @@ class NumpyInterface(Interface):
|
|||
|
||||
input_types = (ndarray, list)
|
||||
return_type = ndarray
|
||||
priority = -1
|
||||
"""
|
||||
The numpy interface is usually the interface of last resort.
|
||||
We want to use any more specific interface that we might have,
|
||||
because the numpy interface checks for anything that could be coerced
|
||||
to a numpy array (see :meth:`.NumpyInterface.check` )
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def check(cls, array: Any) -> bool:
|
||||
|
|
|
@ -13,7 +13,7 @@ try:
|
|||
import zarr
|
||||
from zarr.core import Array as ZarrArray
|
||||
from zarr.storage import StoreLike
|
||||
except ImportError:
|
||||
except ImportError: # pragma: no cover
|
||||
ZarrArray = None
|
||||
StoreLike = None
|
||||
storage = None
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import Callable, Optional, Tuple, Type, Union
|
||||
from typing import Any, Callable, Optional, Tuple, Type, Union
|
||||
|
||||
import h5py
|
||||
import numpy as np
|
||||
|
@ -85,6 +85,16 @@ def model_rgb() -> Type[BaseModel]:
|
|||
return RGB
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def model_blank() -> Type[BaseModel]:
|
||||
"""A model with any shape and dtype"""
|
||||
|
||||
class BlankModel(BaseModel):
|
||||
array: NDArray[Shape["*, ..."], Any]
|
||||
|
||||
return BlankModel
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def hdf5_file(tmp_output_dir_func) -> h5py.File:
|
||||
h5f_file = tmp_output_dir_func / "h5f.h5"
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import pdb
|
||||
|
||||
import pytest
|
||||
import json
|
||||
|
||||
import dask.array as da
|
||||
from pydantic import ValidationError
|
||||
|
@ -42,3 +45,12 @@ def test_dask_shape(shape_cases):
|
|||
|
||||
def test_dask_dtype(dtype_cases):
|
||||
_test_dask_case(dtype_cases)
|
||||
|
||||
|
||||
def test_dask_to_json(array_model):
|
||||
array_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
|
||||
array = da.array(array_list)
|
||||
model = array_model((3, 3), int)
|
||||
instance = model(array=array)
|
||||
jsonified = json.loads(instance.model_dump_json())
|
||||
assert jsonified["array"] == array_list
|
||||
|
|
|
@ -1,7 +1,69 @@
|
|||
import pdb
|
||||
import json
|
||||
import pytest
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, ValidationError
|
||||
|
||||
from numpydantic.interface import H5Interface
|
||||
from numpydantic.interface.hdf5 import H5ArrayPath
|
||||
from numpydantic.exceptions import DtypeError, ShapeError
|
||||
|
||||
from tests.conftest import ValidationCase
|
||||
|
||||
|
||||
def hdf5_array_case(case: ValidationCase, array_func) -> H5ArrayPath:
|
||||
"""
|
||||
Args:
|
||||
case:
|
||||
array_func: ( the function returned from the `hdf5_array` fixture )
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return array_func(case.shape, case.dtype)
|
||||
|
||||
|
||||
def _test_hdf5_case(case: ValidationCase, array_func):
|
||||
array = hdf5_array_case(case, array_func)
|
||||
if case.passes:
|
||||
case.model(array=array)
|
||||
else:
|
||||
with pytest.raises((ValidationError, DtypeError, ShapeError)):
|
||||
case.model(array=array)
|
||||
|
||||
|
||||
def test_hdf5_enabled():
|
||||
assert H5Interface.enabled()
|
||||
|
||||
|
||||
def test_hdf5_check(interface_type):
|
||||
if interface_type[1] is H5Interface:
|
||||
if interface_type[0].__name__ == "_hdf5_array":
|
||||
interface_type = (interface_type[0](), interface_type[1])
|
||||
assert H5Interface.check(interface_type[0])
|
||||
if isinstance(interface_type[0], H5ArrayPath):
|
||||
# also test that we can instantiate from a tuple like the H5ArrayPath
|
||||
assert H5Interface.check((interface_type[0].file, interface_type[0].path))
|
||||
else:
|
||||
assert not H5Interface.check(interface_type[0])
|
||||
|
||||
|
||||
def test_hdf5_shape(shape_cases, hdf5_array):
|
||||
_test_hdf5_case(shape_cases, hdf5_array)
|
||||
|
||||
|
||||
def test_hdf5_dtype(dtype_cases, hdf5_array):
|
||||
if dtype_cases.dtype is str:
|
||||
pytest.skip("hdf5 cant do string arrays")
|
||||
_test_hdf5_case(dtype_cases, hdf5_array)
|
||||
|
||||
|
||||
def test_hdf5_dataset_not_exists(hdf5_array, model_blank):
|
||||
array = hdf5_array()
|
||||
with pytest.raises(ValueError) as e:
|
||||
model_blank(array=H5ArrayPath(file=array.file, path="/some/random/path"))
|
||||
assert "file located" in e
|
||||
assert "no array found" in e
|
||||
|
||||
|
||||
def test_to_json(hdf5_array, array_model):
|
||||
|
|
90
tests/test_interface/test_interface.py
Normal file
90
tests/test_interface/test_interface.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
import pytest
|
||||
|
||||
from numpydantic.interface import Interface
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def interfaces():
|
||||
"""Define test interfaces in this module, and delete afterwards"""
|
||||
|
||||
class Interface1(Interface):
|
||||
input_types = (list,)
|
||||
return_type = tuple
|
||||
|
||||
@classmethod
|
||||
def check(cls, array):
|
||||
if isinstance(array, list):
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def enabled(cls) -> bool:
|
||||
return True
|
||||
|
||||
Interface2 = type("Interface2", Interface1.__bases__, dict(Interface1.__dict__))
|
||||
|
||||
class Interface3(Interface1):
|
||||
@classmethod
|
||||
def enabled(cls) -> bool:
|
||||
return False
|
||||
|
||||
class Interfaces:
|
||||
interface1 = Interface1
|
||||
interface2 = Interface2
|
||||
interface3 = Interface3
|
||||
|
||||
yield Interfaces
|
||||
del Interface1
|
||||
del Interface2
|
||||
del Interface3
|
||||
|
||||
|
||||
def test_interface_match_error(interfaces):
|
||||
"""
|
||||
Test that `match` and `match_output` raises errors when no or multiple matches are found
|
||||
"""
|
||||
with pytest.raises(ValueError) as e:
|
||||
Interface.match([1, 2, 3])
|
||||
assert "Interface1" in e
|
||||
assert "Interface2" in e
|
||||
|
||||
with pytest.raises(ValueError) as e:
|
||||
Interface.match("hey")
|
||||
assert "No matching interfaces" in e
|
||||
|
||||
with pytest.raises(ValueError) as e:
|
||||
Interface.match_output((1, 2, 3))
|
||||
assert "Interface1" in e
|
||||
assert "Interface2" in e
|
||||
|
||||
with pytest.raises(ValueError) as e:
|
||||
Interface.match_output("hey")
|
||||
assert "No matching interfaces" in e
|
||||
|
||||
|
||||
def test_interface_enabled(interfaces):
|
||||
"""
|
||||
An interface shouldn't be included if it's not enabled
|
||||
"""
|
||||
assert not interfaces.Interface3.enabled()
|
||||
assert interfaces.Interface3 not in Interface.interfaces()
|
||||
|
||||
|
||||
def test_interface_type_lists():
|
||||
"""
|
||||
Seems like a silly test, but ensure that our return types and input types
|
||||
lists have all the class attrs
|
||||
"""
|
||||
for interface in Interface.interfaces():
|
||||
|
||||
if isinstance(interface.input_types, (list, tuple)):
|
||||
for atype in interface.input_types:
|
||||
assert atype in Interface.input_types()
|
||||
else:
|
||||
assert interface.input_types in Interface.input_types()
|
||||
|
||||
if isinstance(interface.return_type, (list, tuple)):
|
||||
for atype in interface.return_type:
|
||||
assert atype in Interface.return_types()
|
||||
else:
|
||||
assert interface.return_type in Interface.return_types()
|
|
@ -25,3 +25,9 @@ def test_numpy_shape(shape_cases):
|
|||
|
||||
def test_numpy_dtype(dtype_cases):
|
||||
_test_np_case(dtype_cases)
|
||||
|
||||
|
||||
def test_numpy_coercion(model_blank):
|
||||
"""If no other interface matches, we try and coerce to a numpy array"""
|
||||
instance = model_blank(array=[1, 2, 3])
|
||||
assert isinstance(instance.array, np.ndarray)
|
||||
|
|
Loading…
Reference in a new issue