add option name's validation and rename Option method with objimpl_
[tiramisu.git] / tiramisu / config.py
index dfc3560..a89f8e6 100644 (file)
 # the whole pypy projet is under MIT licence
 # ____________________________________________________________
 #from inspect import getmembers, ismethod
-from tiramisu.error import (PropertiesOptionError, NotFoundError,
-                            AmbigousOptionError, NoMatchingOptionFound, MandatoryError)
+from tiramisu.error import PropertiesOptionError
 from tiramisu.option import OptionDescription, Option, SymLinkOption
-from tiramisu.setting import groups, Setting, apply_requires
+from tiramisu.setting import groups, Setting
 from tiramisu.value import Values
+from tiramisu.i18n import _
 
 
 class SubConfig(object):
@@ -41,6 +41,8 @@ class SubConfig(object):
         :type context: `Config`
         """
         # main option description
+        if not isinstance(descr, OptionDescription):
+            raise ValueError(_('descr must be an optiondescription, not {0}').format(type(descr)))
         self._cfgimpl_descr = descr
         # sub option descriptions
         self._cfgimpl_context = context
@@ -54,6 +56,9 @@ class SubConfig(object):
     def cfgimpl_get_values(self):
         return self._cfgimpl_context._cfgimpl_values
 
+    def cfgimpl_get_consistancies(self):
+        return self.cfgimpl_get_context().cfgimpl_get_description()._consistancies
+
     def cfgimpl_get_description(self):
         return self._cfgimpl_descr
 
@@ -67,41 +72,29 @@ class SubConfig(object):
             return
         self._setattr(name, value)
 
+    def cfgimpl_reset_cache(self, only_expired=False, only=('values', 'settings')):
+        self.cfgimpl_get_context().cfgimpl_reset_cache(only_expired, only)
+
     def _setattr(self, name, value, force_permissive=False):
         if '.' in name:
             homeconfig, name = self.cfgimpl_get_home_by_path(name)
             return homeconfig.__setattr__(name, value)
         child = getattr(self._cfgimpl_descr, name)
         if type(child) != SymLinkOption:
-            self._validate(name, getattr(self._cfgimpl_descr, name), force_permissive=force_permissive)
-            self.setoption(name, child, value)
+            self.cfgimpl_get_values().setitem(child, value,
+                                              force_permissive=force_permissive)
         else:
-            child.setoption(self.cfgimpl_get_context(), value)
-
-    def _validate(self, name, opt_or_descr, force_permissive=False):
-        "validation for the setattr and the getattr"
-        if not isinstance(opt_or_descr, Option) and \
-                not isinstance(opt_or_descr, OptionDescription):
-            raise TypeError('Unexpected object: {0}'.format(repr(opt_or_descr)))
-        properties = set(self.cfgimpl_get_settings().get_properties(opt_or_descr))
-        #remove this properties, those properties are validate in value/setting
-        properties = properties - set(['mandatory', 'frozen'])
-        set_properties = set(self.cfgimpl_get_settings().get_properties())
-        properties = properties & set_properties
-        if force_permissive is True or self.cfgimpl_get_settings().has_property('permissive', is_apply_req=False):
-            properties = properties - set(self.cfgimpl_get_settings().get_permissive())
-        properties = properties - set(self.cfgimpl_get_settings().get_permissive(opt_or_descr))
-        properties = list(properties)
-        if properties != []:
-            raise PropertiesOptionError("trying to access"
-                                        " to an option named: {0} with properties"
-                                        " {1}".format(name, str(properties)),
-                                        properties)
+            child._setoption(self.cfgimpl_get_context(), value)
+
+    def __delattr__(self, name):
+        child = getattr(self._cfgimpl_descr, name)
+        del(self.cfgimpl_get_values()[child])
 
     def __getattr__(self, name):
         return self._getattr(name)
 
-    def _getattr(self, name, force_permissive=False, force_properties=None):
+    def _getattr(self, name, force_permissive=False, force_properties=None,
+                 validate=True):
         """
         attribute notation mechanism for accessing the value of an option
         :param name: attribute name
