Skip to content

Commit ed0d1ca

Browse files
authored
Reuse basic validators and serializers (#1795)
1 parent 25b0649 commit ed0d1ca

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

83 files changed

+847
-597
lines changed

src/build_tools.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use std::error::Error;
22
use std::fmt;
3+
use std::ops::Deref;
34
use std::str::FromStr;
5+
use std::sync::OnceLock;
46

57
use pyo3::exceptions::PyException;
68
use pyo3::prelude::*;
@@ -217,3 +219,28 @@ impl FromStr for ExtraBehavior {
217219
}
218220
}
219221
}
222+
223+
/// A lazily-initialized value.
224+
///
225+
/// This is a basic replacement for `LazyLock` which is available only in Rust 1.80+.
226+
pub struct LazyLock<T> {
227+
init: fn() -> T,
228+
value: OnceLock<T>,
229+
}
230+
231+
impl<T> Deref for LazyLock<T> {
232+
type Target = T;
233+
234+
fn deref(&self) -> &Self::Target {
235+
self.value.get_or_init(self.init)
236+
}
237+
}
238+
239+
impl<T> LazyLock<T> {
240+
pub const fn new(init: fn() -> T) -> Self {
241+
Self {
242+
init,
243+
value: OnceLock::new(),
244+
}
245+
}
246+
}

