mirror of
https://github.com/p2p-ld/numpydantic.git
synced 2024-11-14 18:54:28 +00:00
some docs
This commit is contained in:
parent
b0a63af95a
commit
189a3e7919
11 changed files with 325 additions and 0 deletions
31
docs/_static/css/notebooks.css
vendored
Normal file
31
docs/_static/css/notebooks.css
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
div.cell.tag_hide-cell details.above-input > summary,
|
||||||
|
div.cell.tag_hide-input details.above-input > summary,
|
||||||
|
div.cell.tag_hide-output details.below-input > summary{
|
||||||
|
background-color: var(--color-admonition-title-background--admonition-todo);
|
||||||
|
color: var(--color-content-foreground);
|
||||||
|
border: unset;
|
||||||
|
border-left: 2px solid var(--mystnb-source-margin-color);
|
||||||
|
opacity: unset;
|
||||||
|
padding: 0.25em 0 0.25em 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.cell.tag_hide-cell details.above-input > summary > span,
|
||||||
|
div.cell.tag_hide-input details.above-input > summary > span,
|
||||||
|
div.cell.tag_hide-output details.below-input > summary > span
|
||||||
|
{
|
||||||
|
opacity: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.cell details.above-input div.cell_input {
|
||||||
|
border: unset;
|
||||||
|
background-color: unset;
|
||||||
|
border-left: 2px solid var(--mystnb-source-margin-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.cell details.above-input div.cell_input div.highlight {
|
||||||
|
background: var(--color-admonition-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.output.text_html pre {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
|
@ -2,7 +2,18 @@
|
||||||
|
|
||||||
Utilities for testing and 3rd-party interface development.
|
Utilities for testing and 3rd-party interface development.
|
||||||
|
|
||||||
|
See also the [narrative testing docs](../../contributing/testing.md)
|
||||||
|
|
||||||
```{toctree}
|
```{toctree}
|
||||||
|
:maxdepth: 2
|
||||||
|
|
||||||
cases
|
cases
|
||||||
helpers
|
helpers
|
||||||
|
interfaces
|
||||||
|
```
|
||||||
|
|
||||||
|
```{eval-rst}
|
||||||
|
.. automodule:: numpydantic.testing
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
```
|
```
|
7
docs/api/testing/interfaces.md
Normal file
7
docs/api/testing/interfaces.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# interfaces
|
||||||
|
|
||||||
|
```{eval-rst}
|
||||||
|
.. automodule:: numpydantic.testing.interfaces
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
```
|
|
@ -49,6 +49,7 @@ intersphinx_mapping = {
|
||||||
|
|
||||||
html_theme = "furo"
|
html_theme = "furo"
|
||||||
html_static_path = ["_static"]
|
html_static_path = ["_static"]
|
||||||
|
html_css_files = ["css/notebooks.css"]
|
||||||
|
|
||||||
# autodoc
|
# autodoc
|
||||||
autodoc_pydantic_model_show_json_error_strategy = "coerce"
|
autodoc_pydantic_model_show_json_error_strategy = "coerce"
|
||||||
|
|
5
docs/contributing/coc.md
Normal file
5
docs/contributing/coc.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# Code of Conduct
|
||||||
|
|
||||||
|
```{todo}
|
||||||
|
jonny write the code of conduct
|
||||||
|
```
|
8
docs/contributing/index.md
Normal file
8
docs/contributing/index.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
```{toctree}
|
||||||
|
coc
|
||||||
|
process
|
||||||
|
interface
|
||||||
|
testing
|
||||||
|
```
|
5
docs/contributing/interface.md
Normal file
5
docs/contributing/interface.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# Writing an Interface
|
||||||
|
|
||||||
|
```{todo}
|
||||||
|
Jonny write the interface contrib docs
|
||||||
|
```
|
15
docs/contributing/process.md
Normal file
15
docs/contributing/process.md
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# Contribution Process
|
||||||
|
|
||||||
|
```{todo}
|
||||||
|
Jonny write the contribution docs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issues
|
||||||
|
|
||||||
|
### Development Environment
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
### Linting
|
||||||
|
|
||||||
|
### Pull Requests
|
213
docs/contributing/testing.md
Normal file
213
docs/contributing/testing.md
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
---
|
||||||
|
file_format: mystnb
|
||||||
|
mystnb:
|
||||||
|
output_stderr: remove
|
||||||
|
render_text_lexer: python
|
||||||
|
render_markdown_format: myst
|
||||||
|
myst:
|
||||||
|
enable_extensions: ["colon_fence"]
|
||||||
|
---
|
||||||
|
# Testing
|
||||||
|
|
||||||
|
```{code-cell}
|
||||||
|
---
|
||||||
|
tags: [hide-cell]
|
||||||
|
---
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.theme import Theme
|
||||||
|
from rich.style import Style
|
||||||
|
from rich.color import Color
|
||||||
|
|
||||||
|
theme = Theme({
|
||||||
|
"repr.call": Style(color=Color.from_rgb(110,191,38), bold=True),
|
||||||
|
"repr.attrib_name": Style(color="slate_blue1"),
|
||||||
|
"repr.number": Style(color="deep_sky_blue1"),
|
||||||
|
"repr.none": Style(color="bright_magenta", italic=True),
|
||||||
|
"repr.attrib_name": Style(color="white"),
|
||||||
|
"repr.tag_contents": Style(color="light_steel_blue"),
|
||||||
|
"repr.str": Style(color="violet")
|
||||||
|
})
|
||||||
|
console = Console(theme=theme)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```{note}
|
||||||
|
Also see the [`numpydantic.testing` API docs](../api/testing/index.md)
|
||||||
|
and the [Writing an Interface](../interfaces.md) guide
|
||||||
|
```
|
||||||
|
|
||||||
|
Numpydantic exposes a system for combinatoric testing across dtypes, shapes,
|
||||||
|
and interfaces in the {mod}`numpydantic.testing` module.
|
||||||
|
|
||||||
|
These helper classes and functions are included in the distributed package
|
||||||
|
so they can be used for downstream development of independent interfaces
|
||||||
|
(though we always welcome contributions!)
|
||||||
|
|
||||||
|
## Validation Cases
|
||||||
|
|
||||||
|
Each test case is parameterized by a {class}`.ValidationCase`.
|
||||||
|
|
||||||
|
The case is intended to be able to be partially filled in so that multiple
|
||||||
|
validation cases can be merged together, but also used independently
|
||||||
|
by falling back on default values.
|
||||||
|
|
||||||
|
There are three major parts to a validation case:
|
||||||
|
|
||||||
|
- **Annotation specification:** {attr}`~.ValidationCase.annotation_dtype` and
|
||||||
|
{attr}`~.ValidationCase.annotation_shape` specifies how the
|
||||||
|
{class}`.NDArray` {attr}`.ValidationCase.annotation` that is used to test
|
||||||
|
against is generated
|
||||||
|
- **Array specification:** {attr}`~.ValidationCase.dtype` and {attr}`~.ValidationCase.shape`
|
||||||
|
specify that array that will be generated to test against the annotation
|
||||||
|
- **Interface specification:** An {class}`.InterfaceCase` that refers to
|
||||||
|
an {class}`.Interface`, and provides array generation and other auxilary logic.
|
||||||
|
|
||||||
|
Typically, one specifies a dtype along with an annotation dtype or
|
||||||
|
a shape along with an annotation shape (or implicitly against the defaults for either),
|
||||||
|
along with a value for `passes` that indicates if that combination is valid.
|
||||||
|
|
||||||
|
```{code-cell}
|
||||||
|
from numpydantic.testing import ValidationCase
|
||||||
|
|
||||||
|
dtype_case = ValidationCase(
|
||||||
|
id="int_int",
|
||||||
|
dtype=int,
|
||||||
|
annotation_dtype=int,
|
||||||
|
passes=True
|
||||||
|
)
|
||||||
|
shape_case = ValidationCase(
|
||||||
|
id="cool_shape",
|
||||||
|
shape=(1,2,3),
|
||||||
|
annotation_shape=(1,"*","2-4"),
|
||||||
|
passes=True
|
||||||
|
)
|
||||||
|
|
||||||
|
merged = dtype_case.merge(shape_case)
|
||||||
|
console.print(merged.model_dump(exclude={'annotation', 'model'}, exclude_unset=True))
|
||||||
|
```
|
||||||
|
|
||||||
|
When merging validation cases, the merged case only `passes` if all the
|
||||||
|
original cases do.
|
||||||
|
|
||||||
|
```{code-cell}
|
||||||
|
from numpydantic.testing import ValidationCase
|
||||||
|
|
||||||
|
dtype_case = ValidationCase(
|
||||||
|
id="int_int",
|
||||||
|
dtype=int,
|
||||||
|
annotation_dtype=int,
|
||||||
|
passes=True
|
||||||
|
)
|
||||||
|
shape_case = ValidationCase(
|
||||||
|
id="uncool_shape",
|
||||||
|
shape=(1,2,3),
|
||||||
|
annotation_shape=(9,8,7),
|
||||||
|
passes=False
|
||||||
|
)
|
||||||
|
|
||||||
|
merged = dtype_case.merge(shape_case)
|
||||||
|
console.print(merged.model_dump(exclude={'annotation', 'model'}, exclude_unset=True))
|
||||||
|
```
|
||||||
|
|
||||||
|
We provide a convenience function {func}`.merged_product` for creating a merged product of
|
||||||
|
multiple sets of test cases.
|
||||||
|
|
||||||
|
For example, you may want to create a set of dtype and shape cases and validate
|
||||||
|
against all combinations
|
||||||
|
|
||||||
|
```{code-cell}
|
||||||
|
from numpydantic.testing.helpers import merged_product
|
||||||
|
|
||||||
|
dtype_cases = [
|
||||||
|
ValidationCase(dtype=int, annotation_dtype=int, passes=True),
|
||||||
|
ValidationCase(dtype=int, annotation_dtype=float, passes=False)
|
||||||
|
]
|
||||||
|
shape_cases = [
|
||||||
|
ValidationCase(shape=(1,2,3), annotation_shape=(1,2,3), passes=True),
|
||||||
|
ValidationCase(shape=(4,5,6), annotation_shape=(1,2,3), passes=False)
|
||||||
|
]
|
||||||
|
|
||||||
|
iterator = merged_product(dtype_cases, shape_cases)
|
||||||
|
|
||||||
|
console.print([i.model_dump(exclude_unset=True, exclude={'model', 'annotation'}) for i in iterator])
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
You can pass constraints to the {func}`.merged_product` iterator to
|
||||||
|
filter cases that match some value, for example to get only the cases that pass:
|
||||||
|
|
||||||
|
```{code-cell}
|
||||||
|
iterator = merged_product(dtype_cases, shape_cases, conditions={"passes": True})
|
||||||
|
console.print([i.model_dump(exclude_unset=True, exclude={'model', 'annotation'}) for i in iterator])
|
||||||
|
```
|
||||||
|
|
||||||
|
## Interface Cases
|
||||||
|
|
||||||
|
Validation cases can be paired with interface cases that handle
|
||||||
|
generating arrays for the given interface from the specification in the
|
||||||
|
validation case.
|
||||||
|
|
||||||
|
Since some array interfaces like Zarr have multiple possible forms
|
||||||
|
of an array (in memory, on disk, in a zip file, etc.) an interface
|
||||||
|
may have multiple cases that are important to test against.
|
||||||
|
|
||||||
|
The {meth}`.InterfaceCase.make_array` method does what you'd expect it to,
|
||||||
|
creating an array, and returning the appropriate input type for the interface:
|
||||||
|
|
||||||
|
```{code-cell}
|
||||||
|
from numpydantic.testing.interfaces import NumpyCase, ZarrNestedCase
|
||||||
|
|
||||||
|
NumpyCase.make_array(shape=(1,2,3), dtype=float)
|
||||||
|
```
|
||||||
|
|
||||||
|
```{code-cell}
|
||||||
|
ZarrNestedCase.make_array(shape=(1,2,3), dtype=float, path=Path("__tmp__/zarr_dir"))
|
||||||
|
```
|
||||||
|
|
||||||
|
Interface cases also define when an interface should skip a given test
|
||||||
|
parameterization. For example, some array formats can't support arbitrary
|
||||||
|
object serialization, and the video class can only support 8-bit arrays
|
||||||
|
of a specific shape
|
||||||
|
|
||||||
|
```{code-cell}
|
||||||
|
from numpydantic.testing.interfaces import VideoCase
|
||||||
|
|
||||||
|
VideoCase.skip(shape=(1,1), dtype=float)
|
||||||
|
```
|
||||||
|
|
||||||
|
This, and the array generation methods are propagated up into
|
||||||
|
a ValidationCase that contains them
|
||||||
|
|
||||||
|
```{code-cell}
|
||||||
|
case = ValidationCase(shape=(1,2,3), dtype=float, interface=VideoCase)
|
||||||
|
case.skip()
|
||||||
|
```
|
||||||
|
|
||||||
|
The {func}`.merged_product` iterator automatically excludes any
|
||||||
|
combinations of interfaces and test parameterizations that should be skipped.
|
||||||
|
|
||||||
|
## Making Fixtures
|
||||||
|
|
||||||
|
Pytest fixtures are a useful way to re-use validation case products.
|
||||||
|
To keep things tidy, you may want to use marks and ids when creating them
|
||||||
|
so that you can run tests against specific interfaces or conditions
|
||||||
|
with the `pytest -m mark` system.
|
||||||
|
|
||||||
|
```python
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.fixture(
|
||||||
|
params=(
|
||||||
|
pytest.param(
|
||||||
|
p,
|
||||||
|
id=p.id,
|
||||||
|
marks=getattr(pytest.mark, p.interface.interface.name)
|
||||||
|
)
|
||||||
|
for p in iterator
|
||||||
|
)
|
||||||
|
)
|
||||||
|
def my_cases(request):
|
||||||
|
return request.param
|
||||||
|
```
|
|
@ -514,6 +514,7 @@ api/meta
|
||||||
api/schema
|
api/schema
|
||||||
api/serialization
|
api/serialization
|
||||||
api/types
|
api/types
|
||||||
|
api/testing/index
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -523,6 +524,7 @@ api/types
|
||||||
:hidden: true
|
:hidden: true
|
||||||
|
|
||||||
changelog
|
changelog
|
||||||
|
contributing/index
|
||||||
development
|
development
|
||||||
todo
|
todo
|
||||||
```
|
```
|
||||||
|
|
|
@ -83,6 +83,9 @@ SHAPE_CASES = (
|
||||||
id="Union incorrect both",
|
id="Union incorrect both",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
"""
|
||||||
|
Base Shape cases
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
DTYPE_CASES = [
|
DTYPE_CASES = [
|
||||||
|
@ -163,6 +166,9 @@ DTYPE_CASES = [
|
||||||
annotation_dtype=UNION_TYPE, dtype=str, passes=False, id="union-type-str"
|
annotation_dtype=UNION_TYPE, dtype=str, passes=False, id="union-type-str"
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
"""
|
||||||
|
Base Dtype cases
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
if YES_PIPE:
|
if YES_PIPE:
|
||||||
|
@ -214,19 +220,40 @@ INTERFACE_CASES = [
|
||||||
ValidationCase(interface=ZarrNestedCase, id="zarr_nested"),
|
ValidationCase(interface=ZarrNestedCase, id="zarr_nested"),
|
||||||
ValidationCase(interface=VideoCase, id="video"),
|
ValidationCase(interface=VideoCase, id="video"),
|
||||||
]
|
]
|
||||||
|
"""
|
||||||
|
All the interface cases
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
DTYPE_AND_SHAPE_CASES = merged_product(SHAPE_CASES, DTYPE_CASES)
|
DTYPE_AND_SHAPE_CASES = merged_product(SHAPE_CASES, DTYPE_CASES)
|
||||||
|
"""
|
||||||
|
Merged product of dtype and shape cases
|
||||||
|
"""
|
||||||
DTYPE_AND_SHAPE_CASES_PASSING = merged_product(
|
DTYPE_AND_SHAPE_CASES_PASSING = merged_product(
|
||||||
SHAPE_CASES, DTYPE_CASES, conditions={"passes": True}
|
SHAPE_CASES, DTYPE_CASES, conditions={"passes": True}
|
||||||
)
|
)
|
||||||
|
"""
|
||||||
|
Merged product of dtype and shape cases that are valid
|
||||||
|
"""
|
||||||
|
|
||||||
DTYPE_AND_INTERFACE_CASES = merged_product(INTERFACE_CASES, DTYPE_CASES)
|
DTYPE_AND_INTERFACE_CASES = merged_product(INTERFACE_CASES, DTYPE_CASES)
|
||||||
|
"""
|
||||||
|
Merged product of dtype and interface cases
|
||||||
|
"""
|
||||||
DTYPE_AND_INTERFACE_CASES_PASSING = merged_product(
|
DTYPE_AND_INTERFACE_CASES_PASSING = merged_product(
|
||||||
INTERFACE_CASES, DTYPE_CASES, conditions={"passes": True}
|
INTERFACE_CASES, DTYPE_CASES, conditions={"passes": True}
|
||||||
)
|
)
|
||||||
|
"""
|
||||||
|
Merged product of dtype and interface cases that pass
|
||||||
|
"""
|
||||||
|
|
||||||
ALL_CASES = merged_product(SHAPE_CASES, DTYPE_CASES, INTERFACE_CASES)
|
ALL_CASES = merged_product(SHAPE_CASES, DTYPE_CASES, INTERFACE_CASES)
|
||||||
|
"""
|
||||||
|
Merged product of all cases - dtype, shape, and interface
|
||||||
|
"""
|
||||||
ALL_CASES_PASSING = merged_product(
|
ALL_CASES_PASSING = merged_product(
|
||||||
SHAPE_CASES, DTYPE_CASES, INTERFACE_CASES, conditions={"passes": True}
|
SHAPE_CASES, DTYPE_CASES, INTERFACE_CASES, conditions={"passes": True}
|
||||||
)
|
)
|
||||||
|
"""
|
||||||
|
Merged product of all cases, but only those that pass
|
||||||
|
"""
|
||||||
|
|
Loading…
Reference in a new issue