@@ -115,49 +108,31 @@ class SubConfig(object):
                                                              force_permissive=force_permissive,
                                                              force_properties=force_properties)
             return homeconfig._getattr(name, force_permissive=force_permissive,
-                                       force_properties=force_properties)
-        opt_or_descr = getattr(self._cfgimpl_descr, name)
-        # symlink options
-        if type(opt_or_descr) == SymLinkOption:
-            rootconfig = self.cfgimpl_get_context()
-            path = rootconfig.cfgimpl_get_description().get_path_by_opt(opt_or_descr.opt)
-            return getattr(rootconfig, path)
-        self._validate(name, opt_or_descr, force_permissive=force_permissive)
-        if isinstance(opt_or_descr, OptionDescription):
-            children = self.cfgimpl_get_description()._children
-            if opt_or_descr not in children[1]:
-                raise AttributeError("{0} with name {1} object has "
-                                     "no attribute {2}".format(self.__class__,
-                                                               opt_or_descr._name,
-                                                               name))
-            return SubConfig(opt_or_descr, self._cfgimpl_context)
+                                       force_properties=force_properties,
+                                       validate=validate)
         # special attributes
-        if name.startswith('_cfgimpl_'):
+        if name.startswith('_cfgimpl_') or name.startswith('cfgimpl_'):
             # if it were in __dict__ it would have been found already
             object.__getattr__(self, name)
-        return self.cfgimpl_get_values()._getitem(opt_or_descr,
-                                                  force_properties=force_properties)
-
-    def setoption(self, name, child, value):
-        """effectively modifies the value of an Option()
-        (typically called by the __setattr__)
-        """
-        setting = self.cfgimpl_get_settings()
-        #needed ?
-        apply_requires(child, self)
-        #needed to ?
-        if child not in self._cfgimpl_descr._children[1]:
-            raise AttributeError('unknown option %s' % (name))
-
-        if setting.has_property('everything_frozen'):
-            raise TypeError("cannot set a value to the option {} if the whole "
-                            "config has been frozen".format(name))
-
-        if setting.has_property('frozen') and setting.has_property('frozen',
-                                                                   child, is_apply_req=False):
-            raise TypeError('cannot change the value to %s for '
-                            'option %s this option is frozen' % (str(value), name))
-        self.cfgimpl_get_values()[child] = value
+        opt_or_descr = getattr(self.cfgimpl_get_description(), name)
+        # symlink options
+        if isinstance(opt_or_descr, SymLinkOption):
+            rootconfig = self.cfgimpl_get_context()
+            path = rootconfig.cfgimpl_get_description().objimpl_get_path_by_opt(opt_or_descr.opt)
+            return rootconfig._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_context)
+        else:
+            return self.cfgimpl_get_values().getitem(opt_or_descr,
+                                                     validate=validate,
+                                                     force_properties=force_properties,
+                                                     force_permissive=force_permissive)
 
     def cfgimpl_get_home_by_path(self, path, force_permissive=False, force_properties=None):
         """:returns: tuple (config, name)"""
@@ -168,44 +143,44 @@ class SubConfig(object):
                                  force_properties=force_properties)
         return self, path[-1]
 
-    def getkey(self):
-        return self._cfgimpl_descr.getkey(self)
-
     def __hash__(self):
-        return hash(self.getkey())
+        return hash(self.cfgimpl_get_description().objimpl_getkey(self))
 
     def __eq__(self, other):
         "Config comparison"
         if not isinstance(other, Config):
             return False
-        return self.getkey() == other.getkey()
+        return self.cfgimpl_get_description().objimpl_getkey(self) == \
+            other.cfgimpl_get_description().objimpl_getkey(other)
 
     def __ne__(self, other):
         "Config comparison"
