Source code for gemseo.core.grammars.base_grammar

# Copyright 2021 IRT Saint Exupéry, https://www.irt-saintexupery.com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
# Contributors:
#    INITIAL AUTHORS - initial API and implementation and/or initial
#                         documentation
#        :author: Francois Gallard
#    OTHER AUTHORS   - MACROSCOPIC CHANGES
"""Base class for validating data structures."""
from __future__ import annotations

import abc
import collections
import logging
from typing import Container
from typing import Iterable
from typing import KeysView
from typing import Sequence
from typing import TYPE_CHECKING

from docstring_inheritance import GoogleDocstringInheritanceMeta

from gemseo.core.discipline_data import Data
from gemseo.core.namespaces import namespaces_separator
from gemseo.core.namespaces import NamespacesMapping
from gemseo.core.namespaces import update_namespaces

if TYPE_CHECKING:
    from gemseo.core.grammars.simple_grammar import SimpleGrammar

LOGGER = logging.getLogger(__name__)


class __MetaClass(abc.ABCMeta, GoogleDocstringInheritanceMeta):
    pass


[docs]class BaseGrammar(collections.abc.Mapping, metaclass=__MetaClass): """An abstract base class for grammars with a dictionary-like interface. A grammar considers a certain type of data defined by mandatory and optional names bound to types. A name-type pair is referred to as a grammar *element*. A grammar can validate a data from these elements. """ name: str """The name of the grammar.""" to_namespaced: NamespacesMapping """The mapping from element names without namespace prefix to element names with namespace prefix.""" from_namespaced: NamespacesMapping """The mapping from element names with namespace prefix to element names without namespace prefix.""" def __init__( self, name: str, ) -> None: """ Args: name: The name of the grammar. Raises: ValueError: If the name is empty. """ if not name: raise ValueError("The grammar name cannot be empty.") self.name = name self.to_namespaced = {} self.from_namespaced = {} self.clear() def __str__(self) -> str: return f"Grammar name: {self.name}" @property def names(self) -> KeysView[str]: """The names of the elements.""" return self.keys()
[docs] @abc.abstractmethod def clear(self) -> None: """Empty the grammar."""
[docs] @abc.abstractmethod def update( self, grammar: BaseGrammar | Iterable[str], exclude_names: Container[str] | None = None, ) -> None: """Update the grammar. Args: grammar: The grammar or names to update from. exclude_names: The names of the elements that shall not be updated. """
[docs] @abc.abstractmethod def validate( self, data: Data, raise_exception: bool = True, ) -> None: """Validate data against the grammar. Args: data: The data to be checked, with a dictionary-like format: ``{element_name: element_value}``. raise_exception: Whether to raise an exception when the validation fails. Raises: InvalidDataException: If the validation fails and ``raise_exception`` is ``True``. """
[docs] @abc.abstractmethod def is_array(self, name: str) -> bool: """Check whether an element type shall be an array. Args: name: The name of the element. Returns: Whether the element type shall be an array. Raises: KeyError: If the element is not in the grammar. """
[docs] @abc.abstractmethod def convert_to_simple_grammar(self) -> SimpleGrammar: """Convert the grammar to a :class:`.SimpleGrammar`. Returns: A :class:`.SimpleGrammar` version of the current grammar. """
[docs] @abc.abstractmethod def update_from_data( self, data: Data, ) -> None: """Update the grammar from name-value pairs. Args: data: The data from which to get the names and types, typically ``{element_name: element_value}``. Raises: TypeError: If a value has a bad type. """
[docs] @abc.abstractmethod def restrict_to( self, names: Sequence[str], ) -> None: """Restrict the grammar to the given names. Args: names: The names of the elements to restrict the grammar to. Raises: KeyError: If a name is not in the grammar. """
@property @abc.abstractmethod def required_names(self) -> set[str]: """The names of the required elements."""
[docs] @abc.abstractmethod def rename_element(self, current_name: str, new_name: str) -> None: """Rename an element. Args: current_name: The current name of the element. new_name: The new name of the element. """
def _update_namespaces_from_grammar(self, grammar: BaseGrammar) -> None: """Update the namespaces according to another grammar namespaces. Args: grammar: The grammar to update from. """ if grammar.to_namespaced: update_namespaces(self.to_namespaced, grammar.to_namespaced) if grammar.from_namespaced: update_namespaces(self.from_namespaced, grammar.from_namespaced)
[docs] def add_namespace(self, name: str, namespace: str) -> None: """Add a namespace prefix to an existing grammar element. The updated element name will be ``namespace``+:data:`~gemseo.core.namespaces.namespace_separator`+``name``. Args: name: The element name to rename. namespace: The name of the namespace. """ self._check_name(name) if namespaces_separator in name: raise ValueError(f"Variable {name} has already a namespace.") new_name = namespace + namespaces_separator + name self.rename_element(name, new_name) self.to_namespaced[name] = new_name self.from_namespaced[new_name] = name
@abc.abstractmethod def _check_name(self, *names: str) -> None: """Check that the names of elements are valid. Args: *names: The names to be checked. Raises: KeyError: If a name is not valid. """