Skip to content

Commit d80c454

Browse files
fix: nan inf float (#1062)
Co-authored-by: JeanArhancet <jean.arhancetebehere@gmail.com>
1 parent 5de6b75 commit d80c454

File tree

13 files changed

+1496
-8
lines changed

13 files changed

+1496
-8
lines changed

python/pydantic_core/core_schema.py

+3
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ class CoreConfig(TypedDict, total=False):
6868
allow_inf_nan: Whether to allow infinity and NaN values for float fields. Default is `True`.
6969
ser_json_timedelta: The serialization option for `timedelta` values. Default is 'iso8601'.
7070
ser_json_bytes: The serialization option for `bytes` values. Default is 'utf8'.
71+
ser_json_inf_nan: The serialization option for infinity and NaN values
72+
in float fields. Default is 'null'.
7173
hide_input_in_errors: Whether to hide input data from `ValidationError` representation.
7274
validation_error_cause: Whether to add user-python excs to the __cause__ of a ValidationError.
7375
Requires exceptiongroup backport pre Python 3.11.
@@ -102,6 +104,7 @@ class CoreConfig(TypedDict, total=False):
102104
# the config options are used to customise serialization to JSON
103105
ser_json_timedelta: Literal['iso8601', 'float'] # default: 'iso8601'
104106
ser_json_bytes: Literal['utf8', 'base64', 'hex'] # default: 'utf8'
107+
ser_json_inf_nan: Literal['null', 'constants'] # default: 'null'
105108
# used to hide input data from ValidationError repr
106109
hide_input_in_errors: bool
107110
validation_error_cause: bool # default: False

src/errors/validation_exception.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -324,12 +324,12 @@ impl ValidationError {
324324
Some(indent) => {
325325
let indent = vec![b' '; indent];
326326
let formatter = PrettyFormatter::with_indent(&indent);
327-
let mut ser = serde_json::Serializer::with_formatter(writer, formatter);
327+
let mut ser = crate::serializers::ser::PythonSerializer::with_formatter(writer, formatter);
328328
serializer.serialize(&mut ser).map_err(json_py_err)?;
329329
ser.into_inner()
330330
}
331331
None => {
332-
let mut ser = serde_json::Serializer::new(writer);
332+
let mut ser = crate::serializers::ser::PythonSerializer::new(writer);
333333
serializer.serialize(&mut ser).map_err(json_py_err)?;
334334
ser.into_inner()
335335
}

src/serializers/config.rs

+29
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,32 @@ pub fn utf8_py_error(py: Python, err: Utf8Error, data: &[u8]) -> PyErr {
189189
Err(err) => err,
190190
}
191191
}
192+
193+
#[derive(Default, Debug, Clone, PartialEq, Eq)]
194+
pub(crate) enum InfNanMode {
195+
#[default]
196+
Null,
197+
Constants,
198+
}
199+
200+
impl FromStr for InfNanMode {
201+
type Err = PyErr;
202+
203+
fn from_str(s: &str) -> Result<Self, Self::Err> {
204+
match s {
205+
"null" => Ok(Self::Null),
206+
"constants" => Ok(Self::Constants),
207+
s => py_schema_err!(
208+
"Invalid inf_nan serialization mode: `{}`, expected `null` or `constants`",
209+
s
210+
),
211+
}
212+
}
213+
}
214+
215+
impl FromPyObject<'_> for InfNanMode {
216+
fn extract(ob: &'_ PyAny) -> PyResult<Self> {
217+
let s = ob.extract::<&str>()?;
218+
Self::from_str(s)
219+
}
220+
}

src/serializers/errors.rs

+26-1
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,33 @@ pub(super) fn py_err_se_err<T: ser::Error, E: fmt::Display>(py_error: E) -> T {
1414
T::custom(py_error.to_string())
1515
}
1616

17+
#[pyclass(extends=PyValueError, module="pydantic_core._pydantic_core")]
18+
#[derive(Debug, Clone)]
19+
pub struct PythonSerializerError {
20+
pub message: String,
21+
}
22+
23+
impl fmt::Display for PythonSerializerError {
24+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25+
write!(f, "{}", self.message)
26+
}
27+
}
28+
29+
impl std::error::Error for PythonSerializerError {}
30+
31+
impl serde::ser::Error for PythonSerializerError {
32+
fn custom<T>(msg: T) -> Self
33+
where
34+
T: fmt::Display,
35+
{
36+
PythonSerializerError {
37+
message: format!("{msg}"),
38+
}
39+
}
40+
}
41+
1742
/// convert a serde serialization error into a `PyErr`
18-
pub(super) fn se_err_py_err(error: serde_json::Error) -> PyErr {
43+
pub(super) fn se_err_py_err(error: PythonSerializerError) -> PyErr {
1944
let s = error.to_string();
2045
if let Some(msg) = s.strip_prefix(UNEXPECTED_TYPE_SER_MARKER) {
2146
if msg.is_empty() {

src/serializers/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ mod fields;
2323
mod filter;
2424
mod infer;
2525
mod ob_type;
26+
pub mod ser;
2627
mod shared;
2728
mod type_serializers;
2829

0 commit comments

Comments
 (0)