Skip to content

Commit

Permalink
create testing module, add to docs
Browse files Browse the repository at this point in the history
  • Loading branch information
sneakers-the-rat committed Oct 4, 2024
1 parent 124024f commit f291afb
Show file tree
Hide file tree
Showing 11 changed files with 199 additions and 166 deletions.
7 changes: 7 additions & 0 deletions docs/api/testing/cases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# cases

```{eval-rst}
.. automodule:: numpydantic.testing.cases
:members:
:undoc-members:
```
7 changes: 7 additions & 0 deletions docs/api/testing/helpers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# helpers

```{eval-rst}
.. automodule:: numpydantic.testing.helpers
:members:
:undoc-members:
```
8 changes: 8 additions & 0 deletions docs/api/testing/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# testing

Utilities for testing and 3rd-party interface development.

```{toctree}
cases
helpers
```
Empty file.
132 changes: 132 additions & 0 deletions src/numpydantic/testing/cases.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import sys
from typing import TypeAlias, 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

if sys.version_info.minor >= 10:
from typing import TypeAlias

YES_PIPE = True
else:
from typing_extensions import TypeAlias

YES_PIPE = False



class BasicModel(BaseModel):
x: int


class BadModel(BaseModel):
x: int


class SubClass(BasicModel):
pass


RGB_UNION: TypeAlias = Union[
NDArray[Shape["* x, * y"], Number],
NDArray[Shape["* x, * y, 3 r_g_b"], Number],
NDArray[Shape["* x, * y, 3 r_g_b, 4 r_g_b_a"], Number],
]
NUMBER: TypeAlias = NDArray[Shape["*, *, *"], Number]
INTEGER: TypeAlias = NDArray[Shape["*, *, *"], Integer]
FLOAT: TypeAlias = NDArray[Shape["*, *, *"], Float]
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]
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),
]


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",
]

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",
]
)

39 changes: 39 additions & 0 deletions src/numpydantic/testing/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from typing import Any, Tuple, Type, Union

from pydantic import BaseModel, ConfigDict, computed_field

from numpydantic import NDArray, Shape
from numpydantic.dtype import Float


class ValidationCase(BaseModel):
"""
Test case for validating an array.
Contains both the validating model and the parameterization for an array to
test in a given interface
"""

annotation: Any = NDArray[Shape["10, 10, *"], Float]
"""
Array annotation used in the validating model
Any typed because the types of type annotations are weird
"""
shape: Tuple[int, ...] = (10, 10, 10)
"""Shape of the array to validate"""
dtype: Union[Type, np.dtype] = float
"""Dtype of the array to validate"""
passes: bool
"""Whether the validation should pass or not"""

model_config = ConfigDict(arbitrary_types_allowed=True)

@computed_field()
def model(self) -> Type[BaseModel]:
"""A model with a field ``array`` with the given annotation"""
annotation = self.annotation

class Model(BaseModel):
array: annotation

return Model
164 changes: 2 additions & 162 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,10 @@
import sys

import pytest
from typing import Any, Tuple, Union, Type

from pydantic import BaseModel, computed_field, ConfigDict
from numpydantic import NDArray, Shape
from numpydantic.ndarray import NDArrayMeta
from numpydantic.dtype import Float, Number, Integer
import numpy as np
from numpydantic.testing.cases import YES_PIPE, RGB_UNION, UNION_PIPE, DTYPE_CASES, DTYPE_IDS

from numpydantic.testing.helpers import ValidationCase
from tests.fixtures import *

if sys.version_info.minor >= 10:
from typing import TypeAlias

YES_PIPE = True
else:
from typing_extensions import TypeAlias

YES_PIPE = False


def pytest_addoption(parser):
parser.addoption(
Expand All @@ -29,66 +14,8 @@ def pytest_addoption(parser):
)


class ValidationCase(BaseModel):
"""
Test case for validating an array.
Contains both the validating model and the parameterization for an array to
test in a given interface
"""

annotation: Any = NDArray[Shape["10, 10, *"], Float]
"""
Array annotation used in the validating model
Any typed because the types of type annotations are weird
"""
shape: Tuple[int, ...] = (10, 10, 10)
"""Shape of the array to validate"""
dtype: Union[Type, np.dtype] = float
"""Dtype of the array to validate"""
passes: bool
"""Whether the validation should pass or not"""

model_config = ConfigDict(arbitrary_types_allowed=True)

@computed_field()
def model(self) -> Type[BaseModel]:
"""A model with a field ``array`` with the given annotation"""
annotation = self.annotation

class Model(BaseModel):
array: annotation

return Model


class BasicModel(BaseModel):
x: int


class BadModel(BaseModel):
x: int


class SubClass(BasicModel):
pass


RGB_UNION: TypeAlias = Union[
NDArray[Shape["* x, * y"], Number],
NDArray[Shape["* x, * y, 3 r_g_b"], Number],
NDArray[Shape["* x, * y, 3 r_g_b, 4 r_g_b_a"], Number],
]

NUMBER: TypeAlias = NDArray[Shape["*, *, *"], Number]
INTEGER: TypeAlias = NDArray[Shape["*, *, *"], Integer]
FLOAT: TypeAlias = NDArray[Shape["*, *, *"], Float]
STRING: TypeAlias = NDArray[Shape["*, *, *"], str]
MODEL: TypeAlias = NDArray[Shape["*, *, *"], BasicModel]
UNION_TYPE: TypeAlias = NDArray[Shape["*, *, *"], Union[np.uint32, np.float32]]
if YES_PIPE:
UNION_PIPE: TypeAlias = NDArray[Shape["*, *, *"], np.uint32 | np.float32]


@pytest.fixture(
scope="module",
Expand Down Expand Up @@ -127,93 +54,6 @@ def shape_cases(request) -> ValidationCase:
return request.param


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),
]

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",
]

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",
]
)


@pytest.fixture(scope="module", params=DTYPE_CASES, ids=DTYPE_IDS)
def dtype_cases(request) -> ValidationCase:
return request.param
2 changes: 1 addition & 1 deletion tests/test_interface/test_dask.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from numpydantic.interface import DaskInterface
from numpydantic.exceptions import DtypeError, ShapeError

from tests.conftest import ValidationCase
from numpydantic.testing.helpers import ValidationCase

pytestmark = pytest.mark.dask

Expand Down
Loading

0 comments on commit f291afb

Please sign in to comment.