mandatory is a true property (no more MandatoryError) + tests
authorEmmanuel Garette <egarette@cadoles.com>
Tue, 16 Apr 2013 20:44:16 +0000 (22:44 +0200)
committerEmmanuel Garette <egarette@cadoles.com>
Tue, 16 Apr 2013 20:44:16 +0000 (22:44 +0200)
test/test_config.py
test/test_mandatory.py [new file with mode: 0644]
test/test_option_default.py
tiramisu/config.py
tiramisu/error.py
tiramisu/option.py
tiramisu/value.py

index 6c13c32..d487cb9 100644 (file)
@@ -106,15 +106,3 @@ def test_cfgimpl_get_home_by_path():
     assert config.getpaths(include_groups=False) == ['gc.name', 'gc.dummy', 'gc.float', 'bool', 'objspace', 'wantref', 'str', 'wantframework', 'int', 'boolop']
     assert config.getpaths(include_groups=True) == ['gc', 'gc.name', 'gc.dummy', 'gc.float', 'bool', 'objspace', 'wantref', 'str', 'wantframework', 'int', 'boolop']
 
-def test_mandatory_warnings():
-    descr = make_description()
-    config = Config(descr)
-    assert(MandatoryError, "config.str = ''")
-    setting = config.cfgimpl_get_settings()
-    setting.read_write()
-    assert list(mandatory_warnings(config)) == []
-    setting.disable_property('mandatory')
-    config.str = ''
-    assert list(mandatory_warnings(config)) == ['str']
-    setting.enable_property('mandatory')
-    assert list(mandatory_warnings(config)) == ['str']
diff --git a/test/test_mandatory.py b/test/test_mandatory.py
new file mode 100644 (file)
index 0000000..c598cd3
--- /dev/null
@@ -0,0 +1,214 @@
+import autopath
+
+#from py.test import raises
+from tiramisu.config import Config, mandatory_warnings
+from tiramisu.option import StrOption, OptionDescription
+from tiramisu.error import PropertiesOptionError
+
+
+def make_description():
+    stroption = StrOption('str', 'Test string option', default="abc",
+                          properties=('mandatory', ))
+    stroption1 = StrOption('str1', 'Test string option',
+                           properties=('mandatory', ))
+    stroption2 = StrOption('str2', 'Test string option',
+                           properties=('mandatory', ))
+    stroption3 = StrOption('str3', 'Test string option', multi=True,
+                           properties=('mandatory', ))
+    descr = OptionDescription('tiram', '', [stroption, stroption1, stroption2, stroption3])
+    return descr
+
+
+def test_mandatory_ro():
+    descr = make_description()
+    config = Config(descr)
+    setting = config.cfgimpl_get_settings()
+    setting.read_only()
+    prop = []
+    try:
+        config.str1
+    except PropertiesOptionError, err:
+        prop = err.proptype
+    assert 'mandatory' in prop
+    setting.read_write()
+    config.str1 = 'yes'
+    setting.read_only()
+    assert config.str1 == 'yes'
+
+
+def test_mandatory_rw():
+    descr = make_description()
+    config = Config(descr)
+    setting = config.cfgimpl_get_settings()
+    setting.read_write()
+    #not mandatory in rw
+    config.str2
+    config.str2 = 'yes'
+    assert config.str2 == 'yes'
+
+
+def test_mandatory_default():
+    descr = make_description()
+    config = Config(descr)
+    setting = config.cfgimpl_get_settings()
+    setting.read_only()
+    #not mandatory in rw
+    config.str
+    setting.read_write()
+    config.str = 'yes'
+    setting.read_only()
+    config.str
+    setting.read_write()
+    config.str = None
+    setting.read_only()
+    prop = []
+    try:
+        config.str
+    except PropertiesOptionError, err:
+        prop = err.proptype
+    assert 'mandatory' in prop
+
+
+#valeur vide : None, '', u'', ...
+def test_mandatory_none():
+    descr = make_description()
+    config = Config(descr)
+    config.str1 = None
+    setting = config.cfgimpl_get_settings()
+    assert config.cfgimpl_get_values().getowner(descr.str1) == 'user'
+    setting.read_only()
+    prop = []
+    try:
+        config.str1
+    except PropertiesOptionError, err:
+        prop = err.proptype
+    assert 'mandatory' in prop
+
+
+def test_mandatory_empty():
+    descr = make_description()
+    config = Config(descr)
+    config.str1 = ''
+    setting = config.cfgimpl_get_settings()
+    assert config.cfgimpl_get_values().getowner(descr.str1) == 'user'
+    setting.read_only()
+    prop = []
+    try:
+        config.str1
+    except PropertiesOptionError, err:
+        prop = err.proptype
+    assert 'mandatory' in prop
+
+
+def test_mandatory_multi_none():
+    descr = make_description()
+    config = Config(descr)
+    setting = config.cfgimpl_get_settings()
+    config.str3 = [None]
+    setting.read_only()
+    assert config.cfgimpl_get_values().getowner(descr.str3) == 'user'
+    prop = []
+    try:
+        config.str3
+    except PropertiesOptionError, err:
+        prop = err.proptype
+    assert 'mandatory' in prop
+    setting.read_write()
+    config.str3 = ['yes', None]
+    setting.read_only()
+    assert config.cfgimpl_get_values().getowner(descr.str3) == 'user'
+    prop = []
+    try:
+        config.str3
+    except PropertiesOptionError, err:
+        prop = err.proptype
+    assert 'mandatory' in prop
+
+
+def test_mandatory_multi_empty():
+    descr = make_description()
+    config = Config(descr)
+    setting = config.cfgimpl_get_settings()
+    config.str3 = ['']
+    setting.read_only()
+    assert config.cfgimpl_get_values().getowner(descr.str3) == 'user'
+    prop = []
+    try:
+        config.str3
+    except PropertiesOptionError, err:
+        prop = err.proptype
+    assert 'mandatory' in prop
+    setting.read_write()
+    config.str3 = ['yes', '']
+    setting.read_only()
+    assert config.cfgimpl_get_values().getowner(descr.str3) == 'user'
+    prop = []
+    try:
+        config.str3
+    except PropertiesOptionError, err:
+        prop = err.proptype
+    assert 'mandatory' in prop
+
+
+def test_mandatory_disabled():
+    descr = make_description()
+    config = Config(descr)
+    setting = config.cfgimpl_get_settings()
+    config.str1
+    setting.read_only()
+    prop = []
+    try:
+        config.str1
+    except PropertiesOptionError, err:
+        prop = err.proptype
+    assert prop == ['mandatory']
+    setting.add_property('disabled', descr.str1)
+    prop = []
+    try:
+        config.str1
+    except PropertiesOptionError, err:
+        prop = err.proptype
+    assert prop == ['disabled', 'mandatory']
+
+
+def test_mandatory_warnings_ro():
+    descr = make_description()
+    config = Config(descr)
+    config.str = ''
+    setting = config.cfgimpl_get_settings()
+    setting.read_only()
+    proc = []
+    try:
+        config.str
+    except PropertiesOptionError, err:
+        proc = err.proptype
+    assert proc == ['mandatory']
+    assert list(mandatory_warnings(config)) == ['str', 'str1', 'str2', 'str3']
+    setting.read_write()
+    config.str = 'a'
+    setting.read_only()
+    assert list(mandatory_warnings(config)) == ['str1', 'str2', 'str3']
+
+
+def test_mandatory_warnings_rw():
+    descr = make_description()
+    config = Config(descr)
+    config.str = ''
+    setting = config.cfgimpl_get_settings()
+    setting.read_write()
+    config.str
+    assert list(mandatory_warnings(config)) == ['str', 'str1', 'str2', 'str3']
+    config.str = 'a'
+    assert list(mandatory_warnings(config)) == ['str1', 'str2', 'str3']
+
+
+def test_mandatory_warnings_disabled():
+    descr = make_description()
+    config = Config(descr)
+    config.str = ''
+    setting = config.cfgimpl_get_settings()
+    setting.read_write()
+    config.str
+    assert list(mandatory_warnings(config)) == ['str', 'str1', 'str2', 'str3']
+    setting.add_property('disabled', descr.str)
+    assert list(mandatory_warnings(config)) == ['str1', 'str2', 'str3']
index ad1e1a8..dfe4733 100644 (file)
@@ -4,7 +4,7 @@ import autopath
 from py.test import raises
 from tiramisu.config import *
 from tiramisu.option import *
