Skip to content

Commit 3e23511

Browse files
authored
Merge branch 'main' into add-millisecond
2 parents d313c90 + bc0c97a commit 3e23511

File tree

9 files changed

+75
-87
lines changed

9 files changed

+75
-87
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "pydantic-core"
3-
version = "2.23.3"
3+
version = "2.23.4"
44
edition = "2021"
55
license = "MIT"
66
homepage = "https://github.com/pydantic/pydantic-core"

python/pydantic_core/_pydantic_core.pyi

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import datetime
2+
from collections.abc import Mapping
23
from typing import Any, Callable, Generic, Literal, TypeVar, final
34

45
from _typeshed import SupportsAllComparisons
@@ -237,7 +238,7 @@ class SchemaValidator:
237238
`None` if the schema has no default value, otherwise a [`Some`][pydantic_core.Some] containing the default.
238239
"""
239240

240-
_IncEx: TypeAlias = set[int] | set[str] | dict[int, _IncEx | bool] | dict[str, _IncEx | bool]
241+
_IncEx: TypeAlias = set[int] | set[str] | Mapping[int, _IncEx | Literal[True]] | Mapping[str, _IncEx | Literal[True]]
241242

242243
@final
243244
class SchemaSerializer:

src/serializers/extra.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,15 @@ impl CollectWarnings {
392392
if value.is_none() {
393393
Ok(())
394394
} else if extra.check.enabled() {
395-
Err(PydanticSerializationUnexpectedValue::new_err(None))
395+
let type_name = value
396+
.get_type()
397+
.qualname()
398+
.unwrap_or_else(|_| PyString::new_bound(value.py(), "<unknown python object>"));
399+
400+
let value_str = truncate_safe_repr(value, None);
401+
Err(PydanticSerializationUnexpectedValue::new_err(Some(format!(
402+
"Expected `{field_type}` but got `{type_name}` with value `{value_str}` - serialized value may not be as expected"
403+
))))
396404
} else {
397405
self.fallback_warning(field_type, value);
398406
Ok(())

src/serializers/type_serializers/union.rs

Lines changed: 29 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ use std::borrow::Cow;
88
use crate::build_tools::py_schema_err;
99
use crate::common::union::{Discriminator, SMALL_UNION_THRESHOLD};
1010
use crate::definitions::DefinitionsBuilder;
11-
use crate::serializers::type_serializers::py_err_se_err;
1211
use crate::tools::{truncate_safe_repr, SchemaDict};
13-
use crate::PydanticSerializationUnexpectedValue;
1412

1513
use super::{
1614
infer_json_key, infer_serialize, infer_to_python, BuildSerializer, CombinedSerializer, Extra, SerCheck,
@@ -77,7 +75,6 @@ fn to_python(
7775
exclude: Option<&Bound<'_, PyAny>>,
7876
extra: &Extra,
7977
choices: &[CombinedSerializer],
80-
name: &str,
8178
retry_with_lax_check: bool,
8279
) -> PyResult<PyObject> {
8380
// try the serializers in left to right order with error_on fallback=true
@@ -88,22 +85,15 @@ fn to_python(
8885
for comb_serializer in choices {
8986
match comb_serializer.to_python(value, include, exclude, &new_extra) {
9087
Ok(v) => return Ok(v),
91-
Err(err) => match err.is_instance_of::<PydanticSerializationUnexpectedValue>(value.py()) {
92-
true => (),
93-
false => errors.push(err),
94-
},
88+
Err(err) => errors.push(err),
9589
}
9690
}
9791

9892
if retry_with_lax_check {
9993
new_extra.check = SerCheck::Lax;
10094
for comb_serializer in choices {
101-
match comb_serializer.to_python(value, include, exclude, &new_extra) {
102-
Ok(v) => return Ok(v),
103-
Err(err) => match err.is_instance_of::<PydanticSerializationUnexpectedValue>(value.py()) {
104-
true => (),
105-
false => errors.push(err),
106-
},
95+
if let Ok(v) = comb_serializer.to_python(value, include, exclude, &new_extra) {
96+
return Ok(v);
10797
}
10898
}
10999
}
@@ -112,15 +102,13 @@ fn to_python(
112102
extra.warnings.custom_warning(err.to_string());
113103
}
114104

115-
extra.warnings.on_fallback_py(name, value, extra)?;
116105
infer_to_python(value, include, exclude, extra)
117106
}
118107

119108
fn json_key<'a>(
120109
key: &'a Bound<'_, PyAny>,
121110
extra: &Extra,
122111
choices: &[CombinedSerializer],
123-
name: &str,
124112
retry_with_lax_check: bool,
125113
) -> PyResult<Cow<'a, str>> {
126114
let mut new_extra = extra.clone();
@@ -130,22 +118,15 @@ fn json_key<'a>(
130118
for comb_serializer in choices {
131119
match comb_serializer.json_key(key, &new_extra) {
132120
Ok(v) => return Ok(v),
133-
Err(err) => match err.is_instance_of::<PydanticSerializationUnexpectedValue>(key.py()) {
134-
true => (),
135-
false => errors.push(err),
136-
},
121+
Err(err) => errors.push(err),
137122
}
138123
}
139124

140125
if retry_with_lax_check {
141126
new_extra.check = SerCheck::Lax;
142127
for comb_serializer in choices {
143-
match comb_serializer.json_key(key, &new_extra) {
144-
Ok(v) => return Ok(v),
145-
Err(err) => match err.is_instance_of::<PydanticSerializationUnexpectedValue>(key.py()) {
146-
true => (),
147-
false => errors.push(err),
148-
},
128+
if let Ok(v) = comb_serializer.json_key(key, &new_extra) {
129+
return Ok(v);
149130
}
150131
}
151132
}
@@ -154,7 +135,6 @@ fn json_key<'a>(
154135
extra.warnings.custom_warning(err.to_string());
155136
}
156137

157-
extra.warnings.on_fallback_py(name, key, extra)?;
158138
infer_json_key(key, extra)
159139
}
160140

@@ -166,7 +146,6 @@ fn serde_serialize<S: serde::ser::Serializer>(
166146
exclude: Option<&Bound<'_, PyAny>>,
167147
extra: &Extra,
168148
choices: &[CombinedSerializer],
169-
name: &str,
170149
retry_with_lax_check: bool,
171150
) -> Result<S::Ok, S::Error> {
172151
let py = value.py();
@@ -177,22 +156,15 @@ fn serde_serialize<S: serde::ser::Serializer>(
177156
for comb_serializer in choices {
178157
match comb_serializer.to_python(value, include, exclude, &new_extra) {
179158
Ok(v) => return infer_serialize(v.bind(py), serializer, None, None, extra),
180-
Err(err) => match err.is_instance_of::<PydanticSerializationUnexpectedValue>(py) {
181-
true => (),
182-
false => errors.push(err),
183-
},
159+
Err(err) => errors.push(err),
184160
}
185161
}
186162

187163
if retry_with_lax_check {
188164
new_extra.check = SerCheck::Lax;
189165
for comb_serializer in choices {
190-
match comb_serializer.to_python(value, include, exclude, &new_extra) {
191-
Ok(v) => return infer_serialize(v.bind(py), serializer, None, None, extra),
192-
Err(err) => match err.is_instance_of::<PydanticSerializationUnexpectedValue>(py) {
193-
true => (),
194-
false => errors.push(err),
195-
},
166+
if let Ok(v) = comb_serializer.to_python(value, include, exclude, &new_extra) {
167+
return infer_serialize(v.bind(py), serializer, None, None, extra);
196168
}
197169
}
198170
}
@@ -201,7 +173,6 @@ fn serde_serialize<S: serde::ser::Serializer>(
201173
extra.warnings.custom_warning(err.to_string());
202174
}
203175

204-
extra.warnings.on_fallback_ser::<S>(name, value, extra)?;
205176
infer_serialize(value, serializer, include, exclude, extra)
206177
}
207178

@@ -219,13 +190,12 @@ impl TypeSerializer for UnionSerializer {
219190
exclude,
220191
extra,
221192
&self.choices,
222-
self.get_name(),
223193
self.retry_with_lax_check(),
224194
)
225195
}
226196

227197
fn json_key<'a>(&self, key: &'a Bound<'_, PyAny>, extra: &Extra) -> PyResult<Cow<'a, str>> {
228-
json_key(key, extra, &self.choices, self.get_name(), self.retry_with_lax_check())
198+
json_key(key, extra, &self.choices, self.retry_with_lax_check())
229199
}
230200

231201
fn serde_serialize<S: serde::ser::Serializer>(
@@ -243,7 +213,6 @@ impl TypeSerializer for UnionSerializer {
243213
exclude,
244214
extra,
245215
&self.choices,
246-
self.get_name(),
247216
self.retry_with_lax_check(),
248217
)
249218
}
@@ -313,8 +282,6 @@ impl TypeSerializer for TaggedUnionSerializer {
313282
exclude: Option<&Bound<'_, PyAny>>,
314283
extra: &Extra,
315284
) -> PyResult<PyObject> {
316-
let py = value.py();
317-
318285
let mut new_extra = extra.clone();
319286
new_extra.check = SerCheck::Strict;
320287

@@ -325,15 +292,14 @@ impl TypeSerializer for TaggedUnionSerializer {
325292

326293
match serializer.to_python(value, include, exclude, &new_extra) {
327294
Ok(v) => return Ok(v),
328-
Err(err) => match err.is_instance_of::<PydanticSerializationUnexpectedValue>(py) {
329-
true => {
330-
if self.retry_with_lax_check() {
331-
new_extra.check = SerCheck::Lax;
332-
return serializer.to_python(value, include, exclude, &new_extra);
295+
Err(_) => {
296+
if self.retry_with_lax_check() {
297+
new_extra.check = SerCheck::Lax;
298+
if let Ok(v) = serializer.to_python(value, include, exclude, &new_extra) {
299+
return Ok(v);
333300
}
334301
}
335-
false => return Err(err),
336-
},
302+
}
337303
}
338304
}
339305
}
@@ -344,13 +310,11 @@ impl TypeSerializer for TaggedUnionSerializer {
344310
exclude,
345311
extra,
346312
&self.choices,
347-
self.get_name(),
348313
self.retry_with_lax_check(),
349314
)
350315
}
351316

352317
fn json_key<'a>(&self, key: &'a Bound<'_, PyAny>, extra: &Extra) -> PyResult<Cow<'a, str>> {
353-
let py = key.py();
354318
let mut new_extra = extra.clone();
355319
new_extra.check = SerCheck::Strict;
356320

@@ -361,20 +325,19 @@ impl TypeSerializer for TaggedUnionSerializer {
361325

362326
match serializer.json_key(key, &new_extra) {
363327
Ok(v) => return Ok(v),
364-
Err(err) => match err.is_instance_of::<PydanticSerializationUnexpectedValue>(py) {
365-
true => {
366-
if self.retry_with_lax_check() {
367-
new_extra.check = SerCheck::Lax;
368-
return serializer.json_key(key, &new_extra);
328+
Err(_) => {
329+
if self.retry_with_lax_check() {
330+
new_extra.check = SerCheck::Lax;
331+
if let Ok(v) = serializer.json_key(key, &new_extra) {
332+
return Ok(v);
369333
}
370334
}
371-
false => return Err(err),
372-
},
335+
}
373336
}
374337
}
375338
}
376339

377-
json_key(key, extra, &self.choices, self.get_name(), self.retry_with_lax_check())
340+
json_key(key, extra, &self.choices, self.retry_with_lax_check())
378341
}
379342

380343
fn serde_serialize<S: serde::ser::Serializer>(
@@ -396,18 +359,14 @@ impl TypeSerializer for TaggedUnionSerializer {
396359

397360
match selected_serializer.to_python(value, include, exclude, &new_extra) {
398361
Ok(v) => return infer_serialize(v.bind(py), serializer, None, None, extra),
399-
Err(err) => match err.is_instance_of::<PydanticSerializationUnexpectedValue>(py) {
400-
true => {
401-
if self.retry_with_lax_check() {
402-
new_extra.check = SerCheck::Lax;
403-
match selected_serializer.to_python(value, include, exclude, &new_extra) {
404-
Ok(v) => return infer_serialize(v.bind(py), serializer, None, None, extra),
405-
Err(err) => return Err(py_err_se_err(err)),
406-
}
362+
Err(_) => {
363+
if self.retry_with_lax_check() {
364+
new_extra.check = SerCheck::Lax;
365+
if let Ok(v) = selected_serializer.to_python(value, include, exclude, &new_extra) {
366+
return infer_serialize(v.bind(py), serializer, None, None, extra);
407367
}
408368
}
409-
false => return Err(py_err_se_err(err)),
410-
},
369+
}
411370
}
412371
}
413372
}
@@ -419,7 +378,6 @@ impl TypeSerializer for TaggedUnionSerializer {
419378
exclude,
420379
extra,
421380
&self.choices,
422-
self.get_name(),
423381
self.retry_with_lax_check(),
424382
)
425383
}

src/validators/config.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::borrow::Cow;
22
use std::str::FromStr;
33

4-
use base64::engine::general_purpose::{STANDARD, URL_SAFE};
5-
use base64::{DecodeError, Engine};
4+
use base64::engine::general_purpose::GeneralPurpose;
5+
use base64::engine::{DecodePaddingMode, GeneralPurposeConfig};
6+
use base64::{alphabet, DecodeError, Engine};
67
use pyo3::types::{PyDict, PyString};
78
use pyo3::{intern, prelude::*};
89

@@ -11,6 +12,15 @@ use crate::input::EitherBytes;
1112
use crate::serializers::BytesMode;
1213
use crate::tools::SchemaDict;
1314

15+
const URL_SAFE_OPTIONAL_PADDING: GeneralPurpose = GeneralPurpose::new(
16+
&alphabet::URL_SAFE,
17+
GeneralPurposeConfig::new().with_decode_padding_mode(DecodePaddingMode::Indifferent),
18+
);
19+
const STANDARD_OPTIONAL_PADDING: GeneralPurpose = GeneralPurpose::new(
20+
&alphabet::STANDARD,
21+
GeneralPurposeConfig::new().with_decode_padding_mode(DecodePaddingMode::Indifferent),
22+
);
23+
1424
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1525
pub struct ValBytesMode {
1626
pub ser: BytesMode,
@@ -29,10 +39,10 @@ impl ValBytesMode {
2939
pub fn deserialize_string<'py>(self, s: &str) -> Result<EitherBytes<'_, 'py>, ErrorType> {
3040
match self.ser {
3141
BytesMode::Utf8 => Ok(EitherBytes::Cow(Cow::Borrowed(s.as_bytes()))),
32-
BytesMode::Base64 => URL_SAFE
42+
BytesMode::Base64 => URL_SAFE_OPTIONAL_PADDING
3343
.decode(s)
3444
.or_else(|err| match err {
35-
DecodeError::InvalidByte(_, b'/' | b'+') => STANDARD.decode(s),
45+
DecodeError::InvalidByte(_, b'/' | b'+') => STANDARD_OPTIONAL_PADDING.decode(s),
3646
_ => Err(err),
3747
})
3848
.map(EitherBytes::from)

tests/serializers/test_model.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
from pydantic_core import (
1717
PydanticSerializationError,
18-
PydanticSerializationUnexpectedValue,
1918
SchemaSerializer,
2019
SchemaValidator,
2120
core_schema,
@@ -1150,8 +1149,6 @@ class BModel(BasicModel): ...
11501149
)
11511150
)
11521151

1153-
with pytest.raises(
1154-
PydanticSerializationUnexpectedValue, match='Expected 2 fields but got 1 for type `.*AModel` with value `.*`.+'
1155-
):
1152+
with pytest.warns(UserWarning, match='Expected 2 fields but got 1 for type `.*AModel` with value `.*`.+'):
11561153
value = BasicModel(root=AModel(type='a'))
11571154
s.to_python(value)

0 commit comments

Comments
 (0)