serialize metaconfig/groupconfig

This commit is contained in:
Emmanuel Garette 2013-09-30 16:22:08 +02:00
parent 6b7db20716
commit feeb9842f5
4 changed files with 227 additions and 105 deletions

View File

@ -2,8 +2,8 @@
import autopath import autopath
#from py.test import raises #from py.test import raises
from tiramisu.config import Config from tiramisu.config import Config, GroupConfig, MetaConfig
from tiramisu.option import BoolOption, OptionDescription from tiramisu.option import BoolOption, IntOption, OptionDescription
import weakref import weakref
@ -109,3 +109,31 @@ def test_deref_optiondescription_config():
assert w() is not None assert w() is not None
del(c) del(c)
assert w() is None assert w() is None
def test_deref_groupconfig():
i1 = IntOption('i1', '')
od1 = OptionDescription('od1', '', [i1])
od2 = OptionDescription('od2', '', [od1])
conf1 = Config(od2)
conf2 = Config(od2)
meta = GroupConfig([conf1, conf2])
w = weakref.ref(conf1)
del(conf1)
assert w() is not None
del(meta)
assert w() is None
def test_deref_metaconfig():
i1 = IntOption('i1', '')
od1 = OptionDescription('od1', '', [i1])
od2 = OptionDescription('od2', '', [od1])
conf1 = Config(od2)
conf2 = Config(od2)
meta = MetaConfig([conf1, conf2])
w = weakref.ref(conf1)
del(conf1)
assert w() is not None
del(meta)
assert w() is None

View File

@ -3,7 +3,7 @@ import autopath
from py.test import raises from py.test import raises
from tiramisu.setting import owners from tiramisu.setting import owners
from tiramisu.config import Config, MetaConfig from tiramisu.config import Config, GroupConfig, MetaConfig
from tiramisu.option import IntOption, OptionDescription from tiramisu.option import IntOption, OptionDescription
from tiramisu.error import ConfigError from tiramisu.error import ConfigError
@ -26,6 +26,7 @@ def make_description():
#FIXME ne pas mettre 2 meta dans une config #FIXME ne pas mettre 2 meta dans une config
#FIXME ne pas mettre 2 OD differents dans un meta #FIXME ne pas mettre 2 OD differents dans un meta
#FIXME serialization
def test_none(): def test_none():
meta = make_description() meta = make_description()
conf1, conf2 = meta._impl_children conf1, conf2 = meta._impl_children
@ -89,7 +90,7 @@ def test_contexts():
conf1, conf2 = meta._impl_children conf1, conf2 = meta._impl_children
assert conf1.od1.i2 == conf2.od1.i2 == 1 assert conf1.od1.i2 == conf2.od1.i2 == 1
assert conf1.getowner(conf1.unwrap_from_path('od1.i2')) is conf2.getowner(conf2.unwrap_from_path('od1.i2')) is owners.default assert conf1.getowner(conf1.unwrap_from_path('od1.i2')) is conf2.getowner(conf2.unwrap_from_path('od1.i2')) is owners.default
meta.set_contexts('od1.i2', 6) meta.setattrs('od1.i2', 6)
assert meta.od1.i2 == 1 assert meta.od1.i2 == 1
assert conf1.od1.i2 == conf2.od1.i2 == 6 assert conf1.od1.i2 == conf2.od1.i2 == 6
assert conf1.getowner(conf1.unwrap_from_path('od1.i2')) is conf2.getowner(conf2.unwrap_from_path('od1.i2')) is owners.user assert conf1.getowner(conf1.unwrap_from_path('od1.i2')) is conf2.getowner(conf2.unwrap_from_path('od1.i2')) is owners.user
@ -142,14 +143,14 @@ def test_meta_meta_set():
meta2 = MetaConfig([meta1]) meta2 = MetaConfig([meta1])
meta2.cfgimpl_get_settings().setowner(owners.meta) meta2.cfgimpl_get_settings().setowner(owners.meta)
conf1, conf2 = meta1._impl_children conf1, conf2 = meta1._impl_children
meta2.set_contexts('od1.i1', 7) meta2.setattrs('od1.i1', 7)
assert conf1.od1.i1 == conf2.od1.i1 == 7 assert conf1.od1.i1 == conf2.od1.i1 == 7
assert conf1.getowner(conf1.unwrap_from_path('od1.i1')) is conf2.getowner(conf2.unwrap_from_path('od1.i1')) is owners.user assert conf1.getowner(conf1.unwrap_from_path('od1.i1')) is conf2.getowner(conf2.unwrap_from_path('od1.i1')) is owners.user
assert [conf1, conf2] == meta2.find_first_contexts(byname='i1', byvalue=7) assert [conf1, conf2] == meta2.find_firsts(byname='i1', byvalue=7)
conf1.od1.i1 = 8 conf1.od1.i1 = 8
assert [conf2] == meta2.find_first_contexts(byname='i1', byvalue=7) assert [conf2] == meta2.find_firsts(byname='i1', byvalue=7)
assert [conf1] == meta2.find_first_contexts(byname='i1', byvalue=8) assert [conf1] == meta2.find_firsts(byname='i1', byvalue=8)
raises(AttributeError, "meta2.find_first_contexts(byname='i1', byvalue=10)") raises(AttributeError, "meta2.find_firsts(byname='i1', byvalue=10)")
def test_not_meta(): def test_not_meta():
@ -158,10 +159,10 @@ def test_not_meta():
od2 = OptionDescription('od2', '', [od1]) od2 = OptionDescription('od2', '', [od1])
conf1 = Config(od2) conf1 = Config(od2)
conf2 = Config(od2) conf2 = Config(od2)
meta = MetaConfig([conf1, conf2], False) meta = GroupConfig([conf1, conf2])
raises(ConfigError, 'meta.od1.i1') raises(ConfigError, 'meta.od1.i1')
conf1, conf2 = meta._impl_children conf1, conf2 = meta._impl_children
meta.set_contexts('od1.i1', 7) meta.setattrs('od1.i1', 7)
assert conf1.od1.i1 == conf2.od1.i1 == 7 assert conf1.od1.i1 == conf2.od1.i1 == 7
assert conf1.getowner(conf1.unwrap_from_path('od1.i1')) is conf2.getowner(conf2.unwrap_from_path('od1.i1')) is owners.user assert conf1.getowner(conf1.unwrap_from_path('od1.i1')) is conf2.getowner(conf2.unwrap_from_path('od1.i1')) is owners.user