-from tiramisu.error import MandatoryError
+from tiramisu.error import PropertiesOptionError
 
 def make_description():
     gcoption = ChoiceOption('name', 'GC name', ['ref', 'framework'], 'ref')
@@ -50,27 +50,6 @@ def test_set_defaut_value_from_option_object():
     b = BoolOption("boolean", "", default=False)
     assert b.getdefault() == False
 
-def test_mandatory():
-    dummy1 = BoolOption('dummy1', 'doc dummy', properties=('mandatory', ))
-    dummy2 = BoolOption('dummy2', 'doc dummy', properties=('mandatory', ))
-    group = OptionDescription('group', '', [dummy1, dummy2])
-    config = Config(group)
-    setting = config.cfgimpl_get_settings()
-    setting.read_only()
-#    config.setoption('dummy1', True)
-    raises(MandatoryError, 'config.dummy1')
-    setting.read_write()
-    config.dummy1 = True
-    setting.read_only()
-    assert config.dummy1 == True
-    raises(MandatoryError, 'config.dummy2 == None')
-#    raises(MandatoryError, "config.override({'dummy2':None})")
-    setting.read_write()
-    config.set(dummy2=True)
-    config.dummy2 = False
-    setting.read_only()
-    assert config.dummy2 == False
-
 def test_force_default_on_freeze():
     "a frozen option wich is forced returns his default"
     dummy1 = BoolOption('dummy1', 'doc dummy', default=False, properties=('force_default_on_freeze',))
