Source code for sharpy.utils.settings

"""
Settings Generator Utilities
"""
import configparser
import ctypes as ct
import numpy as np
import sharpy.utils.exceptions as exceptions
import sharpy.utils.cout_utils as cout
import ast


class DictConfigParser(configparser.ConfigParser):
    def as_dict(self):
        d = dict(self._sections)
        for k in d:
            d[k] = dict(self._defaults, **d[k])
            d[k].pop('__name__', None)
        return d


def cast(k, v, pytype, ctype, default):
    try:
        # if default is None:
        #     raise TypeError
        val = ctype(pytype(v))
    except KeyError:
        val = ctype(default)
        cout.cout_wrap("--- The variable " + k + " has no given value, using the default " + default, 2)
    except TypeError:
        raise exceptions.NoDefaultValueException(k)
    except ValueError:
        val = ctype(v.value)
    return val


def to_custom_types(dictionary, types, default, options=dict(), no_ctype=True):
    for k, v in types.items():
        if type(v) != list:
            data_type = v
        else:
            if k in dictionary:
                data_type = get_data_type_for_several_options(dictionary[k], v, k)
            else:
                # Choose first data type  in list for default value
                data_type = v[0]
        dictionary[k] = get_custom_type(dictionary, data_type, k, default, no_ctype)
      
    check_settings_in_options(dictionary, types, options)

    unrecognised_settings = []
    for k in dictionary.keys():
        if k not in list(types.keys()):
            unrecognised_settings.append(exceptions.NotRecognisedSetting(k))

    for setting in unrecognised_settings:
        cout.cout_wrap(repr(setting), 4)

    if unrecognised_settings:
        raise Exception(unrecognised_settings)


def get_data_type_for_several_options(dict_value, list_settings_types, setting_name):
    """
    Checks the data type of the setting input in case of several data type options. 
    Only a scalar or list can be the case for these cases.  

    Args:
        dict_values: Dictionary value of processed settings
        list_settings_types (list): Possible setting type options for this setting

    Raises:
        exception.NotValidSetting: if the setting is not allowed.
    """
    for data_type in list_settings_types:
        if 'list' in data_type and (type(dict_value) == list or not np.isscalar(dict_value)):
                return data_type
        elif 'list' not in data_type and np.isscalar(dict_value):
                return data_type
    exceptions.NotValidSettingType(setting_name, dict_value, list_settings_types)

def get_default_value(default_value, k, v, data_type = None, py_type = None):
    if default_value is None:
        raise exceptions.NoDefaultValueException(k)
    if v in ['float', 'int', 'bool']:
        converted_value = cast(k, default_value, py_type, data_type, default_value)
    elif v == 'str':
        converted_value = cast(k, default_value, eval(v), eval(v), default_value)
    else:
        converted_value = default_value.copy()
    notify_default_value(k, converted_value)
    return converted_value