src/serializers/computed_fields.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::sync::Arc;
2+
13
use pyo3::prelude::*;
24
use pyo3::types::{PyDict, PyList, PyString};
35
use pyo3::{intern, PyTraverseError, PyVisit};
@@ -21,7 +23,7 @@ impl ComputedFields {
2123
pub fn new(
2224
schema: &Bound<'_, PyDict>,
2325
config: Option<&Bound<'_, PyDict>>,
24-
definitions: &mut DefinitionsBuilder<CombinedSerializer>,
26+
definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
2527
) -> PyResult<Option<Self>> {
2628
let py = schema.py();
2729
if let Some(computed_fields) = schema.get_as::<Bound<'_, PyList>>(intern!(py, "computed_fields"))? {
@@ -182,7 +184,7 @@ struct ComputedFieldToSerialize<'a, 'py> {
182184
struct ComputedField {
183185
property_name: String,
184186
property_name_py: Py<PyString>,
185-
serializer: CombinedSerializer,
187+
serializer: Arc<CombinedSerializer>,
186188
alias: String,
187189
alias_py: Py<PyString>,
188190
serialize_by_alias: Option<bool>,
@@ -192,7 +194,7 @@ impl ComputedField {
192194
pub fn new(
193195
schema: &Bound<'_, PyAny>,
194196
config: Option<&Bound<'_, PyDict>>,
195-
definitions: &mut DefinitionsBuilder<CombinedSerializer>,
197+
definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
196198
) -> PyResult<Self> {
197199
let py = schema.py();
198200
let schema: &Bound<'_, PyDict> = schema.downcast()?;

src/serializers/fields.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::borrow::Cow;
2+
use std::sync::Arc;
23

34
use pyo3::prelude::*;
45
use pyo3::types::{PyDict, PyString};
@@ -25,7 +26,7 @@ pub(super) struct SerField {
2526
pub alias: Option<String>,
2627
pub alias_py: Option<Py<PyString>>,
2728
// None serializer means exclude
28-
pub serializer: Option<CombinedSerializer>,
29+
pub serializer: Option<Arc<CombinedSerializer>>,
2930
pub required: bool,
3031
pub serialize_by_alias: Option<bool>,
3132
pub serialization_exclude_if: Option<Py<PyAny>>,
@@ -38,7 +39,7 @@ impl SerField {
3839
py: Python,
3940
key_py: Py<PyString>,
4041
alias: Option<String>,
41-
serializer: Option<CombinedSerializer>,
42+
serializer: Option<Arc<CombinedSerializer>>,
4243
required: bool,
4344
serialize_by_alias: Option<bool>,
4445
serialization_exclude_if: Option<Py<PyAny>>,
@@ -113,7 +114,7 @@ pub struct GeneralFieldsSerializer {
113114
fields: AHashMap<String, SerField>,
114115
computed_fields: Option<ComputedFields>,
115116
mode: FieldsMode,
116-
extra_serializer: Option<Box<CombinedSerializer>>,
117+
extra_serializer: Option<Arc<CombinedSerializer>>,
117118
// isize because we look up filter via `.hash()` which returns an isize
118119
filter: SchemaFilter<isize>,
119120
required_fields: usize,
@@ -132,14 +133,14 @@ impl GeneralFieldsSerializer {
132133
pub(super) fn new(
133134
fields: AHashMap<String, SerField>,
134135
mode: FieldsMode,
135-
extra_serializer: Option<CombinedSerializer>,
136+
extra_serializer: Option<Arc<CombinedSerializer>>,
136137
computed_fields: Option<ComputedFields>,
137138
) -> Self {
138139
let required_fields = fields.values().filter(|f| f.required).count();
139140
Self {
140141
fields,
141142
mode,
142-
extra_serializer: extra_serializer.map(Box::new),
143+
extra_serializer,
143144
filter: SchemaFilter::default(),
144145
computed_fields,
145146
required_fields,

src/serializers/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::fmt::Debug;
22
use std::sync::atomic::{AtomicUsize, Ordering};
3+
use std::sync::Arc;
34

45
use pyo3::prelude::*;
56
use pyo3::types::{PyBytes, PyDict, PyTuple, PyType};
@@ -39,8 +40,8 @@ pub enum WarningsArg {
3940
#[pyclass(module = "pydantic_core._pydantic_core", frozen)]
4041
#[derive(Debug)]
4142
pub struct SchemaSerializer {
42-
serializer: CombinedSerializer,
43-
definitions: Definitions<CombinedSerializer>,
43+
serializer: Arc<CombinedSerializer>,
44+
definitions: Definitions<Arc<CombinedSerializer>>,
4445
expected_json_size: AtomicUsize,
4546
config: SerializationConfig,
4647
// References to the Python schema and config objects are saved to enable

src/serializers/prebuilt.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ impl PrebuiltSerializer {
1818
pub fn try_get_from_schema(type_: &str, schema: &Bound<'_, PyDict>) -> PyResult<Option<CombinedSerializer>> {
1919
get_prebuilt(type_, schema, "__pydantic_serializer__", |py_any| {
2020
let schema_serializer = py_any.extract::<Py<SchemaSerializer>>()?;
21-
if matches!(schema_serializer.get().serializer, CombinedSerializer::FunctionWrap(_)) {
21+
if matches!(
22+
schema_serializer.get().serializer.as_ref(),
23+
CombinedSerializer::FunctionWrap(_)
24+
) {
2225
return Ok(None);
2326
}
2427
Ok(Some(Self { schema_serializer }.into()))

src/serializers/shared.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::borrow::Cow;
22
use std::fmt::Debug;
33
use std::io::{self, Write};
4+
use std::sync::Arc;
45

56
use pyo3::exceptions::PyTypeError;
67
use pyo3::prelude::*;
@@ -30,8 +31,8 @@ pub(crate) trait BuildSerializer: Sized {
3031
fn build(
3132
schema: &Bound<'_, PyDict>,
3233
config: Option<&Bound<'_, PyDict>>,
33-
definitions: &mut DefinitionsBuilder<CombinedSerializer>,
34-
) -> PyResult<CombinedSerializer>;
34+
definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
35+
) -> PyResult<Arc<CombinedSerializer>>;
3536
}
3637

3738
/// Build the `CombinedSerializer` enum and implement a `find_serializer` method for it.
@@ -53,8 +54,8 @@ macro_rules! combined_serializer {
5354
lookup_type: &str,
5455
schema: &Bound<'_, PyDict>,
5556
config: Option<&Bound<'_, PyDict>>,
56-
definitions: &mut DefinitionsBuilder<CombinedSerializer>
57-
) -> PyResult<CombinedSerializer> {
57+
definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>
58+
) -> PyResult<Arc<CombinedSerializer>> {
5859
match lookup_type {
5960
$(
6061
<$b_serializer>::EXPECTED_TYPE => match <$b_serializer>::build(schema, config, definitions) {
@@ -156,17 +157,17 @@ impl CombinedSerializer {
156157
pub fn build_base(
157158
schema: &Bound<'_, PyDict>,
158159
config: Option<&Bound<'_, PyDict>>,
159-
definitions: &mut DefinitionsBuilder<CombinedSerializer>,
160-
) -> PyResult<CombinedSerializer> {
160+
definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
161+
) -> PyResult<Arc<CombinedSerializer>> {
161162
Self::_build(schema, config, definitions, false)
162163
}
163164

164165
fn _build(
165166
schema: &Bound<'_, PyDict>,
166167
config: Option<&Bound<'_, PyDict>>,
167-
definitions: &mut DefinitionsBuilder<CombinedSerializer>,
168+
definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
168169
use_prebuilt: bool,
169-
) -> PyResult<CombinedSerializer> {
170+
) -> PyResult<Arc<CombinedSerializer>> {
170171
let py = schema.py();
171172
let type_key = intern!(py, "type");
172173

@@ -217,7 +218,7 @@ impl CombinedSerializer {
217218
if let Ok(Some(prebuilt_serializer)) =
218219
super::prebuilt::PrebuiltSerializer::try_get_from_schema(type_, schema)
219220
{
220-
return Ok(prebuilt_serializer);
221+
return Ok(Arc::new(prebuilt_serializer));
221222
}
222223
}
223224

@@ -300,8 +301,8 @@ impl BuildSerializer for CombinedSerializer {
300301
fn build(
301302
schema: &Bound<'_, PyDict>,
302303
config: Option<&Bound<'_, PyDict>>,
303-
definitions: &mut DefinitionsBuilder<CombinedSerializer>,
304-
) -> PyResult<CombinedSerializer> {
304+
definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
305+
) -> PyResult<Arc<CombinedSerializer>> {
305306
Self::_build(schema, config, definitions, true)
306307
}
307308
}

src/serializers/type_serializers/any.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ impl BuildSerializer for AnySerializer {
3030
fn build(
3131
_schema: &Bound<'_, PyDict>,
3232
_config: Option<&Bound<'_, PyDict>>,
33-
_definitions: &mut DefinitionsBuilder<CombinedSerializer>,
34-
) -> PyResult<CombinedSerializer> {
35-
Ok(Self {}.into())
33+
_definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
34+
) -> PyResult<Arc<CombinedSerializer>> {
35+
Ok(Self::get().clone())
3636
}
3737
}
3838

src/serializers/type_serializers/bytes.rs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use std::borrow::Cow;
2+
use std::sync::Arc;
23

34
use pyo3::types::{PyBytes, PyDict};
45
use pyo3::{prelude::*, IntoPyObjectExt};
56

7+
use crate::build_tools::LazyLock;
68
use crate::definitions::DefinitionsBuilder;
79
use crate::serializers::config::{BytesMode, FromConfig};
810

@@ -16,16 +18,47 @@ pub struct BytesSerializer {
1618
bytes_mode: BytesMode,
1719
}
1820

21+
static BYTES_SERIALIZER_UTF8: LazyLock<Arc<CombinedSerializer>> = LazyLock::new(|| {
22+
Arc::new(
23+
BytesSerializer {
24+
bytes_mode: BytesMode::Utf8,
25+
}
26+
.into(),
27+
)
28+
});
29+
30+
static BYTES_SERIALIZER_BASE64: LazyLock<Arc<CombinedSerializer>> = LazyLock::new(|| {
31+
Arc::new(
32+
BytesSerializer {
33+
bytes_mode: BytesMode::Base64,
34+
}
35+
.into(),
36+
)
37+
});
38+
39+
static BYTES_SERIALIZER_HEX: LazyLock<Arc<CombinedSerializer>> = LazyLock::new(|| {
40+
Arc::new(
41+
BytesSerializer {
42+
bytes_mode: BytesMode::Hex,
43+
}
44+
.into(),
45+
)
46+
});
47+
1948
impl BuildSerializer for BytesSerializer {
2049
const EXPECTED_TYPE: &'static str = "bytes";
2150

2251
fn build(
2352
_schema: &Bound<'_, PyDict>,
2453
config: Option<&Bound<'_, PyDict>>,
25-
_definitions: &mut DefinitionsBuilder<CombinedSerializer>,
26-
) -> PyResult<CombinedSerializer> {
54+
_definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
55+
) -> PyResult<Arc<CombinedSerializer>> {
2756
let bytes_mode = BytesMode::from_config(config)?;
28-
Ok(Self { bytes_mode }.into())
57+
match bytes_mode {
58+
BytesMode::Utf8 => Ok(BYTES_SERIALIZER_UTF8.clone()),
59+
BytesMode::Base64 => Ok(BYTES_SERIALIZER_BASE64.clone()),
60+
BytesMode::Hex => Ok(BYTES_SERIALIZER_HEX.clone()),
61+
}
2962
}
3063
}
3164

src/serializers/type_serializers/complex.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
11
use std::borrow::Cow;
2+
use std::sync::Arc;
23

34
use pyo3::types::{PyComplex, PyDict};
45
use pyo3::{prelude::*, IntoPyObjectExt};
56

7+
use crate::build_tools::LazyLock;
68
use crate::definitions::DefinitionsBuilder;
79

810
use super::{infer_serialize, infer_to_python, BuildSerializer, CombinedSerializer, Extra, SerMode, TypeSerializer};
911

1012
#[derive(Debug, Clone)]
1113
pub struct ComplexSerializer {}
1214

15+
static COMPLEX_SERIALIZER: LazyLock<Arc<CombinedSerializer>> = LazyLock::new(|| Arc::new(ComplexSerializer {}.into()));
16+
1317
impl BuildSerializer for ComplexSerializer {
1418
const EXPECTED_TYPE: &'static str = "complex";
1519
fn build(
1620
_schema: &Bound<'_, PyDict>,
1721
_config: Option<&Bound<'_, PyDict>>,
18-
_definitions: &mut DefinitionsBuilder<CombinedSerializer>,
19-
) -> PyResult<CombinedSerializer> {
20-
Ok(Self {}.into())
22+
_definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
23+
) -> PyResult<Arc<CombinedSerializer>> {
24+
Ok(COMPLEX_SERIALIZER.clone())
2125
}
2226
}
2327

src/serializers/type_serializers/dataclass.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use pyo3::intern;
22
use pyo3::prelude::*;
33
use pyo3::types::{PyDict, PyList, PyString, PyType};
44
use std::borrow::Cow;
5+
use std::sync::Arc;
56

67
use ahash::AHashMap;
78
use serde::ser::SerializeMap;
@@ -24,8 +25,8 @@ impl BuildSerializer for DataclassArgsBuilder {
2425
fn build(
2526
schema: &Bound<'_, PyDict>,
2627
config: Option<&Bound<'_, PyDict>>,
27-
definitions: &mut DefinitionsBuilder<CombinedSerializer>,
28-
) -> PyResult<CombinedSerializer> {
28+
definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
29+
) -> PyResult<Arc<CombinedSerializer>> {
2930
let py = schema.py();
3031

3132
let fields_list: Bound<'_, PyList> = schema.get_as_req(intern!(py, "fields"))?;
@@ -74,14 +75,14 @@ impl BuildSerializer for DataclassArgsBuilder {
7475
}
7576
let computed_fields = ComputedFields::new(schema, config, definitions)?;
7677

77-
Ok(GeneralFieldsSerializer::new(fields, fields_mode, None, computed_fields).into())
78+
Ok(CombinedSerializer::Fields(GeneralFieldsSerializer::new(fields, fields_mode, None, computed_fields)).into())
7879
}
7980
}
8081

8182
#[derive(Debug)]
8283
pub struct DataclassSerializer {
8384
class: Py<PyType>,
84-
serializer: Box<CombinedSerializer>,
85+
serializer: Arc<CombinedSerializer>,
8586
fields: Vec<Py<PyString>>,
8687
name: String,
8788
}
@@ -92,29 +93,29 @@ impl BuildSerializer for DataclassSerializer {
9293
fn build(
9394
schema: &Bound<'_, PyDict>,
9495
_config: Option<&Bound<'_, PyDict>>,
95-
definitions: &mut DefinitionsBuilder<CombinedSerializer>,
96-
) -> PyResult<CombinedSerializer> {
96+
definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
97+
) -> PyResult<Arc<CombinedSerializer>> {
9798
let py = schema.py();
9899

99100
// models ignore the parent config and always use the config from this model
100101
let config = schema.get_as(intern!(py, "config"))?;
101102

102103
let class: Bound<'_, PyType> = schema.get_as_req(intern!(py, "cls"))?;
103104
let sub_schema = schema.get_as_req(intern!(py, "schema"))?;
104-
let serializer = Box::new(CombinedSerializer::build(&sub_schema, config.as_ref(), definitions)?);
105+
let serializer = CombinedSerializer::build(&sub_schema, config.as_ref(), definitions)?;
105106

106107
let fields = schema
107108
.get_as_req::<Bound<'_, PyList>>(intern!(py, "fields"))?
108109
.iter()
109110
.map(|s| Ok(s.downcast_into::<PyString>()?.unbind()))
110111
.collect::<PyResult<Vec<_>>>()?;
111112

112-
Ok(Self {
113+
Ok(CombinedSerializer::Dataclass(Self {
113114
class: class.clone().unbind(),
114115
serializer,
115116
fields,
116117
name: class.getattr(intern!(py, "__name__"))?.extract()?,
117-
}
118+
})
118119
.into())
119120
}
120121
}

0 commit comments

Comments
 (0)