Skip to content

Commit ca38362

Browse files
committed
feat: use eitherfloat
1 parent 8fc0231 commit ca38362

File tree

9 files changed

+78
-78
lines changed

9 files changed

+78
-78
lines changed

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ url = "2.3.1"
4141
idna = "0.3.0"
4242
base64 = "0.13.1"
4343
num-bigint = "0.4.3"
44-
num-traits = "0.2.15"
4544

4645
[lib]
4746
name = "_pydantic_core"

Makefile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ install-rust-coverage:
1818
.PHONY: build-dev
1919
build-dev:
2020
@rm -f pydantic_core/*.so
21-
cargo build --target=aarch64-apple-darwin --features extension-module
22-
@rm -f target/aarch64-apple-darwin/debug/lib_pydantic_core.d
23-
@rm -f target/aarch64-apple-darwin/debug/lib_pydantic_core.rlib
24-
@mv target/aarch64-apple-darwin/debug/lib_pydantic_core.* pydantic_core/_pydantic_core.so
21+
cargo build --features extension-module
22+
@rm -f target/debug/lib_pydantic_core.d
23+
@rm -f target/debug/lib_pydantic_core.rlib
24+
@mv target/debug/lib_pydantic_core.* pydantic_core/_pydantic_core.so
2525

2626
.PHONY: build-prod
2727
build-prod:

src/input/input_json.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ impl<'a> Input<'a> for JsonInput {
142142
}
143143
}
144144
fn strict_float(&'a self) -> ValResult<EitherFloat<'a>> {
145-
dbg!("strict float python here");
146145
match self {
147146
JsonInput::Float(f) => Ok(EitherFloat::F64(*f)),
148147
JsonInput::Int(i) => Ok(EitherFloat::F64(*i as f64)),
@@ -151,7 +150,6 @@ impl<'a> Input<'a> for JsonInput {
151150
}
152151
}
153152
fn lax_float(&'a self) -> ValResult<EitherFloat<'a>> {
154-
dbg!("lax float python here");
155153
match self {
156154
JsonInput::Bool(b) => match *b {
157155
true => Ok(EitherFloat::F64(1.0)),
@@ -383,7 +381,7 @@ impl<'a> Input<'a> for String {
383381
}
384382
fn lax_float(&'a self) -> ValResult<EitherFloat<'a>> {
385383
match self.parse() {
386-
Ok(i) => Ok(EitherFloat::F64(i)),
384+
Ok(f) => Ok(EitherFloat::F64(f)),
387385
Err(_) => Err(ValError::new(ErrorType::FloatParsing, self)),
388386
}
389387
}

src/input/input_python.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -305,28 +305,32 @@ impl<'a> Input<'a> for PyAny {
305305
}
306306
}
307307
fn strict_float(&'a self) -> ValResult<EitherFloat<'a>> {
308-
if let Ok(float) = self.extract::<f64>() {
308+
if PyFloat::is_exact_type_of(self) {
309+
Ok(EitherFloat::Py(self))
310+
} else if let Ok(float) = self.extract::<f64>() {
309311
// bools are cast to floats as either 0.0 or 1.0, so check for bool type in this specific case
310312
if (float == 0.0 || float == 1.0) && PyBool::is_exact_type_of(self) {
311313
Err(ValError::new(ErrorType::FloatType, self))
312314
} else {
313-
Ok(EitherFloat::F64(float))
315+
Ok(EitherFloat::Py(self))
314316
}
315317
} else {
316318
Err(ValError::new(ErrorType::FloatType, self))
317319
}
318320
}
319321

320322
fn lax_float(&'a self) -> ValResult<EitherFloat<'a>> {
321-
if let Ok(float) = self.extract::<f64>() {
322-
Ok(EitherFloat::F64(float))
323-
} else if let Some(cow_str) = maybe_as_string(self, ErrorType::FloatParsing)? {
323+
if PyFloat::is_exact_type_of(self) {
324+
Ok(EitherFloat::Py(self))
325+
} else if let Some(cow_str) = maybe_as_string(self, ErrorType::FloatParsing)? {
324326
match cow_str.as_ref().parse::<f64>() {
325327
Ok(i) => Ok(EitherFloat::F64(i)),
326328
Err(_) => Err(ValError::new(ErrorType::FloatParsing, self)),
327329
}
328-
} else {
329-
Err(ValError::new(ErrorType::FloatType, self))
330+
} else if let Ok(float) = self.extract::<f64>() {
331+
Ok(EitherFloat::F64(float))
332+
} else {
333+
Err(ValError::new(ErrorType::FloatType, self))
330334
}
331335
}
332336

src/input/return_enums.rs

Lines changed: 4 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use std::borrow::Cow;
22
use std::slice::Iter as SliceIter;
33

44
use num_bigint::BigInt;
5-
use num_traits::{FromPrimitive, ToPrimitive};
65

76
use pyo3::prelude::*;
87
use pyo3::types::iter::PyDictIterator;
@@ -897,58 +896,24 @@ impl<'a> IntoPy<PyObject> for EitherInt<'a> {
897896
#[cfg_attr(debug_assertions, derive(Debug))]
898897
pub enum EitherFloat<'a> {
899898
F64(f64),
900-
BigInt(BigInt),
901899
Py(&'a PyAny),
902900
}
903901

904-
impl<'a> EitherFloat<'a> {
905-
pub fn into_f64(self, py: Python<'a>) -> ValResult<'a, f64> {
902+
impl<'a> TryInto<f64> for EitherFloat<'a> {
903+
type Error = ValError<'a>;
904+
905+
fn try_into(self) -> ValResult<'a, f64> {
906906
match self {
907907
EitherFloat::F64(f) => Ok(f),
908-
EitherFloat::BigInt(f) => f
909-
.to_f64()
910-
.ok_or(ValError::new(ErrorType::FloatParsingSize, f.into_py(py).into_ref(py))),
911908
EitherFloat::Py(i) => i.extract().map_err(|_| ValError::new(ErrorType::FloatParsingSize, i)),
912909
}
913910
}
914-
915-
pub fn as_bool(&self) -> Option<bool> {
916-
match self {
917-
EitherFloat::F64(f) => match *f {
918-
x if x == 0.0 => Some(false),
919-
x if x == 1.0 => Some(true),
920-
_ => None,
921-
},
922-
EitherFloat::BigInt(f) => match u8::try_from(f) {
923-
Ok(0) => Some(false),
924-
Ok(1) => Some(true),
925-
_ => None,
926-
},
927-
EitherFloat::Py(f) => match f.extract::<u8>() {
928-
Ok(0) => Some(false),
929-
Ok(1) => Some(true),
930-
_ => None,
931-
},
932-
}
933-
}
934-
935-
pub fn as_bigint(&self) -> PyResult<Option<BigInt>> {
936-
match self {
937-
EitherFloat::F64(f) => {
938-
let float = BigInt::from_f64(*f);
939-
Ok(float)
940-
}
941-
EitherFloat::BigInt(f) => Ok(Some(f.clone())),
942-
EitherFloat::Py(f) => f.extract(),
943-
}
944-
}
945911
}
946912

947913
impl<'a> IntoPy<PyObject> for EitherFloat<'a> {
948914
fn into_py(self, py: Python<'_>) -> PyObject {
949915
match self {
950916
Self::F64(float) => float.into_py(py),
951-
Self::BigInt(float) => float.into_py(py),
952917
Self::Py(float) => float.into_py(py),
953918
}
954919
}

src/input/shared.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use num_bigint::BigInt;
33
use crate::errors::{ErrorType, ValError, ValResult};
44
use crate::input::EitherInt;
55

6-
use super::{EitherFloat, Input};
6+
use super::Input;
77

88
pub fn map_json_err<'a>(input: &'a impl Input<'a>, error: serde_json::Error) -> ValError<'a> {
99
ValError::new(

src/validators/float.rs

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
1+
use pyo3::intern;
2+
use pyo3::prelude::*;
3+
use pyo3::types::PyDict;
4+
15
use crate::build_tools::{is_strict, schema_or_config_same};
26
use crate::errors::{ErrorType, ValError, ValResult};
37
use crate::input::Input;
48
use crate::recursion_guard::RecursionGuard;
59
use crate::tools::SchemaDict;
6-
use num_bigint::BigInt;
7-
use num_bigint::ToBigInt;
8-
use num_traits::FromPrimitive;
9-
use num_traits::Signed;
10-
use num_traits::ToPrimitive;
11-
use num_traits::Zero;
12-
use pyo3::intern;
13-
use pyo3::prelude::*;
14-
use pyo3::types::PyDict;
1510

1611
use super::{BuildValidator, CombinedValidator, Definitions, DefinitionsBuilder, Extra, Validator};
1712

@@ -24,7 +19,6 @@ impl BuildValidator for FloatBuilder {
2419
config: Option<&PyDict>,
2520
definitions: &mut DefinitionsBuilder<CombinedValidator>,
2621
) -> PyResult<CombinedValidator> {
27-
dbg!("build validator {:?}", schema);
2822
let py = schema.py();
2923
let use_constrained = schema.get_item(intern!(py, "multiple_of")).is_some()
3024
|| schema.get_item(intern!(py, "le")).is_some()
@@ -75,10 +69,9 @@ impl Validator for FloatValidator {
7569
_definitions: &'data Definitions<CombinedValidator>,
7670
_recursion_guard: &'s mut RecursionGuard,
7771
) -> ValResult<'data, PyObject> {
78-
dbg!("Hello 1");
7972
let either_float = input.validate_float(extra.strict.unwrap_or(self.strict), extra.ultra_strict)?;
80-
let float = either_float.as_bigint()?;
81-
if float.is_none() && !self.allow_inf_nan{
73+
let float: f64 = either_float.try_into()?;
74+
if !self.allow_inf_nan && !float.is_finite() {
8275
return Err(ValError::new(ErrorType::FiniteNumber, input));
8376
} else {
8477
Ok(float.into_py(py))
@@ -123,17 +116,43 @@ impl Validator for ConstrainedFloatValidator {
123116
_recursion_guard: &'s mut RecursionGuard,
124117
) -> ValResult<'data, PyObject> {
125118
let either_float = input.validate_float(extra.strict.unwrap_or(self.strict), extra.ultra_strict)?;
126-
let float = either_float.as_bigint()?;
127-
dbg!("float", &float);
128-
if float.is_none() && !self.allow_inf_nan{
119+
let float: f64 = either_float.try_into()?;
120+
if !self.allow_inf_nan && !float.is_finite() {
129121
return Err(ValError::new(ErrorType::FiniteNumber, input));
130-
} else if let Some(f) = &float {
131-
if let Some(ref lt) = self.lt {
132-
dbg!("lt", &lt);
122+
}
123+
if let Some(multiple_of) = self.multiple_of {
124+
let rem = float % multiple_of;
125+
let threshold = float / 1e9;
126+
if rem.abs() > threshold && (rem - multiple_of).abs() > threshold {
127+
return Err(ValError::new(
128+
ErrorType::MultipleOf {
129+
multiple_of: multiple_of.into(),
130+
},
131+
input,
132+
));
133+
}
134+
}
135+
if let Some(le) = self.le {
136+
if float > le {
137+
return Err(ValError::new(ErrorType::LessThanEqual { le: le.into() }, input));
138+
}
139+
}
140+
if let Some(lt) = self.lt {
141+
if float >= lt {
142+
return Err(ValError::new(ErrorType::LessThan { lt: lt.into() }, input));
143+
}
144+
}
145+
if let Some(ge) = self.ge {
146+
if float < ge {
147+
return Err(ValError::new(ErrorType::GreaterThanEqual { ge: ge.into() }, input));
148+
}
149+
}
150+
if let Some(gt) = self.gt {
151+
if float <= gt {
152+
return Err(ValError::new(ErrorType::GreaterThan { gt: gt.into() }, input));
133153
}
134-
135154
}
136-
Ok(either_float.into_py(py))
155+
Ok(float.into_py(py))
137156
}
138157

139158
fn different_strict_behavior(
@@ -160,7 +179,6 @@ impl BuildValidator for ConstrainedFloatValidator {
160179
config: Option<&PyDict>,
161180
_definitions: &mut DefinitionsBuilder<CombinedValidator>,
162181
) -> PyResult<CombinedValidator> {
163-
dbg!("schema {:?}", &schema);
164182
let py = schema.py();
165183
Ok(Self {
166184
strict: is_strict(schema, config)?,

tests/validators/test_float.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,23 @@ def test_float_strict(py_and_json: PyAndJson, input_value, expected):
6666
@pytest.mark.parametrize(
6767
'kwargs,input_value,expected',
6868
[
69+
({}, 0, 0),
70+
({}, '123.456', 123.456),
71+
({'ge': 0}, 0, 0),
72+
(
73+
{'ge': 0},
74+
-0.1,
75+
Err(
76+
'Input should be greater than or equal to 0 '
77+
'[type=greater_than_equal, input_value=-0.1, input_type=float]'
78+
),
79+
),
80+
({'gt': 0}, 0.1, 0.1),
81+
({'gt': 0}, 0, Err('Input should be greater than 0 [type=greater_than, input_value=0, input_type=int]')),
82+
({'le': 0}, 0, 0),
83+
({'le': 0}, -1, -1),
84+
({'le': 0}, 0.1, Err('Input should be less than or equal to 0')),
85+
({'lt': 0}, 0, Err('Input should be less than 0')),
6986
({'lt': 0.123456}, 1, Err('Input should be less than 0.123456')),
7087
],
7188
)

0 commit comments

Comments
 (0)