Merge branch 'master' of ssh://git.labs.libre-entreprise.org/gitroot/tiramisu
[tiramisu.git] / tiramisu / setting.py
index 7417662..86f900e 100644 (file)
@@ -22,7 +22,9 @@
 # ____________________________________________________________
 from time import time
 from copy import copy
-from tiramisu.error import RequirementError, PropertiesOptionError
+import weakref
+from tiramisu.error import (RequirementError, PropertiesOptionError,
+                            ConstError, ConfigError)
 from tiramisu.i18n import _
 
 
@@ -34,28 +36,45 @@ ro_append = ('frozen', 'disabled', 'validator', 'everything_frozen',
 rw_remove = ('permissive', 'everything_frozen', 'mandatory')
 rw_append = ('frozen', 'disabled', 'validator', 'hidden')
 default_properties = ('expire', 'validator')
-storage_type = 'dictionary'
 
 
-class _const:
+class StorageType:
+    default_storage = 'dictionary'
+    storage_type = None
+
+    def set_storage(self, name):
+        if self.storage_type is not None:
+            raise ConfigError(_('storage_type is already set, cannot rebind it'))
+        self.storage_type = name
+
+    def get_storage(self):
+        if self.storage_type is None:
+            self.storage_type = self.default_storage
+        storage = self.storage_type
+        return 'tiramisu.storage.{0}.storage'.format(
+            storage)
+
+
+storage_type = StorageType()
+
+
+class _NameSpace:
     """convenient class that emulates a module
     and builds constants (that is, unique names)"""
-    class ConstError(TypeError):
-        pass
 
     def __setattr__(self, name, value):
         if name in self.__dict__:
-            raise self.ConstError, _("can't rebind group ({})").format(name)
+            raise ConstError(_("can't rebind {0}").format(name))
         self.__dict__[name] = value
 
     def __delattr__(self, name):
         if name in self.__dict__:
-            raise self.ConstError, _("can't unbind group ({})").format(name)
+            raise ConstError(_("can't unbind {0}").format(name))
         raise ValueError(name)
 
 
 # ____________________________________________________________
-class GroupModule(_const):
+class GroupModule(_NameSpace):
     "emulates a module to manage unique group (OptionDescription) names"
     class GroupType(str):
         """allowed normal group (OptionDescription) names
@@ -87,7 +106,7 @@ populate_groups()
 
 
 # ____________________________________________________________
-class OwnerModule(_const):
+class OwnerModule(_NameSpace):
     """emulates a module to manage unique owner names.
 
     owners are living in `Config._cfgimpl_value_owners`
@@ -113,18 +132,18 @@ def populate_owners():
     setattr(owners, 'default', owners.DefaultOwner('default'))
     setattr(owners, 'user', owners.Owner('user'))
 
-    def add_owner(name):
+    def addowner(name):
         """
         :param name: the name of the new owner
         """
         setattr(owners, name, owners.Owner(name))
-    setattr(owners, 'add_owner', add_owner)
+    setattr(owners, 'addowner', addowner)
 
 # names are in the module now
 populate_owners()
 
 
-class MultiTypeModule(_const):
+class MultiTypeModule(_NameSpace):
     "namespace for the master/slaves"
     class MultiType(str):
         pass
@@ -161,6 +180,11 @@ class Property(object):
         self._properties = prop
 
     def append(self, propname):
+        if self._opt is not None and self._opt._calc_properties is not None \
+                and propname in self._opt._calc_properties:
+            raise ValueError(_('cannot append {0} property for option {1}: '
+                               'this property is calculated').format(
+                                   propname, self._opt._name))
         self._properties.add(propname)
         self._setting._setproperties(self._properties, self._opt, self._path)
 
@@ -170,7 +194,7 @@ class Property(object):
             self._setting._setproperties(self._properties, self._opt, self._path)
 
     def reset(self):
-        self._setting.reset(path=self._path)
+        self._setting.reset(_path=self._path)
 
     def __contains__(self, propname):
         return propname in self._properties
@@ -179,10 +203,43 @@ class Property(object):
         return str(list(self._properties))
 
 
+def set_storage(name, **args):
+    storage_type.set_storage(name)
+    settings = __import__(storage_type.get_storage(), globals(), locals(),
+                          ['Setting'], -1).Setting()
+    for option, value in args.items():
+        try:
+            getattr(settings, option)
+            setattr(settings, option, value)
+        except AttributeError:
+            raise ValueError(_('option {0} not already exists in storage {1}'
+                               '').format(option, name))
+
+
+def get_storage(context, session_id, persistent):
+    def gen_id(config):
+        return str(id(config)) + str(time())
+
+    if session_id is None:
+        session_id = gen_id(context)
+    return __import__(storage_type.get_storage(), globals(), locals(),
+                      ['Storage'], -1).Storage(session_id, persistent)
+
+
+def list_sessions():
+    return __import__(storage_type.get_storage(), globals(), locals(),
+                      ['list_sessions'], -1).list_sessions()
+
+
+def delete_session(session_id):
+    return __import__(storage_type.get_storage(), globals(), locals(),
+                      ['delete_session'], -1).delete_session(session_id)
+
+
 #____________________________________________________________
 class Settings(object):
     "``Config()``'s configuration options"
-    __slots__ = ('context', '_owner', '_p_')
+    __slots__ = ('context', '_owner', '_p_', '__weakref__')
 
     def __init__(self, context, storage):
         """
@@ -196,8 +253,8 @@ class Settings(object):
         """
         # generic owner
         self._owner = owners.user
-        self.context = context
-        import_lib = 'tiramisu.storage.{0}.setting'.format(storage_type)
+        self.context = weakref.ref(context)
+        import_lib = 'tiramisu.storage.{0}.setting'.format(storage.storage)
         self._p_ = __import__(import_lib, globals(), locals(), ['Settings'],
                               -1).Settings(storage)
 
@@ -211,47 +268,43 @@ class Settings(object):
         return str(list(self._getproperties()))
 
     def __getitem__(self, opt):
-        if opt is None:
-            path = None
-        else:
-            path = self._get_opt_path(opt)
+        path = self._get_opt_path(opt)
         return self._getitem(opt, path)
 
     def _getitem(self, opt, path):
         return Property(self, self._getproperties(opt, path), opt, path)
 
     def __setitem__(self, opt, value):
-        raise ValueError('you must only append/remove properties')
+        raise ValueError('you should only append/remove properties')
 
-    def reset(self, opt=None, all_properties=False):
-        if all_properties and opt:
+    def reset(self, opt=None, _path=None, all_properties=False):
+        if all_properties and (_path or opt):
             raise ValueError(_('opt and all_properties must not be set '
                                'together in reset'))
         if all_properties:
             self._p_.reset_all_propertives()
         else:
-            if opt is None:
-                path = None
-            else:
-                path = self._get_opt_path(opt)
-            self._p_.reset_properties(path)
-        self.context.cfgimpl_reset_cache()
+            if opt is not None and _path is None:
+                _path = self._get_opt_path(opt)
+            self._p_.reset_properties(_path)
+        self.context().cfgimpl_reset_cache()
 
     def _getproperties(self, opt=None, path=None, is_apply_req=True):
         if opt is None:
             props = self._p_.getproperties(path, default_properties)
         else:
             if path is None:
-                raise ValueError(_('if opt is not None, path should not be None in _getproperties'))
+                raise ValueError(_('if opt is not None, path should not be'
+                                   ' None in _getproperties'))
             ntime = None
             if self._p_.hascache('property', path):
                 ntime = time()
                 is_cached, props = self._p_.getcache('property', path, ntime)
                 if is_cached:
                     return props
-            if is_apply_req:
-                self.apply_requires(opt, path)
             props = self._p_.getproperties(path, opt._properties)
+            if is_apply_req:
+                props |= self.apply_requires(opt, path)
             if 'expire' in self:
                 if ntime is None:
                     ntime = time()
@@ -260,11 +313,16 @@ class Settings(object):
 
     def append(self, propname):
         "puts property propname in the Config's properties attribute"
-        Property(self, self._getproperties()).append(propname)
+        props = self._p_.getproperties(None, default_properties)
+        props.add(propname)
+        self._setproperties(props, None, None)
 
     def remove(self, propname):
         "deletes property propname in the Config's properties attribute"
-        Property(self, self._getproperties()).remove(propname)
+        props = self._p_.getproperties(None, default_properties)
+        if propname in props:
+            props.remove(propname)
+            self._setproperties(props, None, None)
 
     def _setproperties(self, properties, opt, path):
         """save properties for specified opt
@@ -273,11 +331,13 @@ class Settings(object):
         if opt is None:
             self._p_.setproperties(None, properties)
         else:
+            if opt._calc_properties is not None:
+                properties -= opt._calc_properties
             if set(opt._properties) == properties:
                 self._p_.reset_properties(path)
             else:
                 self._p_.setproperties(path, properties)
-        self.context.cfgimpl_reset_cache()
+        self.context().cfgimpl_reset_cache()
 
     #____________________________________________________________
     def validate_properties(self, opt_or_descr, is_descr, is_write, path,
@@ -287,13 +347,15 @@ class Settings(object):
         validation upon the properties related to `opt_or_descr`
 
         :param opt_or_descr: an option or an option description object
-        :param force_permissive: behaves as if the permissive property was present
+        :param force_permissive: behaves as if the permissive property
+                                 was present
         :param is_descr: we have to know if we are in an option description,
-                         just because the mandatory property doesn't exist there
+                         just because the mandatory property
+                         doesn't exist here
 
         :param is_write: in the validation process, an option is to be modified,
-                         the behavior can be different (typically with the `frozen`
-                         property)
+                         the behavior can be different
+                         (typically with the `frozen` property)
         """
         # opt properties
         properties = copy(self._getproperties(opt_or_descr, path))
@@ -315,7 +377,7 @@ class Settings(object):
             properties -= frozenset(('mandatory', 'frozen'))
         else:
             if 'mandatory' in properties and \
-                    not self.context.cfgimpl_get_values()._isempty(
+                    not self.context().cfgimpl_get_values()._isempty(
                         opt_or_descr, value):
                 properties.remove('mandatory')
             if is_write and 'everything_frozen' in self_properties:
@@ -374,14 +436,54 @@ class Settings(object):
             self._p_.reset_all_cache('property')
 
     def apply_requires(self, opt, path):
-        "carries out the jit (just in time requirements between options"
+        """carries out the jit (just in time) requirements between options
+
+        a requirement is a tuple of this form that comes from the option's
+        requirements validation::
+
+            (option, expected, action, inverse, transitive, same_action)
+
+        let's have a look at all the tuple's items:
+
+        - **option** is the target option's name or path
+
+        - **expected** is the target option's value that is going to trigger an action
+
+        - **action** is the (property) action to be accomplished if the target option
+          happens to have the expected value
+
+        - if **inverse** is `True` and if the target option's value does not
+          apply, then the property action must be removed from the option's
+          properties list (wich means that the property is inverted)
+
+        - **transitive**: but what happens if the target option cannot be
+          accessed ? We don't kown the target option's value. Actually if some
+          property in the target option is not present in the permissive, the
+          target option's value cannot be accessed. In this case, the
+          **action** have to be applied to the option. (the **action** property
+          is then added to the option).
+
+        - **same_action**: actually, if **same_action** is `True`, the
+          transitivity is not accomplished. The transitivity is accomplished
+          only if the target option **has the same property** that the demanded
+          action. If the target option's value is not accessible because of
+          another reason, because of a property of another type, then an
+          exception :exc:`~error.RequirementError` is raised.
+
+        And at last, if no target option matches the expected values, the
+        action must be removed from the option's properties list.
+
+        :param opt: the option on wich the requirement occurs
+        :type opt: `option.Option()`
+        :param path: the option's path in the config
+        :type path: str
+        """
         if opt._requires is None:
-            return
+            return frozenset()
 
         # filters the callbacks
-        setting = Property(self, self._getproperties(opt, path, False), opt, path=path)
+        calc_properties = set()
         for requires in opt._requires:
-            matches = False
             for require in requires:
                 option, expected, action, inverse, \
                     transitive, same_action = require
@@ -392,7 +494,8 @@ class Settings(object):
                                              " '{0}' with requirement on: "
                                              "'{1}'").format(path, reqpath))
                 try:
-                    value = self.context._getattr(reqpath, force_permissive=True)
+                    value = self.context()._getattr(reqpath,
+                                                    force_permissive=True)
                 except PropertiesOptionError, err:
                     if not transitive:
                         continue
@@ -407,19 +510,13 @@ class Settings(object):
                     # transitive action, force expected
                     value = expected[0]
                     inverse = False
-                except AttributeError:
-                    raise AttributeError(_("required option not found: "
-                                           "{0}").format(reqpath))
                 if (not inverse and
                         value in expected or
                         inverse and value not in expected):
-                    matches = True
-                    setting.append(action)
+                    calc_properties.add(action)
                     # the calculation cannot be carried out
                     break
-            # no requirement has been triggered, then just reverse the action
-            if not matches:
-                setting.remove(action)
+            return calc_properties
 
     def _get_opt_path(self, opt):
-        return self.context.cfgimpl_get_description().impl_get_path_by_opt(opt)
+        return self.context().cfgimpl_get_description().impl_get_path_by_opt(opt)