better propertyerror message

This commit is contained in:
Emmanuel Garette 2016-09-14 20:17:25 +02:00
parent 408e4cf088
commit 19b676967d
5 changed files with 154 additions and 36 deletions

View File

@ -4,6 +4,8 @@ do_autopath()
from py.test import raises from py.test import raises
from tiramisu.i18n import _
from tiramisu.error import display_list
from tiramisu.setting import owners, groups from tiramisu.setting import owners, groups
from tiramisu.config import Config from tiramisu.config import Config
from tiramisu.option import ChoiceOption, BoolOption, IntOption, FloatOption, \ from tiramisu.option import ChoiceOption, BoolOption, IntOption, FloatOption, \
@ -471,3 +473,65 @@ def test_reset_properties_force_store_value():
setting.reset(all_properties=True) setting.reset(all_properties=True)
assert setting._p_.get_modified_properties() == {} assert setting._p_.get_modified_properties() == {}
raises(ValueError, 'setting.reset(all_properties=True, opt=option)') raises(ValueError, 'setting.reset(all_properties=True, opt=option)')
def test_pprint():
msg_error = _('cannot access to {} {} because has {} {}')
msg_is = _("the value of {0} is {1}")
msg_is_not = _("the value of {0} is not {1}")
s = StrOption("string", "", default=["string"], default_multi="string", multi=True, properties=('hidden', 'disabled'))
s2 = StrOption("string2", "", default="string")
s3 = StrOption("string3", "", default=["string"], default_multi="string", multi=True, properties=('hidden',))
intoption = IntOption('int', 'Test int option', default=0)
stroption = StrOption('str', 'Test string option', default="abc",
requires=[{'option': intoption, 'expected': 2, 'action': 'hidden', 'inverse': True},
{'option': intoption, 'expected': 3, 'action': 'hidden', 'inverse': True},
{'option': intoption, 'expected': 4, 'action': 'hidden', 'inverse': True},
{'option': intoption, 'expected': 1, 'action': 'disabled'},
{'option': s2, 'expected': 'string', 'action': 'disabled'}])
val2 = StrOption('val2', "")
descr2 = OptionDescription("options", "", [val2], requires=[{'option': intoption, 'expected': 1, 'action': 'hidden'}])
val3 = StrOption('val3', "", requires=[{'option': stroption, 'expected': '2', 'action': 'hidden', 'inverse': True}])
descr = OptionDescription("options", "", [s, s2, s3, intoption, stroption, descr2, val3])
config = Config(descr)
setting = config.cfgimpl_get_settings()
config.read_write()
config.int = 1
try:
config.str
except Exception, err:
pass
assert str(err) == msg_error.format('option', 'str', 'properties', display_list(['disabled (' + display_list([msg_is.format('int', '1'), msg_is.format('string2', 'string')]) + ')', 'hidden (' + msg_is_not.format('int', display_list([2, 3, 4], 'or')) + ')']))
try:
config.options.val2
except Exception, err:
pass
assert str(err) == msg_error.format('optiondescription', 'options', 'property', 'hidden (' + msg_is.format('int', 1) + ')')
try:
config.val3
except Exception, err:
pass
assert str(err) == msg_error.format('option', 'val3', 'property', 'hidden (' + display_list([msg_is.format('int', 1), msg_is.format('string2', 'string'), msg_is_not.format('int', display_list([2, 3, 4], 'or'))]) + ')')
try:
config.string
except Exception, err:
pass
assert str(err) == msg_error.format('option', 'string', 'properties', display_list(['disabled', 'hidden']))
try:
config.string3
except Exception, err:
pass
assert str(err) == msg_error.format('option', 'string3', 'property', 'hidden')

View File

@ -15,15 +15,45 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# ____________________________________________________________ # ____________________________________________________________
"user defined exceptions" "user defined exceptions"
from .i18n import _
def display_list(lst, separator='and'):
if len(lst) == 1:
return lst[0]
else:
lst_ = []
for l in lst[:-1]:
lst_.append(str(l))
return ', '.join(lst_) + _(' {} ').format(separator) + str(lst[-1])
# Exceptions for an Option # Exceptions for an Option
class PropertiesOptionError(AttributeError): class PropertiesOptionError(AttributeError):
"attempt to access to an option with a property that is not allowed" "attempt to access to an option with a property that is not allowed"
def __init__(self, msg, proptype): def __init__(self, msg, proptype, settings, datas, option_type):
self.proptype = proptype self.proptype = proptype
self._settings = settings
self._datas = datas
self._type = option_type
super(PropertiesOptionError, self).__init__(msg) super(PropertiesOptionError, self).__init__(msg)
def __str__(self):
#this part is a bit slow, so only execute when display
req = self._settings.apply_requires(**self._datas)
if req != {}:
msg = []
for action, msg_ in req.items():
msg.append('{0} ({1})'.format(action, display_list(msg_)))
if len(req) == 1:
prop_msg = _('property')
else:
prop_msg = _('properties')
msg = display_list(msg)
return _('cannot access to {0} {1} because has {2} {3}').format(self._type, self._datas['path'], prop_msg, msg)
else:
return self.message
#____________________________________________________________ #____________________________________________________________
# Exceptions for a Config # Exceptions for a Config

