Skip to content

Commit

Permalink
Enhance valid_values to use __contains__
Browse files Browse the repository at this point in the history
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
  • Loading branch information
iranzo committed Oct 11, 2016
1 parent 6cf9c87 commit abeb5c4
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 17 deletions.
55 changes: 43 additions & 12 deletions neutron_lib/api/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 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

: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.
"""
if data not in valid_values:
msg = (_("'%(data)s' is not in %(valid_values)s") %
{'data': data, 'valid_values': valid_values})
LOG.debug(msg)
return msg
# 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:
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):
Expand Down
58 changes: 53 additions & 5 deletions neutron_lib/tests/unit/api/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,65 @@ def test_is_attr_set(self):
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)
Expand Down Expand Up @@ -203,7 +251,7 @@ def test_validate_integer_values(self):
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'
Expand Down

0 comments on commit abeb5c4

Please sign in to comment.