def test_optional():
    '''Test that optional values are validated if present, but no error is raised if not.'''
    d1 = {'age': 28, 'city': 'utrecht'}
    d2 = {'city': 'utrecht'}
    dval = DictValue({'age': Value(int, kind=Value.optional), 'city': str})
    assert dval.validate(d1) == d1
    assert dval.validate(d2) == d2
def test_NamedValue(value):
    '''Test the parsing of dictionary elements with NamedValue'''
    d = {value: 5}
    assert NamedValue(value, int).validate(d) == d  # simple constructor
    assert NamedValue(value, Value(int, val_max=6)).validate(d) == d  # constructor from Value
    d_dval = {value: {value: 5}}
    # constructor from DictValue
    assert NamedValue(value, DictValue({value: int})).validate(d_dval) == d_dval
    d2 = {value: value}
    assert NamedValue(value, type(value)).validate(d2) == d2
    assert NamedValue(value, Value(type(value))).validate(d2) == d2

    # the key must match exactly
    with pytest.raises(SettingsValueError) as excinfo:
        NamedValue(value, int).validate({'wrong_key': 5})
    assert excinfo.match(r"Setting ")
    assert excinfo.match(r" not in dictionary")
    assert excinfo.type == SettingsValueError

    # the value must have the right type
    with pytest.raises(SettingsValueError) as excinfo:
        NamedValue(value, int).validate({value: 'wrong_value'})
    assert excinfo.match('does not have the right type')
    assert excinfo.type == SettingsValueError

    # the value to validate must be a dictionary
    with pytest.raises(SettingsValueError) as excinfo:
        NamedValue(value, int).validate(value)
    assert excinfo.match('is not a dictionary')
    assert excinfo.type == SettingsValueError
Esempio n. 3
0
    def __init__(self, values_dict: Dict) -> None:  # pylint: disable=W0231
        super(Settings, self).__init__({})

        val_copy = copy.deepcopy(values_dict)
        self._dict_value = DictValue(val_copy)

        namedvalue_list = self._dict_value.values_list
        self._needed_values = set(namedvalue.key
                                  for namedvalue in namedvalue_list
                                  if namedvalue.kind is Value.mandatory)
        self._optional_values = set(namedvalue.key
                                    for namedvalue in namedvalue_list
                                    if namedvalue.kind is Value.optional)
        self._exclusive_values = set(namedvalue.key
                                     for namedvalue in namedvalue_list
                                     if namedvalue.kind is Value.exclusive)
        self._optional_values = self._optional_values | self._exclusive_values

        self._config_file = None  # type: str
def test_DictValue_in_Dict():
    '''Test the combination of DictValue and Dict.'''
    d = {
        'key1': {
            'age': 28,
            'city': 'utrecht'
        },
        'key2': {
            'age': 52,
            'city': 'london'
        },
        'key3': {
            'age': 24,
            'city': 'rome'
        }
    }
    assert Value(Dict[str, DictValue({
        'age': int,
        'city': str
    })]).validate(d) == d

    # wrong key type
    with pytest.raises(SettingsValueError) as excinfo:
        assert Value(Dict[int, DictValue({
            'age': int,
            'city': str
        })]).validate(d) == d
    assert excinfo.match("Setting")
    assert excinfo.match("does not have the right type")
    assert excinfo.type == SettingsValueError

    # wrong key type
    with pytest.raises(SettingsValueError) as excinfo:
        assert Value(Dict[int, DictValue({
            'age': int,
            'city': str
        })]).validate(45) == d
    assert excinfo.match("Setting")
    assert excinfo.match("does not have the right type")
    assert excinfo.match("This type can only validate dictionaries")
    assert excinfo.type == SettingsValueError
