new api documentation
[tiramisu.git] / tiramisu / setting.py
index 5af26f9..7bb7360 100644 (file)
 # 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 copy import copy
 from tiramisu.error import RequirementRecursionError, PropertiesOptionError
 from tiramisu.i18n import _
 
 
+expires_time = 5
+ro_remove = ('permissive', 'hidden')
+ro_append = ('frozen', 'disabled', 'validator', 'everything_frozen', 'mandatory')
+rw_remove = ('permissive', 'everything_frozen', 'mandatory')
+rw_append = ('frozen', 'disabled', 'validator', 'hidden')
+
+
 class _const:
     """convenient class that emulates a module
     and builds constants (that is, unique names)"""
@@ -136,154 +144,190 @@ def populate_multitypes():
 populate_multitypes()
 
 
+class Property(object):
+    "a property is responsible of the option's value access rules"
+    __slots__ = ('_setting', '_properties', '_opt')
+
+    def __init__(self, setting, prop, opt=None):
+        self._opt = opt
+        self._setting = setting
+        self._properties = prop
+
+    def append(self, propname):
+        self._properties.add(propname)
+        self._setting._set_properties(self._properties, self._opt)
+        self._setting.context.cfgimpl_reset_cache()
+
+    def remove(self, propname):
+        if propname in self._properties:
+            self._properties.remove(propname)
+            self._setting._set_properties(self._properties, self._opt)
+            self._setting.context.cfgimpl_reset_cache()
+
+    def __contains__(self, propname):
+        return propname in self._properties
+
+    def __repr__(self):
+        return str(list(self._properties))
+
+
 #____________________________________________________________
 class Setting(object):
     "``Config()``'s configuration options"
-    __slots__ = ('properties', 'permissives', 'owner', 'context')
+    __slots__ = ('context', '_properties', '_permissives', '_owner', '_cache')
 
     def __init__(self, context):
         # properties attribute: the name of a property enables this property
         # key is None for global properties
-        self.properties = {None: []}  # ['hidden', 'disabled', 'mandatory', 'frozen', 'validator']}
+        self._properties = {None: set(('expire', 'validator'))}
         # permissive properties
-        self.permissives = {}
+        self._permissives = {}
         # generic owner
-        self.owner = owners.user
+        self._owner = owners.user
         self.context = context
+        self._cache = {}
 
     #____________________________________________________________
     # properties methods
-    def has_properties(self, opt=None, is_apply_req=True):
-        "has properties means the Config's properties attribute is not empty"
-        return bool(len(self.get_properties(opt, is_apply_req)))
+    def __contains__(self, propname):
+        return propname in self._get_properties()
+
+    def __repr__(self):
+        return str(list(self._get_properties()))
 
-    def get_properties(self, opt=None, is_apply_req=True):
+    def __getitem__(self, opt):
+        return Property(self, self._get_properties(opt), opt)
+
+    def __setitem__(self, opt, value):
+        raise ValueError('you must only append/remove properties')
+
+    def _get_properties(self, opt=None, is_apply_req=True):
         if opt is None:
-            default = []
+            props = self._properties.get(opt, set())
         else:
+            exp = None
+            if opt in self._cache:
+                exp = time()
+                props, created = self._cache[opt]
+                if exp < created:
+                    return props
             if is_apply_req:
                 apply_requires(opt, self.context)
-            default = list(opt._properties)
-        return self.properties.get(opt, default)
-
-    def has_property(self, propname, opt=None, is_apply_req=True):
-        """has property propname in the Config's properties attribute
-        :param property: string wich is the name of the property"""
-        return propname in self.get_properties(opt, is_apply_req)
+            props = self._properties.get(opt, opt._properties)
+            self._set_cache(opt, props, exp)
+        return props
 
-    def enable_property(self, propname):
+    def append(self, propname):
         "puts property propname in the Config's properties attribute"
-        props = self.get_properties()
-        if propname not in props:
-            props.append(propname)
-        self.set_properties(props)
+        Property(self, self._get_properties()).append(propname)
 
-    def disable_property(self, propname):
+    def remove(self, propname):
         "deletes property propname in the Config's properties attribute"
-        props = self.get_properties()
-        if propname in props:
-            props.remove(propname)
-        self.set_properties(props)
+        Property(self, self._get_properties()).remove(propname)
 
