Use a pydantic grammar

from __future__ import annotations

from numpy import array
from pydantic import BaseModel
from pydantic import Field

from gemseo.core.grammars.errors import InvalidDataError
from gemseo.core.grammars.pydantic_grammar import PydanticGrammar
from gemseo.core.grammars.pydantic_ndarray import NDArrayPydantic

Create the pydantic model

The pydantic model is a class deriving from pydantic.BaseModel that describes the names and types of the data to be validated. Descriptions are defined with docstrings, default values can be defined naturally. Mind that default values with a mutable object must be defined with the default_factory of a Field. By default, pydantic does not handle the typing of NumPy arrays. To support it, a special type shall be used, NDArrayPydantic. Like the standard NumPy type for ndarray, NDArray, this type can be specialized with the dtype like NDArrayPydantic[int].

class Model(BaseModel):
    """The description of the model."""

    a_int: int
    """The description of an integer."""

    an_ndarray: NDArrayPydantic
    """The description of an ndarray."""

    an_ndarray_of_int: NDArrayPydantic[int]
    """The description of an ndarray of integers."""

    an_ndarray_with_default: NDArrayPydantic = Field(default_factory=lambda: array([0]))
    """The description of an ndarray with a default value."""

    a_str_with_default: str = "default"
    """The description of a string with a default value."""

Create the grammar

grammar = PydanticGrammar("grammar", model=Model)

Show the contents of the grammar.

grammar
Grammar name: grammar
  • Required elements:
    • a_int:
      • Type: <class 'int'>
    • an_ndarray:
      • Type: gemseo.core.grammars.pydantic_ndarray._NDArrayPydantic[typing.Any, numpy.dtype[+_ScalarType_co]]
    • an_ndarray_of_int:
      • Type: gemseo.core.grammars.pydantic_ndarray._NDArrayPydantic[typing.Any, numpy.dtype[int]]
  • Optional elements:
    • an_ndarray_with_default:
      • Type: gemseo.core.grammars.pydantic_ndarray._NDArrayPydantic[typing.Any, numpy.dtype[+_ScalarType_co]]
      • Default: [0]
    • a_str_with_default:
      • Type: <class 'str'>
      • Default: default


Validate data against the grammar

Validating missing data will raise an error shows the missing required elements, here the first 3 elements are missing.

try:
    grammar.validate({})
except InvalidDataError as error:
    print(error)
Grammar grammar: validation failed.
Missing required names: a_int, an_ndarray, an_ndarray_of_int.

Validating data with bad type will raise an error shows the bad elements, here the first elements shall be an int and the third one shall be a ndarray of int.

try:
    grammar.validate({
        "a_int": 0.0,
        "an_ndarray": array([1]),
        "an_ndarray_of_int": array([1.0]),
    })
except InvalidDataError as error:
    print(error)
Grammar grammar: validation failed.
2 validation errors for Model
a_int
  Input should be a valid integer [type=int_type, input_value=0.0, input_type=float]
    For further information visit https://errors.pydantic.dev/2.6/v/int_type
an_ndarray_of_int
  Value error, Input dtype should be <class 'int'>: got the dtype <class 'numpy.float64'> [type=value_error, input_value=array([1.]), input_type=ndarray]
    For further information visit https://errors.pydantic.dev/2.6/v/value_error

Validating compliant data.

grammar.validate({
    "a_int": 0,
    "an_ndarray": array([1]),
    "an_ndarray_of_int": array([1]),
})

Grammar defaults

As compared to the other types of grammars, the grammar defaults are be defined in the pydantic model and does not require to be manually defined from the grammar.

grammar.defaults
{'an_ndarray_with_default': array([0]), 'a_str_with_default': 'default'}

Model inheritance

Since pydantic models are classes, one can easily build grammar via inheritance of the pydantic model. Here we change the type of one element, and we add a new one.

class Model2(Model):
    """A model that inherits from a parent model."""

    an_ndarray: NDArrayPydantic[float] = Field(default_factory=lambda: array([1.0]))
    """The new description of an_ndarray."""

    a_bool: bool = True
    """The description of a_bool."""


grammar = PydanticGrammar("grammar", model=Model2)

Show the contents of the grammar.

grammar
Grammar name: grammar
  • Required elements:
    • a_int:
      • Type: <class 'int'>
    • an_ndarray_of_int:
      • Type: gemseo.core.grammars.pydantic_ndarray._NDArrayPydantic[typing.Any, numpy.dtype[int]]
  • Optional elements:
    • an_ndarray:
      • Type: gemseo.core.grammars.pydantic_ndarray._NDArrayPydantic[typing.Any, numpy.dtype[float]]
      • Default: [1.]
    • an_ndarray_with_default:
      • Type: gemseo.core.grammars.pydantic_ndarray._NDArrayPydantic[typing.Any, numpy.dtype[+_ScalarType_co]]
      • Default: [0]
    • a_str_with_default:
      • Type: <class 'str'>
      • Default: default
    • a_bool:
      • Type: <class 'bool'>
      • Default: True


Total running time of the script: (0 minutes 0.012 seconds)

Gallery generated by Sphinx-Gallery