Source code for gemseo.core.grammar

# -*- coding: utf-8 -*-
# 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
"""
Data rules and checks for disciplines inputs/outputs validation
***************************************************************
"""
from __future__ import absolute_import, division, print_function, unicode_literals

from future import standard_library

standard_library.install_aliases()
from gemseo import LOGGER


[docs]class AbstractGrammar(object): """Abstract Grammar : defines the abstraction for a Grammar A grammar subclass instance stores the input or output data types and structure an MDODiscipline It is able to check the inputs and outputs against predefined types """ INPUT_GRAMMAR = "input" OUTPUT_GRAMMAR = "output"
[docs] def load_data(self, data_dict, raise_exception=True): """Loads the data dictionary in the grammar and checks it against self properties :param data_dict: the input data :param raise_exception: if False, no exception is raised when data is invalid (Default value = True) """ raise NotImplementedError()
[docs] def get_data_names(self): """Returns the list of data names :returns: the data names alphabetically sorted """ raise NotImplementedError()
[docs] def update_from(self, input_grammar): """Adds properties coming from another grammar :param input_grammar: the grammar to take inputs from """ raise NotImplementedError()
[docs] def update_from_if_not_in(self, input_grammar, exclude_grammar): """Adds properties coming from input_grammar if they are not in exclude_grammar :param input_grammar: the grammar to take inputs from :param exclude_grammar: exclusion grammar """ raise NotImplementedError()
[docs] def is_data_name_existing(self, data_name): """Checks if data_name is present in grammar :param data_name: the data name :returns: True if data is in grammar """ raise NotImplementedError()
[docs] def is_all_data_names_existing(self, data_names): """Checks if data_names are present in grammar :param data_names: the data names list :returns: True if all data are in grammar """ raise NotImplementedError()
[docs] def clear(self): """Clears the data to produce an empty grammar""" raise NotImplementedError()
[docs]class SimpleGrammar(object): """A grammar instance stores the input or output data types and structure a MDODiscipline It is able to check the inputs and outputs against predefined types """ def __init__(self, name): """ Constructor :param name : grammar name """ super(SimpleGrammar, self).__init__() # Data names list self.data_names = [] # Data types list in the same order as self.data_names self.data_types = [] # Default data dict, keys must be in self.data_names self.defaults = {} self.name = name self.data = None
[docs] def load_data(self, data_dict, raise_exception=True): """Loads the data dictionary in the grammar and checks it against self properties :param data_dict: the input data :param raise_exception: if False, no exception is raised when data is invalid (Default value = True) """ self.data = data_dict self._load_defaults() self.check(raise_exception) return self.data
[docs] def check(self, raise_exception=True): """Checks local data against self properties :param raise_exception: if False, no exception is raised when data is invalid (Default value = True) """ failed = False if not isinstance(self.data, dict): failed = True LOGGER.error("Grammar data is not a dict, in %s", self.name) if raise_exception: raise InvalidDataException("Invalid data in " + str(self.name)) for data_type in self.data_types: if not isinstance(data_type, type): raise TypeError( "Invalid data_types in grammar :" + str(self.name) + ", " + str(data_type) + " is not a type" ) for data_type, data_name in zip(self.data_types, self.data_names): if data_name not in self.data: failed = True LOGGER.error("Missing input: %s in %s", str(data_name), self.name) elif not isinstance(self.data[data_name], data_type): failed = True LOGGER.error( "Wrong input type for: %s in %s got %s instead of %s", str(data_name), self.name, str(type(self.data[data_name])), str(data_type), ) if failed and raise_exception: raise InvalidDataException("Invalid data in " + str(self.name))
[docs] def initialize_from_base_dict(self, typical_data_dict): """Initialize the grammar with types and names from a typical data entry :param typical_data_dict: a data dictionary """ self.data_names = [] self.data_types = [] for key, value in typical_data_dict.items(): self.data_names.append(key) self.data_types.append(type(value))
def _load_defaults(self): """Loads defaults values in self""" for key, value in self.defaults.items(): if key not in self.data: self.data[key] = value
[docs] def get_data_names(self): """Returns the list of data names :returns: the data names alphabetically sorted """ return self.data_names
[docs] def is_all_data_names_existing(self, data_names): """Checks if data_names are present in grammar :param data_names: the data names list :returns: True if all data are in grammar """ for data_name in data_names: if not self.is_data_name_existing(data_name): return False return True
def _update_field(self, data_name, data_type): """Updates self properties with a new property of data_name,data_type Adds it if self has no property named data_name Updates self.data_types otherwise :param data_name: the name of the property :param data_type: the type of the property """ if data_name in self.data_names: indx = self.data_names.index(data_name) self.data_names[indx] = data_name self.data_types[indx] = data_type else: self.data_names.append(data_name) self.data_types.append(data_type)
[docs] def get_type_of_data_named(self, data_name): """Gets the associated type to the data named data_name :param data_name: the name of the property :returns: data type associated to data_name """ if not self.is_data_name_existing(data_name): raise ValueError("Unknown data named :" + str(data_name)) indx = self.data_names.index(data_name) return self.data_types[indx]
[docs] def update_from(self, input_grammar): """Adds properties coming from another grammar :param input_grammar: the grammar to take inputs from """ if not isinstance(input_grammar, SimpleGrammar): LOGGER.warning( "Cannot update grammar %s of type %s with %s of type %s", self.name, type(self).__name__, input_grammar.name, type(input_grammar).__name__, ) return for g_name, g_type in zip(input_grammar.data_names, input_grammar.data_types): self._update_field(g_name, g_type)
[docs] def update_from_if_not_in(self, input_grammar, exclude_grammar): """Adds properties coming from input_grammar if they are not in exclude_grammar :param input_grammar: the grammar to take inputs from :param exclude_grammar: exclusion grammar """ if not isinstance(input_grammar, SimpleGrammar) or not isinstance( exclude_grammar, SimpleGrammar ): raise TypeError( "Cannot update grammar %s of type %s with %s of type %s " + " and %s, of type %s", self.name, type(self).__name__, input_grammar.name, type(input_grammar).__name__, exclude_grammar.name, type(exclude_grammar).__name__, ) for g_name, g_type in zip(input_grammar.data_names, input_grammar.data_types): if exclude_grammar.is_data_name_existing(g_name): ex_data_type = exclude_grammar.get_type_of_data_named(g_name) if g_type != ex_data_type: raise ValueError( "Inconsistent grammar update " + str(g_type) + " !=" + str(ex_data_type) ) else: self._update_field(g_name, g_type)
[docs] def is_data_name_existing(self, data_name): """Checks if data_name is present in grammar :param data_name: the data name :returns: True if data is in grammar """ return data_name in self.data_names
[docs] def clear(self): """Clears the data to produce an empty grammar""" self.data_names = [] self.data_types = [] self.defaults.clear()
# AbstractGrammar.register(SimpleGrammar)
[docs]class InvalidDataException(Exception): """Exception raised when data is not valid against grammar rules."""