attributes in Option are now read-only if option set in Config (_name is everytime read-only)

This commit is contained in:
Emmanuel Garette 2013-08-30 21:15:55 +02:00
parent c01f14920d
commit 5893f8ad72
2 changed files with 119 additions and 6 deletions

View File

@ -37,6 +37,84 @@ def test_slots_option():
raises(AttributeError, "c.x = 1") raises(AttributeError, "c.x = 1")
def test_slots_option_readonly():
a = ChoiceOption('a', '', ('a',))
b = BoolOption('b', '')
c = IntOption('c', '')
d = FloatOption('d', '')
e = StrOption('e', '')
g = UnicodeOption('g', '')
h = IPOption('h', '')
i = PortOption('i', '')
j = NetworkOption('j', '')
k = NetmaskOption('k', '')
l = DomainnameOption('l', '')
m = OptionDescription('m', '', [a, b, c, d, e, g, h, i, j, k, l])
a._requires = 'a'
b._requires = 'b'
c._requires = 'c'
d._requires = 'd'
e._requires = 'e'
g._requires = 'g'
h._requires = 'h'
i._requires = 'i'
j._requires = 'j'
k._requires = 'k'
l._requires = 'l'
m._requires = 'm'
Config(m)
raises(AttributeError, "a._requires = 'a'")
raises(AttributeError, "b._requires = 'b'")
raises(AttributeError, "c._requires = 'c'")
raises(AttributeError, "d._requires = 'd'")
raises(AttributeError, "e._requires = 'e'")
raises(AttributeError, "g._requires = 'g'")
raises(AttributeError, "h._requires = 'h'")
raises(AttributeError, "i._requires = 'i'")
raises(AttributeError, "j._requires = 'j'")
raises(AttributeError, "k._requires = 'k'")
raises(AttributeError, "l._requires = 'l'")
raises(AttributeError, "m._requires = 'm'")
def test_slots_option_readonly_name():
a = ChoiceOption('a', '', ('a',))
b = BoolOption('b', '')
c = IntOption('c', '')
d = FloatOption('d', '')
e = StrOption('e', '')
f = SymLinkOption('f', c)
g = UnicodeOption('g', '')
h = IPOption('h', '')
i = PortOption('i', '')
j = NetworkOption('j', '')
k = NetmaskOption('k', '')
l = DomainnameOption('l', '')
m = OptionDescription('m', '', [a, b, c, d, e, f, g, h, i, j, k, l])
raises(AttributeError, "a._name = 'a'")
raises(AttributeError, "b._name = 'b'")
raises(AttributeError, "c._name = 'c'")
raises(AttributeError, "d._name = 'd'")
raises(AttributeError, "e._name = 'e'")
raises(AttributeError, "f._name = 'f'")
raises(AttributeError, "g._name = 'g'")
raises(AttributeError, "h._name = 'h'")
raises(AttributeError, "i._name = 'i'")
raises(AttributeError, "j._name = 'j'")
raises(AttributeError, "k._name = 'k'")
raises(AttributeError, "l._name = 'l'")
raises(AttributeError, "m._name = 'm'")
def test_slots_description():
# __slots__ for OptionDescription must be complete
slots = set()
for subclass in OptionDescription.__mro__:
if subclass is not object:
slots.update(subclass.__slots__)
assert slots == set(OptionDescription.__slots__)
def test_slots_config(): def test_slots_config():
od1 = OptionDescription('a', '', []) od1 = OptionDescription('a', '', [])
od2 = OptionDescription('a', '', [od1]) od2 = OptionDescription('a', '', [od1])

View File