def get_custom_type(dictionary, v, k, default, no_ctype):
    if v == 'int':
        if no_ctype:
            data_type = int
        else:
            data_type = ct.c_int
        try:
            dictionary[k] = cast(k, dictionary[k], int, data_type, default[k])
        except KeyError:
            dictionary[k] = get_default_value(default[k], k, v, data_type=data_type, py_type=int)

    elif v == 'float':
        if no_ctype:
            data_type = float
        else:
            data_type = ct.c_double
        try:
            dictionary[k] = cast(k, dictionary[k], float, data_type, default[k])
        except KeyError:
            dictionary[k] = get_default_value(default[k], k, v, data_type=data_type, py_type=float)

    elif v == 'str':
        try:
            dictionary[k] = cast(k, dictionary[k], str, str, default[k])
        except KeyError:
            dictionary[k] = get_default_value(default[k], k, v)

    elif v == 'bool':
        if no_ctype:
            data_type = bool
        else:
            data_type = ct.c_bool
        try:
            dictionary[k] = cast(k, dictionary[k], str2bool, data_type, default[k])
        except KeyError:
            dictionary[k] = get_default_value(default[k], k, v, data_type=data_type, py_type=str2bool)

    elif v == 'list(str)':
        try:
            # if isinstance(dictionary[k], list):
            #     continue
            # dictionary[k] = dictionary[k].split(',')
            # getting rid of leading and trailing spaces
            dictionary[k] = list(map(lambda x: x.strip(), dictionary[k]))
        except KeyError:
            dictionary[k] = get_default_value(default[k], k, v)

    elif v == 'list(dict)':
        try:
            # if isinstance(dictionary[k], list):
            #     continue
            # dictionary[k] = dictionary[k].split(',')
            # getting rid of leading and trailing spaces
            for i in range(len(dictionary[k])):
                dictionary[k][i] = ast.literal_eval(dictionary[k][i])
        except KeyError:
            dictionary[k] = get_default_value(default[k], k, v)

    elif v == 'list(float)':
        try:
            dictionary[k]
        except KeyError:
            dictionary[k] = get_default_value(default[k], k, v)

        if isinstance(dictionary[k], np.ndarray):
            return dictionary[k]
        if isinstance(dictionary[k], list):
            for i in range(len(dictionary[k])):
                dictionary[k][i] = float(dictionary[k][i])
            dictionary[k] = np.array(dictionary[k])
            return dictionary[k]
        # dictionary[k] = dictionary[k].split(',')
        # # getting rid of leading and trailing spaces
        # dictionary[k] = list(map(lambda x: x.strip(), dictionary[k]))
        if dictionary[k].find(',') < 0:
            dictionary[k] = np.fromstring(dictionary[k].strip('[]'), sep=' ', dtype=ct.c_double)
        else:
            dictionary[k] = np.fromstring(dictionary[k].strip('[]'), sep=',', dtype=ct.c_double)

    elif v == 'list(int)':
        try:
            dictionary[k]
        except KeyError:
            dictionary[k] = get_default_value(default[k], k, v)

        if isinstance(dictionary[k], np.ndarray):
            return dictionary[k]
        if isinstance(dictionary[k], list):
            for i in range(len(dictionary[k])):
                dictionary[k][i] = int(dictionary[k][i])
            dictionary[k] = np.array(dictionary[k])
            return dictionary[k]
        # dictionary[k] = dictionary[k].split(',')
        # # getting rid of leading and trailing spaces
        # dictionary[k] = list(map(lambda x: x.strip(), dictionary[k]))
        if dictionary[k].find(',') < 0:
            dictionary[k] = np.fromstring(dictionary[k].strip('[]'), sep=' ').astype(ct.c_int)
        else:
            dictionary[k] = np.fromstring(dictionary[k].strip('[]'), sep=',').astype(ct.c_int)

    elif v == 'list(complex)':
        try:
            dictionary[k]
        except KeyError:
            dictionary[k] = get_default_value(default[k], k, v)

        if isinstance(dictionary[k], np.ndarray):
            return dictionary[k]
        if isinstance(dictionary[k], list):
            for i in range(len(dictionary[k])):
                dictionary[k][i] = complex(dictionary[k][i])
            dictionary[k] = np.array(dictionary[k])
            return dictionary[k]
        # dictionary[k] = dictionary[k].split(',')
        # # getting rid of leading and trailing spaces
        # dictionary[k] = list(map(lambda x: x.strip(), dictionary[k]))
        if dictionary[k].find(',') < 0:
            dictionary[k] = np.fromstring(dictionary[k].strip('[]'), sep=' ').astype(complex)
        else:
            dictionary[k] = np.fromstring(dictionary[k].strip('[]'), sep=',').astype(complex)

    elif v == 'dict':
        try:
            if not isinstance(dictionary[k], dict):
                raise TypeError('Setting for {:s} is not a dictionary'.format(k))
        except KeyError:
            dictionary[k] = get_default_value(default[k], k, v)
    else:
        raise TypeError('Variable %s has an unknown type (%s) that cannot be casted' % (k, v))
    return dictionary[k]

def check_settings_in_options(settings, settings_types, settings_options):
    """
    Checks that settings given a type ``str`` or ``int`` and allowable options are indeed valid.

    Args:
        settings (dict): Dictionary of processed settings
        settings_types (dict): Dictionary of settings types
        settings_options (dict): Dictionary of options (may be empty)

    Raises:
        exception.NotValidSetting: if the setting is not allowed.
    """
    for k in settings_options:
        if settings_types[k] == 'int':
            try:
                value = settings[k].value
            except AttributeError:
                value = settings[k]
            if value not in settings_options[k]:
                raise exceptions.NotValidSetting(k, value, settings_options[k])

        elif settings_types[k] == 'str':
            value = settings[k]
            if value not in settings_options[k] and value:
                # checks that the value is within the options and that it is not an empty string.
                raise exceptions.NotValidSetting(k, value, settings_options[k])

        elif settings_types[k] == 'list(str)':
            for item in settings[k]:
                if item not in settings_options[k] and item:
                    raise exceptions.NotValidSetting(k, item, settings_options[k])

        else:
            pass  # no other checks implemented / required


def load_config_file(file_name: str) -> dict:
    """This function reads the flight condition and solver input files.

    Args:
        file_name (str): contains the path and file name of the file to be read by the ``configparser``
            reader.

    Returns:
        config (dict): a ``ConfigParser`` object that behaves like a dictionary
    """
    # config = DictConfigParser()
    # config.read(file_name)
    # dict_config = config.as_dict()
    import configobj
    dict_config = configobj.ConfigObj(file_name)
    return dict_config


def str2bool(string):
    false_list = ['false', 'off', '0', 'no']
    if isinstance(string, bool):
        return string
    if isinstance(string, ct.c_bool):
        return string.value

    if not string:
        return False
    elif string.lower() in false_list:
        return False
    else:
        return True


def notify_default_value(k, v):
    cout.cout_wrap('Variable ' + k + ' has no assigned value in the settings file.')
    cout.cout_wrap('    will default to the value: ' + str(v), 1)


[docs]class SettingsTable: """ Generates the documentation's setting table at runtime. Sphinx is our chosen documentation manager and takes docstrings in reStructuredText format. Given that the SHARPy solvers contain several settings, this class produces a table in reStructuredText format with the solver's settings and adds it to the solver's docstring. This table will then be printed alongside the remaining docstrings. To generate the table, parse the setting's description to a solver dictionary named ``settings_description``, in a similar fashion to what is done with ``settings_types`` and ``settings_default``. If no description is given it will be left blank. Then, add at the end of the solver's class declaration method an instance of the ``SettingsTable`` class and a call to the ``SettingsTable.generate()`` method. Examples: The end of the solver's class declaration should contain .. code-block:: python # Generate documentation table settings_table = settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) to generate the settings table. """ def __init__(self): self.n_fields = 4 self.n_settings = 0 self.field_length = [0] * self.n_fields self.titles = ['Name', 'Type', 'Description', 'Default'] self.settings_types = dict() self.settings_description = dict() self.settings_default = dict() self.settings_options = dict() self.settings_options_strings = dict() self.line_format = '' self.table_string = ''
[docs] def generate(self, settings_types, settings_default, settings_description, settings_options=dict(), header_line=None): """ Returns a rst-format table with the settings' names, types, description and default values Args: settings_types (dict): Setting types. settings_default (dict): Settings default value. settings_description (dict): Setting description. header_line (str): Header line description (optional) Returns: str: .rst formatted string with a table containing the settings' information. """ self.settings_types = settings_types self.settings_default = settings_default self.n_settings = len(self.settings_types) # if header_line is None: header_line = 'The settings that this solver accepts are given by a dictionary, ' \ 'with the following key-value pairs:' else: assert type(header_line) == str, 'header_line not a string, verify order of arguments' if type(settings_options) != dict: raise TypeError('settings_options is not a dictionary') if settings_options: # if settings_options are provided self.settings_options = settings_options self.n_fields += 1 self.field_length.append(0) self.titles.append('Options') self.process_options() try: self.settings_description = settings_description except AttributeError: pass self.set_field_length() self.line_format = self.setting_line_format() table_string = '\n ' + header_line + '\n' table_string += '\n ' + self.print_divider_line() table_string += ' ' + self.print_header() table_string += ' ' + self.print_divider_line() for setting in self.settings_types: table_string += ' ' + self.print_setting(setting) table_string += ' ' + self.print_divider_line() self.table_string = table_string return table_string
def process_options(self): self.settings_options_strings = self.settings_options.copy() for k, v in self.settings_options.items(): opts = '' for option in v: opts += ' ``%s``,' %str(option) self.settings_options_strings[k] = opts[1:-1] # removes the initial whitespace and final comma def set_field_length(self): field_lengths = [[] for i in range(self.n_fields)] for setting in self.settings_types: stype = str(self.settings_types.get(setting, '')) description = self.settings_description.get(setting, '') default = str(self.settings_default.get(setting, '')) option = str(self.settings_options_strings.get(setting, '')) field_lengths[0].append(len(setting) + 4) # length of name field_lengths[1].append(len(stype) + 4) # length of type + 4 for the rst ``X`` field_lengths[2].append(len(description)) # length of type field_lengths[3].append(len(default) + 4) # length of type + 4 for the rst ``X`` if self.settings_options: field_lengths[4].append(len(option)) for i_field in range(self.n_fields): field_lengths[i_field].append(len(self.titles[i_field])) self.field_length[i_field] = max(field_lengths[i_field]) + 2 # add the two spaces as column dividers def print_divider_line(self): divider = '' for i_field in range(self.n_fields): divider += '='*(self.field_length[i_field]-2) + ' ' divider += '\n' return divider def print_setting(self, setting): type = '``' + str(self.settings_types.get(setting, '')) + '``' description = self.settings_description.get(setting, '') default = '``' + str(self.settings_default.get(setting, '')) + '``' if self.settings_options: option = self.settings_options_strings.get(setting, '') line = self.line_format.format(['``' + str(setting) + '``', type, description, default, option]) + '\n' else: line = self.line_format.format(['``' + str(setting) + '``', type, description, default]) + '\n' return line def print_header(self): header = self.line_format.format(self.titles) + '\n' return header def setting_line_format(self): string = '' for i_field in range(self.n_fields): string += '{0[' + str(i_field) + ']:<' + str(self.field_length[i_field]) + '}' return string
def set_value_or_default(dictionary, key, default_val): try: value = dictionary[key] except KeyError: value = default_val return value