Skip to content

Commit

Permalink
Added Number validation
Browse files Browse the repository at this point in the history
Added test cases for Number validations

1) test cases for while scale is None,  precision is None,  Both are None
2) Beaking down of Invalid conditions

removed extra space

1) Used Decimal and string conversions
2) __get_precision_scale as private

Modification in comment

Removed InvalidOperation from import

Change is class comment

Made some changes in test cases

class and __call__ method description

Fixed class description

Fixed class description

1) Moved Decimal import up
2) Added InvalidOperation exception instead of generic
3) Made __get_precision_scale to internal as _get_precision_scale
4) Added assert for doctests
5) Added case with only invalid precision and no scale

Added assert for doctests

1) Added Yeild flag(decimal/string)
2) Fixed tests naming convention

Fixed exception
  • Loading branch information
nareshnootoo committed Sep 28, 2016
1 parent 6802ce0 commit 5b1624c
Show file tree
Hide file tree
Showing 2 changed files with 160 additions and 2 deletions.
100 changes: 99 additions & 1 deletion voluptuous/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Schema, Required, Extra, Invalid, In, Remove, Literal,
Url, MultipleInvalid, LiteralInvalid, NotIn, Match, Email,
Replace, Range, Coerce, All, Any, Length, FqdnUrl, ALLOW_EXTRA, PREVENT_EXTRA,
validate, ExactSequence, Equal, Unordered
validate, ExactSequence, Equal, Unordered, Number
)
from voluptuous.humanize import humanize_error

Expand Down Expand Up @@ -557,3 +557,101 @@ def fn(arg):
return "hello"

assert_raises(Invalid, fn, 1)


def test_number_validation_with_string():
""" test with Number with string"""
schema = Schema({"number" : Number(precision=6, scale=2)})
try:
schema({"number": 'teststr'})
except MultipleInvalid as e:
assert_equal(str(e),
"Value must be a number enclosed with string for dictionary value @ data['number']")
else:
assert False, "Did not raise Invalid for String"


def test_number_validation_with_invalid_precision_invalid_scale():
""" test with Number with invalid precision and scale"""
schema = Schema({"number" : Number(precision=6, scale=2)})
try:
schema({"number": '123456.712'})
except MultipleInvalid as e:
assert_equal(str(e),
"Precision must be equal to 6, and Scale must be equal to 2 for dictionary value @ data['number']")
else:
assert False, "Did not raise Invalid for String"


def test_number_validation_with_valid_precision_scale():
""" test with Number with valid precision and scale"""
schema = Schema({"number" : Number(precision=6, scale=2, yield_decimal=True)})
out_ = schema({"number": '1234.00'})
assert_equal(float(out_.get("number")), 1234.00)


def test_number_when_precision_scale_none():
""" test with Number with no precision and scale"""
schema = Schema({"number" : Number(yield_decimal=True)})
out_ = schema({"number": '12345678901234'})
assert_equal(out_.get("number"), 12345678901234)


def test_number_when_precision_none_n_valid_scale_case1():
""" test with Number with no precision and valid scale case 1"""
schema = Schema({"number" : Number(scale=2, yield_decimal=True)})
out_ = schema({"number": '123456789.34'})
assert_equal(float(out_.get("number")), 123456789.34)


def test_number_when_precision_none_n_valid_scale_case2():
""" test with Number with no precision and valid scale case 2 with zero in decimal part"""
schema = Schema({"number" : Number(scale=2, yield_decimal=True)})
out_ = schema({"number": '123456789012.00'})
assert_equal(float(out_.get("number")), 123456789012.00)


def test_number_when_precision_none_n_invalid_scale():
""" test with Number with no precision and invalid scale"""
schema = Schema({"number" : Number(scale=2, yield_decimal=True)})
try:
schema({"number": '12345678901.234'})
except MultipleInvalid as e:
assert_equal(str(e),
"Scale must be equal to 2 for dictionary value @ data['number']")
else:
assert False, "Did not raise Invalid for String"


def test_number_when_valid_precision_n_scale_none():
""" test with Number with no precision and valid scale"""
schema = Schema({"number" : Number(precision=14, yield_decimal=True)})
out_ = schema({"number": '1234567.8901234'})
assert_equal(float(out_.get("number")), 1234567.8901234)


def test_number_when_invalid_precision_n_scale_none():
""" test with Number with no precision and invalid scale"""
schema = Schema({"number" : Number(precision=14, yield_decimal=True)})
try:
schema({"number": '12345674.8901234'})
except MultipleInvalid as e:
assert_equal(str(e),
"Precision must be equal to 14 for dictionary value @ data['number']")
else:
assert False, "Did not raise Invalid for String"


def test_number_validation_with_valid_precision_scale_yield_decimal_none():
""" test with Number with valid precision, scale and no yield_decimal"""
schema = Schema({"number" : Number(precision=6, scale=2)})
out_ = schema({"number": '1234.00'})
assert_equal(out_.get("number"), '1234.00')


def test_number_validation_with_valid_precision_scale_yield_decimal_false():
""" test with Number with valid precision, scale and no yield_decimal"""
schema = Schema({"number" : Number(precision=6, scale=2, yield_decimal=False)})
out_ = schema({"number": '1234.00'})
assert_equal(out_.get("number"), '1234.00')

62 changes: 61 additions & 1 deletion voluptuous/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import datetime
import sys
from functools import wraps

from decimal import Decimal, InvalidOperation

try:
from schema_builder import Schema, raises, message
Expand Down Expand Up @@ -794,3 +794,63 @@ def __call__(self, v):

def __repr__(self):
return 'Unordered([{}])'.format(", ".join(repr(v) for v in self.validators))



class Number(object):
"""
Verify the number of digits that are present in the number(Precision),
and the decimal places(Scale)
:raises Invalid: If the value does not match the provided Precision and Scale.
>>> schema = Schema(Number(precision=6, scale=2))
>>> schema('1234.01')
'1234.01'
>>> schema = Schema(Number(precision=6, scale=2, yield_decimal=True))
>>> schema('1234.01')
Decimal('1234.01')
"""

def __init__(self, precision=None, scale=None, msg=None, yield_decimal=False):
self.precision = precision
self.scale = scale
self.msg = msg
self.yield_decimal = yield_decimal

def __call__(self, v):
"""
:param v: is a number enclosed with string
:return: Decimal number
"""
precision, scale, decimal_num = self._get_precision_scale(v)

if self.precision is not None and self.scale is not None and\
precision != self.precision and scale != self.scale:
raise Invalid(self.msg or "Precision must be equal to %s, and Scale must be equal to %s" %(self.precision, self.scale))
else:
if self.precision is not None and precision != self.precision:
raise Invalid(self.msg or "Precision must be equal to %s"%self.precision)

if self.scale is not None and scale != self.scale :
raise Invalid(self.msg or "Scale must be equal to %s"%self.scale)

if self.yield_decimal:
return decimal_num
else:
return v

def __repr__(self):
return ('Number(precision=%s, scale=%s, msg=%s)' % (self.precision, self.scale, self.msg))

def _get_precision_scale(self, number):
"""
:param number:
:return: tuple(precision, scale, decimal_number)
"""
try:
decimal_num = Decimal(number)
except InvalidOperation:
raise Invalid(self.msg or 'Value must be a number enclosed with string')

return (len(decimal_num.as_tuple().digits), -(decimal_num.as_tuple().exponent), decimal_num)

0 comments on commit 5b1624c

Please # to comment.