View File

@ -1,6 +1,6 @@
from tiramisu.option import BoolOption, UnicodeOption, SymLinkOption, \ from tiramisu.option import BoolOption, UnicodeOption, SymLinkOption, \
OptionDescription IntOption, OptionDescription
from tiramisu.config import Config from tiramisu.config import Config, GroupConfig, MetaConfig
from tiramisu.setting import owners from tiramisu.setting import owners
from tiramisu.storage import delete_session from tiramisu.storage import delete_session
from tiramisu.error import ConfigError from tiramisu.error import ConfigError
@ -90,6 +90,45 @@ def _diff_opt(opt1, opt2):
assert val1 == val2 assert val1 == val2
def _diff_conf(cfg1, cfg2):
attr1 = set(_get_slots(cfg1))
attr2 = set(_get_slots(cfg2))
diff1 = attr1 - attr2
diff2 = attr2 - attr1
if diff1 != set():
raise Exception('more attribute in cfg1 {0}'.format(list(diff1)))
if diff2 != set():
raise Exception('more attribute in cfg2 {0}'.format(list(diff2)))
for attr in attr1:
if attr in ('_impl_context', '__weakref__'):
continue
err1 = False
err2 = False
val1 = None
val2 = None
try:
val1 = getattr(cfg1, attr)
except:
err1 = True
try:
val2 = getattr(cfg2, attr)
except:
err2 = True
assert err1 == err2
if val1 is None:
assert val1 == val2
elif attr == '_impl_values':
assert cfg1.cfgimpl_get_values().get_modified_values() == cfg2.cfgimpl_get_values().get_modified_values()
elif attr == '_impl_settings':
assert cfg1.cfgimpl_get_settings().get_modified_properties() == cfg2.cfgimpl_get_settings().get_modified_properties()
assert cfg1.cfgimpl_get_settings().get_modified_permissives() == cfg2.cfgimpl_get_settings().get_modified_permissives()
elif attr == '_impl_descr':
_diff_opt(cfg1.cfgimpl_get_description(), cfg2.cfgimpl_get_description())
else:
assert val1 == val2
def test_diff_opt(): def test_diff_opt():
b = BoolOption('b', '') b = BoolOption('b', '')
u = UnicodeOption('u', '', requires=[{'option': b, 'expected': True, 'action': 'disabled', 'inverse': True}]) u = UnicodeOption('u', '', requires=[{'option': b, 'expected': True, 'action': 'disabled', 'inverse': True}])
@ -169,10 +208,7 @@ def test_state_config():
cfg._impl_test = True cfg._impl_test = True
a = dumps(cfg) a = dumps(cfg)
q = loads(a) q = loads(a)
_diff_opt(maconfig, q.cfgimpl_get_description()) _diff_conf(cfg, q)
assert cfg.cfgimpl_get_values().get_modified_values() == q.cfgimpl_get_values().get_modified_values()
assert cfg.cfgimpl_get_settings().get_modified_properties() == q.cfgimpl_get_settings().get_modified_properties()
assert cfg.cfgimpl_get_settings().get_modified_permissives() == q.cfgimpl_get_settings().get_modified_permissives()
try: try:
delete_session('29090931') delete_session('29090931')
except ConfigError: except ConfigError:
@ -191,12 +227,9 @@ def test_state_properties():
cfg.cfgimpl_get_settings()[val1].append('test') cfg.cfgimpl_get_settings()[val1].append('test')
a = dumps(cfg) a = dumps(cfg)
q = loads(a) q = loads(a)
_diff_opt(maconfig, q.cfgimpl_get_description()) _diff_conf(cfg, q)
assert cfg.cfgimpl_get_values().get_modified_values() == q.cfgimpl_get_values().get_modified_values()
assert cfg.cfgimpl_get_settings().get_modified_properties() == q.cfgimpl_get_settings().get_modified_properties()
assert cfg.cfgimpl_get_settings().get_modified_permissives() == q.cfgimpl_get_settings().get_modified_permissives()
try: try:
delete_session('29090931') delete_session('29090932')
except ConfigError: except ConfigError:
pass pass
@ -212,15 +245,12 @@ def test_state_values():
cfg.val1 = True cfg.val1 = True
a = dumps(cfg) a = dumps(cfg)
q = loads(a) q = loads(a)
_diff_opt(maconfig, q.cfgimpl_get_description()) _diff_conf(cfg, q)
assert cfg.cfgimpl_get_values().get_modified_values() == q.cfgimpl_get_values().get_modified_values()
assert cfg.cfgimpl_get_settings().get_modified_properties() == q.cfgimpl_get_settings().get_modified_properties()
assert cfg.cfgimpl_get_settings().get_modified_permissives() == q.cfgimpl_get_settings().get_modified_permissives()
q.val1 = False q.val1 = False
#assert cfg.val1 is True #assert cfg.val1 is True
assert q.val1 is False assert q.val1 is False
try: try:
delete_session('29090931') delete_session('29090933')
except ConfigError: except ConfigError:
pass pass
@ -238,14 +268,53 @@ def test_state_values_owner():
cfg.val1 = True cfg.val1 = True
a = dumps(cfg) a = dumps(cfg)
q = loads(a) q = loads(a)
_diff_opt(maconfig, q.cfgimpl_get_description()) _diff_conf(cfg, q)
assert cfg.cfgimpl_get_values().get_modified_values() == q.cfgimpl_get_values().get_modified_values()
assert cfg.cfgimpl_get_settings().get_modified_properties() == q.cfgimpl_get_settings().get_modified_properties()
assert cfg.cfgimpl_get_settings().get_modified_permissives() == q.cfgimpl_get_settings().get_modified_permissives()
q.val1 = False q.val1 = False
nval1 = q.cfgimpl_get_description().val1 nval1 = q.cfgimpl_get_description().val1
assert q.getowner(nval1) == owners.newowner assert q.getowner(nval1) == owners.newowner
try: try:
delete_session('29090931') delete_session('29090934')
except ConfigError:
pass
def test_state_metaconfig():
i1 = IntOption('i1', '')
od1 = OptionDescription('od1', '', [i1])
od2 = OptionDescription('od2', '', [od1])
conf1 = Config(od2, session_id='29090935')
conf1._impl_test = True
conf2 = Config(od2, session_id='29090936')
conf2._impl_test = True
meta = MetaConfig([conf1, conf2], session_id='29090937')
meta._impl_test = True
a = dumps(meta)
q = loads(a)
_diff_conf(meta, q)
try:
delete_session('29090935')
delete_session('29090936')
delete_session('29090937')
except ConfigError:
pass
def test_state_groupconfig():
i1 = IntOption('i1', '')
od1 = OptionDescription('od1', '', [i1])
od2 = OptionDescription('od2', '', [od1])
conf1 = Config(od2, session_id='29090935')
conf1._impl_test = True
conf2 = Config(od2, session_id='29090936')
conf2._impl_test = True
meta = GroupConfig([conf1, conf2], session_id='29090937')
meta._impl_test = True
a = dumps(meta)
q = loads(a)
_diff_conf(meta, q)
try:
delete_session('29090935')
delete_session('29090936')
delete_session('29090937')
except ConfigError: except ConfigError:
pass pass

