diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 41ed3eecb..23b2ba6b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -403,9 +403,3 @@ jobs: files: | wasm/*.whl prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') }} - - - name: upload wasm to pypi - run: twine upload wasm/* - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.pypi_token }} diff --git a/Cargo.lock b/Cargo.lock index 4b41fe63f..fd4904bd9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -199,7 +199,7 @@ dependencies = [ [[package]] name = "pydantic-core" -version = "0.4.0" +version = "0.4.1" dependencies = [ "ahash", "enum_dispatch", diff --git a/Cargo.toml b/Cargo.toml index 240e09e7c..47e395eb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pydantic-core" -version = "0.4.0" +version = "0.4.1" edition = "2021" license = "MIT" homepage = "https://github.com/pydantic/pydantic-core" diff --git a/src/validators/float.rs b/src/validators/float.rs index 1d1208e21..c145cd787 100644 --- a/src/validators/float.rs +++ b/src/validators/float.rs @@ -87,7 +87,9 @@ impl Validator for ConstrainedFloatValidator { return Err(ValError::new(ErrorKind::FiniteNumber, input)); } if let Some(multiple_of) = self.multiple_of { - if float % multiple_of != 0.0 { + let rem = float % multiple_of; + let threshold = float / 1e9; + if rem.abs() > threshold && (rem - multiple_of).abs() > threshold { return Err(ValError::new( ErrorKind::MultipleOf { multiple_of: multiple_of.into(), diff --git a/tests/validators/test_float.py b/tests/validators/test_float.py index 8de986717..d02026970 100644 --- a/tests/validators/test_float.py +++ b/tests/validators/test_float.py @@ -84,26 +84,53 @@ def test_float_strict(py_and_json: PyAndJson, input_value, expected): ({'le': 0}, 0.1, Err('Input should be less than or equal to 0')), ({'lt': 0}, 0, Err('Input should be less than 0')), ({'lt': 0.123456}, 1, Err('Input should be less than 0.123456')), - ({'multiple_of': 0.5}, 0.5, 0.5), - ({'multiple_of': 0.5}, 1, 1), - ({'multiple_of': 0.5}, 0.6, Err('Input should be a multiple of 0.5')), ], ) def test_float_kwargs(py_and_json: PyAndJson, kwargs: Dict[str, Any], input_value, expected): v = py_and_json({'type': 'float', **kwargs}) if isinstance(expected, Err): - with pytest.raises(ValidationError, match=re.escape(expected.message)) as exc_info: + with pytest.raises(ValidationError, match=re.escape(expected.message)): v.validate_test(input_value) - errors = exc_info.value.errors() - assert len(errors) == 1 - if 'context' in errors[0]: - assert errors[0]['context'] == kwargs else: output = v.validate_test(input_value) assert output == expected assert isinstance(output, float) +@pytest.mark.parametrize( + 'multiple_of,input_value,error', + [ + (0.5, 0.5, None), + (0.5, 1, None), + (0.5, 0.6, Err('Input should be a multiple of 0.5')), + (0.5, 0.51, Err('Input should be a multiple of 0.5')), + (0.5, 0.501, Err('Input should be a multiple of 0.5')), + (0.5, 1_000_000.5, None), + (0.5, 1_000_000.49, Err('Input should be a multiple of 0.5')), + (0.1, 0, None), + (0.1, 0.0, None), + (0.1, 0.2, None), + (0.1, 0.3, None), + (0.1, 0.4, None), + (0.1, 0.5, None), + (0.1, 0.5001, Err('Input should be a multiple of 0.1')), + (0.1, 1, None), + (0.1, 1.0, None), + (0.1, int(5e10), None), + ], + ids=repr, +) +def test_float_multiple_of(py_and_json: PyAndJson, multiple_of, input_value, error): + v = py_and_json({'type': 'float', 'multiple_of': multiple_of}) + if error: + with pytest.raises(ValidationError, match=re.escape(error.message)): + v.validate_test(input_value) + else: + output = v.validate_test(input_value) + assert output == input_value + assert isinstance(output, float) + + def test_union_float(py_and_json: PyAndJson): v = py_and_json( {'type': 'union', 'choices': [{'type': 'float', 'strict': True}, {'type': 'float', 'multiple_of': 7}]}