Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/build_tools.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::error::Error;
use std::fmt;
use std::ops::Deref;
use std::str::FromStr;
use std::sync::OnceLock;

use pyo3::exceptions::PyException;
use pyo3::prelude::*;
Expand Down Expand Up @@ -217,3 +219,28 @@ impl FromStr for ExtraBehavior {
}
}
}

/// A lazily-initialized value.
///
/// This is a basic replacement for `LazyLock` which is available only in Rust 1.80+.
pub struct LazyLock<T> {
init: fn() -> T,
value: OnceLock<T>,
}

impl<T> Deref for LazyLock<T> {
type Target = T;

fn deref(&self) -> &Self::Target {
self.value.get_or_init(self.init)
}
}

impl<T> LazyLock<T> {
pub const fn new(init: fn() -> T) -> Self {
Self {
init,
value: OnceLock::new(),
}
}
}
8 changes: 5 additions & 3 deletions src/serializers/computed_fields.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::sync::Arc;

use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList, PyString};
use pyo3::{intern, PyTraverseError, PyVisit};
Expand All @@ -21,7 +23,7 @@ impl ComputedFields {
pub fn new(
schema: &Bound<'_, PyDict>,
config: Option<&Bound<'_, PyDict>>,
definitions: &mut DefinitionsBuilder<CombinedSerializer>,
definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
) -> PyResult<Option<Self>> {
let py = schema.py();
if let Some(computed_fields) = schema.get_as::<Bound<'_, PyList>>(intern!(py, "computed_fields"))? {
Expand Down Expand Up @@ -182,7 +184,7 @@ struct ComputedFieldToSerialize<'a, 'py> {
struct ComputedField {
property_name: String,
property_name_py: Py<PyString>,
serializer: CombinedSerializer,
serializer: Arc<CombinedSerializer>,
alias: String,
alias_py: Py<PyString>,
serialize_by_alias: Option<bool>,
Expand All @@ -192,7 +194,7 @@ impl ComputedField {
pub fn new(
schema: &Bound<'_, PyAny>,
config: Option<&Bound<'_, PyDict>>,
definitions: &mut DefinitionsBuilder<CombinedSerializer>,
definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
) -> PyResult<Self> {
let py = schema.py();
let schema: &Bound<'_, PyDict> = schema.downcast()?;
Expand Down
11 changes: 6 additions & 5 deletions src/serializers/fields.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::borrow::Cow;
use std::sync::Arc;

use pyo3::prelude::*;
use pyo3::types::{PyDict, PyString};
Expand All @@ -25,7 +26,7 @@ pub(super) struct SerField {
pub alias: Option<String>,
pub alias_py: Option<Py<PyString>>,
// None serializer means exclude
pub serializer: Option<CombinedSerializer>,
pub serializer: Option<Arc<CombinedSerializer>>,
pub required: bool,
pub serialize_by_alias: Option<bool>,
pub serialization_exclude_if: Option<Py<PyAny>>,
Expand All @@ -38,7 +39,7 @@ impl SerField {
py: Python,
key_py: Py<PyString>,
alias: Option<String>,
serializer: Option<CombinedSerializer>,
serializer: Option<Arc<CombinedSerializer>>,
required: bool,
serialize_by_alias: Option<bool>,
serialization_exclude_if: Option<Py<PyAny>>,
Expand Down Expand Up @@ -113,7 +114,7 @@ pub struct GeneralFieldsSerializer {
fields: AHashMap<String, SerField>,
computed_fields: Option<ComputedFields>,
mode: FieldsMode,
extra_serializer: Option<Box<CombinedSerializer>>,
extra_serializer: Option<Arc<CombinedSerializer>>,
// isize because we look up filter via `.hash()` which returns an isize
filter: SchemaFilter<isize>,
required_fields: usize,
Expand All @@ -132,14 +133,14 @@ impl GeneralFieldsSerializer {
pub(super) fn new(
fields: AHashMap<String, SerField>,
mode: FieldsMode,
extra_serializer: Option<CombinedSerializer>,
extra_serializer: Option<Arc<CombinedSerializer>>,
computed_fields: Option<ComputedFields>,
) -> Self {
let required_fields = fields.values().filter(|f| f.required).count();
Self {
fields,
mode,
extra_serializer: extra_serializer.map(Box::new),
extra_serializer,
filter: SchemaFilter::default(),
computed_fields,
required_fields,
Expand Down
5 changes: 3 additions & 2 deletions src/serializers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::fmt::Debug;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;

use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyDict, PyTuple, PyType};
Expand Down Expand Up @@ -39,8 +40,8 @@ pub enum WarningsArg {
#[pyclass(module = "pydantic_core._pydantic_core", frozen)]
#[derive(Debug)]
pub struct SchemaSerializer {
serializer: CombinedSerializer,
definitions: Definitions<CombinedSerializer>,
serializer: Arc<CombinedSerializer>,
definitions: Definitions<Arc<CombinedSerializer>>,
expected_json_size: AtomicUsize,
config: SerializationConfig,
// References to the Python schema and config objects are saved to enable
Expand Down
5 changes: 4 additions & 1 deletion src/serializers/prebuilt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ impl PrebuiltSerializer {
pub fn try_get_from_schema(type_: &str, schema: &Bound<'_, PyDict>) -> PyResult<Option<CombinedSerializer>> {
get_prebuilt(type_, schema, "__pydantic_serializer__", |py_any| {
let schema_serializer = py_any.extract::<Py<SchemaSerializer>>()?;
if matches!(schema_serializer.get().serializer, CombinedSerializer::FunctionWrap(_)) {
if matches!(
schema_serializer.get().serializer.as_ref(),
CombinedSerializer::FunctionWrap(_)
) {
return Ok(None);
}
Ok(Some(Self { schema_serializer }.into()))
Expand Down
23 changes: 12 additions & 11 deletions src/serializers/shared.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::borrow::Cow;
use std::fmt::Debug;
use std::io::{self, Write};
use std::sync::Arc;

use pyo3::exceptions::PyTypeError;
use pyo3::prelude::*;
Expand Down Expand Up @@ -30,8 +31,8 @@ pub(crate) trait BuildSerializer: Sized {
fn build(
schema: &Bound<'_, PyDict>,
config: Option<&Bound<'_, PyDict>>,
definitions: &mut DefinitionsBuilder<CombinedSerializer>,
) -> PyResult<CombinedSerializer>;
definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
) -> PyResult<Arc<CombinedSerializer>>;
}

/// Build the `CombinedSerializer` enum and implement a `find_serializer` method for it.
Expand All @@ -53,8 +54,8 @@ macro_rules! combined_serializer {
lookup_type: &str,
schema: &Bound<'_, PyDict>,
config: Option<&Bound<'_, PyDict>>,
definitions: &mut DefinitionsBuilder<CombinedSerializer>
) -> PyResult<CombinedSerializer> {
definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>
) -> PyResult<Arc<CombinedSerializer>> {
match lookup_type {
$(
<$b_serializer>::EXPECTED_TYPE => match <$b_serializer>::build(schema, config, definitions) {
Expand Down Expand Up @@ -156,17 +157,17 @@ impl CombinedSerializer {
pub fn build_base(
schema: &Bound<'_, PyDict>,
config: Option<&Bound<'_, PyDict>>,
definitions: &mut DefinitionsBuilder<CombinedSerializer>,
) -> PyResult<CombinedSerializer> {
definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
) -> PyResult<Arc<CombinedSerializer>> {
Self::_build(schema, config, definitions, false)
}

fn _build(
schema: &Bound<'_, PyDict>,
config: Option<&Bound<'_, PyDict>>,
definitions: &mut DefinitionsBuilder<CombinedSerializer>,
definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
use_prebuilt: bool,
) -> PyResult<CombinedSerializer> {
) -> PyResult<Arc<CombinedSerializer>> {
let py = schema.py();
let type_key = intern!(py, "type");

Expand Down Expand Up @@ -217,7 +218,7 @@ impl CombinedSerializer {
if let Ok(Some(prebuilt_serializer)) =
super::prebuilt::PrebuiltSerializer::try_get_from_schema(type_, schema)
{
return Ok(prebuilt_serializer);
return Ok(Arc::new(prebuilt_serializer));
}
}

Expand Down Expand Up @@ -300,8 +301,8 @@ impl BuildSerializer for CombinedSerializer {
fn build(
schema: &Bound<'_, PyDict>,
config: Option<&Bound<'_, PyDict>>,
definitions: &mut DefinitionsBuilder<CombinedSerializer>,
) -> PyResult<CombinedSerializer> {
definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
) -> PyResult<Arc<CombinedSerializer>> {
Self::_build(schema, config, definitions, true)
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/serializers/type_serializers/any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ impl BuildSerializer for AnySerializer {
fn build(
_schema: &Bound<'_, PyDict>,
_config: Option<&Bound<'_, PyDict>>,
_definitions: &mut DefinitionsBuilder<CombinedSerializer>,
) -> PyResult<CombinedSerializer> {
Ok(Self {}.into())
_definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
) -> PyResult<Arc<CombinedSerializer>> {
Ok(Self::get().clone())
}
}

Expand Down
39 changes: 36 additions & 3 deletions src/serializers/type_serializers/bytes.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use std::borrow::Cow;
use std::sync::Arc;

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

use crate::build_tools::LazyLock;
use crate::definitions::DefinitionsBuilder;
use crate::serializers::config::{BytesMode, FromConfig};

Expand All @@ -16,16 +18,47 @@ pub struct BytesSerializer {
bytes_mode: BytesMode,
}

static BYTES_SERIALIZER_UTF8: LazyLock<Arc<CombinedSerializer>> = LazyLock::new(|| {
Arc::new(
BytesSerializer {
bytes_mode: BytesMode::Utf8,
}
.into(),
)
});

static BYTES_SERIALIZER_BASE64: LazyLock<Arc<CombinedSerializer>> = LazyLock::new(|| {
Arc::new(
BytesSerializer {
bytes_mode: BytesMode::Base64,
}
.into(),
)
});

static BYTES_SERIALIZER_HEX: LazyLock<Arc<CombinedSerializer>> = LazyLock::new(|| {
Arc::new(
BytesSerializer {
bytes_mode: BytesMode::Hex,
}
.into(),
)
});

impl BuildSerializer for BytesSerializer {
const EXPECTED_TYPE: &'static str = "bytes";

fn build(
_schema: &Bound<'_, PyDict>,
config: Option<&Bound<'_, PyDict>>,
_definitions: &mut DefinitionsBuilder<CombinedSerializer>,
) -> PyResult<CombinedSerializer> {
_definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
) -> PyResult<Arc<CombinedSerializer>> {
let bytes_mode = BytesMode::from_config(config)?;
Ok(Self { bytes_mode }.into())
match bytes_mode {
BytesMode::Utf8 => Ok(BYTES_SERIALIZER_UTF8.clone()),
BytesMode::Base64 => Ok(BYTES_SERIALIZER_BASE64.clone()),
BytesMode::Hex => Ok(BYTES_SERIALIZER_HEX.clone()),
}
}
}

Expand Down
10 changes: 7 additions & 3 deletions src/serializers/type_serializers/complex.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
use std::borrow::Cow;
use std::sync::Arc;

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

use crate::build_tools::LazyLock;
use crate::definitions::DefinitionsBuilder;

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

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

static COMPLEX_SERIALIZER: LazyLock<Arc<CombinedSerializer>> = LazyLock::new(|| Arc::new(ComplexSerializer {}.into()));

impl BuildSerializer for ComplexSerializer {
const EXPECTED_TYPE: &'static str = "complex";
fn build(
_schema: &Bound<'_, PyDict>,
_config: Option<&Bound<'_, PyDict>>,
_definitions: &mut DefinitionsBuilder<CombinedSerializer>,
) -> PyResult<CombinedSerializer> {
Ok(Self {}.into())
_definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
) -> PyResult<Arc<CombinedSerializer>> {
Ok(COMPLEX_SERIALIZER.clone())
}
}

Expand Down
19 changes: 10 additions & 9 deletions src/serializers/type_serializers/dataclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use pyo3::intern;
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList, PyString, PyType};
use std::borrow::Cow;
use std::sync::Arc;

use ahash::AHashMap;
use serde::ser::SerializeMap;
Expand All @@ -24,8 +25,8 @@ impl BuildSerializer for DataclassArgsBuilder {
fn build(
schema: &Bound<'_, PyDict>,
config: Option<&Bound<'_, PyDict>>,
definitions: &mut DefinitionsBuilder<CombinedSerializer>,
) -> PyResult<CombinedSerializer> {
definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
) -> PyResult<Arc<CombinedSerializer>> {
let py = schema.py();

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

Ok(GeneralFieldsSerializer::new(fields, fields_mode, None, computed_fields).into())
Ok(CombinedSerializer::Fields(GeneralFieldsSerializer::new(fields, fields_mode, None, computed_fields)).into())
}
}

#[derive(Debug)]
pub struct DataclassSerializer {
class: Py<PyType>,
serializer: Box<CombinedSerializer>,
serializer: Arc<CombinedSerializer>,
fields: Vec<Py<PyString>>,
name: String,
}
Expand All @@ -92,29 +93,29 @@ impl BuildSerializer for DataclassSerializer {
fn build(
schema: &Bound<'_, PyDict>,
_config: Option<&Bound<'_, PyDict>>,
definitions: &mut DefinitionsBuilder<CombinedSerializer>,
) -> PyResult<CombinedSerializer> {
definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
) -> PyResult<Arc<CombinedSerializer>> {
let py = schema.py();

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

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

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

Ok(Self {
Ok(CombinedSerializer::Dataclass(Self {
class: class.clone().unbind(),
serializer,
fields,
name: class.getattr(intern!(py, "__name__"))?.extract()?,
}
})
.into())
}
}
Expand Down
Loading
Loading