diff --git a/src/serializers/errors.rs b/src/serializers/errors.rs index ffd43407f..5f736c11b 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).to_py_err() } else { - PydanticSerializationUnexpectedValue::new_err(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()) @@ -94,30 +97,90 @@ 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) -> 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 to_py_err(&self) -> PyErr { + PyErr::new::, Option, Option)>(( + self.message.clone(), + self.field_type.clone(), + self.input_value.clone(), + )) } } #[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", + pub(crate) fn __str__(&self, py: Python) -> 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 input_type = 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!(" [input_value={value_str}, input_type={input_type}]")); } + + if message.is_empty() { + message = "Unexpected Value".to_string(); + } + + message } - 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..82c432342 100644 --- a/src/serializers/extra.rs +++ b/src/serializers/extra.rs @@ -17,7 +17,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` @@ -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); } } @@ -415,15 +414,11 @@ 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( + Some(field_type.to_string()), + Some(value.clone().unbind()), + ) + .to_py_err()) } else { self.fallback_warning(field_type, value); Ok(()) @@ -452,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(()); @@ -479,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 b0da9ba56..b89468d4e 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_err(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_err(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 3486b5999..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_err(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 fd79a88b5..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_err(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 79317035d..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__()); + 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 274fb48a0..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_err(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 2cbed2a58..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_err(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 1d8057475..e5e3da353 100644 --- a/src/serializers/type_serializers/tuple.rs +++ b/src/serializers/type_serializers/tuple.rs @@ -214,11 +214,12 @@ 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 - )))); + ))) + .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 2c10c1f7b..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_err(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..09d492177 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` - serialized value may not be as expected [input_value=b'bang', input_type=bytes]" + 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` - serialized value may not be as expected [input_value=b'bang', input_type=bytes]" + in warning_info.list[0].message.args[0] + ) def test_any_config_timedelta_float(): 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 5e8443805..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"' - 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" - ] - - 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)): + "Expected `list[any]` - serialized value may not be as expected [input_value='apple', input_type=str]" in warning_info.list[ + 0 + ].message.args[0] + + 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] @@ -52,22 +59,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` - 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]) + 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(): @@ -249,26 +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 [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' - ] - 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)): + 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] + ) + + 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] @@ -398,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 8786359e8..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'} @@ -1171,7 +1180,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` - 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'