index b622113..717fc49 100644 (file)
@@ -22,7 +22,7 @@
 # ____________________________________________________________
 #from inspect import getmembers, ismethod
 from tiramisu.error import (PropertiesOptionError, ConfigError,
-                            AmbigousOptionError, MandatoryError)
+                            AmbigousOptionError)
 from tiramisu.option import OptionDescription, Option, SymLinkOption
 from tiramisu.setting import groups, Setting, apply_requires
 from tiramisu.value import Values
@@ -79,13 +79,13 @@ class SubConfig(object):
             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._validate(name, getattr(self._cfgimpl_descr, name), value,
+                           force_permissive=force_permissive)
             self.setoption(name, child, value)
         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"
+    def _validate_descr(self, name, opt_or_descr, force_permissive=False, is_raise=True):
         if not isinstance(opt_or_descr, Option) and \
                 not isinstance(opt_or_descr, OptionDescription):
             raise TypeError(_('unexpected object: {0}').format(repr(opt_or_descr)))
@@ -98,6 +98,24 @@ class SubConfig(object):
             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 is_raise:
+            if properties != []:
+                raise PropertiesOptionError(_("trying to access"
+                                            " to an option named: {0} with properties"
+                                            " {1}").format(name, str(properties)),
+                                            properties)
+        else:
+            return properties
+
+    def _validate(self, name, opt_or_descr, value, force_permissive=False,
+                  force_properties=None):
+        "validation for the setattr and the getattr"
+        properties = self._validate_descr(name, opt_or_descr,
+                                          force_permissive=force_permissive,
+                                          is_raise=False)
+        if self.cfgimpl_get_context().cfgimpl_get_values().is_mandatory_err(
+                opt_or_descr, value, force_properties=force_properties):
+            properties.append('mandatory')
         if properties != []:
             raise PropertiesOptionError(_("trying to access"
                                         " to an option named: {0} with properties"
@@ -130,8 +148,8 @@ class SubConfig(object):
             rootconfig = self.cfgimpl_get_context()
             path = rootconfig.cfgimpl_get_description().get_path_by_opt(opt_or_descr.opt)
             return rootconfig._getattr(path, validate=validate)
-        self._validate(name, opt_or_descr, force_permissive=force_permissive)
         if isinstance(opt_or_descr, OptionDescription):
+            self._validate_descr(name, opt_or_descr, force_permissive=force_permissive)
             children = self.cfgimpl_get_description()._children
             if opt_or_descr not in children[1]:
                 raise AttributeError(_("{0} with name {1} object has "
@@ -143,9 +161,12 @@ class SubConfig(object):
         if 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,
-                                                  validate=validate)
+        value = self.cfgimpl_get_values()._getitem(opt_or_descr,
+                                                   validate=validate)
+        self._validate(name, opt_or_descr, value,
+                       force_permissive=force_permissive,
+                       force_properties=force_properties)
+        return value
 
     def setoption(self, name, child, value):
         """effectively modifies the value of an Option()
@@ -258,13 +279,12 @@ class SubConfig(object):
 
     __repr__ = __str__
 
-    def getpaths(self, include_groups=False, allpaths=False, mandatory=False):
+    def getpaths(self, include_groups=False, allpaths=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 = []
@@ -274,9 +294,6 @@ class SubConfig(object):
             else:
                 try:
                     getattr(self, path)
-                except MandatoryError:
-                    if mandatory:
-                        paths.append(path)
                 except PropertiesOptionError:
                     pass
                 else:
@@ -421,12 +438,11 @@ class Config(SubConfig):
             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 PropertiesOptionError, e:
-                    raise e  # HiddenOptionError or DisabledOptionError
+                getattr(homeconfig, name)
+                #except MandatoryError:
+                #    pass
+                #except PropertiesOptionError, e:
+                #    raise e  # HiddenOptionError or DisabledOptionError
                 child = getattr(homeconfig._cfgimpl_descr, name)
                 homeconfig.setoption(name, child, value)
             elif len(candidates) > 1:
@@ -534,7 +550,6 @@ def mandatory_warnings(config):
     for path in config.cfgimpl_get_description().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
index 5088e53..43083c9 100644 (file)
@@ -1,3 +1,25 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors)
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
+# The original `Config` design model is unproudly borrowed from
+# the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/
+# the whole pypy projet is under MIT licence
+# ____________________________________________________________
+
 #ValueError if function's parameter not correct
 #           or if not logical
 #           or if validation falied
@@ -34,11 +56,6 @@ class RequirementRecursionError(StandardError):
     pass
 
 
-class MandatoryError(Exception):
-    "mandatory error"
-    pass
-
-
 class MultiTypeError(Exception):
     """multi must be a list
     or error with multi length"""
index 88dd9e3..d410fc9 100644 (file)
@@ -78,7 +78,7 @@ class Option(BaseInformation):
     """
     __slots__ = ('_name', '_requires', '_multi', '_validator', '_default_multi',
                  '_default', '_properties', '_callback', '_multitype',
-                 '_master_slaves', '_consistencies')
+                 '_master_slaves', '_consistencies', '_empty')
 
     def __init__(self, name, doc, default=None, default_multi=None,
                  requires=None, multi=False, callback=None,
@@ -110,6 +110,7 @@ class Option(BaseInformation):
         validate_requires_arg(requires, self._name)
         self._requires = requires
         self._multi = multi
+        self._empty = ''
         self._consistencies = None
         if validator is not None:
             if type(validator) != FunctionType:
index 0fcd1d0..fb3ddba 100644 (file)
@@ -17,7 +17,7 @@
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 #
 # ____________________________________________________________
-from tiramisu.error import MandatoryError, MultiTypeError, ConfigError
+from tiramisu.error import MultiTypeError, ConfigError
 from tiramisu.setting import owners, multitypes
 from tiramisu.autolib import carry_out_calculation
 from tiramisu.i18n import _
@@ -67,36 +67,25 @@ class Values(object):
 
     def _is_empty(self, opt, value):
         "convenience method to know if an option is empty"
-        #FIXME: buggy ?
-        #if value is not None:
-        #    return False
-        if (not opt.is_multi() and value is None) or \
+        empty = opt._empty
+        if (not opt.is_multi() and (value is None or value == empty)) or \
            (opt.is_multi() and (value == [] or
-                                None in self._get_value(opt))):
+                                None in value or empty in value)):
+            return True
+        if self.is_default_owner(opt) and opt.is_empty_by_default():
             return True
         return False
 
-    def _test_mandatory(self, opt, value, force_properties=None):
+    def is_mandatory_err(self, opt, value, force_properties=None):
         setting = self.context.cfgimpl_get_settings()
-        if force_properties is None:
-            set_mandatory = setting.has_property('mandatory')
-        else:
+        set_mandatory = setting.has_property('mandatory')
+        if force_properties is not None:
             set_mandatory = ('mandatory' in force_properties or
-                             setting.has_property('mandatory'))
-        if setting.has_property('mandatory', opt, False) and set_mandatory:
-            if self._is_empty(opt, value) and opt.is_empty_by_default():
-                raise MandatoryError(_("option: {0} is mandatory "
-                                     "and shall have a value").format(opt._name))
-            #empty value
-            if opt.is_multi():
-                for val in value:
-                    if val == '':
-                        raise MandatoryError(_("option: {0} is mandatory "
-                                             "and shall have not empty value").format(opt._name))
-            else:
-                if value == '':
-                    raise MandatoryError(_("option: {0} is mandatory "
-                                         "and shall have not empty value").format(opt._name))
+                             set_mandatory)
+        if set_mandatory and setting.has_property('mandatory', opt, False) and \
+                self._is_empty(opt, value):
+            return True
+        return False
 
     def fill_multi(self, opt, result):
         """fills a multi option with default and calculated values
@@ -118,7 +107,7 @@ class Values(object):
     def __getitem__(self, opt):
         return self._getitem(opt)
 
-    def _getitem(self, opt, force_properties=None, validate=True):
+    def _getitem(self, opt, validate=True):
         # options with callbacks
         value = self._get_value(opt)
         setting = self.context.cfgimpl_get_settings()
@@ -142,13 +131,12 @@ class Values(object):
             value = opt.getdefault()
             if opt.is_multi():
                 value = self.fill_multi(opt, value)
-        self._test_mandatory(opt, value, force_properties)
         if validate and not opt.validate(value, self.context, setting.has_property('validator')):
             raise ValueError(_('invalid calculated value returned'
                              ' for option {0}: {1}').format(opt._name, value))
         if self.is_default_owner(opt) and \
                 setting.has_property('force_store_value', opt, False):
-            self.setitem(opt, value)
+            self.setitem(opt, value, validate=validate)
         return value
 
     def __setitem__(self, opt, value):
@@ -182,7 +170,6 @@ class Values(object):
         if type(value) == list:
             raise MultiTypeError(_("the type of the value {0} which is multi shall "
                                  "be Multi and not list").format(str(value)))
-        self._test_mandatory(opt, value)
         self.values[opt] = (self.context.cfgimpl_get_settings().getowner(), value)
 
     def __contains__(self, opt):