mandatory is a true property (no more MandatoryError) + tests

This commit is contained in:
Emmanuel Garette 2013-04-16 22:44:16 +02:00
parent 6097f3af84
commit 656b751995
7 changed files with 293 additions and 92 deletions

View 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']

214
test/test_mandatory.py Normal file
View file

@ -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']

View 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',))

View 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

View 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"""

View 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:

View 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):