+        if not isinstance(other, Config):
+            return False
         return not self == other
 
     # ______________________________________________________________________
     def __iter__(self):
         """Pythonesque way of parsing group's ordered options.
         iteration only on Options (not OptionDescriptions)"""
-        for child in self._cfgimpl_descr._children[1]:
+        for child in self.cfgimpl_get_description().objimpl_getchildren():
             if not isinstance(child, OptionDescription):
                 try:
                     yield child._name, getattr(self, child._name)
                 except GeneratorExit:
                     raise StopIteration
-                except:
+                except PropertiesOptionError:
                     pass  # option with properties
 
     def iter_all(self):
         """A way of parsing options **and** groups.
         iteration on Options and OptionDescriptions."""
-        for child in self._cfgimpl_descr._children[1]:
+        for child in self.cfgimpl_get_description().objimpl_getchildren():
             try:
                 yield child._name, getattr(self, child._name)
             except GeneratorExit:
                 raise StopIteration
-            except:
+            except PropertiesOptionError:
                 pass  # option with properties
 
     def iter_groups(self, group_type=None):
@@ -220,18 +195,16 @@ class SubConfig(object):
         """
         if group_type is not None:
             if not isinstance(group_type, groups.GroupType):
-                raise TypeError("Unknown group_type: {0}".format(group_type))
-        for child in self._cfgimpl_descr._children[1]:
+                raise TypeError(_("unknown group_type: {0}").format(group_type))
+        for child in self.cfgimpl_get_description().objimpl_getchildren():
             if isinstance(child, OptionDescription):
                 try:
-                    if group_type is not None:
-                        if child.get_group_type() == group_type:
-                            yield child._name, getattr(self, child._name)
-                    else:
+                    if group_type is None or (group_type is not None and
+                                              child.objimpl_get_group_type() == group_type):
                         yield child._name, getattr(self, child._name)
                 except GeneratorExit:
                     raise StopIteration
-                except:
+                except PropertiesOptionError:
                     pass
     # ______________________________________________________________________
 
@@ -243,73 +216,44 @@ class SubConfig(object):
         for name, value in self:
             try:
                 lines.append("%s = %s" % (name, value))
-            except:
+            except PropertiesOptionError:
                 pass
         return '\n'.join(lines)
 
     __repr__ = __str__
 
-    def getpaths(self, include_groups=False, allpaths=False, mandatory=False):
-        """returns a list of all paths in self, recursively, taking care of
-        the context of properties (hidden/disabled)
-
-        :param include_groups: if true, OptionDescription are included
-        :param allpaths: all the options (event the properties protected ones)
-        :param mandatory: includes the mandatory options
-        :returns: list of all paths
-        """
-        paths = []
-        for path in self._cfgimpl_descr.getpaths(include_groups=include_groups):
-            if allpaths:
-                paths.append(path)
-            else:
-                try:
-                    getattr(self, path)
-                except MandatoryError:
-                    if mandatory:
-                        paths.append(path)
-                except PropertiesOptionError:
-                    pass
-                else:
-                    paths.append(path)
-        return paths
-
-    def getpath(self):
+    def cfgimpl_get_path(self):
         descr = self.cfgimpl_get_description()
         context_descr = self.cfgimpl_get_context().cfgimpl_get_description()
-        return context_descr.get_path_by_opt(descr)
+        return context_descr.objimpl_get_path_by_opt(descr)
 
-    def find(self, bytype=None, byname=None, byvalue=None, byattrs=None,
-             type_='option'):
+    def find(self, bytype=None, byname=None, byvalue=None, type_='option'):
         """
             finds a list of options recursively in the config
 
             :param bytype: Option class (BoolOption, StrOption, ...)
             :param byname: filter by Option._name
             :param byvalue: filter by the option's value
