Skip to content

Commit

Permalink
finish replacing interface tests with new helper system
Browse files Browse the repository at this point in the history
  • Loading branch information
sneakers-the-rat committed Oct 11, 2024
1 parent 3356738 commit 5d4f03a
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 181 deletions.
97 changes: 49 additions & 48 deletions src/numpydantic/testing/cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,53 @@
YES_PIPE = False


def merged_product(
*args: Sequence[ValidationCase],
) -> Generator[ValidationCase, None, None]:
"""
Generator for the product of the iterators of validation cases,
merging each tuple, and respecting if they should be :meth:`.ValidationCase.skip`
or not.
Examples:
.. code-block:: python
shape_cases = [
ValidationCase(shape=(10, 10, 10), passes=True, id="valid shape"),
ValidationCase(shape=(10, 10), passes=False, id="missing dimension"),
]
dtype_cases = [
ValidationCase(dtype=float, passes=True, id="float"),
ValidationCase(dtype=int, passes=False, id="int"),
]
iterator = merged_product(shape_cases, dtype_cases))
next(iterator)
# ValidationCase(
# shape=(10, 10, 10),
# dtype=float,
# passes=True,
# id="valid shape-float"
# )
next(iterator)
# ValidationCase(
# shape=(10, 10, 10),
# dtype=int,
# passes=False,
# id="valid shape-int"
# )
"""
iterator = product(*args)
for case_tuple in iterator:
case = merge_cases(case_tuple)
if case.skip():
continue
yield case


class BasicModel(BaseModel):
x: int

Expand Down Expand Up @@ -58,7 +105,6 @@ class SubClass(BasicModel):
STRING: TypeAlias = NDArray[Shape["*, *, *"], str]
MODEL: TypeAlias = NDArray[Shape["*, *, *"], BasicModel]
UNION_TYPE: TypeAlias = NDArray[Shape["*, *, *"], Union[np.uint32, np.float32]]
UNION_PIPE: TypeAlias = NDArray[Shape["*, *, *"], np.uint32 | np.float32]

