Skip to content

Commit

Permalink
avoid coercing int subclasses to floats (#914)
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelcolvin authored Aug 23, 2023
1 parent 4a1ef3a commit 815fd92
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 2 deletions.
9 changes: 8 additions & 1 deletion src/input/input_python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,9 @@ impl<'a> Input<'a> for PyAny {
if PyBool::is_exact_type_of(self) {
Err(ValError::new(ErrorTypeDefaults::IntType, self))
} else {
Ok(EitherInt::Py(self))
// force to an int to upcast to a pure python int
let int = self.extract::<i64>()?;
Ok(EitherInt::I64(int))
}
} else {
Err(ValError::new(ErrorTypeDefaults::IntType, self))
Expand All @@ -314,7 +316,12 @@ impl<'a> Input<'a> for PyAny {
if PyInt::is_exact_type_of(self) {
Ok(EitherInt::Py(self))
} else if let Some(cow_str) = maybe_as_string(self, ErrorTypeDefaults::IntParsing)? {
// Try strings before subclasses of int as that will be far more common
str_as_int(self, &cow_str)
} else if PyInt::is_type_of(self) {
// force to an int to upcast to a pure python int to maintain current behaviour
let int = self.extract::<i64>()?;
Ok(EitherInt::I64(int))
} else if let Ok(float) = self.extract::<f64>() {
float_as_int(self, float)
} else {
Expand Down
32 changes: 31 additions & 1 deletion tests/validators/test_int.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def test_int_py_and_json(py_and_json: PyAndJson, input_value, expected):
else:
output = v.validate_test(input_value)
assert output == expected
assert isinstance(output, int)
assert type(output) == int


@pytest.mark.parametrize(
Expand Down Expand Up @@ -405,3 +405,33 @@ def test_string_as_int_with_underscores() -> None:
v.validate_python(edge_case)
with pytest.raises(ValidationError):
v.validate_json(f'"{edge_case}"')


class IntSubclass(int):
pass


def test_int_subclass() -> None:
v = SchemaValidator({'type': 'int'})
v_lax = v.validate_python(IntSubclass(1))
assert v_lax == 1
assert type(v_lax) == int
v_strict = v.validate_python(IntSubclass(1), strict=True)
assert v_strict == 1
assert type(v_strict) == int

assert v.validate_python(IntSubclass(1136885225876639845)) == 1136885225876639845
assert v.validate_python(IntSubclass(1136885225876639845), strict=True) == 1136885225876639845


def test_int_subclass_constraint() -> None:
v = SchemaValidator({'type': 'int', 'gt': 0})
v_lax = v.validate_python(IntSubclass(1))
assert v_lax == 1
assert type(v_lax) == int
v_strict = v.validate_python(IntSubclass(1), strict=True)
assert v_strict == 1
assert type(v_strict) == int

with pytest.raises(ValidationError, match='Input should be greater than 0'):
v.validate_python(IntSubclass(0))

0 comments on commit 815fd92

Please sign in to comment.