From 37efc681e04f30a7bfa7476760e87a0d388a4f51 Mon Sep 17 00:00:00 2001 From: sydney-runkle Date: Thu, 27 Feb 2025 13:31:22 -0500 Subject: [PATCH 1/4] Defer pyobject introspection until warnings are actually raised, leading to perf boost for union serialization --- src/serializers/errors.rs | 56 ++++++++++++++----- src/serializers/extra.rs | 15 ++--- src/serializers/fields.rs | 4 +- .../type_serializers/datetime_etc.rs | 2 +- src/serializers/type_serializers/float.rs | 2 +- src/serializers/type_serializers/function.rs | 2 +- src/serializers/type_serializers/model.rs | 2 +- src/serializers/type_serializers/simple.rs | 2 +- src/serializers/type_serializers/tuple.rs | 2 +- src/serializers/type_serializers/union.rs | 2 +- 10 files changed, 57 insertions(+), 32 deletions(-) diff --git a/src/serializers/errors.rs b/src/serializers/errors.rs index ffd43407f..28cd6ac97 100644 --- a/src/serializers/errors.rs +++ b/src/serializers/errors.rs @@ -2,6 +2,9 @@ use std::fmt; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; +use pyo3::types::PyString; + +use crate::tools::truncate_safe_repr; use serde::ser; @@ -44,9 +47,9 @@ pub(super) fn se_err_py_err(error: PythonSerializerError) -> PyErr { let s = error.to_string(); if let Some(msg) = s.strip_prefix(UNEXPECTED_TYPE_SER_MARKER) { if msg.is_empty() { - PydanticSerializationUnexpectedValue::new_err(None) + PydanticSerializationUnexpectedValue::new_from_msg(None) } else { - PydanticSerializationUnexpectedValue::new_err(Some(msg.to_string())) + PydanticSerializationUnexpectedValue::new_from_msg(Some(msg.to_string())) } } else if let Some(msg) = s.strip_prefix(SERIALIZATION_ERR_MARKER) { PydanticSerializationError::new_err(msg.to_string()) @@ -94,30 +97,57 @@ impl PydanticSerializationError { #[derive(Debug, Clone)] pub struct PydanticSerializationUnexpectedValue { message: Option, + field_type: Option, + input_value: Option, } impl PydanticSerializationUnexpectedValue { - pub(crate) fn new_err(msg: Option) -> PyErr { - PyErr::new::>(msg) + pub fn new_from_msg(message: Option) -> PyErr { + PyErr::new::, Option, Option)>((message, None, None)) + } + + pub fn new_from_parts(field_type: String, input_value: PyObject) -> PyErr { + PyErr::new::, Option, Option)>(( + None, + Some(field_type), + Some(input_value), + )) } } #[pymethods] impl PydanticSerializationUnexpectedValue { #[new] - #[pyo3(signature = (message=None))] - fn py_new(message: Option) -> Self { - Self { message } + #[pyo3(signature = (message=None, field_type=None, input_value=None))] + fn py_new(message: Option, field_type: Option, input_value: Option) -> Self { + Self { + message, + field_type, + input_value, + } } - fn __str__(&self) -> &str { - match self.message { - Some(ref s) => s, - None => "Unexpected Value", + fn __str__(&self, py: Python) -> String { + match &self.message { + Some(s) => s.to_string(), + None => match (&self.field_type, &self.input_value) { + (Some(ref field_type), Some(ref input_value)) => { + let bound_input = input_value.bind(py); + + let type_name = bound_input + .get_type() + .qualname() + .unwrap_or_else(|_| PyString::new(py, "")); + + let value_str = truncate_safe_repr(bound_input, None); + format!("Expected `{field_type}` but got `{type_name}` with value `{value_str}` - serialized value may not be as expected") + } + _ => "Unexpected value - serialized value may not be as expected".to_string(), + }, } } - pub(crate) fn __repr__(&self) -> String { - format!("PydanticSerializationUnexpectedValue({})", self.__str__()) + pub(crate) fn __repr__(&self, py: Python) -> String { + format!("PydanticSerializationUnexpectedValue({})", self.__str__(py)) } } diff --git a/src/serializers/extra.rs b/src/serializers/extra.rs index a369859fb..0dcfc0a97 100644 --- a/src/serializers/extra.rs +++ b/src/serializers/extra.rs @@ -3,6 +3,7 @@ use std::ffi::CString; use std::fmt; use std::sync::Mutex; +use crate::tools::truncate_safe_repr; use pyo3::exceptions::{PyTypeError, PyUserWarning, PyValueError}; use pyo3::prelude::*; use pyo3::types::{PyBool, PyString}; @@ -17,7 +18,6 @@ use crate::recursion_guard::ContainsRecursionState; use crate::recursion_guard::RecursionError; use crate::recursion_guard::RecursionGuard; use crate::recursion_guard::RecursionState; -use crate::tools::truncate_safe_repr; use crate::PydanticSerializationError; /// this is ugly, would be much better if extra could be stored in `SerializationState` @@ -415,15 +415,10 @@ impl CollectWarnings { if value.is_none() { Ok(()) } else if extra.check.enabled() { - let type_name = value - .get_type() - .qualname() - .unwrap_or_else(|_| PyString::new(value.py(), "")); - - let value_str = truncate_safe_repr(value, None); - Err(PydanticSerializationUnexpectedValue::new_err(Some(format!( - "Expected `{field_type}` but got `{type_name}` with value `{value_str}` - serialized value may not be as expected" - )))) + Err(PydanticSerializationUnexpectedValue::new_from_parts( + field_type.to_string(), + value.clone().unbind(), + )) } else { self.fallback_warning(field_type, value); Ok(()) diff --git a/src/serializers/fields.rs b/src/serializers/fields.rs index b0da9ba56..25d4b14c2 100644 --- a/src/serializers/fields.rs +++ b/src/serializers/fields.rs @@ -202,7 +202,7 @@ impl GeneralFieldsSerializer { output_dict.set_item(key, value)?; } else if field_extra.check == SerCheck::Strict { let type_name = field_extra.model_type_name(); - return Err(PydanticSerializationUnexpectedValue::new_err(Some(format!( + return Err(PydanticSerializationUnexpectedValue::new_from_msg(Some(format!( "Unexpected field `{key}`{for_type_name}", for_type_name = if let Some(type_name) = type_name { format!(" for type `{type_name}`") @@ -227,7 +227,7 @@ impl GeneralFieldsSerializer { None => "".to_string(), }; - Err(PydanticSerializationUnexpectedValue::new_err(Some(format!( + Err(PydanticSerializationUnexpectedValue::new_from_msg(Some(format!( "Expected {required_fields} fields but got {used_req_fields}{for_type_name} with value `{field_value}` - serialized value may not be as expected.", for_type_name = if let Some(type_name) = type_name { format!(" for type `{type_name}`") } else { String::new() }, )))) diff --git a/src/serializers/type_serializers/datetime_etc.rs b/src/serializers/type_serializers/datetime_etc.rs index 3486b5999..6eb50f1fe 100644 --- a/src/serializers/type_serializers/datetime_etc.rs +++ b/src/serializers/type_serializers/datetime_etc.rs @@ -34,7 +34,7 @@ fn downcast_date_reject_datetime<'a, 'py>(py_date: &'a Bound<'py, PyAny>) -> PyR } } - Err(PydanticSerializationUnexpectedValue::new_err(None)) + Err(PydanticSerializationUnexpectedValue::new_from_msg(None)) } macro_rules! build_serializer { diff --git a/src/serializers/type_serializers/float.rs b/src/serializers/type_serializers/float.rs index fd79a88b5..427a8a778 100644 --- a/src/serializers/type_serializers/float.rs +++ b/src/serializers/type_serializers/float.rs @@ -75,7 +75,7 @@ impl TypeSerializer for FloatSerializer { match extra.ob_type_lookup.is_type(value, ObType::Float) { IsType::Exact => Ok(value.clone().unbind()), IsType::Subclass => match extra.check { - SerCheck::Strict => Err(PydanticSerializationUnexpectedValue::new_err(None)), + SerCheck::Strict => Err(PydanticSerializationUnexpectedValue::new_from_msg(None)), SerCheck::Lax | SerCheck::None => match extra.mode { SerMode::Json => value.extract::()?.into_py_any(py), _ => infer_to_python(value, include, exclude, extra), diff --git a/src/serializers/type_serializers/function.rs b/src/serializers/type_serializers/function.rs index 79317035d..2525f2aa5 100644 --- a/src/serializers/type_serializers/function.rs +++ b/src/serializers/type_serializers/function.rs @@ -194,7 +194,7 @@ fn on_error(py: Python, err: PyErr, function_name: &str, extra: &Extra) -> PyRes if extra.check.enabled() { Err(err) } else { - extra.warnings.custom_warning(ser_err.__repr__()); + extra.warnings.custom_warning(ser_err.__repr__(py)); Ok(()) } } else if let Ok(err) = exception.extract::() { diff --git a/src/serializers/type_serializers/model.rs b/src/serializers/type_serializers/model.rs index 274fb48a0..74737e0ba 100644 --- a/src/serializers/type_serializers/model.rs +++ b/src/serializers/type_serializers/model.rs @@ -190,7 +190,7 @@ impl TypeSerializer for ModelSerializer { let py = value.py(); let root = value.getattr(intern!(py, ROOT_FIELD)).map_err(|original_err| { if root_extra.check.enabled() { - PydanticSerializationUnexpectedValue::new_err(None) + PydanticSerializationUnexpectedValue::new_from_msg(None) } else { original_err } diff --git a/src/serializers/type_serializers/simple.rs b/src/serializers/type_serializers/simple.rs index 2cbed2a58..023e0cf61 100644 --- a/src/serializers/type_serializers/simple.rs +++ b/src/serializers/type_serializers/simple.rs @@ -123,7 +123,7 @@ macro_rules! build_simple_serializer { match extra.ob_type_lookup.is_type(value, $ob_type) { IsType::Exact => Ok(value.clone().unbind()), IsType::Subclass => match extra.check { - SerCheck::Strict => Err(PydanticSerializationUnexpectedValue::new_err(None)), + SerCheck::Strict => Err(PydanticSerializationUnexpectedValue::new_from_msg(None)), SerCheck::Lax | SerCheck::None => match extra.mode { SerMode::Json => value.extract::<$rust_type>()?.into_py_any(py), _ => infer_to_python(value, include, exclude, extra), diff --git a/src/serializers/type_serializers/tuple.rs b/src/serializers/type_serializers/tuple.rs index 1d8057475..d7270b3c3 100644 --- a/src/serializers/type_serializers/tuple.rs +++ b/src/serializers/type_serializers/tuple.rs @@ -214,7 +214,7 @@ impl TupleSerializer { .chain(self.serializers[variadic_item_index + 1..].iter()); use_serializers!(serializers_iter); } else if extra.check == SerCheck::Strict && n_items != self.serializers.len() { - return Err(PydanticSerializationUnexpectedValue::new_err(Some(format!( + return Err(PydanticSerializationUnexpectedValue::new_from_msg(Some(format!( "Expected {} items, but got {}", self.serializers.len(), n_items diff --git a/src/serializers/type_serializers/union.rs b/src/serializers/type_serializers/union.rs index 2c10c1f7b..1f3efdc9f 100644 --- a/src/serializers/type_serializers/union.rs +++ b/src/serializers/type_serializers/union.rs @@ -111,7 +111,7 @@ fn union_serialize( // care of the formatting for us else if !errors.is_empty() { let message = errors.iter().map(ToString::to_string).collect::>().join("\n"); - return Err(PydanticSerializationUnexpectedValue::new_err(Some(message))); + return Err(PydanticSerializationUnexpectedValue::new_from_msg(Some(message))); } Ok(None) From 40889039caf5606b3c5899ecee5d3ac6e7ec6621 Mon Sep 17 00:00:00 2001 From: sydney-runkle Date: Thu, 27 Feb 2025 17:00:34 -0500 Subject: [PATCH 2/4] fixing tests, more refactoring --- src/serializers/errors.rs | 77 +++++++++++++------ src/serializers/extra.rs | 34 ++++---- src/serializers/fields.rs | 31 +++----- .../type_serializers/datetime_etc.rs | 2 +- src/serializers/type_serializers/float.rs | 2 +- src/serializers/type_serializers/function.rs | 2 +- src/serializers/type_serializers/model.rs | 2 +- src/serializers/type_serializers/simple.rs | 2 +- src/serializers/type_serializers/tuple.rs | 7 +- src/serializers/type_serializers/union.rs | 30 ++++++-- src/tools.rs | 1 + tests/serializers/test_any.py | 15 ++-- tests/serializers/test_list_tuple.py | 35 ++++----- tests/serializers/test_model.py | 4 +- 14 files changed, 138 insertions(+), 106 deletions(-) diff --git a/src/serializers/errors.rs b/src/serializers/errors.rs index 28cd6ac97..88cf4d09b 100644 --- a/src/serializers/errors.rs +++ b/src/serializers/errors.rs @@ -47,9 +47,9 @@ pub(super) fn se_err_py_err(error: PythonSerializerError) -> PyErr { let s = error.to_string(); if let Some(msg) = s.strip_prefix(UNEXPECTED_TYPE_SER_MARKER) { if msg.is_empty() { - PydanticSerializationUnexpectedValue::new_from_msg(None) + PydanticSerializationUnexpectedValue::new_from_msg(None).to_py_err() } else { - PydanticSerializationUnexpectedValue::new_from_msg(Some(msg.to_string())) + PydanticSerializationUnexpectedValue::new_from_msg(Some(msg.to_string())).to_py_err() } } else if let Some(msg) = s.strip_prefix(SERIALIZATION_ERR_MARKER) { PydanticSerializationError::new_err(msg.to_string()) @@ -102,15 +102,35 @@ pub struct PydanticSerializationUnexpectedValue { } impl PydanticSerializationUnexpectedValue { - pub fn new_from_msg(message: Option) -> PyErr { - PyErr::new::, Option, Option)>((message, None, None)) + pub fn new_from_msg(message: Option) -> Self { + Self { + message, + field_type: None, + input_value: None, + } + } + + pub fn new_from_parts(field_type: Option, input_value: Option) -> Self { + Self { + message: None, + field_type, + input_value, + } + } + + pub fn new(message: Option, field_type: Option, input_value: Option) -> Self { + Self { + message, + field_type, + input_value, + } } - pub fn new_from_parts(field_type: String, input_value: PyObject) -> PyErr { + pub fn to_py_err(&self) -> PyErr { PyErr::new::, Option, Option)>(( - None, - Some(field_type), - Some(input_value), + self.message.clone(), + self.field_type.clone(), + self.input_value.clone(), )) } } @@ -127,24 +147,31 @@ impl PydanticSerializationUnexpectedValue { } } - fn __str__(&self, py: Python) -> String { - match &self.message { - Some(s) => s.to_string(), - None => match (&self.field_type, &self.input_value) { - (Some(ref field_type), Some(ref input_value)) => { - let bound_input = input_value.bind(py); - - let type_name = bound_input - .get_type() - .qualname() - .unwrap_or_else(|_| PyString::new(py, "")); - - let value_str = truncate_safe_repr(bound_input, None); - format!("Expected `{field_type}` but got `{type_name}` with value `{value_str}` - serialized value may not be as expected") - } - _ => "Unexpected value - serialized value may not be as expected".to_string(), - }, + pub(crate) fn __str__(&self, py: Python) -> String { + let mut message = self.message.as_deref().unwrap_or("Unexpected Value").to_string(); + + if let Some(field_type) = &self.field_type { + message.push_str(&format!("Expected `{field_type}`")); + } + + if let Some(input_value) = &self.input_value { + let bound_input = input_value.bind(py); + let type_name = bound_input + .get_type() + .name() + .unwrap_or_else(|_| PyString::new(py, "")) + .to_string(); + + let value_str = truncate_safe_repr(bound_input, None); + + message.push_str(&format!(" but got `{type_name}` with value `{value_str}`")); } + + if self.input_value.is_some() || self.field_type.is_some() { + message.push_str(" - serialized value may not be as expected."); + } + + message } pub(crate) fn __repr__(&self, py: Python) -> String { diff --git a/src/serializers/extra.rs b/src/serializers/extra.rs index 0dcfc0a97..82c432342 100644 --- a/src/serializers/extra.rs +++ b/src/serializers/extra.rs @@ -3,7 +3,6 @@ use std::ffi::CString; use std::fmt; use std::sync::Mutex; -use crate::tools::truncate_safe_repr; use pyo3::exceptions::{PyTypeError, PyUserWarning, PyValueError}; use pyo3::prelude::*; use pyo3::types::{PyBool, PyString}; @@ -384,7 +383,7 @@ impl From for WarningsMode { pub(crate) struct CollectWarnings { mode: WarningsMode, // FIXME: mutex is to satisfy PyO3 0.23, we should be able to refactor this away - warnings: Mutex>, + warnings: Mutex>, } impl Clone for CollectWarnings { @@ -404,9 +403,9 @@ impl CollectWarnings { } } - pub fn custom_warning(&self, warning: String) { + pub fn register_warning(&self, warning: PydanticSerializationUnexpectedValue) { if self.mode != WarningsMode::None { - self.add_warning(warning); + self.warnings.lock().expect("lock poisoned").push(warning); } } @@ -416,9 +415,10 @@ impl CollectWarnings { Ok(()) } else if extra.check.enabled() { Err(PydanticSerializationUnexpectedValue::new_from_parts( - field_type.to_string(), - value.clone().unbind(), - )) + Some(field_type.to_string()), + Some(value.clone().unbind()), + ) + .to_py_err()) } else { self.fallback_warning(field_type, value); Ok(()) @@ -447,23 +447,13 @@ impl CollectWarnings { fn fallback_warning(&self, field_type: &str, value: &Bound<'_, PyAny>) { if self.mode != WarningsMode::None { - let type_name = value - .get_type() - .qualname() - .unwrap_or_else(|_| PyString::new(value.py(), "")); - - let value_str = truncate_safe_repr(value, None); - - self.add_warning(format!( - "Expected `{field_type}` but got `{type_name}` with value `{value_str}` - serialized value may not be as expected" + self.register_warning(PydanticSerializationUnexpectedValue::new_from_parts( + Some(field_type.to_string()), + Some(value.clone().unbind()), )); } } - fn add_warning(&self, message: String) { - self.warnings.lock().expect("lock poisoned").push(message); - } - pub fn final_check(&self, py: Python) -> PyResult<()> { if self.mode == WarningsMode::None { return Ok(()); @@ -474,7 +464,9 @@ impl CollectWarnings { return Ok(()); } - let message = format!("Pydantic serializer warnings:\n {}", warnings.join("\n ")); + let formatted_warnings: Vec = warnings.iter().map(|w| w.__repr__(py).to_string()).collect(); + + let message = format!("Pydantic serializer warnings:\n {}", formatted_warnings.join("\n ")); if self.mode == WarningsMode::Warn { let user_warning_type = PyUserWarning::type_object(py); PyErr::warn(py, &user_warning_type, &CString::new(message)?, 0) diff --git a/src/serializers/fields.rs b/src/serializers/fields.rs index 25d4b14c2..7aa25c2f7 100644 --- a/src/serializers/fields.rs +++ b/src/serializers/fields.rs @@ -9,7 +9,6 @@ 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; @@ -201,15 +200,12 @@ impl GeneralFieldsSerializer { }; output_dict.set_item(key, value)?; } else if field_extra.check == SerCheck::Strict { - let type_name = field_extra.model_type_name(); - return Err(PydanticSerializationUnexpectedValue::new_from_msg(Some(format!( - "Unexpected field `{key}`{for_type_name}", - for_type_name = if let Some(type_name) = type_name { - format!(" for type `{type_name}`") - } else { - String::new() - }, - )))); + return Err(PydanticSerializationUnexpectedValue::new( + Some(format!("Unexpected field `{key}`")), + field_extra.model_type_name().map(|bound| bound.to_string()), + None, + ) + .to_py_err()); } } } @@ -221,16 +217,13 @@ impl GeneralFieldsSerializer { && self.required_fields > used_req_fields { let required_fields = self.required_fields; - let type_name = extra.model_type_name(); - let field_value = match extra.model { - Some(model) => truncate_safe_repr(model, Some(100)), - None => "".to_string(), - }; - Err(PydanticSerializationUnexpectedValue::new_from_msg(Some(format!( - "Expected {required_fields} fields but got {used_req_fields}{for_type_name} with value `{field_value}` - serialized value may not be as expected.", - for_type_name = if let Some(type_name) = type_name { format!(" for type `{type_name}`") } else { String::new() }, - )))) + Err(PydanticSerializationUnexpectedValue::new( + Some(format!("Expected {required_fields} fields but got {used_req_fields}. ").to_string()), + extra.model_type_name().map(|bound| bound.to_string()), + extra.model.map(|bound| bound.clone().unbind()), + ) + .to_py_err()) } else { Ok(output_dict) } diff --git a/src/serializers/type_serializers/datetime_etc.rs b/src/serializers/type_serializers/datetime_etc.rs index 6eb50f1fe..0b601745a 100644 --- a/src/serializers/type_serializers/datetime_etc.rs +++ b/src/serializers/type_serializers/datetime_etc.rs @@ -34,7 +34,7 @@ fn downcast_date_reject_datetime<'a, 'py>(py_date: &'a Bound<'py, PyAny>) -> PyR } } - Err(PydanticSerializationUnexpectedValue::new_from_msg(None)) + Err(PydanticSerializationUnexpectedValue::new_from_msg(None).to_py_err()) } macro_rules! build_serializer { diff --git a/src/serializers/type_serializers/float.rs b/src/serializers/type_serializers/float.rs index 427a8a778..41539813b 100644 --- a/src/serializers/type_serializers/float.rs +++ b/src/serializers/type_serializers/float.rs @@ -75,7 +75,7 @@ impl TypeSerializer for FloatSerializer { match extra.ob_type_lookup.is_type(value, ObType::Float) { IsType::Exact => Ok(value.clone().unbind()), IsType::Subclass => match extra.check { - SerCheck::Strict => Err(PydanticSerializationUnexpectedValue::new_from_msg(None)), + SerCheck::Strict => Err(PydanticSerializationUnexpectedValue::new_from_msg(None).to_py_err()), SerCheck::Lax | SerCheck::None => match extra.mode { SerMode::Json => value.extract::()?.into_py_any(py), _ => infer_to_python(value, include, exclude, extra), diff --git a/src/serializers/type_serializers/function.rs b/src/serializers/type_serializers/function.rs index 2525f2aa5..3c66583e2 100644 --- a/src/serializers/type_serializers/function.rs +++ b/src/serializers/type_serializers/function.rs @@ -194,7 +194,7 @@ fn on_error(py: Python, err: PyErr, function_name: &str, extra: &Extra) -> PyRes if extra.check.enabled() { Err(err) } else { - extra.warnings.custom_warning(ser_err.__repr__(py)); + extra.warnings.register_warning(ser_err); Ok(()) } } else if let Ok(err) = exception.extract::() { diff --git a/src/serializers/type_serializers/model.rs b/src/serializers/type_serializers/model.rs index 74737e0ba..4bae243fc 100644 --- a/src/serializers/type_serializers/model.rs +++ b/src/serializers/type_serializers/model.rs @@ -190,7 +190,7 @@ impl TypeSerializer for ModelSerializer { let py = value.py(); let root = value.getattr(intern!(py, ROOT_FIELD)).map_err(|original_err| { if root_extra.check.enabled() { - PydanticSerializationUnexpectedValue::new_from_msg(None) + PydanticSerializationUnexpectedValue::new_from_msg(None).to_py_err() } else { original_err } diff --git a/src/serializers/type_serializers/simple.rs b/src/serializers/type_serializers/simple.rs index 023e0cf61..41d69ce75 100644 --- a/src/serializers/type_serializers/simple.rs +++ b/src/serializers/type_serializers/simple.rs @@ -123,7 +123,7 @@ macro_rules! build_simple_serializer { match extra.ob_type_lookup.is_type(value, $ob_type) { IsType::Exact => Ok(value.clone().unbind()), IsType::Subclass => match extra.check { - SerCheck::Strict => Err(PydanticSerializationUnexpectedValue::new_from_msg(None)), + SerCheck::Strict => Err(PydanticSerializationUnexpectedValue::new_from_msg(None).to_py_err()), SerCheck::Lax | SerCheck::None => match extra.mode { SerMode::Json => value.extract::<$rust_type>()?.into_py_any(py), _ => infer_to_python(value, include, exclude, extra), diff --git a/src/serializers/type_serializers/tuple.rs b/src/serializers/type_serializers/tuple.rs index d7270b3c3..e5e3da353 100644 --- a/src/serializers/type_serializers/tuple.rs +++ b/src/serializers/type_serializers/tuple.rs @@ -218,7 +218,8 @@ impl TupleSerializer { "Expected {} items, but got {}", self.serializers.len(), n_items - )))); + ))) + .to_py_err()); } else { use_serializers!(self.serializers.iter()); let mut warned = false; @@ -226,7 +227,9 @@ impl TupleSerializer { if !warned { extra .warnings - .custom_warning("Unexpected extra items present in tuple".to_string()); + .register_warning(PydanticSerializationUnexpectedValue::new_from_msg(Some( + "Unexpected extra items present in tuple".to_string(), + ))); warned = true; } let op_next = self diff --git a/src/serializers/type_serializers/union.rs b/src/serializers/type_serializers/union.rs index 1f3efdc9f..62779a640 100644 --- a/src/serializers/type_serializers/union.rs +++ b/src/serializers/type_serializers/union.rs @@ -9,7 +9,7 @@ use crate::build_tools::py_schema_err; use crate::common::union::{Discriminator, SMALL_UNION_THRESHOLD}; use crate::definitions::DefinitionsBuilder; use crate::serializers::PydanticSerializationUnexpectedValue; -use crate::tools::{truncate_safe_repr, SchemaDict}; +use crate::tools::SchemaDict; use super::{ infer_json_key, infer_serialize, infer_to_python, BuildSerializer, CombinedSerializer, Extra, SerCheck, @@ -78,6 +78,7 @@ fn union_serialize( extra: &Extra, choices: &[CombinedSerializer], retry_with_lax_check: bool, + py: Python<'_>, ) -> PyResult> { // try the serializers in left to right order with error_on fallback=true let mut new_extra = extra.clone(); @@ -104,14 +105,23 @@ fn union_serialize( // If extra.check is SerCheck::None, we're in a top-level union. We should thus raise the warnings if extra.check == SerCheck::None { for err in &errors { - extra.warnings.custom_warning(err.to_string()); + if err.is_instance_of::(py) { + let pydantic_err: PydanticSerializationUnexpectedValue = err.value(py).extract()?; + extra.warnings.register_warning(pydantic_err); + } else { + extra + .warnings + .register_warning(PydanticSerializationUnexpectedValue::new_from_msg(Some( + err.to_string(), + ))); + } } } // Otherwise, if we've encountered errors, return them to the parent union, which should take // care of the formatting for us else if !errors.is_empty() { let message = errors.iter().map(ToString::to_string).collect::>().join("\n"); - return Err(PydanticSerializationUnexpectedValue::new_from_msg(Some(message))); + return Err(PydanticSerializationUnexpectedValue::new_from_msg(Some(message)).to_py_err()); } Ok(None) @@ -130,6 +140,7 @@ impl TypeSerializer for UnionSerializer { extra, &self.choices, self.retry_with_lax_check(), + value.py(), )? .map_or_else(|| infer_to_python(value, include, exclude, extra), Ok) } @@ -140,6 +151,7 @@ impl TypeSerializer for UnionSerializer { extra, &self.choices, self.retry_with_lax_check(), + key.py(), )? .map_or_else(|| infer_json_key(key, extra), Ok) } @@ -157,6 +169,7 @@ impl TypeSerializer for UnionSerializer { extra, &self.choices, self.retry_with_lax_check(), + value.py(), ) { Ok(Some(v)) => infer_serialize(v.bind(value.py()), serializer, None, None, extra), Ok(None) => infer_serialize(value, serializer, include, exclude, extra), @@ -328,10 +341,11 @@ impl TaggedUnionSerializer { } else if extra.check == SerCheck::None { // If extra.check is SerCheck::None, we're in a top-level union. We should thus raise // this warning - let value_str = truncate_safe_repr(value, None); - extra.warnings.custom_warning( - format!( - "Failed to get discriminator value for tagged union serialization with value `{value_str}` - defaulting to left to right union serialization." + extra.warnings.register_warning( + PydanticSerializationUnexpectedValue::new( + Some("Defaulting to left to right union serialization - failed to get discriminator value for tagged union serialization".to_string()), + None, + Some(value.clone().unbind()), ) ); } @@ -339,6 +353,6 @@ impl TaggedUnionSerializer { // if we haven't returned at this point, we should fallback to the union serializer // which preserves the historical expectation that we do our best with serialization // even if that means we resort to inference - union_serialize(selector, extra, &self.choices, self.retry_with_lax_check()) + union_serialize(selector, extra, &self.choices, self.retry_with_lax_check(), value.py()) } } diff --git a/src/tools.rs b/src/tools.rs index 21631b451..96146d30c 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -118,6 +118,7 @@ pub fn safe_repr<'py>(v: &Bound<'py, PyAny>) -> ReprOutput<'py> { } } +// warning: this function can be incredibly slow, so use with caution pub fn truncate_safe_repr(v: &Bound<'_, PyAny>, max_len: Option) -> String { let max_len = max_len.unwrap_or(50); // default to 100 bytes let input_str = safe_repr(v); diff --git a/tests/serializers/test_any.py b/tests/serializers/test_any.py index c3b3e2f8f..d2391d8a2 100644 --- a/tests/serializers/test_any.py +++ b/tests/serializers/test_any.py @@ -163,9 +163,10 @@ def test_any_with_date_serializer(): with pytest.warns(UserWarning) as warning_info: assert s.to_python(b'bang', mode='json') == 'bang' - assert [w.message.args[0] for w in warning_info.list] == [ - "Pydantic serializer warnings:\n Expected `date` but got `bytes` with value `b'bang'` - serialized value may not be as expected" - ] + assert ( + "Expected `date` but got `bytes` with value `b'bang'` - serialized value may not be as expected" + in warning_info.list[0].message.args[0] + ) def test_any_with_timedelta_serializer(): @@ -177,10 +178,10 @@ def test_any_with_timedelta_serializer(): with pytest.warns(UserWarning) as warning_info: assert s.to_python(b'bang', mode='json') == 'bang' - assert [w.message.args[0] for w in warning_info.list] == [ - "Pydantic serializer warnings:\n Expected `timedelta` but got `bytes` with value `b'bang'` - " - 'serialized value may not be as expected' - ] + assert ( + "Expected `timedelta` but got `bytes` with value `b'bang'` - serialized value may not be as expected" + in warning_info.list[0].message.args[0] + ) def test_any_config_timedelta_float(): diff --git a/tests/serializers/test_list_tuple.py b/tests/serializers/test_list_tuple.py index 5e8443805..b3f400aba 100644 --- a/tests/serializers/test_list_tuple.py +++ b/tests/serializers/test_list_tuple.py @@ -30,9 +30,9 @@ def test_list_fallback(): with pytest.warns(UserWarning) as warning_info: assert v.to_json('apple') == b'"apple"' - assert [w.message.args[0] for w in warning_info.list] == [ - "Pydantic serializer warnings:\n Expected `list[any]` but got `str` with value `'apple'` - serialized value may not be as expected" - ] + "Expected `list[any]` but got `str` with value `'apple'` - serialized value may not be as expected" in warning_info.list[ + 0 + ].message.args[0] msg = "Expected `list[any]` but got `bytes` with value `b'apple'` - serialized value may not be as expected" with pytest.warns(UserWarning, match=re.escape(msg)): @@ -52,22 +52,20 @@ def test_list_str_fallback(): v = SchemaSerializer(core_schema.list_schema(core_schema.str_schema())) with pytest.warns(UserWarning) as warning_info: assert v.to_json([1, 2, 3]) == b'[1,2,3]' - assert [w.message.args[0] for w in warning_info.list] == [ - 'Pydantic serializer warnings:\n' - ' Expected `str` but got `int` with value `1` - serialized value may not be as expected\n' - ' Expected `str` but got `int` with value `2` - serialized value may not be as expected\n' - ' Expected `str` but got `int` with value `3` - serialized value may not be as expected' + + expected_warnings = [ + 'Expected `str` but got `int` with value `1`', + 'Expected `str` but got `int` with value `2`', + 'Expected `str` but got `int` with value `3`', ] + + all_warnings = ''.join([w.message.args[0] for w in warning_info.list]) + assert all([x in all_warnings for x in expected_warnings]) + with pytest.raises(PydanticSerializationError) as warning_ex: v.to_json([1, 2, 3], warnings='error') - assert str(warning_ex.value) == ''.join( - [ - 'Pydantic serializer warnings:\n' - ' Expected `str` but got `int` with value `1` - serialized value may not be as expected\n' - ' Expected `str` but got `int` with value `2` - serialized value may not be as expected\n' - ' Expected `str` but got `int` with value `3` - serialized value may not be as expected' - ] - ) + + assert all([x in str(warning_ex.value) for x in expected_warnings]) def test_tuple_any(): @@ -256,8 +254,9 @@ def test_tuple_fallback(): with pytest.warns(UserWarning) as warning_info: assert v.to_json([1, 2, 3]) == b'[1,2,3]' assert [w.message.args[0] for w in warning_info.list] == [ - 'Pydantic serializer warnings:\n Expected `tuple[any, ...]` but got `list` with value `[1, 2, 3]` - ' - 'serialized value may not be as expected' + 'Pydantic serializer warnings:\n' + ' PydanticSerializationUnexpectedValue(Expected `tuple[any, ...]` but got ' + '`list` with value `[1, 2, 3]` - serialized value may not be as expected.)', ] msg = "Expected `tuple[any, ...]` but got `bytes` with value `b'apple'` - serialized value may not be as expected" diff --git a/tests/serializers/test_model.py b/tests/serializers/test_model.py index 8786359e8..c9c9f5c02 100644 --- a/tests/serializers/test_model.py +++ b/tests/serializers/test_model.py @@ -1171,7 +1171,9 @@ class BModel(BasicModel): ... ) ) - with pytest.warns(UserWarning, match='Expected 2 fields but got 1 for type `.*AModel` with value `.*`.+'): + with pytest.warns( + UserWarning, match='Expected 2 fields but got 1. Expected `AModel` but got `AModel` with value `.*`.+' + ): value = BasicModel(root=AModel(type='a')) s.to_python(value) From 4db4ec8c748274fd12bf2293f47e4b7685ebc52c Mon Sep 17 00:00:00 2001 From: sydney-runkle Date: Thu, 27 Feb 2025 17:11:20 -0500 Subject: [PATCH 3/4] fix test --- tests/serializers/test_list_tuple.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/serializers/test_list_tuple.py b/tests/serializers/test_list_tuple.py index b3f400aba..0878db396 100644 --- a/tests/serializers/test_list_tuple.py +++ b/tests/serializers/test_list_tuple.py @@ -253,11 +253,8 @@ def test_tuple_fallback(): with pytest.warns(UserWarning) as warning_info: assert v.to_json([1, 2, 3]) == b'[1,2,3]' - assert [w.message.args[0] for w in warning_info.list] == [ - 'Pydantic serializer warnings:\n' - ' PydanticSerializationUnexpectedValue(Expected `tuple[any, ...]` but got ' - '`list` with value `[1, 2, 3]` - serialized value may not be as expected.)', - ] + + assert 'Expected `tuple[any, ...]` but got `list` with value `[1, 2, 3]`' in warning_info.list[0].message.args[0] msg = "Expected `tuple[any, ...]` but got `bytes` with value `b'apple'` - serialized value may not be as expected" with pytest.warns(UserWarning, match=re.escape(msg)): From 747a2930254ec5916e7416779aeb77e7788badfd Mon Sep 17 00:00:00 2001 From: sydney-runkle Date: Mon, 3 Mar 2025 10:51:42 -0500 Subject: [PATCH 4/4] fix tests and message formatting --- src/serializers/errors.rs | 16 ++++--- src/serializers/fields.rs | 2 +- tests/serializers/test_any.py | 4 +- tests/serializers/test_bytes.py | 12 +++-- tests/serializers/test_datetime.py | 4 +- tests/serializers/test_decimal.py | 6 ++- tests/serializers/test_enum.py | 30 ++++++++----- tests/serializers/test_functions.py | 26 +++++++---- tests/serializers/test_generator.py | 24 +++++++--- tests/serializers/test_list_tuple.py | 60 ++++++++++++++++--------- tests/serializers/test_model.py | 19 +++++--- tests/serializers/test_nullable.py | 3 +- tests/serializers/test_other.py | 3 +- tests/serializers/test_set_frozenset.py | 29 +++++++----- tests/serializers/test_simple.py | 6 +-- tests/serializers/test_string.py | 35 +++++++++------ tests/serializers/test_timedelta.py | 4 +- tests/serializers/test_union.py | 22 ++++----- tests/serializers/test_url.py | 4 +- tests/serializers/test_uuid.py | 6 ++- 20 files changed, 202 insertions(+), 113 deletions(-) diff --git a/src/serializers/errors.rs b/src/serializers/errors.rs index 88cf4d09b..5f736c11b 100644 --- a/src/serializers/errors.rs +++ b/src/serializers/errors.rs @@ -148,15 +148,21 @@ impl PydanticSerializationUnexpectedValue { } pub(crate) fn __str__(&self, py: Python) -> String { - let mut message = self.message.as_deref().unwrap_or("Unexpected Value").to_string(); + let mut message = self.message.as_deref().unwrap_or("").to_string(); if let Some(field_type) = &self.field_type { + if !message.is_empty() { + message.push_str(": "); + } message.push_str(&format!("Expected `{field_type}`")); + if self.input_value.is_some() { + message.push_str(" - serialized value may not be as expected"); + } } if let Some(input_value) = &self.input_value { let bound_input = input_value.bind(py); - let type_name = bound_input + let input_type = bound_input .get_type() .name() .unwrap_or_else(|_| PyString::new(py, "")) @@ -164,11 +170,11 @@ impl PydanticSerializationUnexpectedValue { let value_str = truncate_safe_repr(bound_input, None); - message.push_str(&format!(" but got `{type_name}` with value `{value_str}`")); + message.push_str(&format!(" [input_value={value_str}, input_type={input_type}]")); } - if self.input_value.is_some() || self.field_type.is_some() { - message.push_str(" - serialized value may not be as expected."); + if message.is_empty() { + message = "Unexpected Value".to_string(); } message diff --git a/src/serializers/fields.rs b/src/serializers/fields.rs index 7aa25c2f7..b89468d4e 100644 --- a/src/serializers/fields.rs +++ b/src/serializers/fields.rs @@ -219,7 +219,7 @@ impl GeneralFieldsSerializer { let required_fields = self.required_fields; Err(PydanticSerializationUnexpectedValue::new( - Some(format!("Expected {required_fields} fields but got {used_req_fields}. ").to_string()), + Some(format!("Expected {required_fields} fields but got {used_req_fields}").to_string()), extra.model_type_name().map(|bound| bound.to_string()), extra.model.map(|bound| bound.clone().unbind()), ) diff --git a/tests/serializers/test_any.py b/tests/serializers/test_any.py index d2391d8a2..09d492177 100644 --- a/tests/serializers/test_any.py +++ b/tests/serializers/test_any.py @@ -164,7 +164,7 @@ def test_any_with_date_serializer(): assert s.to_python(b'bang', mode='json') == 'bang' assert ( - "Expected `date` but got `bytes` with value `b'bang'` - serialized value may not be as expected" + "Expected `date` - serialized value may not be as expected [input_value=b'bang', input_type=bytes]" in warning_info.list[0].message.args[0] ) @@ -179,7 +179,7 @@ def test_any_with_timedelta_serializer(): assert s.to_python(b'bang', mode='json') == 'bang' assert ( - "Expected `timedelta` but got `bytes` with value `b'bang'` - serialized value may not be as expected" + "Expected `timedelta` - serialized value may not be as expected [input_value=b'bang', input_type=bytes]" in warning_info.list[0].message.args[0] ) diff --git a/tests/serializers/test_bytes.py b/tests/serializers/test_bytes.py index 32c11fac9..b5d22aba0 100644 --- a/tests/serializers/test_bytes.py +++ b/tests/serializers/test_bytes.py @@ -47,19 +47,23 @@ def test_bytes_dict_key(): def test_bytes_fallback(): s = SchemaSerializer(core_schema.bytes_schema()) with pytest.warns( - UserWarning, match='Expected `bytes` but got `int` with value `123` - serialized value may not be as expected' + UserWarning, + match=r'Expected `bytes` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert s.to_python(123) == 123 with pytest.warns( - UserWarning, match='Expected `bytes` but got `int` with value `123` - serialized value may not be as expected' + UserWarning, + match=r'Expected `bytes` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert s.to_python(123, mode='json') == 123 with pytest.warns( - UserWarning, match='Expected `bytes` but got `int` with value `123` - serialized value may not be as expected' + UserWarning, + match=r'Expected `bytes` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert s.to_json(123) == b'123' with pytest.warns( - UserWarning, match="Expected `bytes` but got `str` with value `'foo'` - serialized value may not be as expected" + UserWarning, + match=r"Expected `bytes` - serialized value may not be as expected \[input_value='foo', input_type=str\]", ): assert s.to_json('foo') == b'"foo"' diff --git a/tests/serializers/test_datetime.py b/tests/serializers/test_datetime.py index 19d5477f3..e6f71ba0c 100644 --- a/tests/serializers/test_datetime.py +++ b/tests/serializers/test_datetime.py @@ -14,13 +14,13 @@ def test_datetime(): with pytest.warns( UserWarning, - match='Expected `datetime` but got `int` with value `123` - serialized value may not be as expected', + match=r'Expected `datetime` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert v.to_python(123, mode='json') == 123 with pytest.warns( UserWarning, - match='Expected `datetime` but got `int` with value `123` - serialized value may not be as expected', + match=r'Expected `datetime` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert v.to_json(123) == b'123' diff --git a/tests/serializers/test_decimal.py b/tests/serializers/test_decimal.py index 277843aec..e92529b72 100644 --- a/tests/serializers/test_decimal.py +++ b/tests/serializers/test_decimal.py @@ -21,12 +21,14 @@ def test_decimal(): ) with pytest.warns( - UserWarning, match='Expected `decimal` but got `int` with value `123` - serialized value may not be as expected' + UserWarning, + match=r'Expected `decimal` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert v.to_python(123, mode='json') == 123 with pytest.warns( - UserWarning, match='Expected `decimal` but got `int` with value `123` - serialized value may not be as expected' + UserWarning, + match=r'Expected `decimal` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert v.to_json(123) == b'123' diff --git a/tests/serializers/test_enum.py b/tests/serializers/test_enum.py index 66ab00691..bac3aa406 100644 --- a/tests/serializers/test_enum.py +++ b/tests/serializers/test_enum.py @@ -18,11 +18,13 @@ class MyEnum(Enum): assert v.to_json(MyEnum.a) == b'1' with pytest.warns( - UserWarning, match='Expected `enum` but got `int` with value `1` - serialized value may not be as expected' + UserWarning, + match=r'Expected `enum` - serialized value may not be as expected \[input_value=1, input_type=int\]', ): assert v.to_python(1) == 1 with pytest.warns( - UserWarning, match='Expected `enum` but got `int` with value `1` - serialized value may not be as expected' + UserWarning, + match=r'Expected `enum` - serialized value may not be as expected \[input_value=1, input_type=int\]', ): assert v.to_json(1) == b'1' @@ -40,11 +42,13 @@ class MyEnum(int, Enum): assert v.to_json(MyEnum.a) == b'1' with pytest.warns( - UserWarning, match='Expected `enum` but got `int` with value `1` - serialized value may not be as expected' + UserWarning, + match=r'Expected `enum` - serialized value may not be as expected \[input_value=1, input_type=int\]', ): assert v.to_python(1) == 1 with pytest.warns( - UserWarning, match='Expected `enum` but got `int` with value `1` - serialized value may not be as expected' + UserWarning, + match=r'Expected `enum` - serialized value may not be as expected \[input_value=1, input_type=int\]', ): assert v.to_json(1) == b'1' @@ -62,11 +66,13 @@ class MyEnum(str, Enum): assert v.to_json(MyEnum.a) == b'"a"' with pytest.warns( - UserWarning, match="Expected `enum` but got `str` with value `'a'` - serialized value may not be as expected" + UserWarning, + match=r"Expected `enum` - serialized value may not be as expected \[input_value='a', input_type=str\]", ): assert v.to_python('a') == 'a' with pytest.warns( - UserWarning, match="Expected `enum` but got `str` with value `'a'` - serialized value may not be as expected" + UserWarning, + match=r"Expected `enum` - serialized value may not be as expected \[input_value='a', input_type=str\]", ): assert v.to_json('a') == b'"a"' @@ -89,11 +95,13 @@ class MyEnum(Enum): assert v.to_json({MyEnum.a: 'x'}) == b'{"1":"x"}' with pytest.warns( - UserWarning, match="Expected `enum` but got `str` with value `'x'` - serialized value may not be as expected" + UserWarning, + match=r"Expected `enum` - serialized value may not be as expected \[input_value='x', input_type=str\]", ): assert v.to_python({'x': 'x'}) == {'x': 'x'} with pytest.warns( - UserWarning, match="Expected `enum` but got `str` with value `'x'` - serialized value may not be as expected" + UserWarning, + match=r"Expected `enum` - serialized value may not be as expected \[input_value='x', input_type=str\]", ): assert v.to_json({'x': 'x'}) == b'{"x":"x"}' @@ -116,10 +124,12 @@ class MyEnum(int, Enum): assert v.to_json({MyEnum.a: 'x'}) == b'{"1":"x"}' with pytest.warns( - UserWarning, match="Expected `enum` but got `str` with value `'x'` - serialized value may not be as expected" + UserWarning, + match=r"Expected `enum` - serialized value may not be as expected \[input_value='x', input_type=str\]", ): assert v.to_python({'x': 'x'}) == {'x': 'x'} with pytest.warns( - UserWarning, match="Expected `enum` but got `str` with value `'x'` - serialized value may not be as expected" + UserWarning, + match=r"Expected `enum` - serialized value may not be as expected \[input_value='x', input_type=str\]", ): assert v.to_json({'x': 'x'}) == b'{"x":"x"}' diff --git a/tests/serializers/test_functions.py b/tests/serializers/test_functions.py index 738e8acc2..926749721 100644 --- a/tests/serializers/test_functions.py +++ b/tests/serializers/test_functions.py @@ -207,7 +207,7 @@ def append_42(value, _info): assert s.to_python([1, 2, 3], mode='json') == [1, 2, 3, 42] assert s.to_json([1, 2, 3]) == b'[1,2,3,42]' - msg = r"Expected `list\[int\]` but got `str` with value `'abc'` - serialized value may not be as expected" + msg = r"Expected `list\[int\]` - serialized value may not be as expected \[input_value='abc', input_type=str\]" with pytest.warns(UserWarning, match=msg): assert s.to_python('abc') == 'abc' @@ -323,15 +323,18 @@ def test_wrong_return_type(): ) ) with pytest.warns( - UserWarning, match="Expected `int` but got `str` with value `'123'` - serialized value may not be as expected" + UserWarning, + match=r"Expected `int` - serialized value may not be as expected \[input_value='123', input_type=str\]", ): assert s.to_python(123) == '123' with pytest.warns( - UserWarning, match="Expected `int` but got `str` with value `'123'` - serialized value may not be as expected" + UserWarning, + match=r"Expected `int` - serialized value may not be as expected \[input_value='123', input_type=str\]", ): assert s.to_python(123, mode='json') == '123' with pytest.warns( - UserWarning, match="Expected `int` but got `str` with value `'123'` - serialized value may not be as expected" + UserWarning, + match=r"Expected `int` - serialized value may not be as expected \[input_value='123', input_type=str\]", ): assert s.to_json(123) == b'"123"' @@ -363,15 +366,18 @@ def f(value, serializer): assert s.to_python(3, mode='json') == 'result=3' assert s.to_json(3) == b'"result=3"' with pytest.warns( - UserWarning, match='Expected `str` but got `int` with value `42` - serialized value may not be as expected' + UserWarning, + match=r'Expected `str` - serialized value may not be as expected \[input_value=42, input_type=int\]', ): assert s.to_python(42) == 42 with pytest.warns( - UserWarning, match='Expected `str` but got `int` with value `42` - serialized value may not be as expected' + UserWarning, + match=r'Expected `str` - serialized value may not be as expected \[input_value=42, input_type=int\]', ): assert s.to_python(42, mode='json') == 42 with pytest.warns( - UserWarning, match='Expected `str` but got `int` with value `42` - serialized value may not be as expected' + UserWarning, + match=r'Expected `str` - serialized value may not be as expected \[input_value=42, input_type=int\]', ): assert s.to_json(42) == b'42' @@ -624,7 +630,8 @@ def f(value, _info): s = SchemaSerializer(core_schema.with_info_after_validator_function(f, core_schema.int_schema())) with pytest.warns( - UserWarning, match="Expected `int` but got `str` with value `'abc'` - serialized value may not be as expected" + UserWarning, + match=r"Expected `int` - serialized value may not be as expected \[input_value='abc', input_type=str\]", ): assert s.to_python('abc') == 'abc' @@ -635,7 +642,8 @@ def f(value, handler, _info): s = SchemaSerializer(core_schema.with_info_wrap_validator_function(f, core_schema.int_schema())) with pytest.warns( - UserWarning, match="Expected `int` but got `str` with value `'abc'` - serialized value may not be as expected" + UserWarning, + match=r"Expected `int` - serialized value may not be as expected \[input_value='abc', input_type=str\]", ): assert s.to_python('abc') == 'abc' diff --git a/tests/serializers/test_generator.py b/tests/serializers/test_generator.py index cbd72ee8e..b0ddbf0b1 100644 --- a/tests/serializers/test_generator.py +++ b/tests/serializers/test_generator.py @@ -54,12 +54,20 @@ def test_generator_any(): assert s.to_json(iter(['a', b'b', 3])) == b'["a","b",3]' assert s.to_json(gen_ok('a', b'b', 3)) == b'["a","b",3]' - msg = 'Expected `generator` but got `int` with value `4` - serialized value may not be as expected' - with pytest.warns(UserWarning, match=msg): + with pytest.warns( + UserWarning, + match=r'Expected `generator` - serialized value may not be as expected \[input_value=4, input_type=int\]', + ): assert s.to_python(4) == 4 - with pytest.warns(UserWarning, match="Expected `generator` but got `tuple` with value `\\('a', b'b', 3\\)`"): + with pytest.warns( + UserWarning, + match=r"Expected `generator` - serialized value may not be as expected \[input_value=\('a', b'b', 3\), input_type=tuple\]", + ): assert s.to_python(('a', b'b', 3)) == ('a', b'b', 3) - with pytest.warns(UserWarning, match="Expected `generator` but got `str` with value `'abc'`"): + with pytest.warns( + UserWarning, + match=r"Expected `generator` - serialized value may not be as expected \[input_value='abc', input_type=str\]", + ): assert s.to_python('abc') == 'abc' with pytest.raises(ValueError, match='oops'): @@ -89,19 +97,21 @@ def test_generator_int(): s.to_json(gen_error(1, 2)) with pytest.warns( - UserWarning, match="Expected `int` but got `str` with value `'a'` - serialized value may not be as expected" + UserWarning, + match=r"Expected `int` - serialized value may not be as expected \[input_value='a', input_type=str\]", ): s.to_json(gen_ok(1, 'a')) gen = s.to_python(gen_ok(1, 'a')) assert next(gen) == 1 with pytest.warns( - UserWarning, match="Expected `int` but got `str` with value `'a'` - serialized value may not be as expected" + UserWarning, + match=r"Expected `int` - serialized value may not be as expected \[input_value='a', input_type=str\]", ): assert next(gen) == 'a' with pytest.warns( UserWarning, - match='Expected `generator` but got `tuple` with value `\\(1, 2, 3\\)` - serialized value may not.+', + match=r'Expected `generator` - serialized value may not be as expected \[input_value=\(1, 2, 3\), input_type=tuple\]', ): s.to_python((1, 2, 3)) diff --git a/tests/serializers/test_list_tuple.py b/tests/serializers/test_list_tuple.py index 0878db396..7473ee261 100644 --- a/tests/serializers/test_list_tuple.py +++ b/tests/serializers/test_list_tuple.py @@ -1,5 +1,4 @@ import json -import re from functools import partial import pytest @@ -24,27 +23,35 @@ def test_list_any(): def test_list_fallback(): v = SchemaSerializer(core_schema.list_schema(core_schema.any_schema())) - msg = "Expected `list[any]` but got `str` with value `'apple'` - serialized value may not be as expected" - with pytest.warns(UserWarning, match=re.escape(msg)): + with pytest.warns( + UserWarning, + match=r"Expected `list\[any\]` - serialized value may not be as expected \[input_value='apple', input_type=str\]", + ): assert v.to_python('apple') == 'apple' with pytest.warns(UserWarning) as warning_info: assert v.to_json('apple') == b'"apple"' - "Expected `list[any]` but got `str` with value `'apple'` - serialized value may not be as expected" in warning_info.list[ + "Expected `list[any]` - serialized value may not be as expected [input_value='apple', input_type=str]" in warning_info.list[ 0 ].message.args[0] - msg = "Expected `list[any]` but got `bytes` with value `b'apple'` - serialized value may not be as expected" - with pytest.warns(UserWarning, match=re.escape(msg)): + with pytest.warns( + UserWarning, + match=r"Expected `list\[any\]` - serialized value may not be as expected \[input_value=b'apple', input_type=bytes\]", + ): assert v.to_json(b'apple') == b'"apple"' - msg = 'Expected `list[any]` but got `tuple` with value `(1, 2, 3)` - serialized value may not be as expected' - with pytest.warns(UserWarning, match=re.escape(msg)): + with pytest.warns( + UserWarning, + match=r'Expected `list\[any\]` - serialized value may not be as expected \[input_value=\(1, 2, 3\), input_type=tuple\]', + ): assert v.to_python((1, 2, 3)) == (1, 2, 3) # # even though we're in the fallback state, non JSON types should still be converted to JSON here - msg = 'Expected `list[any]` but got `tuple` with value `(1, 2, 3)` - serialized value may not be as expected' - with pytest.warns(UserWarning, match=re.escape(msg)): + with pytest.warns( + UserWarning, + match=r'Expected `list\[any\]` - serialized value may not be as expected \[input_value=\(1, 2, 3\), input_type=tuple\]', + ): assert v.to_python((1, 2, 3), mode='json') == [1, 2, 3] @@ -54,9 +61,9 @@ def test_list_str_fallback(): assert v.to_json([1, 2, 3]) == b'[1,2,3]' expected_warnings = [ - 'Expected `str` but got `int` with value `1`', - 'Expected `str` but got `int` with value `2`', - 'Expected `str` but got `int` with value `3`', + 'Expected `str` - serialized value may not be as expected [input_value=1, input_type=int]', + 'Expected `str` - serialized value may not be as expected [input_value=2, input_type=int]', + 'Expected `str` - serialized value may not be as expected [input_value=3, input_type=int]', ] all_warnings = ''.join([w.message.args[0] for w in warning_info.list]) @@ -247,24 +254,33 @@ def test_include_error_call_time(schema_func, seq_f, include, exclude): def test_tuple_fallback(): v = SchemaSerializer(core_schema.tuple_variable_schema(core_schema.any_schema())) - msg = "Expected `tuple[any, ...]` but got `str` with value `'apple'` - serialized value may not be as expected" - with pytest.warns(UserWarning, match=re.escape(msg)): + with pytest.warns( + UserWarning, + match=r"Expected `tuple\[any, ...\]` - serialized value may not be as expected \[input_value='apple', input_type=str\]", + ): assert v.to_python('apple') == 'apple' with pytest.warns(UserWarning) as warning_info: assert v.to_json([1, 2, 3]) == b'[1,2,3]' - assert 'Expected `tuple[any, ...]` but got `list` with value `[1, 2, 3]`' in warning_info.list[0].message.args[0] + assert ( + 'Expected `tuple[any, ...]` - serialized value may not be as expected [input_value=[1, 2, 3], input_type=list]' + in warning_info.list[0].message.args[0] + ) - msg = "Expected `tuple[any, ...]` but got `bytes` with value `b'apple'` - serialized value may not be as expected" - with pytest.warns(UserWarning, match=re.escape(msg)): + with pytest.warns( + UserWarning, + match=r"Expected `tuple\[any, ...\]` - serialized value may not be as expected \[input_value=b'apple', input_type=bytes\]", + ): assert v.to_json(b'apple') == b'"apple"' assert v.to_python((1, 2, 3)) == (1, 2, 3) # even though we're in the fallback state, non JSON types should still be converted to JSON here - msg = 'Expected `tuple[any, ...]` but got `list` with value `[1, 2, 3]` - serialized value may not be as expected' - with pytest.warns(UserWarning, match=re.escape(msg)): + with pytest.warns( + UserWarning, + match=r'Expected `tuple\[any, ...\]` - serialized value may not be as expected \[input_value=\[1, 2, 3\], input_type=list\]', + ): assert v.to_python([1, 2, 3], mode='json') == [1, 2, 3] @@ -394,13 +410,13 @@ def f(prefix, value, _info): def test_list_dict_key(): s = SchemaSerializer(core_schema.dict_schema(core_schema.list_schema(), core_schema.int_schema())) - with pytest.warns(UserWarning, match=r'Expected `list\[any\]` but got `str`'): + with pytest.warns(UserWarning, match=r'Expected `list\[any\]`.+ input_type=str'): assert s.to_python({'xx': 1}) == {'xx': 1} def test_tuple_var_dict_key(): s = SchemaSerializer(core_schema.dict_schema(core_schema.tuple_variable_schema(), core_schema.int_schema())) - with pytest.warns(UserWarning, match=r'Expected `tuple\[any, ...\]` but got `str`'): + with pytest.warns(UserWarning, match=r'Expected `tuple\[any, ...\]`.+input_type=str'): assert s.to_python({'xx': 1}) == {'xx': 1} assert s.to_python({(1, 2): 1}) == {(1, 2): 1} diff --git a/tests/serializers/test_model.py b/tests/serializers/test_model.py index c9c9f5c02..65871e050 100644 --- a/tests/serializers/test_model.py +++ b/tests/serializers/test_model.py @@ -236,16 +236,25 @@ def test_model_wrong_warn(): assert s.to_python(None, mode='json') is None assert s.to_json(None) == b'null' - with pytest.warns(UserWarning, match='Expected `MyModel` but got `int` with value `123` - serialized value may.+'): + with pytest.warns( + UserWarning, + match=r'Expected `MyModel` - serialized value may not be as expected \[input_value=123, input_type=int\]', + ): assert s.to_python(123) == 123 - with pytest.warns(UserWarning, match='Expected `MyModel` but got `int` with value `123` - serialized value may.+'): + with pytest.warns( + UserWarning, + match=r'Expected `MyModel` - serialized value may not be as expected \[input_value=123, input_type=int\]', + ): assert s.to_python(123, mode='json') == 123 - with pytest.warns(UserWarning, match='Expected `MyModel` but got `int` with value `123` - serialized value may.+'): + with pytest.warns( + UserWarning, + match=r'Expected `MyModel` - serialized value may not be as expected \[input_value=123, input_type=int\]', + ): assert s.to_json(123) == b'123' with pytest.warns( UserWarning, - match="Expected `MyModel` but got `dict` with value `{'foo': 1, 'bar': b'more'}` - serialized value may.+", + match=r"Expected `MyModel` - serialized value may not be as expected \[input_value={'foo': 1, 'bar': b'more'}, input_type=dict\]", ): assert s.to_python({'foo': 1, 'bar': b'more'}) == {'foo': 1, 'bar': b'more'} @@ -1172,7 +1181,7 @@ class BModel(BasicModel): ... ) with pytest.warns( - UserWarning, match='Expected 2 fields but got 1. Expected `AModel` but got `AModel` with value `.*`.+' + UserWarning, match='Expected 2 fields but got 1: Expected `AModel` - serialized value may not be as expected .+' ): value = BasicModel(root=AModel(type='a')) s.to_python(value) diff --git a/tests/serializers/test_nullable.py b/tests/serializers/test_nullable.py index 86b64a213..0fa00f15a 100644 --- a/tests/serializers/test_nullable.py +++ b/tests/serializers/test_nullable.py @@ -12,6 +12,7 @@ def test_nullable(): assert s.to_json(1) == b'1' assert s.to_json(None) == b'null' with pytest.warns( - UserWarning, match="Expected `int` but got `str` with value `'aaa'` - serialized value may not be as expected" + UserWarning, + match=r"Expected `int` - serialized value may not be as expected \[input_value='aaa', input_type=str\]", ): assert s.to_json('aaa') == b'"aaa"' diff --git a/tests/serializers/test_other.py b/tests/serializers/test_other.py index 13877d096..d6036db04 100644 --- a/tests/serializers/test_other.py +++ b/tests/serializers/test_other.py @@ -45,7 +45,8 @@ def test_lax_or_strict(): assert s.to_json('abc') == b'"abc"' with pytest.warns( - UserWarning, match='Expected `str` but got `int` with value `123` - serialized value may not be as expected' + UserWarning, + match=r'Expected `str` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert s.to_json(123) == b'123' diff --git a/tests/serializers/test_set_frozenset.py b/tests/serializers/test_set_frozenset.py index c2df80f0a..c7be8b69e 100644 --- a/tests/serializers/test_set_frozenset.py +++ b/tests/serializers/test_set_frozenset.py @@ -24,28 +24,37 @@ def test_frozenset_any(): @pytest.mark.parametrize( - 'input_value,json_output,warning_type', + 'input_value,json_output,expected_type', [ - ('apple', 'apple', r"`set\[int\]` but got `str` with value `'apple'`"), - ([1, 2, 3], [1, 2, 3], r'`set\[int\]` but got `list` with value `\[1, 2, 3\]`'), - ((1, 2, 3), [1, 2, 3], r'`set\[int\]` but got `tuple` with value `\(1, 2, 3\)`'), + ('apple', 'apple', r'set\[int\]'), + ([1, 2, 3], [1, 2, 3], r'set\[int\]'), + ((1, 2, 3), [1, 2, 3], r'set\[int\]'), ( frozenset([1, 2, 3]), IsList(1, 2, 3, check_order=False), - r'`set\[int\]` but got `frozenset` with value `frozenset\({1, 2, 3}\)`', + r'set\[int\]', ), - ({1, 2, 'a'}, IsList(1, 2, 'a', check_order=False), "`int` but got `str` with value `'a'`"), + ({1, 2, 'a'}, IsList(1, 2, 'a', check_order=False), 'int'), ], ) -def test_set_fallback(input_value, json_output, warning_type): +def test_set_fallback(input_value, json_output, expected_type): v = SchemaSerializer(core_schema.set_schema(core_schema.int_schema())) assert v.to_python({1, 2, 3}) == {1, 2, 3} - with pytest.warns(UserWarning, match=f'Expected {warning_type} - serialized value may not be as expected'): + with pytest.warns( + UserWarning, + match=f'Expected `{expected_type}` - serialized value may not be as expected', + ): assert v.to_python(input_value) == input_value - with pytest.warns(UserWarning, match=f'Expected {warning_type} - serialized value may not be as expected'): + with pytest.warns( + UserWarning, + match=f'Expected `{expected_type}` - serialized value may not be as expected', + ): assert v.to_python(input_value, mode='json') == json_output - with pytest.warns(UserWarning, match=f'Expected {warning_type} - serialized value may not be as expected'): + with pytest.warns( + UserWarning, + match=f'Expected `{expected_type}` - serialized value may not be as expected', + ): assert json.loads(v.to_json(input_value)) == json_output diff --git a/tests/serializers/test_simple.py b/tests/serializers/test_simple.py index 303c23463..cbedcf501 100644 --- a/tests/serializers/test_simple.py +++ b/tests/serializers/test_simple.py @@ -110,19 +110,19 @@ def test_simple_serializers_fallback(schema_type): s = SchemaSerializer({'type': schema_type}) with pytest.warns( UserWarning, - match=f'Expected `{schema_type}` but got `list` with value `\\[1, 2, 3\\]` - serialized value may not be as expected', + match=rf'Expected `{schema_type}` - serialized value may not be as expected \[input_value=\[1, 2, 3\], input_type=list\]', ): assert s.to_python([1, 2, 3]) == [1, 2, 3] with pytest.warns( UserWarning, - match=f"Expected `{schema_type}` but got `list` with value `\\[1, 2, b'bytes'\\]` - serialized value may not be as expected", + match=rf"Expected `{schema_type}` - serialized value may not be as expected \[input_value=\[1, 2, b'bytes'\], input_type=list\]", ): assert s.to_python([1, 2, b'bytes'], mode='json') == [1, 2, 'bytes'] with pytest.warns( UserWarning, - match=f'Expected `{schema_type}` but got `list` with value `\\[1, 2, 3\\]` - serialized value may not be as expected', + match=rf'Expected `{schema_type}` - serialized value may not be as expected \[input_value=\[1, 2, 3\], input_type=list\]', ): assert s.to_json([1, 2, 3]) == b'[1,2,3]' diff --git a/tests/serializers/test_string.py b/tests/serializers/test_string.py index 074179d8a..f547d49b2 100644 --- a/tests/serializers/test_string.py +++ b/tests/serializers/test_string.py @@ -25,7 +25,7 @@ def test_str(): def test_huge_str(): v = SchemaSerializer(core_schema.int_schema()) - msg = "Expected `int` but got `str` with value `'123456789012345678901234...89012345678901234567890'` - serialized value may not be as expected" + msg = r"Expected `int` - serialized value may not be as expected \[input_value='123456789012345678901234...89012345678901234567890', input_type=str\]" with pytest.warns(UserWarning, match=msg): v.to_python( '12345678901234567890123456789012345678901234567890123456789012345678901234567890\ @@ -41,39 +41,48 @@ def test_str_fallback(): assert s.to_python(None, mode='json') is None assert s.to_json(None) == b'null' with pytest.warns( - UserWarning, match='Expected `str` but got `int` with value `123` - serialized value may not be as expected' + UserWarning, + match=r'Expected `str` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert s.to_python(123) == 123 with pytest.warns( - UserWarning, match='Expected `str` but got `int` with value `123` - serialized value may not be as expected' + UserWarning, + match=r'Expected `str` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert s.to_python(123, mode='json') == 123 with pytest.warns( - UserWarning, match='Expected `str` but got `int` with value `123` - serialized value may not be as expected' + UserWarning, + match=r'Expected `str` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert s.to_json(123) == b'123' with pytest.warns( - UserWarning, match='Expected `str` but got `int` with value `123` - serialized value may not be as expected' + UserWarning, + match=r'Expected `str` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert s.to_python(123, warnings='warn') == 123 with pytest.warns( - UserWarning, match='Expected `str` but got `int` with value `123` - serialized value may not be as expected' + UserWarning, + match=r'Expected `str` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert s.to_python(123, mode='json', warnings='warn') == 123 with pytest.warns( - UserWarning, match='Expected `str` but got `int` with value `123` - serialized value may not be as expected' + UserWarning, + match=r'Expected `str` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert s.to_json(123, warnings='warn') == b'123' with pytest.warns( - UserWarning, match='Expected `str` but got `int` with value `123` - serialized value may not be as expected' + UserWarning, + match=r'Expected `str` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert s.to_python(123, warnings=True) == 123 with pytest.warns( - UserWarning, match='Expected `str` but got `int` with value `123` - serialized value may not be as expected' + UserWarning, + match=r'Expected `str` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert s.to_python(123, mode='json', warnings=True) == 123 with pytest.warns( - UserWarning, match='Expected `str` but got `int` with value `123` - serialized value may not be as expected' + UserWarning, + match=r'Expected `str` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert s.to_json(123, warnings=True) == b'123' @@ -92,17 +101,17 @@ def test_str_errors(): s = SchemaSerializer(core_schema.str_schema()) with pytest.raises( PydanticSerializationError, - match='Expected `str` but got `int` with value `123` - serialized value may not be as expected', + match=r'Expected `str` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert s.to_python(123, warnings='error') == 123 with pytest.raises( PydanticSerializationError, - match='Expected `str` but got `int` with value `123` - serialized value may not be as expected', + match=r'Expected `str` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert s.to_python(123, mode='json', warnings='error') == 123 with pytest.raises( PydanticSerializationError, - match='Expected `str` but got `int` with value `123` - serialized value may not be as expected', + match=r'Expected `str` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert s.to_json(123, warnings='error') == b'123' diff --git a/tests/serializers/test_timedelta.py b/tests/serializers/test_timedelta.py index 19abb673d..b5603ee79 100644 --- a/tests/serializers/test_timedelta.py +++ b/tests/serializers/test_timedelta.py @@ -19,13 +19,13 @@ def test_timedelta(): with pytest.warns( UserWarning, - match='Expected `timedelta` but got `int` with value `123` - serialized value may not be as expected', + match=r'Expected `timedelta` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert v.to_python(123, mode='json') == 123 with pytest.warns( UserWarning, - match='Expected `timedelta` but got `int` with value `123` - serialized value may not be as expected', + match=r'Expected `timedelta` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert v.to_json(123) == b'123' diff --git a/tests/serializers/test_union.py b/tests/serializers/test_union.py index 15b9bf2c0..7cf11639a 100644 --- a/tests/serializers/test_union.py +++ b/tests/serializers/test_union.py @@ -35,8 +35,8 @@ def test_union_error(): s = SchemaSerializer(core_schema.union_schema([core_schema.bool_schema(), core_schema.int_schema()])) messages = [ - "Expected `bool` but got `str` with value `'a string'` - serialized value may not be as expected", - "Expected `int` but got `str` with value `'a string'` - serialized value may not be as expected", + "Expected `bool` - serialized value may not be as expected [input_value='a string', input_type=str]", + "Expected `int` - serialized value may not be as expected [input_value='a string', input_type=str]", ] with warnings.catch_warnings(record=True) as w: @@ -916,10 +916,10 @@ def test_union_of_unions_of_models_invalid_variant(union_of_unions_schema: core_ s = SchemaSerializer(union_of_unions_schema) # All warnings should be available messages = [ - 'Expected `ModelA` but got `ModelAlien`', - 'Expected `ModelB` but got `ModelAlien`', - 'Expected `ModelCat` but got `ModelAlien`', - 'Expected `ModelDog` but got `ModelAlien`', + 'Expected `ModelA`', + 'Expected `ModelB`', + 'Expected `ModelCat`', + 'Expected `ModelDog`', ] with warnings.catch_warnings(record=True) as w: @@ -927,6 +927,7 @@ def test_union_of_unions_of_models_invalid_variant(union_of_unions_schema: core_ s.to_python(ModelAlien(type_='alien')) for m in messages: assert m in str(w[0].message) + assert 'input_type=ModelAlien' in str(w[0].message) @pytest.fixture @@ -981,10 +982,10 @@ def test_union_of_unions_of_models_with_tagged_union_invalid_variant( s = SchemaSerializer(tagged_union_of_unions_schema) # All warnings should be available messages = [ - 'Expected `ModelA` but got `ModelAlien`', - 'Expected `ModelB` but got `ModelAlien`', - 'Expected `ModelCat` but got `ModelAlien`', - 'Expected `ModelDog` but got `ModelAlien`', + 'Expected `ModelA`', + 'Expected `ModelB`', + 'Expected `ModelCat`', + 'Expected `ModelDog`', ] with warnings.catch_warnings(record=True) as w: @@ -992,6 +993,7 @@ def test_union_of_unions_of_models_with_tagged_union_invalid_variant( s.to_python(ModelAlien(type_='alien')) for m in messages: assert m in str(w[0].message) + assert 'input_type=ModelAlien' in str(w[0].message) def test_mixed_union_models_and_other_types() -> None: diff --git a/tests/serializers/test_url.py b/tests/serializers/test_url.py index be906c75b..8f826e5df 100644 --- a/tests/serializers/test_url.py +++ b/tests/serializers/test_url.py @@ -20,7 +20,7 @@ def test_url(): with pytest.warns( UserWarning, - match="Expected `url` but got `str` with value `'https://example.com'` - serialized value may not be as expected", + match=r"Expected `url` - serialized value may not be as expected \[input_value='https://example.com', input_type=str\]", ): assert s.to_python('https://example.com', mode='json') == 'https://example.com' @@ -40,7 +40,7 @@ def test_multi_host_url(): with pytest.warns( UserWarning, - match="Expected `multi-host-url` but got `str` with value `'https://ex.com,ex.org/path'` - serialized value may not be as expected", + match=r"Expected `multi-host-url` - serialized value may not be as expected \[input_value='https://ex.com,ex.org/path', input_type=str\]", ): assert s.to_python('https://ex.com,ex.org/path', mode='json') == 'https://ex.com,ex.org/path' diff --git a/tests/serializers/test_uuid.py b/tests/serializers/test_uuid.py index fed9cfc98..8aee57763 100644 --- a/tests/serializers/test_uuid.py +++ b/tests/serializers/test_uuid.py @@ -15,12 +15,14 @@ def test_uuid(): assert v.to_json(UUID('12345678-1234-5678-1234-567812345678')) == b'"12345678-1234-5678-1234-567812345678"' with pytest.warns( - UserWarning, match='Expected `uuid` but got `int` with value `123` - serialized value may not be as expected' + UserWarning, + match=r'Expected `uuid` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert v.to_python(123, mode='json') == 123 with pytest.warns( - UserWarning, match='Expected `uuid` but got `int` with value `123` - serialized value may not be as expected' + UserWarning, + match=r'Expected `uuid` - serialized value may not be as expected \[input_value=123, input_type=int\]', ): assert v.to_json(123) == b'123'