def test_nested_DictValue():
    '''Test DictValues with other DictValues as types.'''
    d = {
        'subsection1': {
            'subsubsection1': 'asd',
            'subsubsection2': 5
        },
        'subsection2': [1, 2, 3]
    }
    assert DictValue({
        'subsection1':
        DictValue({
            'subsubsection1': str,
            'subsubsection2': int
        }),
        'subsection2':
        Value(List[int])
    }).validate(d) == d
    # avoid repetition of DictValue in nested types
    assert DictValue({
        'subsection1': {
            'subsubsection1': str,
            'subsubsection2': int
        },
        'subsection2': Value(List[int])
    }).validate(d) == d

    d2 = {
        'subsection1': {
            'subsubsection1': 'asd',
            'subsubsection2': 5
        },
        'subsection2': 1
    }
    with pytest.raises(SettingsValueError) as excinfo:
        DictValue({
            'subsection1': {
                'subsubsection1': str,
                'subsubsection2': int
            },
            'subsection2': Value(List[int])
        }).validate(d2)
    assert excinfo.match('Setting "subsection2"')
    assert excinfo.match("does not have the right type")
    assert excinfo.type == SettingsValueError

    # with optional sub-DictValues
    d3 = {'subsection2': [1, 2, 3]}
    assert DictValue({
        'subsection1':
        DictValue({
            'subsubsection1': str,
            'subsubsection2': int
        },
                  kind=Kind.optional),
        'subsection2':
        Value(List[int])
    }).validate(d3) == d3
def test_exclusive():
    '''Test exclusive values.'''
    d1 = {'age': 28}
    dval = DictValue({'age': Value(int, kind=Value.exclusive)})
    assert dval.validate(d1) == d1
    dval2 = DictValue({
        'age': Value(int, kind=Value.exclusive),
        'city': Value(str, kind=Value.exclusive)
    })
    # only one of them is present, ok
    assert dval2.validate(d1) == d1

    d2 = {'age': 28, 'city': 'utrecht'}
    with pytest.raises(SettingsValueError) as excinfo:
        dval2.validate(d2)
    assert excinfo.match("Only one of the values")
    assert excinfo.match("can be present at the same time")
    assert excinfo.type == SettingsValueError
Esempio n. 7
0
class Settings(dict):
    '''Contains the user settings and a method to validate settings files.'''
    def __init__(self, values_dict: Dict) -> None:  # pylint: disable=W0231
        super(Settings, self).__init__({})

        val_copy = copy.deepcopy(values_dict)
        self._dict_value = DictValue(val_copy)

        namedvalue_list = self._dict_value.values_list
        self._needed_values = set(namedvalue.key
                                  for namedvalue in namedvalue_list
                                  if namedvalue.kind is Value.mandatory)
        self._optional_values = set(namedvalue.key
                                    for namedvalue in namedvalue_list
                                    if namedvalue.kind is Value.optional)
        self._exclusive_values = set(namedvalue.key
                                     for namedvalue in namedvalue_list
                                     if namedvalue.kind is Value.exclusive)
        self._optional_values = self._optional_values | self._exclusive_values

        self._config_file = None  # type: str

    def __repr__(self) -> str:
        '''Representation of a settings instance.'''
        if self._config_file is None:  # not validated
            _dict_value = repr(self._dict_value).replace('DictValue(', '', 1)
            _dict_value = _dict_value[:-1]
            return '{}({})'.format(self.__class__.__name__, _dict_value)
        else:
            return pprint.pformat(self.settings)

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Dict):
            return NotImplemented
        for key in self:
            if not key in other or self[key] != other[key]:
                return False
        return True

    def __ne__(self, other: object) -> bool:
        if not isinstance(other, Dict):
            return NotImplemented
        return not self.__eq__(other)


