Skip to content

Commit

Permalink
interface cases
Browse files Browse the repository at this point in the history
  • Loading branch information
sneakers-the-rat committed Oct 4, 2024
1 parent ad060ce commit e701bf6
Show file tree
Hide file tree
Showing 8 changed files with 542 additions and 123 deletions.
9 changes: 9 additions & 0 deletions docs/api/testing/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

Utilities for testing and 3rd-party interface development.

Only things that *don't* require pytest go in this module.
We want to keep all test-time specific behavior there,
and have this just serve as helpers exposed for downstream interface development.

We want to avoid pytest stuff bleeding in here because then we limit
the ability for downstream developers to configure their own tests.

*(If there is some reason to change this division of labor, just raise an issue and let's chat.)*

```{toctree}
cases
helpers
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ markers = [
"zarr: zarr interface",
]

[tool.black]
target-version = ["py39", "py310", "py311", "py312"]

[tool.ruff]
target-version = "py39"
include = ["src/numpydantic/**/*.py", "tests/**/*.py", "pyproject.toml"]
Expand Down
6 changes: 4 additions & 2 deletions src/numpydantic/interface/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""

from numpydantic.interface.dask import DaskInterface
from numpydantic.interface.hdf5 import H5Interface
from numpydantic.interface.hdf5 import H5ArrayPath, H5Interface
from numpydantic.interface.interface import (
Interface,
InterfaceMark,
Expand All @@ -12,16 +12,18 @@
)
from numpydantic.interface.numpy import NumpyInterface
from numpydantic.interface.video import VideoInterface
from numpydantic.interface.zarr import ZarrInterface
from numpydantic.interface.zarr import ZarrArrayPath, ZarrInterface

__all__ = [
"DaskInterface",
"H5ArrayPath",
"H5Interface",
"Interface",
"InterfaceMark",
"JsonDict",
"MarkedJson",
"NumpyInterface",
"VideoInterface",
"ZarrArrayPath",
"ZarrInterface",
]
6 changes: 6 additions & 0 deletions src/numpydantic/testing/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from numpydantic.testing.helpers import InterfaceCase, ValidationCase

__all__ = [
"InterfaceCase",
"ValidationCase",
]
245 changes: 166 additions & 79 deletions src/numpydantic/testing/cases.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
import sys
from typing import Union
from collections.abc import Sequence
from itertools import product
from typing import Generator, Union

import numpy as np
from pydantic import BaseModel

from numpydantic import NDArray, Shape
from numpydantic.dtype import Float, Integer, Number
from numpydantic.testing.helpers import ValidationCase
from numpydantic.testing.helpers import ValidationCase, merge_cases
from numpydantic.testing.interfaces import (
DaskCase,
HDF5Case,
HDF5CompoundCase,
NumpyCase,
VideoCase,
ZarrCase,
ZarrDirCase,
ZarrNestedCase,
ZarrZipCase,
)

if sys.version_info.minor >= 10:
from typing import TypeAlias
Expand All @@ -30,6 +43,10 @@ class SubClass(BasicModel):
pass


# --------------------------------------------------
# Annotations
# --------------------------------------------------

RGB_UNION: TypeAlias = Union[
NDArray[Shape["* x, * y"], Number],
NDArray[Shape["* x, * y, 3 r_g_b"], Number],
Expand All @@ -42,89 +59,159 @@ class SubClass(BasicModel):
MODEL: TypeAlias = NDArray[Shape["*, *, *"], BasicModel]
UNION_TYPE: TypeAlias = NDArray[Shape["*, *, *"], Union[np.uint32, np.float32]]
UNION_PIPE: TypeAlias = NDArray[Shape["*, *, *"], np.uint32 | np.float32]
DTYPE_CASES = [
ValidationCase(dtype=float, passes=True),
ValidationCase(dtype=int, passes=False),
ValidationCase(dtype=np.uint8, passes=False),
ValidationCase(annotation=NUMBER, dtype=int, passes=True),
ValidationCase(annotation=NUMBER, dtype=float, passes=True),
ValidationCase(annotation=NUMBER, dtype=np.uint8, passes=True),
ValidationCase(annotation=NUMBER, dtype=np.float16, passes=True),
ValidationCase(annotation=NUMBER, dtype=str, passes=False),
ValidationCase(annotation=INTEGER, dtype=int, passes=True),
ValidationCase(annotation=INTEGER, dtype=np.uint8, passes=True),
ValidationCase(annotation=INTEGER, dtype=float, passes=False),
ValidationCase(annotation=INTEGER, dtype=np.float32, passes=False),
ValidationCase(annotation=INTEGER, dtype=str, passes=False),
ValidationCase(annotation=FLOAT, dtype=float, passes=True),
ValidationCase(annotation=FLOAT, dtype=np.float32, passes=True),
ValidationCase(annotation=FLOAT, dtype=int, passes=False),
ValidationCase(annotation=FLOAT, dtype=np.uint8, passes=False),
ValidationCase(annotation=FLOAT, dtype=str, passes=False),
ValidationCase(annotation=STRING, dtype=str, passes=True),
ValidationCase(annotation=STRING, dtype=int, passes=False),
ValidationCase(annotation=STRING, dtype=float, passes=False),
ValidationCase(annotation=MODEL, dtype=BasicModel, passes=True),
ValidationCase(annotation=MODEL, dtype=BadModel, passes=False),
ValidationCase(annotation=MODEL, dtype=int, passes=False),
ValidationCase(annotation=MODEL, dtype=SubClass, passes=True),
ValidationCase(annotation=UNION_TYPE, dtype=np.uint32, passes=True),
ValidationCase(annotation=UNION_TYPE, dtype=np.float32, passes=True),
ValidationCase(annotation=UNION_TYPE, dtype=np.uint64, passes=False),
ValidationCase(annotation=UNION_TYPE, dtype=np.float64, passes=False),
ValidationCase(annotation=UNION_TYPE, dtype=str, passes=False),
]

SHAPE_CASES = (
ValidationCase(shape=(10, 10, 10), passes=True, id="valid shape"),
ValidationCase(shape=(10, 10), passes=False, id="missing dimension"),
ValidationCase(shape=(10, 10, 10, 10), passes=False, id="extra dimension"),
ValidationCase(shape=(11, 10, 10), passes=False, id="dimension too large"),
ValidationCase(shape=(9, 10, 10), passes=False, id="dimension too small"),
ValidationCase(shape=(10, 10, 9), passes=True, id="wildcard smaller"),
ValidationCase(shape=(10, 10, 11), passes=True, id="wildcard larger"),
ValidationCase(annotation=RGB_UNION, shape=(5, 5), passes=True, id="Union 2D"),
ValidationCase(annotation=RGB_UNION, shape=(5, 5, 3), passes=True, id="Union 3D"),
ValidationCase(
annotation=RGB_UNION, shape=(5, 5, 3, 4), passes=True, id="Union 4D"
),
ValidationCase(
annotation=RGB_UNION, shape=(5, 5, 4), passes=False, id="Union incorrect 3D"
),
ValidationCase(
annotation=RGB_UNION, shape=(5, 5, 3, 6), passes=False, id="Union incorrect 4D"
),
ValidationCase(
annotation=RGB_UNION,
shape=(5, 5, 4, 6),
passes=False,
id="Union incorrect both",
),
)


DTYPE_IDS = [
"float",
"int",
"uint8",
"number-int",
"number-float",
"number-uint8",
"number-float16",
"number-str",
"integer-int",
"integer-uint8",
"integer-float",
"integer-float32",
"integer-str",
"float-float",
"float-float32",
"float-int",
"float-uint8",
"float-str",
"str-str",
"str-int",
"str-float",
"model-model",
"model-badmodel",
"model-int",
"model-subclass",
"union-type-uint32",
"union-type-float32",
"union-type-uint64",
"union-type-float64",
"union-type-str",
DTYPE_CASES = [
ValidationCase(dtype=float, passes=True, id="float"),
ValidationCase(dtype=int, passes=False, id="int"),
ValidationCase(dtype=np.uint8, passes=False, id="uint8"),
ValidationCase(annotation=NUMBER, dtype=int, passes=True, id="number-int"),
ValidationCase(annotation=NUMBER, dtype=float, passes=True, id="number-float"),
ValidationCase(annotation=NUMBER, dtype=np.uint8, passes=True, id="number-uint8"),
ValidationCase(
annotation=NUMBER, dtype=np.float16, passes=True, id="number-float16"
),
ValidationCase(annotation=NUMBER, dtype=str, passes=False, id="number-str"),
ValidationCase(annotation=INTEGER, dtype=int, passes=True, id="integer-int"),
ValidationCase(annotation=INTEGER, dtype=np.uint8, passes=True, id="integer-uint8"),
ValidationCase(annotation=INTEGER, dtype=float, passes=False, id="integer-float"),
ValidationCase(
annotation=INTEGER, dtype=np.float32, passes=False, id="integer-float32"
),
ValidationCase(annotation=INTEGER, dtype=str, passes=False, id="integer-str"),
ValidationCase(annotation=FLOAT, dtype=float, passes=True, id="float-float"),
ValidationCase(annotation=FLOAT, dtype=np.float32, passes=True, id="float-float32"),
ValidationCase(annotation=FLOAT, dtype=int, passes=False, id="float-int"),
ValidationCase(annotation=FLOAT, dtype=np.uint8, passes=False, id="float-uint8"),
ValidationCase(annotation=FLOAT, dtype=str, passes=False, id="float-str"),
ValidationCase(annotation=STRING, dtype=str, passes=True, id="str-str"),
ValidationCase(annotation=STRING, dtype=int, passes=False, id="str-int"),
ValidationCase(annotation=STRING, dtype=float, passes=False, id="str-float"),
ValidationCase(annotation=MODEL, dtype=BasicModel, passes=True, id="model-model"),
ValidationCase(annotation=MODEL, dtype=BadModel, passes=False, id="model-badmodel"),
ValidationCase(annotation=MODEL, dtype=int, passes=False, id="model-int"),
ValidationCase(annotation=MODEL, dtype=SubClass, passes=True, id="model-subclass"),
ValidationCase(
annotation=UNION_TYPE, dtype=np.uint32, passes=True, id="union-type-uint32"
),
ValidationCase(
annotation=UNION_TYPE, dtype=np.float32, passes=True, id="union-type-float32"
),
ValidationCase(
annotation=UNION_TYPE, dtype=np.uint64, passes=False, id="union-type-uint64"
),
ValidationCase(
annotation=UNION_TYPE, dtype=np.float64, passes=False, id="union-type-float64"
),
ValidationCase(annotation=UNION_TYPE, dtype=str, passes=False, id="union-type-str"),
]


if YES_PIPE:
DTYPE_CASES.extend(
[
ValidationCase(annotation=UNION_PIPE, dtype=np.uint32, passes=True),
ValidationCase(annotation=UNION_PIPE, dtype=np.float32, passes=True),
ValidationCase(annotation=UNION_PIPE, dtype=np.uint64, passes=False),
ValidationCase(annotation=UNION_PIPE, dtype=np.float64, passes=False),
ValidationCase(annotation=UNION_PIPE, dtype=str, passes=False),
]
)
DTYPE_IDS.extend(
[
"union-pipe-uint32",
"union-pipe-float32",
"union-pipe-uint64",
"union-pipe-float64",
"union-pipe-str",
ValidationCase(
annotation=UNION_PIPE,
dtype=np.uint32,
passes=True,
id="union-pipe-uint32",
),
ValidationCase(
annotation=UNION_PIPE,
dtype=np.float32,
passes=True,
id="union-pipe-float32",
),
ValidationCase(
annotation=UNION_PIPE,
dtype=np.uint64,
passes=False,
id="union-pipe-uint64",
),
ValidationCase(
annotation=UNION_PIPE,
dtype=np.float64,
passes=False,
id="union-pipe-float64",
),
ValidationCase(
annotation=UNION_PIPE, dtype=str, passes=False, id="union-pipe-str"
),
]
)

_INTERFACE_CASES = [
NumpyCase,
HDF5Case,
HDF5CompoundCase,
DaskCase,
ZarrCase,
ZarrDirCase,
ZarrZipCase,
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
Loading

0 comments on commit e701bf6

Please sign in to comment.