_calc_requirement is a set, not a tuple
[tiramisu.git] / tiramisu / config.py
index eb4dd77..dd0b0f9 100644 (file)
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-"pretty small and local configuration management tool"
+"options handler global entry point"
 # Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors)
 #
 # This program is free software; you can redistribute it and/or modify
 # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
 # the whole pypy projet is under MIT licence
 # ____________________________________________________________
+from time import time
 from tiramisu.error import PropertiesOptionError, ConfigError
 from tiramisu.option import OptionDescription, Option, SymLinkOption, \
     BaseInformation
-from tiramisu.setting import groups, Setting, default_encoding
+from tiramisu.setting import groups, Settings, default_encoding, storage_type
 from tiramisu.value import Values
 from tiramisu.i18n import _
 
 
+def gen_id(config):
+    return str(id(config)) + str(time())
+
+
 class SubConfig(BaseInformation):
     "sub configuration management entry"
-    __slots__ = ('_impl_context', '_impl_descr')
+    __slots__ = ('_impl_context', '_impl_descr', '_impl_path')
 
-    def __init__(self, descr, context):
+    def __init__(self, descr, context, subpath=None):
         """ 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`
+        :type subpath: `str` with the path name
         """
         # main option description
         if not isinstance(descr, OptionDescription):
@@ -49,6 +55,7 @@ class SubConfig(BaseInformation):
         if not isinstance(context, SubConfig):
             raise ValueError('context must be a SubConfig')
         self._impl_context = context
+        self._impl_path = subpath
 
     def cfgimpl_reset_cache(self, only_expired=False, only=('values',
                                                             'settings')):
@@ -135,9 +142,11 @@ class SubConfig(BaseInformation):
         for name, grp in self.iter_groups():
             lines.append("[{0}]".format(name))
         for name, value in self:
-            value = value.encode(default_encoding)
             try:
                 lines.append("{0} = {1}".format(name, value))
+            except UnicodeEncodeError:
+                lines.append("{0} = {1}".format(name,
+                                                value.encode(default_encoding)))
             except PropertiesOptionError:
                 pass
         return '\n'.join(lines)
@@ -149,7 +158,8 @@ class SubConfig(BaseInformation):
 
     def cfgimpl_get_description(self):
         if self._impl_descr is None:
-            raise ConfigError(_('no optiondescription for this config (may be MetaConfig without meta)'))
+            raise ConfigError(_('no option description found for this config'
+                                ' (may be metaconfig without meta)'))
         else:
             return self._impl_descr
 
@@ -175,16 +185,21 @@ class SubConfig(BaseInformation):
             return homeconfig.__setattr__(name, value)
         child = getattr(self.cfgimpl_get_description(), name)
         if not isinstance(child, SymLinkOption):
