Skip to content

Use more explicit warning regarding serialization warning for missing fields #1415

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Aug 22, 2024
Merged
18 changes: 14 additions & 4 deletions src/errors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,25 @@ pub fn ceil_char_boundary(value: &str, index: usize) -> usize {
.map_or(upper_bound, |pos| pos + index)
}

pub fn write_truncated_to_50_bytes<F: fmt::Write>(f: &mut F, val: Cow<'_, str>) -> std::fmt::Result {
if val.len() > 50 {
pub fn write_truncated_to_limited_bytes<F: fmt::Write>(
f: &mut F,
val: Cow<'_, str>,
max_len: usize,
) -> std::fmt::Result {
if val.len() > max_len {
let mid_point = (max_len as f64 / 2.0).ceil() as usize;
write!(
f,
"{}...{}",
&val[0..floor_char_boundary(&val, 25)],
&val[ceil_char_boundary(&val, val.len() - 24)..]
&val[0..floor_char_boundary(&val, mid_point)],
&val[ceil_char_boundary(&val, val.len() - (mid_point - 1))..]
)
} else {
write!(f, "{val}")
}
}

// preserved for backwards compatibility, can be removed in a major (or potentially minor) release
pub fn write_truncated_to_50_bytes<F: fmt::Write>(f: &mut F, val: Cow<'_, str>) -> std::fmt::Result {
write_truncated_to_limited_bytes(f, val, 50)
}
11 changes: 3 additions & 8 deletions src/serializers/extra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::recursion_guard::ContainsRecursionState;
use crate::recursion_guard::RecursionError;
use crate::recursion_guard::RecursionGuard;
use crate::recursion_guard::RecursionState;
use crate::tools::safe_repr;
use crate::tools::truncate_safe_repr;
use crate::PydanticSerializationError;

/// this is ugly, would be much better if extra could be stored in `SerializationState`
Expand Down Expand Up @@ -426,15 +426,10 @@ impl CollectWarnings {
.qualname()
.unwrap_or_else(|_| PyString::new_bound(value.py(), "<unknown python object>"));

let input_str = safe_repr(value);
let mut value_str = String::with_capacity(100);
value_str.push_str("with value `");
crate::errors::write_truncated_to_50_bytes(&mut value_str, input_str.to_cow())
.expect("Writing to a `String` failed");
value_str.push('`');
let value_str = truncate_safe_repr(value, None);

self.add_warning(format!(
"Expected `{field_type}` but got `{type_name}` {value_str} - serialized value may not be as expected"
"Expected `{field_type}` but got `{type_name}` with value `{value_str}` - serialized value may not be as expected"
));
}
}
Expand Down
24 changes: 23 additions & 1 deletion src/serializers/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use smallvec::SmallVec;

use crate::serializers::extra::SerCheck;
use crate::serializers::DuckTypingSerMode;
use crate::tools::truncate_safe_repr;
use crate::PydanticSerializationUnexpectedValue;

use super::computed_fields::ComputedFields;
Expand Down Expand Up @@ -210,7 +211,28 @@ impl GeneralFieldsSerializer {
// Check for missing fields, we can't have extra fields here
&& self.required_fields > used_req_fields
{
Err(PydanticSerializationUnexpectedValue::new_err(None))
let required_fields = self.required_fields;
let field_name = extra.field_name.unwrap_or("<unknown field>").to_string();
let type_name = match extra.model {
Some(model) => model
.get_type()
.qualname()
.ok()
.unwrap_or_else(|| PyString::new_bound(py, "<unknown python object>"))
.to_string(),
None => "<unknown python object>".to_string(),
};

let field_value = match extra.model {
Some(model) => truncate_safe_repr(model, Some(100)),
None => "<unknown python object>".to_string(),
};

Err(PydanticSerializationUnexpectedValue::new_err(
Some(format!(
"Expected {required_fields} fields but got {used_req_fields} for field {field_name} of type `{type_name}` with value `{field_value}` - serialized value may not be as expected."
))
))
} else {
Ok(output_dict)
}
Expand Down
3 changes: 1 addition & 2 deletions src/serializers/type_serializers/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,9 @@ impl TypeSerializer for ModelSerializer {
) -> PyResult<PyObject> {
let model = Some(value);
let duck_typing_ser_mode = extra.duck_typing_ser_mode.next_mode();

let model_extra = Extra {
model,
field_name: None,
duck_typing_ser_mode,
..*extra
};
Expand Down Expand Up @@ -221,7 +221,6 @@ impl TypeSerializer for ModelSerializer {
let duck_typing_ser_mode = extra.duck_typing_ser_mode.next_mode();
let model_extra = Extra {
model,
field_name: None,
duck_typing_ser_mode,
..*extra
};
Expand Down
12 changes: 3 additions & 9 deletions src/serializers/type_serializers/union.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ use std::borrow::Cow;
use crate::build_tools::py_schema_err;
use crate::common::union::{Discriminator, SMALL_UNION_THRESHOLD};
use crate::definitions::DefinitionsBuilder;
use crate::errors::write_truncated_to_50_bytes;
use crate::lookup_key::LookupKey;
use crate::serializers::type_serializers::py_err_se_err;
use crate::tools::{safe_repr, SchemaDict};
use crate::tools::{truncate_safe_repr, SchemaDict};
use crate::PydanticSerializationUnexpectedValue;

use super::{
Expand Down Expand Up @@ -446,15 +445,10 @@ impl TaggedUnionSerializer {
Discriminator::Function(func) => func.call1(py, (value,)).ok(),
};
if discriminator_value.is_none() {
let input_str = safe_repr(value);
let mut value_str = String::with_capacity(100);
value_str.push_str("with value `");
write_truncated_to_50_bytes(&mut value_str, input_str.to_cow()).expect("Writing to a `String` failed");
value_str.push('`');

let value_str = truncate_safe_repr(value, None);
extra.warnings.custom_warning(
format!(
"Failed to get discriminator value for tagged union serialization {value_str} - defaulting to left to right union serialization."
"Failed to get discriminator value for tagged union serialization with value `{value_str}` - defaulting to left to right union serialization."
)
);
}
Expand Down
11 changes: 11 additions & 0 deletions src/tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use pyo3::{intern, FromPyObject};

use jiter::{cached_py_string, pystring_fast_new, StringCacheMode};

use crate::errors::write_truncated_to_limited_bytes;

pub trait SchemaDict<'py> {
fn get_as<T>(&self, key: &Bound<'_, PyString>) -> PyResult<Option<T>>
where
Expand Down Expand Up @@ -124,6 +126,15 @@ pub fn safe_repr<'py>(v: &Bound<'py, PyAny>) -> ReprOutput<'py> {
}
}

pub fn truncate_safe_repr(v: &Bound<'_, PyAny>, max_len: Option<usize>) -> String {
let max_len = max_len.unwrap_or(50); // default to 100 bytes
let input_str = safe_repr(v);
let mut limited_str = String::with_capacity(max_len);
write_truncated_to_limited_bytes(&mut limited_str, input_str.to_cow(), max_len)
.expect("Writing to a `String` failed");
limited_str
}

pub fn extract_i64(v: &Bound<'_, PyAny>) -> Option<i64> {
#[cfg(PyPy)]
if !v.is_instance_of::<pyo3::types::PyInt>() {
Expand Down
Loading