Skip to content

Commit 8fc0231

Browse files
committed
feat: add use eitherfloat
1 parent 3d3cb7f commit 8fc0231

File tree

12 files changed

+137
-101
lines changed

12 files changed

+137
-101
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ 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"
4445

4546
[lib]
4647
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 --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
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
2525

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

src/errors/types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ pub enum ErrorType {
181181
// float errors
182182
FloatType,
183183
FloatParsing,
184+
FloatParsingSize,
184185
// ---------------------
185186
// bytes errors
186187
BytesType,
@@ -502,6 +503,7 @@ impl ErrorType {
502503
Self::IntParsingSize => "Unable to parse input string as an integer, exceeded maximum size",
503504
Self::FloatType => "Input should be a valid number",
504505
Self::FloatParsing => "Input should be a valid number, unable to parse string as an number",
506+
Self::FloatParsingSize => "Unable to parse input string as an number, exceeded maximum size",
505507
Self::BytesType => "Input should be a valid bytes",
506508
Self::BytesTooShort {..} => "Data should have at least {min_length} bytes",
507509
Self::BytesTooLong {..} => "Data should have at most {max_length} bytes",

src/input/input_abstract.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{PyMultiHostUrl, PyUrl};
88

99
use super::datetime::{EitherDate, EitherDateTime, EitherTime, EitherTimedelta};
1010
use super::return_enums::{EitherBytes, EitherInt, EitherString};
11-
use super::{GenericArguments, GenericIterable, GenericIterator, GenericMapping, JsonInput};
11+
use super::{EitherFloat, GenericArguments, GenericIterable, GenericIterator, GenericMapping, JsonInput};
1212

1313
#[derive(Debug, Clone, Copy)]
1414
pub enum InputType {
@@ -124,7 +124,7 @@ pub trait Input<'a>: fmt::Debug + ToPyObject {
124124
self.strict_int()
125125
}
126126

127-
fn validate_float(&self, strict: bool, ultra_strict: bool) -> ValResult<f64> {
127+
fn validate_float(&'a self, strict: bool, ultra_strict: bool) -> ValResult<EitherFloat<'a>> {
128128
if ultra_strict {
129129
self.ultra_strict_float()
130130
} else if strict {
@@ -133,10 +133,10 @@ pub trait Input<'a>: fmt::Debug + ToPyObject {
133133
self.lax_float()
134134
}
135135
}
136-
fn ultra_strict_float(&self) -> ValResult<f64>;
137-
fn strict_float(&self) -> ValResult<f64>;
136+
fn ultra_strict_float(&'a self) -> ValResult<EitherFloat<'a>>;
137+
fn strict_float(&'a self) -> ValResult<EitherFloat<'a>>;
138138
#[cfg_attr(has_no_coverage, no_coverage)]
139-
fn lax_float(&self) -> ValResult<f64> {
139+
fn lax_float(&'a self) -> ValResult<EitherFloat<'a>> {
140140
self.strict_float()
141141
}
142142

src/input/input_json.rs

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ use super::datetime::{
1010
use super::parse_json::JsonArray;
1111
use super::shared::{float_as_int, int_as_bool, map_json_err, str_as_bool, str_as_int};
1212
use super::{
13-
EitherBytes, EitherInt, EitherString, EitherTimedelta, GenericArguments, GenericIterable, GenericIterator,
14-
GenericMapping, Input, JsonArgs, JsonInput,
13+
EitherBytes, EitherFloat, EitherInt, EitherString, EitherTimedelta, GenericArguments, GenericIterable,
14+
GenericIterator, GenericMapping, Input, JsonArgs, JsonInput,
1515
};
1616

1717
impl<'a> Input<'a> for JsonInput {
@@ -135,31 +135,33 @@ impl<'a> Input<'a> for JsonInput {
135135
}
136136
}
137137

138-
fn ultra_strict_float(&self) -> ValResult<f64> {
138+
fn ultra_strict_float(&'a self) -> ValResult<EitherFloat<'a>> {
139139
match self {
140-
JsonInput::Float(f) => Ok(*f),
140+
JsonInput::Float(f) => Ok(EitherFloat::F64(*f)),
141141
_ => Err(ValError::new(ErrorType::FloatType, self)),
142142
}
143143
}
144-
fn strict_float(&self) -> ValResult<f64> {
144+
fn strict_float(&'a self) -> ValResult<EitherFloat<'a>> {
145+
dbg!("strict float python here");
145146
match self {
146-
JsonInput::Float(f) => Ok(*f),
147-
JsonInput::Int(i) => Ok(*i as f64),
148-
JsonInput::Uint(u) => Ok(*u as f64),
147+
JsonInput::Float(f) => Ok(EitherFloat::F64(*f)),
148+
JsonInput::Int(i) => Ok(EitherFloat::F64(*i as f64)),
149+
JsonInput::Uint(u) => Ok(EitherFloat::F64(*u as f64)),
149150
_ => Err(ValError::new(ErrorType::FloatType, self)),
150151
}
151152
}
152-
fn lax_float(&self) -> ValResult<f64> {
153+
fn lax_float(&'a self) -> ValResult<EitherFloat<'a>> {
154+
dbg!("lax float python here");
153155
match self {
154156
JsonInput::Bool(b) => match *b {
155-
true => Ok(1.0),
156-
false => Ok(0.0),
157+
true => Ok(EitherFloat::F64(1.0)),
158+
false => Ok(EitherFloat::F64(0.0)),
157159
},
158-
JsonInput::Float(f) => Ok(*f),
159-
JsonInput::Int(i) => Ok(*i as f64),
160-
JsonInput::Uint(u) => Ok(*u as f64),
160+
JsonInput::Float(f) => Ok(EitherFloat::F64(*f)),
161+
JsonInput::Int(i) => Ok(EitherFloat::F64(*i as f64)),
162+
JsonInput::Uint(u) => Ok(EitherFloat::F64(*u as f64)),
161163
JsonInput::String(str) => match str.parse::<f64>() {
162-
Ok(i) => Ok(i),
164+
Ok(i) => Ok(EitherFloat::F64(i)),
163165
Err(_) => Err(ValError::new(ErrorType::FloatParsing, self)),
164166
},
165167
_ => Err(ValError::new(ErrorType::FloatType, self)),
@@ -372,16 +374,16 @@ impl<'a> Input<'a> for String {
372374
}
373375

374376
#[cfg_attr(has_no_coverage, no_coverage)]
375-
fn ultra_strict_float(&self) -> ValResult<f64> {
377+
fn ultra_strict_float(&'a self) -> ValResult<EitherFloat<'a>> {
376378
self.strict_float()
377379
}
378380
#[cfg_attr(has_no_coverage, no_coverage)]
379-
fn strict_float(&self) -> ValResult<f64> {
381+
fn strict_float(&'a self) -> ValResult<EitherFloat<'a>> {
380382
Err(ValError::new(ErrorType::FloatType, self))
381383
}
382-
fn lax_float(&self) -> ValResult<f64> {
384+
fn lax_float(&'a self) -> ValResult<EitherFloat<'a>> {
383385
match self.parse() {
384-
Ok(i) => Ok(i),
386+
Ok(i) => Ok(EitherFloat::F64(i)),
385387
Err(_) => Err(ValError::new(ErrorType::FloatParsing, self)),
386388
}
387389
}

src/input/input_python.rs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::str::from_utf8;
33

44
use pyo3::prelude::*;
55
use pyo3::types::{
6-
PyBool, PyByteArray, PyBytes, PyDate, PyDateTime, PyDelta, PyDict, PyFrozenSet, PyInt, PyIterator, PyList,
6+
PyBool, PyByteArray, PyBytes, PyDate, PyDateTime, PyDelta, PyDict, PyFloat, PyFrozenSet, PyInt, PyIterator, PyList,
77
PyMapping, PySequence, PySet, PyString, PyTime, PyTuple, PyType,
88
};
99
#[cfg(not(PyPy))]
@@ -21,8 +21,8 @@ use super::datetime::{
2121
};
2222
use super::shared::{float_as_int, int_as_bool, map_json_err, str_as_bool, str_as_int};
2323
use super::{
24-
py_string_str, EitherBytes, EitherInt, EitherString, EitherTimedelta, GenericArguments, GenericIterable,
25-
GenericIterator, GenericMapping, Input, JsonInput, PyArgs,
24+
py_string_str, EitherBytes, EitherFloat, EitherInt, EitherString, EitherTimedelta, GenericArguments,
25+
GenericIterable, GenericIterator, GenericMapping, Input, JsonInput, PyArgs,
2626
};
2727

2828
#[cfg(not(PyPy))]
@@ -295,37 +295,38 @@ impl<'a> Input<'a> for PyAny {
295295
}
296296
}
297297

298-
fn ultra_strict_float(&self) -> ValResult<f64> {
298+
fn ultra_strict_float(&'a self) -> ValResult<EitherFloat<'a>> {
299299
if matches!(self.is_instance_of::<PyInt>(), Ok(true)) {
300300
Err(ValError::new(ErrorType::FloatType, self))
301301
} else if let Ok(float) = self.extract::<f64>() {
302-
Ok(float)
302+
Ok(EitherFloat::F64(float))
303303
} else {
304304
Err(ValError::new(ErrorType::FloatType, self))
305305
}
306306
}
307-
fn strict_float(&self) -> ValResult<f64> {
307+
fn strict_float(&'a self) -> ValResult<EitherFloat<'a>> {
308308
if let Ok(float) = self.extract::<f64>() {
309309
// bools are cast to floats as either 0.0 or 1.0, so check for bool type in this specific case
310310
if (float == 0.0 || float == 1.0) && PyBool::is_exact_type_of(self) {
311311
Err(ValError::new(ErrorType::FloatType, self))
312312
} else {
313-
Ok(float)
313+
Ok(EitherFloat::F64(float))
314314
}
315315
} else {
316316
Err(ValError::new(ErrorType::FloatType, self))
317317
}
318318
}
319-
fn lax_float(&self) -> ValResult<f64> {
319+
320+
fn lax_float(&'a self) -> ValResult<EitherFloat<'a>> {
320321
if let Ok(float) = self.extract::<f64>() {
321-
Ok(float)
322-
} else if let Some(cow_str) = maybe_as_string(self, ErrorType::FloatParsing)? {
322+
Ok(EitherFloat::F64(float))
323+
} else if let Some(cow_str) = maybe_as_string(self, ErrorType::FloatParsing)? {
323324
match cow_str.as_ref().parse::<f64>() {
324-
Ok(i) => Ok(i),
325+
Ok(i) => Ok(EitherFloat::F64(i)),
325326
Err(_) => Err(ValError::new(ErrorType::FloatParsing, self)),
326327
}
327-
} else {
328-
Err(ValError::new(ErrorType::FloatType, self))
328+
} else {
329+
Err(ValError::new(ErrorType::FloatType, self))
329330
}
330331
}
331332

src/input/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub(crate) use datetime::{
1717
pub(crate) use input_abstract::{Input, InputType};
1818
pub(crate) use parse_json::{JsonInput, JsonObject};
1919
pub(crate) use return_enums::{
20-
py_string_str, AttributesGenericIterator, DictGenericIterator, EitherBytes, EitherInt, EitherString,
20+
py_string_str, AttributesGenericIterator, DictGenericIterator, EitherBytes, EitherFloat, EitherInt, EitherString,
2121
GenericArguments, GenericIterable, GenericIterator, GenericMapping, JsonArgs, JsonObjectGenericIterator,
2222
MappingGenericIterator, PyArgs,
2323
};

src/input/return_enums.rs

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

44
use num_bigint::BigInt;
5+
use num_traits::{FromPrimitive, ToPrimitive};
56

67
use pyo3::prelude::*;
78
use pyo3::types::iter::PyDictIterator;
@@ -892,3 +893,63 @@ impl<'a> IntoPy<PyObject> for EitherInt<'a> {
892893
}
893894
}
894895
}
896+
897+
#[cfg_attr(debug_assertions, derive(Debug))]
898+
pub enum EitherFloat<'a> {
899+
F64(f64),
900+
BigInt(BigInt),
901+
Py(&'a PyAny),
902+
}
903+
904+
impl<'a> EitherFloat<'a> {
905+
pub fn into_f64(self, py: Python<'a>) -> ValResult<'a, f64> {
906+
match self {
907+
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))),
911+
EitherFloat::Py(i) => i.extract().map_err(|_| ValError::new(ErrorType::FloatParsingSize, i)),
912+
}
913+
}
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+
}
945+
}
946+
947+
impl<'a> IntoPy<PyObject> for EitherFloat<'a> {
948+
fn into_py(self, py: Python<'_>) -> PyObject {
949+
match self {
950+
Self::F64(float) => float.into_py(py),
951+
Self::BigInt(float) => float.into_py(py),
952+
Self::Py(float) => float.into_py(py),
953+
}
954+
}
955+
}

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::Input;
6+
use super::{EitherFloat, Input};
77

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

0 commit comments

Comments
 (0)