SHAPE_CASES = (
ValidationCase(shape=(10, 10, 10), passes=True, id="valid shape"),
Expand Down Expand Up @@ -135,6 +181,8 @@ class SubClass(BasicModel):


if YES_PIPE:
UNION_PIPE: TypeAlias = NDArray[Shape["*, *, *"], np.uint32 | np.float32]

DTYPE_CASES.extend(
[
ValidationCase(
Expand Down Expand Up @@ -178,50 +226,3 @@ class SubClass(BasicModel):
ZarrNestedCase,
VideoCase,
]


def merged_product(
*args: Sequence[ValidationCase],
) -> Generator[ValidationCase, None, None]:
"""
Generator for the product of the iterators of validation cases,
merging each tuple, and respecting if they should be :meth:`.ValidationCase.skip`
or not.
Examples:
.. code-block:: python
shape_cases = [
ValidationCase(shape=(10, 10, 10), passes=True, id="valid shape"),
ValidationCase(shape=(10, 10), passes=False, id="missing dimension"),
]
dtype_cases = [
ValidationCase(dtype=float, passes=True, id="float"),
ValidationCase(dtype=int, passes=False, id="int"),
]
iterator = merged_product(shape_cases, dtype_cases))
next(iterator)
# ValidationCase(
# shape=(10, 10, 10),
# dtype=float,
# passes=True,
# id="valid shape-float"
# )
next(iterator)
# ValidationCase(
# shape=(10, 10, 10),
# dtype=int,
# passes=False,
# id="valid shape-int"
# )
"""
iterator = product(*args)
for case_tuple in iterator:
case = merge_cases(case_tuple)
if case.skip():
continue
yield case
7 changes: 7 additions & 0 deletions src/numpydantic/testing/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,16 @@ def make_array(
shape: Tuple[int, ...] = (10, 10),
dtype: DtypeType = float,
path: Optional[Path] = None,
array: Optional[NDArrayType] = None,
) -> Optional[NDArrayType]:
"""
Make an array from a shape and dtype, and a path if needed
Args:
shape: shape of the array
dtype: dtype of the array
path: Path, if needed to generate on disk
array: Rather than passing shape and dtype, pass a literal arraylike thing
"""

@classmethod
Expand Down
66 changes: 51 additions & 15 deletions src/numpydantic/testing/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
ZarrInterface,
)
from numpydantic.testing.helpers import InterfaceCase
from numpydantic.types import DtypeType
from numpydantic.types import DtypeType, NDArrayType


class NumpyCase(InterfaceCase):
Expand All @@ -33,8 +33,11 @@ def make_array(
shape: Tuple[int, ...] = (10, 10),
dtype: DtypeType = float,
path: Optional[Path] = None,
array: Optional[NDArrayType] = None,
) -> np.ndarray:
if issubclass(dtype, BaseModel):
if array is not None:
return np.array(array, dtype=dtype)
elif issubclass(dtype, BaseModel):
return np.full(shape=shape, fill_value=dtype(x=1))
else:
return np.zeros(shape=shape, dtype=dtype)
Expand All @@ -59,6 +62,7 @@ def make_array(
shape: Tuple[int, ...] = (10, 10),
dtype: DtypeType = float,
path: Optional[Path] = None,
array: Optional[NDArrayType] = None,
) -> Optional[H5ArrayPath]:
if cls.skip(shape, dtype):
return None
Expand All @@ -67,7 +71,9 @@ def make_array(
array_path = "/" + "_".join([str(s) for s in shape]) + "__" + dtype.__name__
generator = np.random.default_rng()

if dtype is str:
if array is not None:
data = np.array(array, dtype=dtype)
elif dtype is str:
data = generator.random(shape).astype(bytes)
elif dtype is datetime:
data = np.empty(shape, dtype="S32")
Expand All @@ -91,13 +97,16 @@ def make_array(
shape: Tuple[int, ...] = (10, 10),
dtype: DtypeType = float,
path: Optional[Path] = None,
array: Optional[NDArrayType] = None,
) -> Optional[H5ArrayPath]:
if cls.skip(shape, dtype):
return None

hdf5_file = path / "h5f.h5"
array_path = "/" + "_".join([str(s) for s in shape]) + "__" + dtype.__name__
if dtype is str:
if array is not None:
data = np.array(array, dtype=dtype)
elif dtype is str:
dt = np.dtype([("data", np.dtype("S10")), ("extra", "i8")])
data = np.array([("hey", 0)] * np.prod(shape), dtype=dt).reshape(shape)
elif dtype is datetime:
Expand Down Expand Up @@ -128,7 +137,10 @@ def make_array(
shape: Tuple[int, ...] = (10, 10),
dtype: DtypeType = float,
path: Optional[Path] = None,
array: Optional[NDArrayType] = None,
) -> da.Array:
if array is not None:
return da.array(array, dtype=dtype, chunks=-1)
if issubclass(dtype, BaseModel):
return da.full(shape=shape, fill_value=dtype(x=1), chunks=-1)
else:
Expand All @@ -142,7 +154,7 @@ class _ZarrMetaCase(InterfaceCase):

@classmethod
def skip(cls, shape: Tuple[int, ...], dtype: DtypeType) -> bool:
return not issubclass(dtype, BaseModel)
return issubclass(dtype, BaseModel)


class ZarrCase(_ZarrMetaCase):
Expand All @@ -154,8 +166,12 @@ def make_array(
shape: Tuple[int, ...] = (10, 10),
dtype: DtypeType = float,
path: Optional[Path] = None,
array: Optional[NDArrayType] = None,
) -> Optional[zarr.Array]:
return zarr.zeros(shape=shape, dtype=dtype)
if array is not None:
return zarr.array(array, dtype=dtype, chunks=-1)
else:
return zarr.zeros(shape=shape, dtype=dtype)


class ZarrDirCase(_ZarrMetaCase):
Expand All @@ -167,9 +183,13 @@ def make_array(
shape: Tuple[int, ...] = (10, 10),
dtype: DtypeType = float,
path: Optional[Path] = None,
array: Optional[NDArrayType] = None,
) -> Optional[zarr.Array]:
store = zarr.DirectoryStore(str(path / "array.zarr"))
return zarr.zeros(shape=shape, dtype=dtype, store=store)
if array is not None:
return zarr.array(array, dtype=dtype, store=store, chunks=-1)
else:
return zarr.zeros(shape=shape, dtype=dtype, store=store)


class ZarrZipCase(_ZarrMetaCase):
Expand All @@ -181,9 +201,13 @@ def make_array(
shape: Tuple[int, ...] = (10, 10),
dtype: DtypeType = float,
path: Optional[Path] = None,
array: Optional[NDArrayType] = None,
) -> Optional[zarr.Array]:
store = zarr.ZipStore(str(path / "array.zarr"), mode="w")
return zarr.zeros(shape=shape, dtype=dtype, store=store)
if array is not None:
return zarr.array(array, dtype=dtype, store=store, chunks=-1)
else:
return zarr.zeros(shape=shape, dtype=dtype, store=store)


class ZarrNestedCase(_ZarrMetaCase):
Expand All @@ -195,11 +219,15 @@ def make_array(
shape: Tuple[int, ...] = (10, 10),
dtype: DtypeType = float,
path: Optional[Path] = None,
array: Optional[NDArrayType] = None,
) -> ZarrArrayPath:
file = str(path / "nested.zarr")
root = zarr.open(file, mode="w")
subpath = "a/b/c"
_ = root.zeros(subpath, shape=shape, dtype=dtype)
if array is not None:
_ = root.array(subpath, array, dtype=dtype)
else:
_ = root.zeros(subpath, shape=shape, dtype=dtype)
return ZarrArrayPath(file=file, path=subpath)


Expand All @@ -214,10 +242,15 @@ def make_array(
shape: Tuple[int, ...] = (10, 10),
dtype: DtypeType = float,
path: Optional[Path] = None,
array: Optional[NDArrayType] = None,
) -> Optional[Path]:
if cls.skip(shape, dtype):
return None

if array is not None:
array = np.ndarray(shape, dtype=np.uint8)
shape = array.shape

is_color = len(shape) == 4
frames = shape[0]
frame_shape = shape[1:]
Expand All @@ -232,13 +265,16 @@ def make_array(
)

for i in range(frames):
# make fresh array every time bc opencv eats them
array = np.zeros(frame_shape, dtype=np.uint8)
if not is_color:
array[i, i] = i
if array is not None:
frame = array[i]
else:
array[i, i, :] = i
writer.write(array)
# make fresh array every time bc opencv eats them
frame = np.zeros(frame_shape, dtype=np.uint8)
if not is_color:
frame[i, i] = i
else:
frame[i, i, :] = i
writer.write(frame)
writer.release()
return video_path

Expand Down
5 changes: 2 additions & 3 deletions tests/fixtures/generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

from numpydantic.interface.hdf5 import H5ArrayPath
from numpydantic.interface.zarr import ZarrArrayPath
from numpydantic.testing import ValidationCase
from numpydantic.testing.interfaces import HDF5Case, HDF5CompoundCase, VideoCase


Expand Down Expand Up @@ -57,8 +56,8 @@ def _make_video(shape=(100, 50), frames=10, is_color=True) -> Path:
shape = (frames, *shape)
if is_color:
shape = (*shape, 3)
return VideoCase.array_from_case(
ValidationCase(shape=shape, dtype=np.uint8), tmp_output_dir_func
return VideoCase.make_array(
shape=shape, dtype=np.uint8, path=tmp_output_dir_func
)

return _make_video
Loading

0 comments on commit 5d4f03a

Please sign in to comment.