@ -91,7 +91,37 @@ class BaseInformation(object):
self.__class__.__name__)) self.__class__.__name__))
class Option(BaseInformation): class _ReadOnlyOption(BaseInformation):
__slots__ = ('_readonly',)
def __setattr__(self, name, value):
is_readonly = False
# never change _name
if name == '_name':
try:
self._name
#so _name is already set
is_readonly = True
except:
pass
try:
if self._readonly is True:
if value is True:
# already readonly and try to re set readonly
# don't raise, just exit
return
is_readonly = True
except AttributeError:
pass
if is_readonly:
raise AttributeError(_("'{0}' ({1}) object attribute '{2}' is"
" read-only").format(
self.__class__.__name__, self._name,
name))
object.__setattr__(self, name, value)
class Option(_ReadOnlyOption):
""" """
Abstract base class for configuration option's. Abstract base class for configuration option's.
@ -450,10 +480,9 @@ else:
raise ValueError(_('value must be an unicode')) raise ValueError(_('value must be an unicode'))
class SymLinkOption(object): class SymLinkOption(_ReadOnlyOption):
__slots__ = ('_name', '_opt') __slots__ = ('_name', '_opt')
_opt_type = 'symlink' _opt_type = 'symlink'
_consistencies = None
def __init__(self, name, opt): def __init__(self, name, opt):
self._name = name self._name = name
@ -462,9 +491,10 @@ class SymLinkOption(object):
'must be an option ' 'must be an option '
'for symlink {0}').format(name)) 'for symlink {0}').format(name))
self._opt = opt self._opt = opt
self._readonly = True
def __getattr__(self, name): def __getattr__(self, name):
if name in ('_name', '_opt', '_consistencies'): if name in ('_name', '_opt', '_opt_type', '_readonly'):
return object.__getattr__(self, name) return object.__getattr__(self, name)
else: else:
return getattr(self._opt, name) return getattr(self._opt, name)
@ -684,13 +714,13 @@ class DomainnameOption(Option):
raise ValueError(_('invalid domainname')) raise ValueError(_('invalid domainname'))
class OptionDescription(BaseInformation): class OptionDescription(_ReadOnlyOption):
"""Config's schema (organisation, group) and container of Options """Config's schema (organisation, group) and container of Options
The `OptionsDescription` objects lives in the `tiramisu.config.Config`. The `OptionsDescription` objects lives in the `tiramisu.config.Config`.
""" """
__slots__ = ('_name', '_requires', '_cache_paths', '_group_type', __slots__ = ('_name', '_requires', '_cache_paths', '_group_type',
'_properties', '_children', '_consistencies', '_properties', '_children', '_consistencies',
'_calc_properties', '__weakref__') '_calc_properties', '__weakref__', '_readonly', '_impl_informations')
def __init__(self, name, doc, children, requires=None, properties=None): def __init__(self, name, doc, children, requires=None, properties=None):
""" """
@ -731,6 +761,8 @@ class OptionDescription(BaseInformation):
return self.impl_get_information('doc') return self.impl_get_information('doc')
def __getattr__(self, name): def __getattr__(self, name):
if name in self.__slots__:
return object.__getattribute__(self, name)
try: try:
return self._children[1][self._children[0].index(name)] return self._children[1][self._children[0].index(name)]
except ValueError: except ValueError:
@ -769,6 +801,7 @@ class OptionDescription(BaseInformation):
_currpath=None, _currpath=None,
_consistencies=None): _consistencies=None):
if _currpath is None and self._cache_paths is not None: if _currpath is None and self._cache_paths is not None:
# cache already set
return return
if _currpath is None: if _currpath is None:
save = True save = True
@ -787,6 +820,7 @@ class OptionDescription(BaseInformation):
raise ConflictError(_('duplicate option: {0}').format(option)) raise ConflictError(_('duplicate option: {0}').format(option))
cache_option.append(option) cache_option.append(option)
option._readonly = True
cache_path.append(str('.'.join(_currpath + [attr]))) cache_path.append(str('.'.join(_currpath + [attr])))
if not isinstance(option, OptionDescription): if not isinstance(option, OptionDescription):
if option._consistencies is not None: if option._consistencies is not None:
@ -807,6 +841,7 @@ class OptionDescription(BaseInformation):
if save: if save:
self._cache_paths = (tuple(cache_option), tuple(cache_path)) self._cache_paths = (tuple(cache_option), tuple(cache_path))
self._consistencies = _consistencies self._consistencies = _consistencies
self._readonly = True
def impl_get_opt_by_path(self, path): def impl_get_opt_by_path(self, path):
try: try: