From 656b7519950dbdd003612e219e9306ec948f32bc Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Tue, 16 Apr 2013 22:44:16 +0200 Subject: [PATCH] mandatory is a true property (no more MandatoryError) + tests --- test/test_config.py | 12 -- test/test_mandatory.py | 214 ++++++++++++++++++++++++++++++++++++ test/test_option_default.py | 23 +--- tiramisu/config.py | 61 ++++++---- tiramisu/error.py | 27 ++++- tiramisu/option.py | 3 +- tiramisu/value.py | 45 +++----- 7 files changed, 293 insertions(+), 92 deletions(-) create mode 100644 test/test_mandatory.py diff --git a/test/test_config.py b/test/test_config.py index 6c13c32..d487cb9 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -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 index 0000000..c598cd3 --- /dev/null +++ b/test/test_mandatory.py @@ -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'] diff --git a/test/test_option_default.py b/test/test_option_default.py index ad1e1a8..dfe4733 100644 --- a/test/test_option_default.py +++ b/test/test_option_default.py @@ -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',)) diff --git a/tiramisu/config.py b/tiramisu/config.py index b622113..717fc49 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -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 diff --git a/tiramisu/error.py b/tiramisu/error.py index 5088e53..43083c9 100644 --- a/tiramisu/error.py +++ b/tiramisu/error.py @@ -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""" diff --git a/tiramisu/option.py b/tiramisu/option.py index 88dd9e3..d410fc9 100644 --- a/tiramisu/option.py +++ b/tiramisu/option.py @@ -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: diff --git a/tiramisu/value.py b/tiramisu/value.py index 0fcd1d0..fb3ddba 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -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):