-            self.cfgimpl_get_values().setitem(child, value,
+            if self._impl_path is None:
+                path = name
+            else:
+                path = self._impl_path + '.' + name
+            self.cfgimpl_get_values().setitem(child, value, path,
                                               force_permissive=force_permissive)
         else:
             context = self.cfgimpl_get_context()
-            path = context.cfgimpl_get_description().impl_get_path_by_opt(child._opt)
+            path = context.cfgimpl_get_description().impl_get_path_by_opt(
+                child._opt)
             context._setattr(path, value, force_permissive=force_permissive)
 
     def __delattr__(self, name):
         child = getattr(self.cfgimpl_get_description(), name)
-        del(self.cfgimpl_get_values()[child])
+        self.cfgimpl_get_values().__delitem__(child)
 
     def __getattr__(self, name):
         return self._getattr(name)
@@ -200,9 +215,9 @@ class SubConfig(BaseInformation):
         # attribute access by passing a path,
         # for instance getattr(self, "creole.general.family.adresse_ip_eth0")
         if '.' in name:
-            homeconfig, name = self.cfgimpl_get_home_by_path(name,
-                                                             force_permissive=force_permissive,
-                                                             force_properties=force_properties)
+            homeconfig, name = self.cfgimpl_get_home_by_path(
+                name, force_permissive=force_permissive,
+                force_properties=force_properties)
             return homeconfig._getattr(name, force_permissive=force_permissive,
                                        force_properties=force_properties,
                                        validate=validate)
@@ -213,23 +228,29 @@ class SubConfig(BaseInformation):
             return object.__getattribute__(self, name)
         opt_or_descr = getattr(self.cfgimpl_get_description(), name)
         # symlink options
+        if self._impl_path is None:
+            subpath = name
+        else:
+            subpath = self._impl_path + '.' + name
         if isinstance(opt_or_descr, SymLinkOption):
             context = self.cfgimpl_get_context()
-            path = context.cfgimpl_get_description().impl_get_path_by_opt(opt_or_descr._opt)
+            path = context.cfgimpl_get_description().impl_get_path_by_opt(
+                opt_or_descr._opt)
             return context._getattr(path, validate=validate,
                                     force_properties=force_properties,
                                     force_permissive=force_permissive)
         elif isinstance(opt_or_descr, OptionDescription):
-            self.cfgimpl_get_settings().validate_properties(opt_or_descr,
-                                                            True, False,
-                                                            force_permissive=force_permissive,
-                                                            force_properties=force_properties)
-            return SubConfig(opt_or_descr, self.cfgimpl_get_context())
+            self.cfgimpl_get_settings().validate_properties(
+                opt_or_descr, True, False, path=subpath,
+                force_permissive=force_permissive,
+                force_properties=force_properties)
+            return SubConfig(opt_or_descr, self.cfgimpl_get_context(), subpath)
         else:
-            return self.cfgimpl_get_values().getitem(opt_or_descr,
-                                                     validate=validate,
-                                                     force_properties=force_properties,
-                                                     force_permissive=force_permissive)
+            return self.cfgimpl_get_values().getitem(
+                opt_or_descr, path=subpath,
+                validate=validate,
+                force_properties=force_properties,
+                force_permissive=force_permissive)
 
     def find(self, bytype=None, byname=None, byvalue=None, type_='option'):
         """
@@ -243,10 +264,11 @@ class SubConfig(BaseInformation):
         return self.cfgimpl_get_context()._find(bytype, byname, byvalue,
                                                 first=False,
                                                 type_=type_,
-                                                _subpath=self.cfgimpl_get_path())
+                                                _subpath=self.cfgimpl_get_path()
+                                                )
 
     def find_first(self, bytype=None, byname=None, byvalue=None,
-                   type_='option'):
+                   type_='option', display_error=True):
         """
             finds an option recursively in the config
 
@@ -255,13 +277,12 @@ class SubConfig(BaseInformation):
             :param byvalue: filter by the option's value
             :returns: list of matching Option objects
         """
-        return self.cfgimpl_get_context()._find(bytype, byname, byvalue,
-                                                first=True,
-                                                type_=type_,
-                                                _subpath=self.cfgimpl_get_path())
+        return self.cfgimpl_get_context()._find(
+            bytype, byname, byvalue, first=True, type_=type_,
+            _subpath=self.cfgimpl_get_path(), display_error=display_error)
 
     def _find(self, bytype, byname, byvalue, first, type_='option',
-              _subpath=None, check_properties=True):
+              _subpath=None, check_properties=True, display_error=True):
         """
         convenience method for finding an option that lives only in the subtree
 
@@ -270,7 +291,8 @@ class SubConfig(BaseInformation):
         """
         def _filter_by_name():
             try:
-                if byname is None or path == byname or path.endswith('.' + byname):
+                if byname is None or path == byname or \
+                        path.endswith('.' + byname):
                     return True
             except IndexError:
                 pass
@@ -283,7 +305,8 @@ class SubConfig(BaseInformation):
                 value = getattr(self, path)
                 if value == byvalue:
                     return True
-            except PropertiesOptionError:  # a property restricts the access of the value
+            except PropertiesOptionError:  # a property is a restriction
+                                           # upon the access of the value
                 pass
             return False
 
@@ -306,7 +329,8 @@ class SubConfig(BaseInformation):
         #                return False
         #    return False
         if type_ not in ('option', 'path', 'context', 'value'):
-            raise ValueError(_('unknown type_ type {0} for _find').format(type_))
+            raise ValueError(_('unknown type_ type {0}'
+                               'for _find').format(type_))
         find_results = []
         opts, paths = self.cfgimpl_get_description()._cache_paths
         for index in range(0, len(paths)):
@@ -344,9 +368,12 @@ class SubConfig(BaseInformation):
             else:
                 find_results.append(retval)
         if find_results == []:
-            #FIXME too slow
-            #raise AttributeError(_("no option found in config with these criteria"))
-            raise AttributeError("no option found in config with these criteria")
+            if display_error:
+                raise AttributeError(_("no option found in config"
+                                       " with these criteria"))
+            else:
+                #translation is slow
+                raise AttributeError()
         else:
             return find_results
 
@@ -369,15 +396,21 @@ class SubConfig(BaseInformation):
                            `OptionDescription` than the `withoption` itself::
 
                                 >>> print cfg.make_dict(withoption='var1')
-                                {'od2.var4': None, 'od2.var5': None, 'od2.var6': None,
-                                'od2.var1': u'value', 'od1.var1': None,
-                                'od1.var3': None, 'od1.var2': None}
+                                {'od2.var4': None, 'od2.var5': None,
+                                'od2.var6': None,
+                                'od2.var1': u'value',
+                                'od1.var1': None,
+                                'od1.var3': None,
+                                'od1.var2': None}
 
         :param withvalue: returns the options that have the value `withvalue`
                           ::
 
-                            >>> print c.make_dict(withoption='var1', withvalue=u'value')
-                            {'od2.var4': None, 'od2.var5': None, 'od2.var6': None,
+                            >>> print c.make_dict(withoption='var1',
+                                                  withvalue=u'value')
+                            {'od2.var4': None,
+                            'od2.var5': None,
+                            'od2.var6': None,
                             'od2.var1': u'value'}
 
         :returns: dict of Option's name (or path) and values
@@ -397,7 +430,8 @@ class SubConfig(BaseInformation):
                                                          type_='path',
                                                          _subpath=mypath):
                 path = '.'.join(path.split('.')[:-1])
-                opt = self.cfgimpl_get_context().cfgimpl_get_description().impl_get_opt_by_path(path)
+                opt = self.cfgimpl_get_context().cfgimpl_get_description(
+                ).impl_get_opt_by_path(path)
                 if mypath is not None:
                     if mypath == path:
                         withoption = None
@@ -425,7 +459,8 @@ class SubConfig(BaseInformation):
         if isinstance(opt, OptionDescription):
             try:
                 pathsvalues += getattr(self, path).make_dict(flatten,
-                                                             _currpath + path.split('.'))
+                                                             _currpath +
+                                                             path.split('.'))
             except PropertiesOptionError:
                 pass  # this just a hidden or disabled option
         else:
@@ -449,6 +484,13 @@ class CommonConfig(SubConfig):
     "abstract base class for the Config and the MetaConfig"
     __slots__ = ('_impl_values', '_impl_settings', '_impl_meta')
 
+    def _init_storage(self, config_id, is_persistent):
+        if config_id is None:
+            config_id = gen_id(self)
+        import_lib = 'tiramisu.storage.{0}.storage'.format(storage_type)
+        return __import__(import_lib, globals(), locals(), ['Storage'],
+                          -1).Storage(config_id, is_persistent)
+
     def _impl_build_all_paths(self):
         self.cfgimpl_get_description().impl_build_cache()
 
@@ -475,8 +517,8 @@ class CommonConfig(SubConfig):
         :returns: Option()
         """
         if '.' in path:
-            homeconfig, path = self.cfgimpl_get_home_by_path(path,
-                                                             force_permissive=force_permissive)
+            homeconfig, path = self.cfgimpl_get_home_by_path(
+                path, force_permissive=force_permissive)
             return getattr(homeconfig.cfgimpl_get_description(), path)
         return getattr(self.cfgimpl_get_description(), path)
 
@@ -492,7 +534,7 @@ class Config(CommonConfig):
     "main configuration management entry"
     __slots__ = tuple()
 
-    def __init__(self, descr):
+    def __init__(self, descr, config_id=None, is_persistent=False):
         """ Configuration option management master class
 
         :param descr: describes the configuration schema
@@ -500,14 +542,17 @@ class Config(CommonConfig):
         :param context: the current root config
         :type context: `Config`
         """
-        self._impl_settings = Setting(self)
-        self._impl_values = Values(self)
+        storage = self._init_storage(config_id, is_persistent)
+        self._impl_settings = Settings(self, storage)
+        self._impl_values = Values(self, storage)
         super(Config, self).__init__(descr, self)
         self._impl_build_all_paths()
         self._impl_meta = None
         self._impl_informations = {}
 
-    def cfgimpl_reset_cache(self, only_expired=False, only=('values', 'settings')):
+    def cfgimpl_reset_cache(self,
+                            only_expired=False,
+                            only=('values', 'settings')):
         if 'values' in only:
             self.cfgimpl_get_values().reset_cache(only_expired=only_expired)
         if 'settings' in only:
@@ -517,27 +562,32 @@ class Config(CommonConfig):
 class MetaConfig(CommonConfig):
     __slots__ = ('_impl_children',)
 
-    def __init__(self, children, meta=True):
+    def __init__(self, children, meta=True, config_id=None, is_persistent=False):
         if not isinstance(children, list):
             raise ValueError(_("metaconfig's children must be a list"))
         self._impl_descr = None
+        self._impl_path = None
         if meta:
             for child in children:
                 if not isinstance(child, CommonConfig):
-                    raise ValueError(_("metaconfig's children must be Config, not {0}"
-                                       "".format(type(child))))
+                    raise ValueError(_("metaconfig's children "
+                                       "must be config, not {0}"
+                                       ).format(type(child)))
                 if self._impl_descr is None:
                     self._impl_descr = child.cfgimpl_get_description()
                 elif not self._impl_descr is child.cfgimpl_get_description():
-                    raise ValueError(_('all config in MetaConfig must have same '
-                                       'optiondescription'))
+                    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
 
+        if config_id is None:
+            config_id = gen_id(self)
         self._impl_children = children
-        self._impl_settings = Setting(self)
-        self._impl_values = Values(self)
+        storage = self._init_storage(config_id, is_persistent)
+        self._impl_settings = Settings(self, storage)
+        self._impl_values = Values(self, storage)
         self._impl_meta = None
         self._impl_informations = {}
 
@@ -548,7 +598,9 @@ class MetaConfig(CommonConfig):
         "a meta config is a config wich has a setting, that is itself"
         return self
 
-    def cfgimpl_reset_cache(self, only_expired=False, only=('values', 'settings')):
+    def cfgimpl_reset_cache(self,
+                            only_expired=False,
+                            only=('values', 'settings')):
         if 'values' in only:
             self.cfgimpl_get_values().reset_cache(only_expired=only_expired)
         if 'settings' in only:
@@ -566,7 +618,8 @@ class MetaConfig(CommonConfig):
             except PropertiesOptionError:
                 pass
 
-    def find_first_contexts(self, byname=None, bypath=None, byvalue=None, type_='context'):
+    def find_first_contexts(self, byname=None, bypath=None, byvalue=None,
+                            type_='context', display_error=True):
         ret = []
         try:
             if bypath is None and byname is not None and \
@@ -590,12 +643,14 @@ class MetaConfig(CommonConfig):
                     else:
                         ret.append(child.find_first(byname=byname,
                                                     byvalue=byvalue,
-                                                    type_=type_))
+                                                    type_=type_,
+                                                    display_error=False))
                 else:
                     ret.extend(child.find_first_contexts(byname=byname,
                                                          bypath=bypath,
                                                          byvalue=byvalue,
-                                                         type_=type_))
+                                                         type_=type_,
+                                                         display_error=False))
             except AttributeError:
                 pass
         return ret
@@ -610,9 +665,12 @@ def mandatory_warnings(config):
     """
     #if value in cache, properties are not calculated
     config.cfgimpl_reset_cache(only=('values',))
-    for path in config.cfgimpl_get_description().impl_getpaths(include_groups=True):
+    for path in config.cfgimpl_get_description().impl_getpaths(
+            include_groups=True):
         try:
             config._getattr(path, force_properties=frozenset(('mandatory',)))
+            # XXX raise Exception("ca passe ici")
+            # XXX depuis l'exterieur on donne un paht maintenant ! perturbant !
         except PropertiesOptionError, err:
             if err.proptype == ['mandatory']:
                 yield path