-            :param byattrs: dict of option attributes (default, callback...)
             :returns: list of matching Option objects
         """
         return self.cfgimpl_get_context()._find(bytype, byname, byvalue,
-                                                byattrs, first=False,
+                                                first=False,
                                                 type_=type_,
-                                                _subpath=self.getpath())
+                                                _subpath=self.cfgimpl_get_path())
 
-    def find_first(self, bytype=None, byname=None, byvalue=None, byattrs=None,
-                   type_='option'):
+    def find_first(self, bytype=None, byname=None, byvalue=None, type_='option'):
         """
             finds an option recursively in the config
 
             :param bytype: Option class (BoolOption, StrOption, ...)
             :param byname: filter by Option._name
             :param byvalue: filter by the option's value
-            :param byattrs: dict of option attributes (default, callback...)
             :returns: list of matching Option objects
         """
         return self.cfgimpl_get_context()._find(bytype, byname, byvalue,
-                                                byattrs, first=True,
+                                                first=True,
                                                 type_=type_,
-                                                _subpath=self.getpath())
+                                                _subpath=self.cfgimpl_get_path())
 
     def make_dict(self, flatten=False, _currpath=None, withoption=None, withvalue=None):
         """export the whole config into a `dict`
@@ -318,18 +262,17 @@ class SubConfig(object):
         if _currpath is None:
             _currpath = []
         if withoption is None and withvalue is not None:
-            raise ValueError("make_dict can't filtering with value without option")
+            raise ValueError(_("make_dict can't filtering with value without option"))
         if withoption is not None:
-            mypath = self.getpath()
+            mypath = self.cfgimpl_get_path()
             for path in self.cfgimpl_get_context()._find(bytype=Option,
                                                          byname=withoption,
                                                          byvalue=withvalue,
-                                                         byattrs=None,
                                                          first=False,
                                                          type_='path',
                                                          _subpath=mypath):
                 path = '.'.join(path.split('.')[:-1])
-                opt = self.cfgimpl_get_context().cfgimpl_get_description().get_opt_by_path(path)
+                opt = self.cfgimpl_get_context().cfgimpl_get_description().objimpl_get_opt_by_path(path)
                 if mypath is not None:
                     if mypath == path:
                         withoption = None
@@ -338,13 +281,13 @@ class SubConfig(object):
                     else:
                         tmypath = mypath + '.'
                         if not path.startswith(tmypath):
-                            raise Exception('unexpected path {}, '
-                                            'should start with {}'.format(path, mypath))
+                            raise AttributeError(_('unexpected path {0}, '
+                                                 'should start with {1}').format(path, mypath))
                         path = path[len(tmypath):]
                 self._make_sub_dict(opt, path, pathsvalues, _currpath, flatten)
         #withoption can be set to None below !
         if withoption is None:
-            for opt in self.cfgimpl_get_description().getchildren():
+            for opt in self.cfgimpl_get_description().objimpl_getchildren():
                 path = opt._name
                 self._make_sub_dict(opt, path, pathsvalues, _currpath, flatten)
         if _currpath == []:
@@ -387,7 +330,13 @@ class Config(SubConfig):
         self._cfgimpl_build_all_paths()
 
     def _cfgimpl_build_all_paths(self):
