tighten up validate_value logic and allow string promotion to integers

This commit is contained in:
Doug Hellmann 2012-12-05 10:51:50 -05:00
parent 15a239350e
commit aedf967cf5
2 changed files with 58 additions and 36 deletions

View File

@ -249,7 +249,7 @@ class TestTypes(unittest.TestCase):
assert value.a is types.Unset assert value.a is types.Unset
def test_validate_dict(self): def test_validate_dict(self):
types.validate_value({int: str}, {1: '1', 5: '5'}) assert types.validate_value({int: str}, {1: '1', 5: '5'})
try: try:
types.validate_value({int: str}, []) types.validate_value({int: str}, [])
@ -257,11 +257,7 @@ class TestTypes(unittest.TestCase):
except ValueError: except ValueError:
pass pass
try: assert types.validate_value({int: str}, {'1': '1', 5: '5'})
types.validate_value({int: str}, {'1': '1', 5: '5'})
assert False, "No ValueError raised"
except ValueError:
pass
try: try:
types.validate_value({int: str}, {1: 1, 5: '5'}) types.validate_value({int: str}, {1: 1, 5: '5'})
@ -273,6 +269,21 @@ class TestTypes(unittest.TestCase):
self.assertEqual(types.validate_value(float, 1), 1.0) self.assertEqual(types.validate_value(float, 1), 1.0)
self.assertEqual(types.validate_value(float, '1'), 1.0) self.assertEqual(types.validate_value(float, '1'), 1.0)
self.assertEqual(types.validate_value(float, 1.1), 1.1) self.assertEqual(types.validate_value(float, 1.1), 1.1)
try:
types.validate_value(float, [])
assert False, "No ValueError raised"
except ValueError:
pass
def test_validate_int(self):
self.assertEqual(types.validate_value(int, 1), 1)
self.assertEqual(types.validate_value(int, '1'), 1)
self.assertEqual(types.validate_value(int, six.u('1')), 1)
try:
types.validate_value(int, 1.1)
assert False, "No ValueError raised"
except ValueError:
pass
def test_register_invalid_array(self): def test_register_invalid_array(self):
self.assertRaises(ValueError, types.register_type, []) self.assertRaises(ValueError, types.register_type, [])

View File

@ -178,6 +178,8 @@ pod_types = six.integer_types + (
dt_types = (datetime.date, datetime.time, datetime.datetime) dt_types = (datetime.date, datetime.time, datetime.datetime)
extra_types = (binary, decimal.Decimal) extra_types = (binary, decimal.Decimal)
native_types = pod_types + dt_types + extra_types native_types = pod_types + dt_types + extra_types
# The types for which we allow promotion to certain numbers.
_promotable_types = six.integer_types + (text, bytes)
def iscomplex(datatype): def iscomplex(datatype):
@ -194,38 +196,47 @@ def isdict(datatype):
def validate_value(datatype, value): def validate_value(datatype, value):
if hasattr(datatype, 'validate'):
return datatype.validate(value)
else:
if value in (Unset, None): if value in (Unset, None):
return value return value
# Try to promote the data type to one of our complex types.
if isinstance(datatype, list): if isinstance(datatype, list):
datatype = ArrayType(datatype[0]) datatype = ArrayType(datatype[0])
if isinstance(datatype, dict): elif isinstance(datatype, dict):
datatype = DictType(*list(datatype.items())[0]) datatype = DictType(*list(datatype.items())[0])
if isarray(datatype):
datatype.validate(value) # If the datatype has its own validator, use that.
elif isdict(datatype): if hasattr(datatype, 'validate'):
datatype.validate(value) return datatype.validate(value)
elif datatype in six.integer_types:
if not isinstance(value, six.integer_types): # Do type promotion/conversion and data validation for builtin
raise ValueError( # types.
"Wrong type. Expected an integer, got '%s'" % ( v_type = type(value)
type(value) if datatype in six.integer_types:
)) if v_type in _promotable_types:
try:
# Try to turn the value into an int
value = datatype(value)
except ValueError:
# An error is raised at the end of the function
# when the types don't match.
pass
elif datatype is float and v_type in _promotable_types:
try:
value = float(value)
except ValueError:
# An error is raised at the end of the function
# when the types don't match.
pass
elif datatype is text and isinstance(value, bytes): elif datatype is text and isinstance(value, bytes):
value = value.decode() value = value.decode()
elif datatype is bytes and isinstance(value, text): elif datatype is bytes and isinstance(value, text):
value = value.encode() value = value.encode()
elif datatype is float and (isinstance(value, int)
or isinstance(value, text) if not isinstance(value, datatype):
or isinstance(value, bytes)):
value = float(value)
elif not isinstance(value, datatype):
raise ValueError( raise ValueError(
"Wrong type. Expected '%s', got '%s'" % ( "Wrong type. Expected '%s', got '%s'" % (
datatype, type(value) datatype, v_type
)) ))
return value return value