-    def set_properties(self, properties, opt=None):
+    def _set_properties(self, properties, opt=None):
         """save properties for specified opt
         (never save properties if same has option properties)
         """
         if opt is None:
-            self.properties[opt] = properties
+            self._properties[opt] = properties
         else:
             if opt._properties == properties:
-                if opt in self.properties:
-                    del(self.properties[opt])
+                if opt in self._properties:
+                    del(self._properties[opt])
             else:
-                self.properties[opt] = properties
-
-    def add_property(self, propname, opt, is_apply_req=True):
-        properties = self.get_properties(opt, is_apply_req)
-        if not propname in properties:
-            properties.append(propname)
-            self.set_properties(properties, opt)
-
-    def del_property(self, propname, opt, is_apply_req=True):
-        properties = self.get_properties(opt, is_apply_req)
-        if propname in properties:
-            properties.remove(propname)
-            self.set_properties(properties, opt)
+                self._properties[opt] = properties
 
     #____________________________________________________________
     def validate_properties(self, opt_or_descr, is_descr, is_write,
                             value=None, force_permissive=False,
                             force_properties=None):
-        properties = set(self.get_properties(opt_or_descr))
-        #remove this properties, those properties are validate in after
-        properties = properties - set(['mandatory', 'frozen'])
-        set_properties = self.get_properties()
+        #opt properties
+        properties = copy(self._get_properties(opt_or_descr))
+        #remove opt permissive
+        properties -= self._get_permissive(opt_or_descr)
+        #remove global permissive if need
+        self_properties = copy(self._get_properties())
+        if force_permissive is True or 'permissive' in self_properties:
+            properties -= self._get_permissive()
+
+        #global properties
         if force_properties is not None:
-            set_properties.extend(force_properties)
-        set_properties = set(set_properties)
-        properties = properties & set_properties
-        if force_permissive is True or self.has_property('permissive', is_apply_req=False):
-            properties = properties - set(self.get_permissive())
-        properties = properties - set(self.get_permissive(opt_or_descr))
-        properties = list(properties)
-        raise_text = _("trying to access"
-                       " to an option named: {0} with properties"
-                       " {1}")
-        if not is_descr:
-            if self.context.cfgimpl_get_values().is_mandatory_err(opt_or_descr,
-                                                                  value,
-                                                                  force_properties=force_properties):
-                properties.append('mandatory')
-            if is_write and (self.has_property('everything_frozen') or (
-                    self.has_property('frozen') and
-                    self.has_property('frozen', opt_or_descr,
-                                      is_apply_req=False))):
-                properties.append('frozen')
-                raise_text = _('cannot change the value to {0} for '
-                               'option {1} this option is frozen')
-        if properties != []:
-            raise PropertiesOptionError(raise_text.format(opt_or_descr._name,
-                                                          str(properties)),
-                                        properties)
-
-    def get_permissive(self, opt=None):
-        return self.permissives.get(opt, [])
+            self_properties.update(force_properties)
+
+        #calc properties
+        properties &= self_properties
+        #mandatory and frozen are special properties
+        if is_descr:
+            properties -= frozenset(('mandatory', 'frozen'))
+        else:
+            if 'mandatory' in properties and \
+                    not self.context.cfgimpl_get_values()._is_empty(opt_or_descr,
+                                                                    value):
+                properties.remove('mandatory')
+            if is_write and 'everything_frozen' in self_properties:
+                properties.add('frozen')
+            elif 'frozen' in properties and not is_write:
+                properties.remove('frozen')
+
+        if properties != frozenset():
+            if 'frozen' in properties:
+                raise_text = 'cannot change the value for option {0} this option is frozen'
+            else:
+                raise_text = "trying to access to an option named: {0} with properties {1}"
+            raise PropertiesOptionError(_(raise_text).format(opt_or_descr._name,
+                                        str(properties)),
+                                        list(properties))
+
+    def _get_permissive(self, opt=None):
+        return self._permissives.get(opt, frozenset())
 
     def set_permissive(self, permissive, opt=None):