-        self._cfgimpl_descr.build_cache()
+        self._cfgimpl_descr.objimpl_build_cache()
+
+    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:
+            self.cfgimpl_get_settings().reset_cache(only_expired=only_expired)
 
     def unwrap_from_path(self, path):
         """convenience method to extract and Option() object from the Config()
@@ -401,42 +350,10 @@ class Config(SubConfig):
             return getattr(homeconfig._cfgimpl_descr, path)
         return getattr(self._cfgimpl_descr, path)
 
-    def set(self, **kwargs):
-        """
-        do what I mean"-interface to option setting. Searches all paths
-        starting from that config for matches of the optional arguments
-        and sets the found option if the match is not ambiguous.
-
-        :param kwargs: dict of name strings to values.
-        """
-        #opts, paths = self.cfgimpl_get_description()._cache_paths
-        all_paths = [p.split(".") for p in self.getpaths(allpaths=True)]
-        for key, value in kwargs.iteritems():
-            key_p = key.split('.')
-            candidates = [p for p in all_paths if p[-len(key_p):] == key_p]
-            if len(candidates) == 1:
-                name = '.'.join(candidates[0])
-                homeconfig, name = self.cfgimpl_get_home_by_path(name)
-                try:
-                    getattr(homeconfig, name)
-                except MandatoryError:
-                    pass
-                except Exception, e:
-                    raise e  # HiddenOptionError or DisabledOptionError
-                child = getattr(homeconfig._cfgimpl_descr, name)
-                homeconfig.setoption(name, child, value)
-            elif len(candidates) > 1:
-                raise AmbigousOptionError(
-                    'more than one option that ends with %s' % (key, ))
-            else:
-                raise NoMatchingOptionFound(
-                    'there is no option that matches %s'
-                    ' or the option is hidden or disabled' % (key, ))
-
-    def getpath(self):
+    def cfgimpl_get_path(self):
         return None
 
-    def _find(self, bytype, byname, byvalue, byattrs, first, type_='option',
+    def _find(self, bytype, byname, byvalue, first, type_='option',
               _subpath=None):
         """
         convenience method for finding an option that lives only in the subtree
@@ -459,7 +376,7 @@ class Config(SubConfig):
                 value = getattr(self, path)
                 if value == byvalue:
                     return True
-            except:  # a property restricts the access of the value
+            except PropertiesOptionError:  # a property restricts the access of the value
                 pass
             return False
 
@@ -470,20 +387,19 @@ class Config(SubConfig):
                 return True
             return False
 
-        def _filter_by_attrs():
-            if byattrs is None:
-                return True
-            for key, value in byattrs.items():
-                if not hasattr(option, key):
-                    return False
-                else:
-                    if getattr(option, key) != value:
-                        return False
-                    else:
-                        continue
-            return True
+        #def _filter_by_attrs():
+        #    if byattrs is None:
+        #        return True
+        #    for key, val in byattrs.items():
+        #        print "----", path, key
+        #        if path == key or path.endswith('.' + key):
+        #            if value == val:
+        #                return True
+        #            else:
+        #                return False
+        #    return False
         if type_ not in ('option', 'path', 'value'):
-            raise ValueError('unknown type_ type {} 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)):
@@ -497,15 +413,15 @@ class Config(SubConfig):
                 continue
             if not _filter_by_value():
                 continue
-            if not _filter_by_type():
-                continue
-            if not _filter_by_attrs():
-                continue
             #remove option with propertyerror, ...
             try:
                 value = getattr(self, path)
-            except:  # a property restricts the access of the value
+            except PropertiesOptionError:  # a property restricts the access of the value
+                continue
+            if not _filter_by_type():
                 continue
+            #if not _filter_by_attrs():
+            #    continue
             if type_ == 'value':
                 retval = value
             elif type_ == 'path':
@@ -517,7 +433,7 @@ class Config(SubConfig):
             else:
                 find_results.append(retval)
         if find_results == []:
-            raise NotFoundError("no option found in config with these criteria")
+            raise AttributeError(_("no option found in config with these criteria"))
         else:
             return find_results
 
@@ -528,10 +444,12 @@ def mandatory_warnings(config):
 
     :returns: generator of mandatory Option's path
     """
-    for path in config.cfgimpl_get_description().getpaths(include_groups=True):
+    #if value in cache, properties are not calculated
+    config.cfgimpl_reset_cache(only=('values',))
+    for path in config.cfgimpl_get_description().objimpl_getpaths(include_groups=True):
         try:
             config._getattr(path, force_properties=('mandatory',))
-        except MandatoryError:
-            yield path
-        except PropertiesOptionError:
-            pass
+        except PropertiesOptionError, err:
+            if err.proptype == ['mandatory']:
+                yield path
+    config.cfgimpl_reset_cache(only=('values',))