View File

@ -24,9 +24,10 @@ import warnings
import sys import sys
from ..i18n import _ from ..i18n import _
from ..setting import log, undefined from ..setting import log, undefined, debug
from ..autolib import carry_out_calculation from ..autolib import carry_out_calculation
from ..error import ConfigError, ValueWarning, PropertiesOptionError from ..error import (ConfigError, ValueWarning, PropertiesOptionError,
display_list)
from ..storage import get_storages_option from ..storage import get_storages_option
@ -38,13 +39,6 @@ forbidden_names = frozenset(['iter_all', 'iter_group', 'find', 'find_first',
'read_write', 'getowner', 'set_contexts']) 'read_write', 'getowner', 'set_contexts'])
def display_list(list_):
if len(list_) == 1:
return list_[0]
else:
return ', '.join(list_[:-1]) + _(' and ') + list_[-1]
def valid_name(name): def valid_name(name):
"an option's name is a str and does not start with 'impl' or 'cfgimpl'" "an option's name is a str and does not start with 'impl' or 'cfgimpl'"
if not isinstance(name, str): # pragma: optional cover if not isinstance(name, str): # pragma: optional cover
@ -417,7 +411,8 @@ class Option(OnlyOption):
returns_raise=True) returns_raise=True)
if isinstance(opt_value, Exception): if isinstance(opt_value, Exception):
if isinstance(opt_value, PropertiesOptionError): if isinstance(opt_value, PropertiesOptionError):
log.debug('propertyerror in _launch_consistency: {0}'.format(opt_value)) if debug:
log.debug('propertyerror in _launch_consistency: {0}'.format(opt_value))
if transitive: if transitive:
return opt_value return opt_value
else: else:
@ -493,10 +488,11 @@ class Option(OnlyOption):
err = self._validate(_value, context, current_opt, err = self._validate(_value, context, current_opt,
returns_raise=True) returns_raise=True)
if err: if err:
log.debug('do_validation: value: {0}, index: {1}, ' if debug:
'submulti_index: {2}'.format(_value, _index, log.debug('do_validation: value: {0}, index: {1}, '
submulti_index), 'submulti_index: {2}'.format(_value, _index,
exc_info=True) submulti_index),
exc_info=True)
err_msg = '{0}'.format(err) err_msg = '{0}'.format(err)
if err_msg: if err_msg:
msg = _('{0} is an invalid {1} for option {2}, {3}' msg = _('{0} is an invalid {1} for option {2}, {3}'
@ -512,8 +508,9 @@ class Option(OnlyOption):
if not error: if not error:
error = self._second_level_validation(_value, self._is_warnings_only()) error = self._second_level_validation(_value, self._is_warnings_only())
if error: if error:
log.debug(_('do_validation for {0}: error in value').format( if debug:
self.impl_getname()), exc_info=True) log.debug(_('do_validation for {0}: error in value').format(
self.impl_getname()), exc_info=True)
if self._is_warnings_only(): if self._is_warnings_only():
warning = error warning = error
error = None error = None
@ -718,7 +715,8 @@ class Option(OnlyOption):
msg = _("value for {0} and {1} should be different") msg = _("value for {0} and {1} should be different")
else: else:
msg = _("value for {0} and {1} must be different") msg = _("value for {0} and {1} must be different")
log.debug('_cons_not_equal: {0} and {1} are not different'.format(val_inf, val_sup)) if debug:
log.debug('_cons_not_equal: {0} and {1} are not different'.format(val_inf, val_sup))
return ValueError(msg.format(opts[idx_inf].impl_getname(), return ValueError(msg.format(opts[idx_inf].impl_getname(),
opts[idx_inf + idx_sup + 1].impl_getname())) opts[idx_inf + idx_sup + 1].impl_getname()))

View File

@ -20,7 +20,7 @@
# the whole pypy projet is under MIT licence # the whole pypy projet is under MIT licence
# ____________________________________________________________ # ____________________________________________________________
from ..i18n import _ from ..i18n import _
from ..setting import log, undefined from ..setting import log, undefined, debug
from ..error import SlaveError, PropertiesOptionError from ..error import SlaveError, PropertiesOptionError
from .baseoption import DynSymLinkOption, SymLinkOption, Option from .baseoption import DynSymLinkOption, SymLinkOption, Option
@ -278,8 +278,9 @@ class MasterSlaves(object):
def validate_slave_length(self, masterlen, valuelen, name, opt, setitem=False): def validate_slave_length(self, masterlen, valuelen, name, opt, setitem=False):
if valuelen > masterlen or (valuelen < masterlen and setitem): # pragma: optional cover if valuelen > masterlen or (valuelen < masterlen and setitem): # pragma: optional cover
log.debug('validate_slave_length: masterlen: {0}, valuelen: {1}, ' if debug:
'setitem: {2}'.format(masterlen, valuelen, setitem)) log.debug('validate_slave_length: masterlen: {0}, valuelen: {1}, '
'setitem: {2}'.format(masterlen, valuelen, setitem))
raise SlaveError(_("invalid len for the slave: {0}" raise SlaveError(_("invalid len for the slave: {0}"
" which has {1} as master").format( " which has {1} as master").format(
name, self.getmaster(opt).impl_getname())) name, self.getmaster(opt).impl_getname()))

View File

@ -20,7 +20,7 @@ from copy import copy
from logging import getLogger from logging import getLogger
import weakref import weakref
from .error import (RequirementError, PropertiesOptionError, from .error import (RequirementError, PropertiesOptionError,
ConstError, ConfigError) ConstError, ConfigError, display_list)
from .i18n import _ from .i18n import _
@ -113,6 +113,7 @@ log = getLogger('tiramisu')
#FIXME #FIXME
#import logging #import logging
#logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG) #logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)
debug = False
# ____________________________________________________________ # ____________________________________________________________
@ -394,7 +395,7 @@ class Settings(object):
if opt.impl_is_multi() and not opt.impl_is_master_slaves('slave'): if opt.impl_is_multi() and not opt.impl_is_master_slaves('slave'):
props.add('empty') props.add('empty')
if apply_requires: if apply_requires:
requires = self.apply_requires(opt, path, setting_properties, index) requires = self.apply_requires(opt, path, setting_properties, index, False)
if requires != set([]): if requires != set([]):
props = copy(props) props = copy(props)
props |= requires props |= requires
@ -441,7 +442,7 @@ class Settings(object):
value=None, force_permissive=False, value=None, force_permissive=False,
setting_properties=undefined, setting_properties=undefined,
self_properties=undefined, self_properties=undefined,
index=None): index=None, debug=False):
""" """
validation upon the properties related to `opt_or_descr` validation upon the properties related to `opt_or_descr`
@ -496,22 +497,30 @@ class Settings(object):
# at this point an option should not remain in properties # at this point an option should not remain in properties
if properties != frozenset(): if properties != frozenset():
props = list(properties) props = list(properties)
datas = {'opt': opt_or_descr, 'path': path, 'setting_properties': setting_properties,
'index': index, 'debug': True}
if is_descr:
opt_type = 'optiondescription'
else:
opt_type = 'option'
if 'frozen' in properties: if 'frozen' in properties:
return PropertiesOptionError(_('cannot change the value for ' return PropertiesOptionError(_('cannot change the value for '
'option {0} this option is' 'option {0} this option is'
' frozen').format( ' frozen').format(
opt_or_descr.impl_getname()), opt_or_descr.impl_getname()),
props) props, self, datas, opt_type)
else: else:
if is_descr: if len(props) == 1:
opt_type = 'optiondescription' prop_msg = 'property'
else: else:
opt_type = 'option' prop_msg = 'properties'
return PropertiesOptionError(_("trying to access to an {0} " return PropertiesOptionError(_("cannot access to {0} {1} "
"named: {1} with properties {2}" "because has {2} {3}"
"").format(opt_type, "").format(opt_type,
opt_or_descr._name, opt_or_descr._name,
str(props)), props) prop_msg,
display_list(props)), props,
self, datas, opt_type)
def setpermissive(self, permissive, opt=None, path=None): def setpermissive(self, permissive, opt=None, path=None):
""" """
@ -571,7 +580,7 @@ class Settings(object):
else: else:
self._p_.reset_all_cache() self._p_.reset_all_cache()
def apply_requires(self, opt, path, setting_properties, index): def apply_requires(self, opt, path, setting_properties, index, debug):
"""carries out the jit (just in time) requirements between options """carries out the jit (just in time) requirements between options
a requirement is a tuple of this form that comes from the option's a requirement is a tuple of this form that comes from the option's
@ -619,7 +628,10 @@ class Settings(object):
return frozenset() return frozenset()
# filters the callbacks # filters the callbacks
calc_properties = set() if debug:
calc_properties = {}
else:
calc_properties = set()
context = self._getcontext() context = self._getcontext()
for requires in opt.impl_getrequires(): for requires in opt.impl_getrequires():
for require in requires: for require in requires:
@ -650,17 +662,30 @@ class Settings(object):
"{1} {2}").format(opt._name, "{1} {2}").format(opt._name,
reqpath, reqpath,
properties)) properties))
orig_value = value
# transitive action, force expected # transitive action, force expected
value = expected[0] value = expected[0]
inverse = False inverse = False
else: else:
raise value raise value
else:
orig_value = value
if (not inverse and if (not inverse and
value in expected or value in expected or
inverse and value not in expected): inverse and value not in expected):
calc_properties.add(action) if debug:
# the calculation cannot be carried out if isinstance(orig_value, PropertiesOptionError):
break for act, msg in orig_value._settings.apply_requires(**orig_value._datas).items():
calc_properties.setdefault(action, []).extend(msg)
else:
if not inverse:
msg = _("the value of {0} is {1}")
else:
msg = _("the value of {0} is not {1}")
calc_properties.setdefault(action, []).append(msg.format(reqpath, display_list(expected, 'or')))
else:
calc_properties.add(action)
break
return calc_properties return calc_properties
def get_modified_properties(self): def get_modified_properties(self):