#    def __getstate__(self):
#        '''For pickle'''
#        d = self.__dict__.copy()
#        d['val_type'] = repr(d['val_type'])
#        return d
#
#    def __setstate__(self, d):
#        '''For pickle'''
#        print(d)
#        d['val_type'] = eval(d['val_type'])
#        self.__dict__ = d

    @staticmethod
    def _get_property(key: str) -> Callable:
        '''Returns functions to get dictionary items. Used to create properties.'''
        def _get_prop(self: 'Settings') -> Any:
            '''Property to get values'''
            return self[key]

        return _get_prop

    @staticmethod
    def _set_property(key: str) -> Callable:
        '''Returns functions to set dictionary items. Used to create properties.'''
        def _set_prop(self: 'Settings', value: Any) -> None:
            '''Property to set values'''
            self[key] = value

        return _set_prop

    @staticmethod
    def _del_property(key: str) -> Callable:
        '''Returns functions to delete dictionary items. Used to create properties.'''
        def _del_prop(self: 'Settings') -> None:
            '''Property to delete values: delete from dictionary and the property.'''
            del self[key]
            delattr(self.__class__, key)

        return _del_prop

    def __setitem__(self, key: str, value: Any) -> None:
        '''All items added to the dictionary are accesible via dot notation'''
        dict.__setitem__(self, key, value)
        setattr(
            self.__class__, str(key),
            property(fget=self._get_property(key),
                     fset=self._set_property(key),
                     fdel=self._del_property(key),
                     doc=str(key)))

    def __setattr__(self, key: str, value: Any) -> None:
        '''Set attributes as items in the dictionary, accesible with dot notation'''
        # this test allows attributes to be set in the __init__ method
        if not '_config_file' in self.__dict__:
            return dict.__setattr__(self, key, value)
        # known attributes set at __init__ are handled normally
        elif key in self.__dict__:
            dict.__setattr__(self, key, value)
        # own dictionary key (it's a property already)
        elif key in self:
            getattr(self.__class__, key).fset(self, value)
        # add unkown attributes to the dictionary
        else:
            self.__setitem__(key, value)

    @staticmethod
    def load_from_dict(d: Dict) -> 'Settings':
        '''Load the dictionary d as settings.'''
        settings = Settings({})
        for key, value in d.items():
            settings[key] = value
        settings._config_file = ''
        return settings

    @property
    def settings(self) -> Dict:
        '''Returns a dictionary with the settings'''
        return {key: value for key, value in self.items()}

    @log_exceptions_warnings
    def _validate_all_values(self, file_dict: Dict) -> Dict:
        '''Validates the settings in the config_dict
            using the settings list.'''
        #        pprint.pprint(file_cte)
        present_values = set(file_dict.keys())

        # if present values don't include all needed values
        if not present_values.issuperset(self._needed_values):
            raise SettingsFileError(
                'Sections that are needed but not present in the file: ' +
                str(self._needed_values - present_values) +
                '. Those sections must be present!')

        set_extra = present_values - self._needed_values
        # if there are extra values and they aren't optional
        if set_extra and not set_extra.issubset(self._optional_values):
            warnings.warn(
                'WARNING! The following values are not recognized:: ' +
                str(set_extra - self._optional_values) +
                '. Those values or sections should not be present',
                SettingsFileWarning)

        parsed_dict = dict(self._dict_value.validate(file_dict))

        #        pprint.pprint(parsed_dict)
        return parsed_dict

    @log_exceptions_warnings
    def validate(self, filename: str) -> None:
        ''' Load filename and extract the settings for the simulations
            If mandatory values are missing, errors are logged
            and exceptions are raised
            Warnings are logged if extra settings are found
        '''
        logger = logging.getLogger(__name__)
        logger.info('Reading settings file (%s)...', filename)

        # load file into config_cte dictionary.
        # the function checks that the file exists and that there are no errors
        file_cte = Loader().load_settings_file(filename)

        # store original configuration file
        with open(filename, 'rt') as file:
            self._config_file = file.read()

        # validate all values in the configuration file
        settings_dict = self._validate_all_values(file_cte)

        # access settings directly as Setting.setting
        for key, value in settings_dict.items():
            self[key] = value

        # log read and validated settings
        # use pretty print
        logger.debug('Settings dump:')
        logger.debug('File dict (config_cte):')
        logger.debug(pprint.pformat(file_cte))
        logger.debug('Validated dict (cte):')
        logger.debug(repr(self))
        logger.info('Settings loaded!')
Esempio n. 8
0
f_float.__name__ = 'float(Fraction())'

# smallest float number
min_float = sys.float_info.min

Vector = Tuple[f_float]
settings = [DictValue('lattice', [Value('name', str),
                                  Value('spacegroup', Union[int, str]),
                                  Value('S_conc', float, val_min=0, val_max=100),
                                  Value('A_conc', float, val_min=0, val_max=100),
                                  Value('a', float, val_min=0),
                                  Value('b', float, val_min=0),
                                  Value('c', float, val_min=0),
                                  Value('alpha', float, val_min=0, val_max=360),
                                  Value('beta', float, val_min=0, val_max=360),
                                  Value('gamma', float, val_min=0, val_max=360),
                                  Value('sites_pos', Union[Vector, List[Vector]],
                                        val_min=0, val_max=1,
                                        len_min=[1, 3], len_max=[None, 3]),
                                  Value('sites_occ', Union[f_float, List[f_float]],
                                        val_min=0, val_max=1, len_min=1),
                                  Value('d_max', float, val_min=0, kind=Value.optional),
                                  Value('d_max_coop', float, val_min=0, kind=Value.optional),
                                  Value('N_uc', int, val_min=1, kind=Value.exclusive),
                                  Value('radius', float, val_min=min_float, kind=Value.exclusive)]),

            DictValue('states', [Value('sensitizer_ion_label', str),
                                 Value('activator_ion_label', str),
                                 Value('sensitizer_states_labels', List[str], len_min=1),
                                 Value('activator_states_labels', List[str], len_min=1)]),
def test_DictValue():
    '''Test DictValues'''
    d = {'age': 28, 'city': 'utrecht'}
    # simple type list
    dval1 = DictValue({'age': int, 'city': str})
    assert dval1.validate(d) == d
    # Value list
    dval2 = DictValue({'age': Value(int, val_min=0), 'city': Value(str)})
    assert dval2.validate(d) == d
    # mixed
    dval3 = DictValue({'age': Value(int, val_min=0), 'city': str})
    assert dval3.validate(d) == d
    # both methods create the same DictValues, their reprs are equal
    assert repr(dval1) == repr(dval2)
    assert repr(dval1) == repr(dval3)

    d = {'age': 28}
    # list of names and types is not a valid constructor
    with pytest.raises(SettingsValueError) as excinfo:
        DictValue(['age', str]).validate(d)
    assert excinfo.match("The first argument must be a dictionary")
    assert excinfo.type == SettingsValueError

    d = 'not a dictionary'
    # DictValues only validate dictionaries
    with pytest.raises(SettingsValueError) as excinfo:
        DictValue({'age': int}).validate(d)
    assert excinfo.match("does not have the right type")
    assert excinfo.type == SettingsValueError

    # extra key:value pairs are ok, but they give off a warning and are ignored
    d = {'age': 28, 'city': 'utrecht'}
    d2 = {'age': 28, 'city': 'utrecht', 'extra': True}
    with pytest.warns(SettingsExtraValueWarning) as warnings:
        assert DictValue({'age': int, 'city': str}).validate(d2) == d
    assert len(warnings) == 1  # one warning
    warning = warnings.pop(SettingsExtraValueWarning)
    assert warning.category == SettingsExtraValueWarning
    assert 'Some values or sections should not be present in the file' in str(
        warning.message)

    d = {'age': 28}
    # the dict doesn't contain all keys
    with pytest.raises(SettingsValueError) as excinfo:
        DictValue({'age': int, 'city': str}).validate(d)
    assert excinfo.match("Setting ")
    assert excinfo.match("not in dictionary")
    assert excinfo.type == SettingsValueError

    d = {'ag': 28, 'city': 'utrecht'}
    # wrong key name
    with pytest.raises(SettingsValueError) as excinfo:
        DictValue({'age': int, 'city': str}).validate(d)
    assert excinfo.match("Setting ")
    assert excinfo.match("not in dictionary")
    assert excinfo.type == SettingsValueError

    d = {'age': 'asd', 'city': 'utrecht'}
    # wrong value type
    with pytest.raises(SettingsValueError) as excinfo:
        DictValue({'age': int, 'city': str}).validate(d)
    assert excinfo.match("Setting ")
    assert excinfo.match("does not have the right type")
    assert excinfo.type == SettingsValueError

    # DictValue in Value
    d = {'age': 28, 'city': 'utrecht'}
    assert Value(DictValue({'age': int, 'city': str})).validate(d) == d