Source code for tBB.settings
#!/usr/bin/python3
#
# tBB is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# tBB 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
This module takes care of representing and handling settings throughout tBB.
"""
import enum
import re
import datetime
valid_item_name = re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*')
timedelta_parse_string = '%M:%S'
[docs]class UndefinedValueException(Exception):
def __init__(self):
super().__init__('self.value needs to be defined before converting it.')
[docs]class ConversionException(Exception):
def __init__(self, value, value_type):
super().__init__("couldn't convert '{}' to {}.".format(value, value_type))
[docs]class UnknownSettingException(Exception):
def __init__(self, setting_path):
self.setting_path = setting_path
super().__init__("defined setting '{}' is unknown.".format(setting_path))
[docs]class InconsistentSettingTypeException(Exception):
def __init__(self, setting_path, should_be, got):
self.setting_path = setting_path
self.should_be = should_be
self.got = got
super().__init__("setting type for '{}' should be {}. Got: {}.".format(setting_path, should_be, got))
[docs]class SettingsTypes(enum.Enum):
unknown = -1
string = 0 # need no conversion
integer = 1 # need no conversion
boolean = 2 # need no conversion
timedelta = 3
settings_item = 4
list = 5 # need no conversion
[docs]class SettingsItem:
def __init__(self, name, value_type):
if type(name) != str:
raise TypeError('argument name must be a string.')
if not isinstance(value_type, SettingsTypes):
raise TypeError('argument value_types must be a SettingsTypes instance.')
if re.match(valid_item_name, name) is None:
raise ValueError('settings item name is not acceptable. See tBB.settings.valid_item_name.')
else:
try:
self.__getattribute__(name)
except AttributeError:
pass
else:
raise ValueError('settings item name is not acceptable. Name reserved.')
self.name = name
self.value = None
self.value_type = value_type
[docs] def convert(self):
if self.value is None:
raise UndefinedValueException()
# static conversions: basically, only do type checking
if self.value_type == SettingsTypes.string:
if type(self.value) != str:
raise ConversionException(self.value, self.value_type)
elif self.value_type == SettingsTypes.integer:
if type(self.value) != int:
raise ConversionException(self.value, self.value_type)
elif self.value_type == SettingsTypes.boolean:
if type(self.value) != bool:
raise ConversionException(self.value, self.value_type)
# complex conversions
elif self.value_type == SettingsTypes.timedelta:
self.value = self.convert_to_timedelta(self.value)
elif self.value_type == SettingsTypes.settings_item:
self.value = self.convert_to_settings_item(self.value)
elif self.value_type == SettingsTypes.unknown: # make a guess on what it could be
if type(self.value) == int:
self.value_type = SettingsTypes.integer
elif type(self.value) == bool:
self.value_type = SettingsTypes.boolean
elif type(self.value) == list:
self.value_type = SettingsTypes.list
elif type(self.value) == dict:
try:
self.value = self.convert_to_settings_item(self.value)
except ConversionException as exc:
raise exc
else:
self.value_type = SettingsTypes.settings_item
elif type(self.value) == str:
try:
self.value = self.convert_to_timedelta(self.value)
except ConversionException:
self.value_type = SettingsTypes.string
else:
self.value_type = SettingsTypes.timedelta
@staticmethod
[docs] def convert_to_timedelta(value):
try:
tmp = datetime.datetime.strptime(value, timedelta_parse_string)
return datetime.timedelta(minutes=tmp.minute, seconds=tmp.second)
except (ValueError, TypeError) as exc:
raise ConversionException(value, SettingsTypes.timedelta) from exc
@staticmethod
[docs] def convert_to_settings_item(value):
if type(value) != dict:
raise ConversionException(value, SettingsTypes.settings_item)
children = {}
for name, elem in value.items():
new_item = SettingsItem(name=name, value_type=SettingsTypes.unknown)
new_item.value = elem
children[name] = new_item
for child in children.values():
child.convert()
return children
def __getattr__(self, item):
try:
return self.__getattribute__(item)
except AttributeError as exc:
if self.value_type == SettingsTypes.settings_item and self.value is not None:
if item in self.value.keys():
return self.value[item]
else:
raise exc
def __repr__(self):
return "<{} '{}' ({})>".format(self.__class__.__name__, self.name, self.value_type)
[docs]class Settings:
def __init__(self, tree):
if not isinstance(tree, SettingsItem):
raise TypeError("expected SettingsItem instance for argument tree. Got: '{}'.".format(tree))
self.tree = tree
[docs] def update(self, new_tree, scope=''):
if not isinstance(new_tree, SettingsItem):
raise TypeError("expected SettingsItem instance for argument tree. "
"Got: '{}'.".format(new_tree))
if type(new_tree.value) != dict:
walked_path = 'self.tree'
try:
setting = self.tree
for selector in scope.split('.')[1:]:
walked_path += '.' + selector
setting = getattr(setting, selector)
except AttributeError:
raise UnknownSettingException(walked_path)
else:
if setting.value_type != new_tree.value_type:
raise InconsistentSettingTypeException(scope, setting.value_type,
new_tree.value_type)
setting.value = new_tree.value
else:
for name in new_tree.value:
if new_tree.value_type == SettingsTypes.settings_item:
self.update(new_tree.value[name], scope=self.tree.name+scope+'.'+name)
else:
raise TypeError("expected iterators inside new_tree to be SettingsTypes.settings_item. "
"Got: {}".format(new_tree.value_type))
@staticmethod
[docs] def parse(json_data, name='toplevel'):
tree = SettingsItem(name=name, value_type=SettingsTypes.settings_item)
tree.value = json_data
tree.convert()
return tree
def __getattr__(self, item):
return self.tree.__getattr__(item)