Enhance valid_values to use __contains__

Actually valid_values doesn't cover many of the use cases or validates
that the value and valid_values provided can be used with 'in' call.

his patch tries to improve this situation to ensure that both are valid
for being checked or fail otherwise plus validating that valid_values
contains the value provided.

Closes-Bug: 1604073
Change-Id: I47a6f7d58d6cbeb8fd245620d187c4c565497290
This commit is contained in:
Pablo Iranzo Gómez 2016-09-24 22:08:19 +02:00
parent 6cf9c87ef3
commit abeb5c4de0
No known key found for this signature in database
GPG Key ID: 034823665BD8E1E4
2 changed files with 95 additions and 16 deletions

View File

@ -93,19 +93,50 @@ def _validate_list_of_items(item_validator, data, *args, **kwargs):
return msg
def validate_values(data, valid_values=None):
"""Validate a data value based on a list of valid values.
def validate_values(data, valid_values=None, valid_values_display=None):
"""Validate that the provided 'data' is within 'valid_values'.
:param data: The data value to validate.
:param valid_values: A list (or sequence) of valid values data can be.
:returns: None if data is in valid_values, otherwise a human readable
message as to why the value is invalid.
:param data: The data to check within valid_values.
:param valid_values: A collection of values that 'data' must be in to be
valid. The collection can be any type that supports the 'in' operation.
:param valid_values_display: A string to display that describes the valid
values. This string is only displayed when an invalid value is
encountered.
If no string is provided, the string "valid_values" will be used.
:returns: The message to return if data not in valid_values.
:raises: TypeError if the values for 'data' or 'valid_values' are not
compatible for comparison or doesn't have __contains__.
If TypeError is raised this is considered a programming error and the
inputs (data) and (valid_values) must be checked so this is never
raised on validation.
"""
# If valid_values is not specified we don't check against it.
if valid_values is None:
return
# Check if we can use 'in' to find membership of data in valid_values
contains = getattr(valid_values, "__contains__", None)
if callable(contains):
try:
if data not in valid_values:
msg = (_("'%(data)s' is not in %(valid_values)s") %
{'data': data, 'valid_values': valid_values})
valid_values_display = valid_values_display or 'valid_values'
msg = (_("%(data)s is not in %(valid_values)s") %
{'data': data, 'valid_values': valid_values_display})
LOG.debug(msg)
return msg
except TypeError:
# This is a programming error
msg = (_("'data' of type '%(typedata)s' and 'valid_values'"
"of type '%(typevalues)s' are not "
"compatible for comparison") %
{'typedata': type(data),
'typevalues': type(valid_values)})
raise TypeError(msg)
else:
# This is a programming error
msg = (_("'valid_values' does not support membership operations"))
raise TypeError(msg)
def validate_not_empty_string_or_none(data, max_len=None):

View File

@ -94,17 +94,65 @@ class TestAttributeValidation(base.BaseTestCase):
self.assertIs(validators.is_attr_set(data), True)
def test_validate_values(self):
# Check that validation is not performed if valid_values is not set
msg = validators.validate_values(4)
self.assertIsNone(msg)
# Check that value is within valid_values
msg = validators.validate_values(4, [4, 6])
self.assertIsNone(msg)
# Check that value is within valid_values
msg = validators.validate_values(4, (4, 6))
self.assertIsNone(msg)
msg = validators.validate_values(7, [4, 6])
self.assertEqual("'7' is not in [4, 6]", msg)
# Check that value is within valid_values with strings
msg = validators.validate_values("1", ["2", "1", "4", "5"])
self.assertIsNone(msg)
msg = validators.validate_values(7, (4, 6))
self.assertEqual("'7' is not in (4, 6)", msg)
# Check that value is not compatible for comparision
response = "'valid_values' does not support membership operations"
self.assertRaisesRegex(TypeError, response,
validators.validate_values, data=None,
valid_values=True)
def test_validate_values_display(self):
# Check that value is NOT within valid_values and report values
msg = validators.validate_values(7, [4, 6],
valid_values_display="[4, 6]")
self.assertEqual("7 is not in [4, 6]", msg)
# Check that value is NOT within valid_values and report values
msg = validators.validate_values(7, (4, 6),
valid_values_display="(4, 6)")
self.assertEqual("7 is not in (4, 6)", msg)
# Check values with a range function showing a custom string
msg = validators.validate_values(8, range(8),
valid_values_display="[0..7]")
self.assertEqual("8 is not in [0..7]", msg)
# Check that value is not within valid_values and custom string
msg = validators.validate_values(1, [2, 3, 4, 5],
valid_values_display="[2, 3, 4, 5]")
self.assertEqual("1 is not in [2, 3, 4, 5]", msg)
# Check that value is not within valid_values and custom string
msg = validators.validate_values("1", ["2", "3", "4", "5"],
valid_values_display="'valid_values"
"_to_show'")
self.assertEqual("1 is not in 'valid_values_to_show'", msg)
# Check that value is not comparable to valid_values and got Exception
data = 1
valid_values = '[2, 3, 4, 5]'
response = "'data' of type '%s' and 'valid_values'of type" \
" '%s' are not compatible for comparison" % (
type(data), type(valid_values))
self.assertRaisesRegex(TypeError, response,
validators.validate_values, data,
valid_values,
valid_values_display="[2, 3, 4, 5]")
def test_validate_not_empty_string(self):
msg = validators.validate_not_empty_string(' ', None)
@ -203,7 +251,7 @@ class TestAttributeValidation(base.BaseTestCase):
msg = validators.validate_integer(2, [2, 3, 4, 5])
self.assertIsNone(msg)
msg = validators.validate_integer(1, [2, 3, 4, 5])
self.assertEqual("'1' is not in [2, 3, 4, 5]", msg)
self.assertEqual("1 is not in valid_values", msg)
def test_validate_no_whitespace(self):
data = 'no_white_space'