View File

@ -161,7 +161,7 @@ class SubConfig(object):
def cfgimpl_get_description(self): def cfgimpl_get_description(self):
if self._impl_descr is None: if self._impl_descr is None:
raise ConfigError(_('no option description found for this config' raise ConfigError(_('no option description found for this config'
' (may be metaconfig without meta)')) ' (may be GroupConfig)'))
else: else:
return self._impl_descr return self._impl_descr
@ -467,9 +467,9 @@ class SubConfig(object):
return context_descr.impl_get_path_by_opt(descr) return context_descr.impl_get_path_by_opt(descr)
class CommonConfig(SubConfig): class _CommonConfig(SubConfig):
"abstract base class for the Config and the MetaConfig" "abstract base class for the Config, GroupConfig and the MetaConfig"
__slots__ = ('_impl_values', '_impl_settings', '_impl_meta') __slots__ = ('_impl_values', '_impl_settings', '_impl_meta', '_impl_test')
def _impl_build_all_paths(self): def _impl_build_all_paths(self):
self.cfgimpl_get_description().impl_build_cache() self.cfgimpl_get_description().impl_build_cache()
@ -508,7 +508,8 @@ class CommonConfig(SubConfig):
return None return None
def cfgimpl_get_meta(self): def cfgimpl_get_meta(self):
return self._impl_meta if self._impl_meta is not None:
return self._impl_meta()
# information # information
def impl_set_information(self, key, value): def impl_set_information(self, key, value):
@ -526,37 +527,12 @@ class CommonConfig(SubConfig):
""" """
return self._impl_values.get_information(key, default) return self._impl_values.get_information(key, default)
# ----- state
# ____________________________________________________________
class Config(CommonConfig):
"main configuration management entry"
__slots__ = ('__weakref__', '_impl_test')
def __init__(self, descr, session_id=None, persistent=False):
""" Configuration option management master class
:param descr: describes the configuration schema
:type descr: an instance of ``option.OptionDescription``
:param context: the current root config
:type context: `Config`
:param session_id: session ID is import with persistent Config to
retrieve good session
:type session_id: `str`
:param persistent: if persistent, don't delete storage when leaving
:type persistent: `boolean`
"""
settings, values = get_storages(self, session_id, persistent)
self._impl_settings = Settings(self, settings)
self._impl_values = Values(self, values)
super(Config, self).__init__(descr, weakref.ref(self))
self._impl_build_all_paths()
self._impl_meta = None
#undocumented option used only in test script
self._impl_test = False
def __getstate__(self): def __getstate__(self):
if self._impl_meta is not None: if self._impl_meta is not None:
raise ConfigError('cannot serialize Config with meta') #FIXME _impl_meta est un weakref => faut pas sauvegarder mais faut bien savoir si c'est un méta ou pas au final ...
#en fait il faut ne pouvoir sérialisé que depuis une MetaConfig ... et pas directement comme pour les options
raise ConfigError('cannot serialize Config with MetaConfig')
slots = set() slots = set()
for subclass in self.__class__.__mro__: for subclass in self.__class__.__mro__:
if subclass is not object: if subclass is not object:
@ -589,6 +565,34 @@ class Config(CommonConfig):
self._impl_values._impl_setstate(storage) self._impl_values._impl_setstate(storage)
self._impl_settings._impl_setstate(storage) self._impl_settings._impl_setstate(storage)
# ____________________________________________________________
class Config(_CommonConfig):
"main configuration management entry"
__slots__ = ('__weakref__',)
def __init__(self, descr, session_id=None, persistent=False):
""" Configuration option management master class
:param descr: describes the configuration schema
:type descr: an instance of ``option.OptionDescription``
:param context: the current root config
:type context: `Config`
:param session_id: session ID is import with persistent Config to
retrieve good session
:type session_id: `str`
:param persistent: if persistent, don't delete storage when leaving
:type persistent: `boolean`
"""
settings, values = get_storages(self, session_id, persistent)
self._impl_settings = Settings(self, settings)
self._impl_values = Values(self, values)
super(Config, self).__init__(descr, weakref.ref(self))
self._impl_build_all_paths()
self._impl_meta = None
#undocumented option used only in test script
self._impl_test = False
def cfgimpl_reset_cache(self, def cfgimpl_reset_cache(self,
only_expired=False, only_expired=False,
only=('values', 'settings')): only=('values', 'settings')):
@ -598,42 +602,29 @@ class Config(CommonConfig):
self.cfgimpl_get_settings().reset_cache(only_expired=only_expired) self.cfgimpl_get_settings().reset_cache(only_expired=only_expired)
class MetaConfig(CommonConfig): class GroupConfig(_CommonConfig):
__slots__ = ('_impl_children', '__weakref__') __slots__ = ('_impl_children', '__weakref__')
def __init__(self, children, meta=True, session_id=None, persistent=False): def __init__(self, children, session_id=None, persistent=False,
_descr=None):
if not isinstance(children, list): if not isinstance(children, list):
raise ValueError(_("metaconfig's children must be a list")) raise ValueError(_("metaconfig's children must be a list"))
descr = None
if meta:
for child in children:
if not isinstance(child, CommonConfig):
raise TypeError(_("metaconfig's children "
"must be config, not {0}"
).format(type(child)))
if descr is None:
descr = child.cfgimpl_get_description()
elif not descr is child.cfgimpl_get_description():
raise ValueError(_('all config in metaconfig must '
'have the same optiondescription'))
if child.cfgimpl_get_meta() is not None:
raise ValueError(_("child has already a metaconfig's"))
child._impl_meta = self
self._impl_children = children self._impl_children = children
settings, values = get_storages(self, session_id, persistent) settings, values = get_storages(self, session_id, persistent)
self._impl_settings = Settings(self, settings) self._impl_settings = Settings(self, settings)
self._impl_values = Values(self, values) self._impl_values = Values(self, values)
super(MetaConfig, self).__init__(descr, weakref.ref(self)) super(GroupConfig, self).__init__(_descr, weakref.ref(self))
self._impl_meta = None self._impl_meta = None
#undocumented option used only in test script
self._impl_test = False
def cfgimpl_get_children(self): def cfgimpl_get_children(self):
return self._impl_children return self._impl_children
def cfgimpl_get_context(self): #def cfgimpl_get_context(self):
"a meta config is a config wich has a setting, that is itself" # "a meta config is a config which has a setting, that is itself"
return self # return self
#
def cfgimpl_reset_cache(self, def cfgimpl_reset_cache(self,
only_expired=False, only_expired=False,
only=('values', 'settings')): only=('values', 'settings')):
@ -644,38 +635,49 @@ class MetaConfig(CommonConfig):
for child in self._impl_children: for child in self._impl_children:
child.cfgimpl_reset_cache(only_expired=only_expired, only=only) child.cfgimpl_reset_cache(only_expired=only_expired, only=only)
def set_contexts(self, path, value): def setattrs(self, path, value):
"""Setattr not in current GroupConfig, but in each children
"""
for child in self._impl_children: for child in self._impl_children:
try: try:
if not isinstance(child, MetaConfig): if not isinstance(child, GroupConfig):
setattr(child, path, value) setattr(child, path, value)
else: else:
child.set_contexts(path, value) child.setattrs(path, value)
except PropertiesOptionError: except PropertiesOptionError:
pass pass
def find_first_contexts(self, byname=None, bypath=None, byvalue=None, def find_firsts(self, byname=None, bypath=None, byvalue=None,
type_='path', display_error=True): type_='path', display_error=True):
"""Find first not in current GroupConfig, but in each children
"""
ret = [] ret = []
#if MetaConfig, all children have same OptionDescription as context
#so search only one time for all children
try: try:
if bypath is None and byname is not None and \ if bypath is None and byname is not None and \
self.cfgimpl_get_description() is not None: isinstance(self, MetaConfig):
bypath = self._find(bytype=None, byvalue=None, byname=byname, bypath = self._find(bytype=None, byvalue=None, byname=byname,
first=True, type_='path', first=True, type_='path',
check_properties=False, check_properties=False,
display_error=display_error) display_error=display_error)
except ConfigError: byname = None
except AttributeError:
pass pass
for child in self._impl_children: for child in self._impl_children:
try: try:
if not isinstance(child, MetaConfig): if not isinstance(child, MetaConfig):
if bypath is not None: if bypath is not None:
#if byvalue is None, try if not raise
value = getattr(child, bypath)
if byvalue is not None: if byvalue is not None:
if getattr(child, bypath) == byvalue: if isinstance(value, Multi):
if byvalue in value:
ret.append(child)
else:
if value == byvalue:
ret.append(child) ret.append(child)
else: else:
#not raise
getattr(child, bypath)
ret.append(child) ret.append(child)
else: else:
ret.append(child.find_first(byname=byname, ret.append(child.find_first(byname=byname,
@ -683,7 +685,7 @@ class MetaConfig(CommonConfig):
type_=type_, type_=type_,
display_error=False)) display_error=False))
else: else:
ret.extend(child.find_first_contexts(byname=byname, ret.extend(child.find_firsts(byname=byname,
bypath=bypath, bypath=bypath,
byvalue=byvalue, byvalue=byvalue,
type_=type_, type_=type_,
@ -693,6 +695,28 @@ class MetaConfig(CommonConfig):
return self._find_return_results(ret, display_error) return self._find_return_results(ret, display_error)
class MetaConfig(GroupConfig):
__slots__ = tuple()
def __init__(self, children, session_id=None, persistent=False):
descr = None
for child in children:
if not isinstance(child, _CommonConfig):
raise TypeError(_("metaconfig's children "
"should be config, not {0}"
).format(type(child)))
if child.cfgimpl_get_meta() is not None:
raise ValueError(_("child has already a metaconfig's"))
if descr is None:
descr = child.cfgimpl_get_description()
elif not descr is child.cfgimpl_get_description():
raise ValueError(_('all config in metaconfig must '
'have the same optiondescription'))
child._impl_meta = weakref.ref(self)
super(MetaConfig, self).__init__(children, session_id, persistent, descr)
def mandatory_warnings(config): def mandatory_warnings(config):
"""convenience function to trace Options that are mandatory and """convenience function to trace Options that are mandatory and
where no value has been set where no value has been set