spekk.util.validation.validate

Contents

spekk.util.validation.validate#

spekk.util.validation.validate(spec: Spec, data: Mapping[Any, Tree] | Sequence[Tree] | Any)[source]#

Validate that the data conforms to the spec, raising a ValidationError if not.

Examples:

>>> import numpy as np
>>> spec = Spec({
...     "foo": {"bar": ["dim1", "dim2"]},
...     "baz": ["dim2"],
... })

The following data is valid because "dim2" under path ["foo", "bar"] has the same size as under path ["baz"] and it has the same structure as the spec:

>>> validate(spec, {
...     "foo": {"bar": np.ones((2, 3))},
...     "baz": np.ones((3,)),
... })

If we try to validate against a spec with a path that is not present in the data, a ValidationError is raised:

>>> validate(spec, {
...     "foo": {"invalid_key": np.ones((2, 3))},
...     "baz": np.ones((3,)),
... })
Traceback (most recent call last):
    ...
ValidationError: Path ['foo', 'bar'] is not present in the data yet it is present in the spec.

All specced values must have a shape attribute (or more generally; be supported by spekk.util.shape.shape()):

>>> validate(spec, {
...     "foo": {"bar": np.ones((2, 3))},
...     "baz": object(),  # <- An object does not have a shape attribute
... })
Traceback (most recent call last):
    ...
ValidationError: Unable to get the shape of value at path ['baz'] with type <class 'object'>.

The data must have at least as many dimensions as the spec:

>>> validate(
...     Spec({"foo": ["dim1", "dim2"]}),
...     {"foo": np.ones((2,))},  # <- Too few dimensions!
... )
Traceback (most recent call last):
    ...
ValidationError: The data has only 1 dimension while the spec specifies dimensions ['dim1', 'dim2'] (2 in total) at the path ['foo'].

It is OK if the data has more dimensions than the spec:

>>> validate(
...     Spec({"foo": ["dim1", "dim2"]}),    # <- spec specifies 2 dimensions
...     {"foo": np.ones((2, 3, 4, 5, 6))},  # <- 5 dimensions is more than 2, which is OK!
... )

The data must have the same size for a given dimension in all places where it is used:

>>> validate(spec, {
...     "foo": {"bar": np.ones((2, 3))},
...     "baz": np.ones((4,)),  # <- Size of dim2 is 4 here, but 3 above!
... })
Traceback (most recent call last):
    ...
ValidationError: Dimension 'dim2' has inconsistent sizes in the data:
    - Size=3 at path ['foo', 'bar'], shape=(2, 3), index=1
    - Size=4 at path ['baz'], shape=(4,), index=0
>>> validate(spec, {
...     "foo": {"bar": np.ones((2, 3))},
...     "baz": np.ones((3,)),  # <- This has the same size as the one above
... })

The spec does not have to specify the dimensions for all paths in the data:

>>> validate(spec, {
...     "foo": {"bar": np.ones((2, 3))},
...     "baz": np.ones((3,)),
...     "qux": np.ones((5, 6)),  # <- This is OK, the spec does not specify dimensions for "qux"
... })