mirror of
https://github.com/kennethreitz/pydantic.git
synced 2026-06-05 23:00:18 +00:00
Add better support for floating point multiple_of values (#652)
- modulo doesn't work with floating point values in many cases, e.g. `0.3 % 0.1 == 0.09999999999999998` - port implementation from: https://github.com/tdegrunt/jsonschema/issues/187#issuecomment-320664251 - add tests for int/float multiple_of values - update history with pr/author
This commit is contained in:
committed by
Samuel Colvin
parent
4c9ee486d8
commit
bc60014518
@@ -5,6 +5,7 @@ History
|
||||
|
||||
v0.31 (unreleased)
|
||||
..................
|
||||
* better support for floating point `multiple_of` values, #652 by @justindujardin
|
||||
* fix schema generation for ``NewType`` and ``Literal``, #649 by @dmontagu
|
||||
* add documentation for Literal type, #651 by @dmontagu
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ from .utils import (
|
||||
AnyCallable,
|
||||
AnyType,
|
||||
ForwardRef,
|
||||
almost_equal_floats,
|
||||
change_exception,
|
||||
display_as_type,
|
||||
is_callable_type,
|
||||
@@ -121,9 +122,10 @@ def float_validator(v: Any) -> float:
|
||||
|
||||
def number_multiple_validator(v: 'Number', field: 'Field') -> 'Number':
|
||||
field_type: ConstrainedNumber = field.type_ # type: ignore
|
||||
if field_type.multiple_of is not None and v % field_type.multiple_of != 0: # type: ignore
|
||||
raise errors.NumberNotMultipleError(multiple_of=field_type.multiple_of)
|
||||
|
||||
if field_type.multiple_of is not None:
|
||||
mod = float(v) / float(field_type.multiple_of) % 1
|
||||
if not almost_equal_floats(mod, 0.0) and not almost_equal_floats(mod, 1.0):
|
||||
raise errors.NumberNotMultipleError(multiple_of=field_type.multiple_of)
|
||||
return v
|
||||
|
||||
|
||||
|
||||
+29
-3
@@ -1422,16 +1422,42 @@ def test_number_le():
|
||||
Model(a=6)
|
||||
|
||||
|
||||
def test_number_multiple_of():
|
||||
@pytest.mark.parametrize('value', ((10), (100), (20)))
|
||||
def test_number_multiple_of_int_valid(value):
|
||||
class Model(BaseModel):
|
||||
a: conint(multiple_of=5)
|
||||
|
||||
assert Model(a=10).dict() == {'a': 10}
|
||||
assert Model(a=value).dict() == {'a': value}
|
||||
|
||||
|
||||
@pytest.mark.parametrize('value', ((1337), (23), (6), (14)))
|
||||
def test_number_multiple_of_int_invalid(value):
|
||||
class Model(BaseModel):
|
||||
a: conint(multiple_of=5)
|
||||
|
||||
multiple_message = base_message.replace('limit_value', 'multiple_of')
|
||||
message = multiple_message.format(msg='a multiple of 5', ty='multiple', value=5)
|
||||
with pytest.raises(ValidationError, match=message):
|
||||
Model(a=42)
|
||||
Model(a=value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('value', ((0.2), (0.3), (0.4), (0.5), (1)))
|
||||
def test_number_multiple_of_float_valid(value):
|
||||
class Model(BaseModel):
|
||||
a: confloat(multiple_of=0.1)
|
||||
|
||||
assert Model(a=value).dict() == {'a': value}
|
||||
|
||||
|
||||
@pytest.mark.parametrize('value', ((0.07), (1.27), (1.003)))
|
||||
def test_number_multiple_of_float_invalid(value):
|
||||
class Model(BaseModel):
|
||||
a: confloat(multiple_of=0.1)
|
||||
|
||||
multiple_message = base_message.replace('limit_value', 'multiple_of')
|
||||
message = multiple_message.format(msg='a multiple of 0.1', ty='multiple', value=0.1)
|
||||
with pytest.raises(ValidationError, match=message):
|
||||
Model(a=value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('fn', [conint, confloat, condecimal])
|
||||
|
||||
Reference in New Issue
Block a user