-        if not isinstance(permissive, list):
-            raise TypeError(_('permissive must be a list'))
-        self.permissives[opt] = permissive
+        if not isinstance(permissive, tuple):
+            raise TypeError(_('permissive must be a tuple'))
+        self._permissives[opt] = frozenset(permissive)
 
     #____________________________________________________________
     def setowner(self, owner):
         ":param owner: sets the default value for owner at the Config level"
         if not isinstance(owner, owners.Owner):
             raise TypeError(_("invalid generic owner {0}").format(str(owner)))
-        self.owner = owner
+        self._owner = owner
 
     def getowner(self):
-        return self.owner
+        return self._owner
 
     #____________________________________________________________
+    def _read(self, remove, append):
+        for prop in remove:
+            self.remove(prop)
+        for prop in append:
+            self.append(prop)
+
     def read_only(self):
         "convenience method to freeze, hidde and disable"
-        self.enable_property('everything_frozen')
-        self.enable_property('frozen')  # can be usefull...
-        self.disable_property('hidden')
-        self.enable_property('disabled')
-        self.enable_property('mandatory')
-        self.enable_property('validator')
-        self.disable_property('permissive')
+        self._read(ro_remove, ro_append)
 
     def read_write(self):
         "convenience method to freeze, hidde and disable"
-        self.disable_property('everything_frozen')
-        self.enable_property('frozen')  # can be usefull...
-        self.enable_property('hidden')
-        self.enable_property('disabled')
-        self.disable_property('mandatory')
-        self.enable_property('validator')
-        self.disable_property('permissive')
+        self._read(rw_remove, rw_append)
+
+    def _set_cache(self, opt, props, exp):
+        if 'expire' in self:
+            if exp is None:
+                exp = time()
+            self._cache[opt] = (props, time() + expires_time)
+
+    def reset_cache(self, only_expired):
+        if only_expired:
+            exp = time()
+            keys = self._cache.keys()
+            for key in keys:
+                props, created = self._cache[key]
+                if exp > created:
+                    del(self._cache[key])
+        else:
+            self._cache.clear()
 
 
 def apply_requires(opt, config):
@@ -298,17 +342,20 @@ def apply_requires(opt, config):
     #for symlink
     if hasattr(opt, '_requires') and opt._requires is not None:
         # filters the callbacks
-        setting = config.cfgimpl_get_settings()
+        settings = config.cfgimpl_get_settings()
+        setting = Property(settings, settings._get_properties(opt, False), opt)
         trigger_actions = build_actions(opt._requires)
-        optpath = config.cfgimpl_get_context().cfgimpl_get_description().get_path_by_opt(opt)
+        descr = config.cfgimpl_get_context().cfgimpl_get_description()
+        optpath = descr.impl_get_path_by_opt(opt)
         for requires in trigger_actions.values():
             matches = False
             for require in requires:
                 if len(require) == 3:
-                    path, expected, action = require
+                    option, expected, action = require
                     inverse = False
                 elif len(require) == 4:
-                    path, expected, action, inverse = require
+                    option, expected, action, inverse = require
+                path = descr.impl_get_path_by_opt(option)
                 if path == optpath or path.startswith(optpath + '.'):
                     raise RequirementRecursionError(_("malformed requirements "
                                                     "imbrication detected for option: '{0}' "
@@ -317,22 +364,20 @@ def apply_requires(opt, config):
                     value = config.cfgimpl_get_context()._getattr(path, force_permissive=True)
                 except PropertiesOptionError, err:
                     properties = err.proptype
-                    #FIXME: AttributeError or PropertiesOptionError ?
-                    raise AttributeError(_("option '{0}' has requirement's property error: "
-                                         "{1} {2}").format(opt._name, path, properties))
+                    raise PropertiesOptionError(_("option '{0}' has requirement's property error: "
+                                                  "{1} {2}").format(opt._name, path, properties), properties)
                 except AttributeError:
                     raise AttributeError(_("required option not found: "
                                          "{0}").format(path))
                 if value == expected:
                     if inverse:
-                        setting.del_property(action, opt, False)
+                        setting.remove(action)
                     else:
-                        setting.add_property(action, opt, False)
+                        setting.append(action)
                     matches = True
-                    #FIXME optimisation : fait un double break non ? voire un return
             # no requirement has been triggered, then just reverse the action
             if not matches:
                 if inverse:
-                    setting.add_property(action, opt, False)
+                    setting.append(action)
                 else:
-                    setting.del_property(action, opt, False)
+                    setting.remove(action)