From 9cb7ca744b9b4289fc0cd262ff6062391fe65cfd Mon Sep 17 00:00:00 2001 From: Matthew Davis <7035647+mdavis-xyz@users.noreply.github.com> Date: Mon, 30 Nov 2020 03:07:51 +1100 Subject: [PATCH] add NonPositive/NonNegative Int/Float (#1987) fix #1975 * add NonPositive/NonNegative Int/Float * delete data.json file --- changes/1975-mdavis-xyz.md | 1 + docs/build/schema_mapping.py | 28 +++++++++++++ docs/examples/types_constrained.py | 8 ++++ pydantic/__init__.py | 4 ++ pydantic/types.py | 20 ++++++++++ tests/test_schema.py | 8 ++++ tests/test_types.py | 64 ++++++++++++++++++++++-------- 7 files changed, 117 insertions(+), 16 deletions(-) create mode 100644 changes/1975-mdavis-xyz.md diff --git a/changes/1975-mdavis-xyz.md b/changes/1975-mdavis-xyz.md new file mode 100644 index 0000000..82cf2eb --- /dev/null +++ b/changes/1975-mdavis-xyz.md @@ -0,0 +1 @@ +Add `NonNegativeInt`, `NonPositiveInt`, `NonNegativeFloat`, `NonPositiveFloat` diff --git a/docs/build/schema_mapping.py b/docs/build/schema_mapping.py index f9731c0..5781727 100755 --- a/docs/build/schema_mapping.py +++ b/docs/build/schema_mapping.py @@ -382,6 +382,20 @@ table = [ 'JSON Schema Validation', '' ], + [ + 'NonNegativeInt', + 'integer', + {'minimum': 0}, + 'JSON Schema Validation', + '' + ], + [ + 'NonPositiveInt', + 'integer', + {'maximum': 0}, + 'JSON Schema Validation', + '' + ], [ 'ConstrainedFloat', 'number', @@ -413,6 +427,20 @@ table = [ 'JSON Schema Validation', '' ], + [ + 'NonNegativeFloat', + 'number', + {'minimum': 0}, + 'JSON Schema Validation', + '' + ], + [ + 'NonPositiveFloat', + 'number', + {'maximum': 0}, + 'JSON Schema Validation', + '' + ], [ 'ConstrainedDecimal', 'number', diff --git a/docs/examples/types_constrained.py b/docs/examples/types_constrained.py index c97fe24..53adddd 100644 --- a/docs/examples/types_constrained.py +++ b/docs/examples/types_constrained.py @@ -6,6 +6,10 @@ from pydantic import ( NegativeInt, PositiveFloat, PositiveInt, + NonNegativeFloat, + NonNegativeInt, + NonPositiveFloat, + NonPositiveInt, conbytes, condecimal, confloat, @@ -29,12 +33,16 @@ class Model(BaseModel): mod_int: conint(multiple_of=5) pos_int: PositiveInt neg_int: NegativeInt + non_neg_int: NonNegativeInt + non_pos_int: NonPositiveInt big_float: confloat(gt=1000, lt=1024) unit_interval: confloat(ge=0, le=1) mod_float: confloat(multiple_of=0.5) pos_float: PositiveFloat neg_float: NegativeFloat + non_neg_float: NonNegativeFloat + non_pos_float: NonPositiveFloat short_list: conlist(int, min_items=1, max_items=4) short_set: conset(int, min_items=1, max_items=4) diff --git a/pydantic/__init__.py b/pydantic/__init__.py index 5cfc058..7b8a609 100644 --- a/pydantic/__init__.py +++ b/pydantic/__init__.py @@ -76,10 +76,14 @@ __all__ = [ 'conint', 'PositiveInt', 'NegativeInt', + 'NonNegativeInt', + 'NonPositiveInt', 'ConstrainedFloat', 'confloat', 'PositiveFloat', 'NegativeFloat', + 'NonNegativeFloat', + 'NonPositiveFloat', 'ConstrainedDecimal', 'condecimal', 'UUID1', diff --git a/pydantic/types.py b/pydantic/types.py index 9c462e1..9c3279b 100644 --- a/pydantic/types.py +++ b/pydantic/types.py @@ -63,10 +63,14 @@ __all__ = [ 'conint', 'PositiveInt', 'NegativeInt', + 'NonNegativeInt', + 'NonPositiveInt', 'ConstrainedFloat', 'confloat', 'PositiveFloat', 'NegativeFloat', + 'NonNegativeFloat', + 'NonPositiveFloat', 'ConstrainedDecimal', 'condecimal', 'UUID1', @@ -379,6 +383,14 @@ class NegativeInt(ConstrainedInt): lt = 0 +class NonPositiveInt(ConstrainedInt): + le = 0 + + +class NonNegativeInt(ConstrainedInt): + ge = 0 + + class StrictInt(ConstrainedInt): strict = True @@ -440,6 +452,14 @@ class NegativeFloat(ConstrainedFloat): lt = 0 +class NonPositiveFloat(ConstrainedFloat): + le = 0 + + +class NonNegativeFloat(ConstrainedFloat): + ge = 0 + + class StrictFloat(ConstrainedFloat): strict = True diff --git a/tests/test_schema.py b/tests/test_schema.py index 9da7d14..13d9258 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -42,6 +42,10 @@ from pydantic.types import ( NoneBytes, NoneStr, NoneStrBytes, + NonNegativeFloat, + NonNegativeInt, + NonPositiveFloat, + NonPositiveInt, PositiveFloat, PositiveInt, PyObject, @@ -754,6 +758,8 @@ def test_secret_types(field_type, inner_type): (conint(multiple_of=5), {'multipleOf': 5}), (PositiveInt, {'exclusiveMinimum': 0}), (NegativeInt, {'exclusiveMaximum': 0}), + (NonNegativeInt, {'minimum': 0}), + (NonPositiveInt, {'maximum': 0}), ], ) def test_special_int_types(field_type, expected_schema): @@ -780,6 +786,8 @@ def test_special_int_types(field_type, expected_schema): (confloat(multiple_of=5), {'multipleOf': 5}), (PositiveFloat, {'exclusiveMinimum': 0}), (NegativeFloat, {'exclusiveMaximum': 0}), + (NonNegativeFloat, {'minimum': 0}), + (NonPositiveFloat, {'maximum': 0}), (ConstrainedDecimal, {}), (condecimal(gt=5, lt=10), {'exclusiveMinimum': 5, 'exclusiveMaximum': 10}), (condecimal(ge=5, le=10), {'minimum': 5, 'maximum': 10}), diff --git a/tests/test_types.py b/tests/test_types.py index 7e4c7e0..b07d140 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -43,6 +43,10 @@ from pydantic import ( NameEmail, NegativeFloat, NegativeInt, + NonNegativeFloat, + NonNegativeInt, + NonPositiveFloat, + NonPositiveInt, PositiveFloat, PositiveInt, PyObject, @@ -1178,15 +1182,17 @@ def test_int_validation(): class Model(BaseModel): a: PositiveInt = None b: NegativeInt = None - c: conint(gt=4, lt=10) = None - d: conint(ge=0, le=10) = None - e: conint(multiple_of=5) = None + c: NonNegativeInt = None + d: NonPositiveInt = None + e: conint(gt=4, lt=10) = None + f: conint(ge=0, le=10) = None + g: conint(multiple_of=5) = None - m = Model(a=5, b=-5, c=5, d=0, e=25) - assert m == {'a': 5, 'b': -5, 'c': 5, 'd': 0, 'e': 25} + m = Model(a=5, b=-5, c=0, d=0, e=5, f=0, g=25) + assert m == {'a': 5, 'b': -5, 'c': 0, 'd': 0, 'e': 5, 'f': 0, 'g': 25} with pytest.raises(ValidationError) as exc_info: - Model(a=-5, b=5, c=-5, d=11, e=42) + Model(a=-5, b=5, c=-5, d=5, e=-5, f=11, g=42) assert exc_info.value.errors() == [ { 'loc': ('a',), @@ -1202,18 +1208,30 @@ def test_int_validation(): }, { 'loc': ('c',), + 'msg': 'ensure this value is greater than or equal to 0', + 'type': 'value_error.number.not_ge', + 'ctx': {'limit_value': 0}, + }, + { + 'loc': ('d',), + 'msg': 'ensure this value is less than or equal to 0', + 'type': 'value_error.number.not_le', + 'ctx': {'limit_value': 0}, + }, + { + 'loc': ('e',), 'msg': 'ensure this value is greater than 4', 'type': 'value_error.number.not_gt', 'ctx': {'limit_value': 4}, }, { - 'loc': ('d',), + 'loc': ('f',), 'msg': 'ensure this value is less than or equal to 10', 'type': 'value_error.number.not_le', 'ctx': {'limit_value': 10}, }, { - 'loc': ('e',), + 'loc': ('g',), 'msg': 'ensure this value is a multiple of 5', 'type': 'value_error.number.not_multiple', 'ctx': {'multiple_of': 5}, @@ -1225,15 +1243,17 @@ def test_float_validation(): class Model(BaseModel): a: PositiveFloat = None b: NegativeFloat = None - c: confloat(gt=4, lt=12.2) = None - d: confloat(ge=0, le=9.9) = None - e: confloat(multiple_of=0.5) = None + c: NonNegativeFloat = None + d: NonPositiveFloat = None + e: confloat(gt=4, lt=12.2) = None + f: confloat(ge=0, le=9.9) = None + g: confloat(multiple_of=0.5) = None - m = Model(a=5.1, b=-5.2, c=5.3, d=9.9, e=2.5) - assert m.dict() == {'a': 5.1, 'b': -5.2, 'c': 5.3, 'd': 9.9, 'e': 2.5} + m = Model(a=5.1, b=-5.2, c=0, d=0, e=5.3, f=9.9, g=2.5) + assert m.dict() == {'a': 5.1, 'b': -5.2, 'c': 0, 'd': 0, 'e': 5.3, 'f': 9.9, 'g': 2.5} with pytest.raises(ValidationError) as exc_info: - Model(a=-5.1, b=5.2, c=-5.3, d=9.91, e=4.2) + Model(a=-5.1, b=5.2, c=-5.1, d=5.1, e=-5.3, f=9.91, g=4.2) assert exc_info.value.errors() == [ { 'loc': ('a',), @@ -1249,18 +1269,30 @@ def test_float_validation(): }, { 'loc': ('c',), + 'msg': 'ensure this value is greater than or equal to 0', + 'type': 'value_error.number.not_ge', + 'ctx': {'limit_value': 0}, + }, + { + 'loc': ('d',), + 'msg': 'ensure this value is less than or equal to 0', + 'type': 'value_error.number.not_le', + 'ctx': {'limit_value': 0}, + }, + { + 'loc': ('e',), 'msg': 'ensure this value is greater than 4', 'type': 'value_error.number.not_gt', 'ctx': {'limit_value': 4}, }, { - 'loc': ('d',), + 'loc': ('f',), 'msg': 'ensure this value is less than or equal to 9.9', 'type': 'value_error.number.not_le', 'ctx': {'limit_value': 9.9}, }, { - 'loc': ('e',), + 'loc': ('g',), 'msg': 'ensure this value is a multiple of 0.5', 'type': 'value_error.number.not_multiple', 'ctx': {'multiple_of': 0.5},