diff --git a/src/build_tools.rs b/src/build_tools.rs index 2abcbd9ca..2ccb82728 100644 --- a/src/build_tools.rs +++ b/src/build_tools.rs @@ -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::*; @@ -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 { + init: fn() -> T, + value: OnceLock, +} + +impl Deref for LazyLock { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.value.get_or_init(self.init) + } +} + +impl LazyLock { + pub const fn new(init: fn() -> T) -> Self { + Self { + init, + value: OnceLock::new(), + } + } +} diff --git a/src/serializers/computed_fields.rs b/src/serializers/computed_fields.rs index ff83800bc..6589f41b0 100644 --- a/src/serializers/computed_fields.rs +++ b/src/serializers/computed_fields.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::prelude::*; use pyo3::types::{PyDict, PyList, PyString}; use pyo3::{intern, PyTraverseError, PyVisit}; @@ -21,7 +23,7 @@ impl ComputedFields { pub fn new( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, + definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); if let Some(computed_fields) = schema.get_as::>(intern!(py, "computed_fields"))? { @@ -182,7 +184,7 @@ struct ComputedFieldToSerialize<'a, 'py> { struct ComputedField { property_name: String, property_name_py: Py, - serializer: CombinedSerializer, + serializer: Arc, alias: String, alias_py: Py, serialize_by_alias: Option, @@ -192,7 +194,7 @@ impl ComputedField { pub fn new( schema: &Bound<'_, PyAny>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, + definitions: &mut DefinitionsBuilder>, ) -> PyResult { let py = schema.py(); let schema: &Bound<'_, PyDict> = schema.downcast()?; diff --git a/src/serializers/fields.rs b/src/serializers/fields.rs index 9b5e97035..6af2a0fc0 100644 --- a/src/serializers/fields.rs +++ b/src/serializers/fields.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::sync::Arc; use pyo3::prelude::*; use pyo3::types::{PyDict, PyString}; @@ -25,7 +26,7 @@ pub(super) struct SerField { pub alias: Option, pub alias_py: Option>, // None serializer means exclude - pub serializer: Option, + pub serializer: Option>, pub required: bool, pub serialize_by_alias: Option, pub serialization_exclude_if: Option>, @@ -38,7 +39,7 @@ impl SerField { py: Python, key_py: Py, alias: Option, - serializer: Option, + serializer: Option>, required: bool, serialize_by_alias: Option, serialization_exclude_if: Option>, @@ -113,7 +114,7 @@ pub struct GeneralFieldsSerializer { fields: AHashMap, computed_fields: Option, mode: FieldsMode, - extra_serializer: Option>, + extra_serializer: Option>, // isize because we look up filter via `.hash()` which returns an isize filter: SchemaFilter, required_fields: usize, @@ -132,14 +133,14 @@ impl GeneralFieldsSerializer { pub(super) fn new( fields: AHashMap, mode: FieldsMode, - extra_serializer: Option, + extra_serializer: Option>, computed_fields: Option, ) -> 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, diff --git a/src/serializers/mod.rs b/src/serializers/mod.rs index b18a90881..1b9ae8493 100644 --- a/src/serializers/mod.rs +++ b/src/serializers/mod.rs @@ -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}; @@ -39,8 +40,8 @@ pub enum WarningsArg { #[pyclass(module = "pydantic_core._pydantic_core", frozen)] #[derive(Debug)] pub struct SchemaSerializer { - serializer: CombinedSerializer, - definitions: Definitions, + serializer: Arc, + definitions: Definitions>, expected_json_size: AtomicUsize, config: SerializationConfig, // References to the Python schema and config objects are saved to enable diff --git a/src/serializers/prebuilt.rs b/src/serializers/prebuilt.rs index 54b328206..923d05870 100644 --- a/src/serializers/prebuilt.rs +++ b/src/serializers/prebuilt.rs @@ -18,7 +18,10 @@ impl PrebuiltSerializer { pub fn try_get_from_schema(type_: &str, schema: &Bound<'_, PyDict>) -> PyResult> { get_prebuilt(type_, schema, "__pydantic_serializer__", |py_any| { let schema_serializer = py_any.extract::>()?; - 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())) diff --git a/src/serializers/shared.rs b/src/serializers/shared.rs index 068250a26..01971d480 100644 --- a/src/serializers/shared.rs +++ b/src/serializers/shared.rs @@ -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::*; @@ -30,8 +31,8 @@ pub(crate) trait BuildSerializer: Sized { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult; + definitions: &mut DefinitionsBuilder>, + ) -> PyResult>; } /// Build the `CombinedSerializer` enum and implement a `find_serializer` method for it. @@ -53,8 +54,8 @@ macro_rules! combined_serializer { lookup_type: &str, schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder - ) -> PyResult { + definitions: &mut DefinitionsBuilder> + ) -> PyResult> { match lookup_type { $( <$b_serializer>::EXPECTED_TYPE => match <$b_serializer>::build(schema, config, definitions) { @@ -156,17 +157,17 @@ impl CombinedSerializer { pub fn build_base( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { Self::_build(schema, config, definitions, false) } fn _build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, + definitions: &mut DefinitionsBuilder>, use_prebuilt: bool, - ) -> PyResult { + ) -> PyResult> { let py = schema.py(); let type_key = intern!(py, "type"); @@ -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)); } } @@ -300,8 +301,8 @@ impl BuildSerializer for CombinedSerializer { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { Self::_build(schema, config, definitions, true) } } diff --git a/src/serializers/type_serializers/any.rs b/src/serializers/type_serializers/any.rs index 555dc8a3a..1f4132907 100644 --- a/src/serializers/type_serializers/any.rs +++ b/src/serializers/type_serializers/any.rs @@ -30,9 +30,9 @@ impl BuildSerializer for AnySerializer { fn build( _schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { - Ok(Self {}.into()) + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { + Ok(Self::get().clone()) } } diff --git a/src/serializers/type_serializers/bytes.rs b/src/serializers/type_serializers/bytes.rs index d6b34222b..8ace2091a 100644 --- a/src/serializers/type_serializers/bytes.rs +++ b/src/serializers/type_serializers/bytes.rs @@ -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}; @@ -16,16 +18,47 @@ pub struct BytesSerializer { bytes_mode: BytesMode, } +static BYTES_SERIALIZER_UTF8: LazyLock> = LazyLock::new(|| { + Arc::new( + BytesSerializer { + bytes_mode: BytesMode::Utf8, + } + .into(), + ) +}); + +static BYTES_SERIALIZER_BASE64: LazyLock> = LazyLock::new(|| { + Arc::new( + BytesSerializer { + bytes_mode: BytesMode::Base64, + } + .into(), + ) +}); + +static BYTES_SERIALIZER_HEX: LazyLock> = 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, - ) -> PyResult { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { 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()), + } } } diff --git a/src/serializers/type_serializers/complex.rs b/src/serializers/type_serializers/complex.rs index fa1a7d58f..8156528f2 100644 --- a/src/serializers/type_serializers/complex.rs +++ b/src/serializers/type_serializers/complex.rs @@ -1,8 +1,10 @@ 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}; @@ -10,14 +12,16 @@ use super::{infer_serialize, infer_to_python, BuildSerializer, CombinedSerialize #[derive(Debug, Clone)] pub struct ComplexSerializer {} +static COMPLEX_SERIALIZER: LazyLock> = 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, - ) -> PyResult { - Ok(Self {}.into()) + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { + Ok(COMPLEX_SERIALIZER.clone()) } } diff --git a/src/serializers/type_serializers/dataclass.rs b/src/serializers/type_serializers/dataclass.rs index 9721ce0a1..6b9f34923 100644 --- a/src/serializers/type_serializers/dataclass.rs +++ b/src/serializers/type_serializers/dataclass.rs @@ -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; @@ -24,8 +25,8 @@ impl BuildSerializer for DataclassArgsBuilder { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let fields_list: Bound<'_, PyList> = schema.get_as_req(intern!(py, "fields"))?; @@ -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, - serializer: Box, + serializer: Arc, fields: Vec>, name: String, } @@ -92,8 +93,8 @@ impl BuildSerializer for DataclassSerializer { fn build( schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); // models ignore the parent config and always use the config from this model @@ -101,7 +102,7 @@ impl BuildSerializer for DataclassSerializer { 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::>(intern!(py, "fields"))? @@ -109,12 +110,12 @@ impl BuildSerializer for DataclassSerializer { .map(|s| Ok(s.downcast_into::()?.unbind())) .collect::>>()?; - Ok(Self { + Ok(CombinedSerializer::Dataclass(Self { class: class.clone().unbind(), serializer, fields, name: class.getattr(intern!(py, "__name__"))?.extract()?, - } + }) .into()) } } diff --git a/src/serializers/type_serializers/datetime_etc.rs b/src/serializers/type_serializers/datetime_etc.rs index 2221e2cd4..9d267cb39 100644 --- a/src/serializers/type_serializers/datetime_etc.rs +++ b/src/serializers/type_serializers/datetime_etc.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::sync::Arc; use pyo3::prelude::*; use pyo3::types::{PyDate, PyDateTime, PyDict, PyTime}; @@ -101,10 +102,10 @@ macro_rules! build_temporal_serializer { fn build( _schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let temporal_mode = TemporalMode::from_config(config)?; - Ok(Self { temporal_mode }.into()) + Ok(Arc::new(Self { temporal_mode }.into())) } } diff --git a/src/serializers/type_serializers/decimal.rs b/src/serializers/type_serializers/decimal.rs index a06b02de8..986415441 100644 --- a/src/serializers/type_serializers/decimal.rs +++ b/src/serializers/type_serializers/decimal.rs @@ -1,8 +1,10 @@ use std::borrow::Cow; +use std::sync::Arc; use pyo3::prelude::*; use pyo3::types::PyDict; +use crate::build_tools::LazyLock; use crate::definitions::DefinitionsBuilder; use crate::serializers::infer::{infer_json_key_known, infer_serialize_known, infer_to_python_known}; use crate::serializers::ob_type::{IsType, ObType}; @@ -14,15 +16,17 @@ use super::{ #[derive(Debug)] pub struct DecimalSerializer {} +static DECIMAL_SERIALIZER: LazyLock> = LazyLock::new(|| Arc::new(DecimalSerializer {}.into())); + impl BuildSerializer for DecimalSerializer { const EXPECTED_TYPE: &'static str = "decimal"; fn build( _schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { - Ok(Self {}.into()) + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { + Ok(DECIMAL_SERIALIZER.clone()) } } diff --git a/src/serializers/type_serializers/definitions.rs b/src/serializers/type_serializers/definitions.rs index 2cc683785..384cd5d3b 100644 --- a/src/serializers/type_serializers/definitions.rs +++ b/src/serializers/type_serializers/definitions.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::sync::Arc; use pyo3::intern; use pyo3::prelude::*; @@ -21,8 +22,8 @@ impl BuildSerializer for DefinitionsSerializerBuilder { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let schema_definitions: Bound<'_, PyList> = schema.get_as_req(intern!(py, "definitions"))?; @@ -40,20 +41,10 @@ impl BuildSerializer for DefinitionsSerializerBuilder { } pub struct DefinitionRefSerializer { - definition: DefinitionRef, + definition: DefinitionRef>, retry_with_lax_check: RecursionSafeCache, } -// TODO(DH): Remove the need to clone serializers -impl Clone for DefinitionRefSerializer { - fn clone(&self) -> Self { - Self { - definition: self.definition.clone(), - retry_with_lax_check: RecursionSafeCache::new(), - } - } -} - impl std::fmt::Debug for DefinitionRefSerializer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("DefinitionRefSerializer") @@ -69,14 +60,14 @@ impl BuildSerializer for DefinitionRefSerializer { fn build( schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let schema_ref: PyBackedStr = schema.get_as_req(intern!(schema.py(), "schema_ref"))?; let definition = definitions.get_definition(&schema_ref); - Ok(Self { + Ok(CombinedSerializer::Recursive(Self { definition, retry_with_lax_check: RecursionSafeCache::new(), - } + }) .into()) } } diff --git a/src/serializers/type_serializers/dict.rs b/src/serializers/type_serializers/dict.rs index 5038e0f35..99908392c 100644 --- a/src/serializers/type_serializers/dict.rs +++ b/src/serializers/type_serializers/dict.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::sync::Arc; use pyo3::intern; use pyo3::prelude::*; @@ -18,8 +19,8 @@ use super::{ #[derive(Debug)] pub struct DictSerializer { - key_serializer: Box, - value_serializer: Box, + key_serializer: Arc, + value_serializer: Arc, // isize because we look up include exclude via `.hash()` which returns an isize filter: SchemaFilter, name: String, @@ -31,8 +32,8 @@ impl BuildSerializer for DictSerializer { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let key_serializer = match schema.get_as(intern!(py, "keys_schema"))? { Some(items_schema) => CombinedSerializer::build(&items_schema, config, definitions)?, @@ -56,12 +57,12 @@ impl BuildSerializer for DictSerializer { key_serializer.get_name(), value_serializer.get_name() ); - Ok(Self { - key_serializer: Box::new(key_serializer), - value_serializer: Box::new(value_serializer), + Ok(CombinedSerializer::Dict(Self { + key_serializer, + value_serializer, filter, name, - } + }) .into()) } } diff --git a/src/serializers/type_serializers/enum_.rs b/src/serializers/type_serializers/enum_.rs index a689ae312..e1aca8f43 100644 --- a/src/serializers/type_serializers/enum_.rs +++ b/src/serializers/type_serializers/enum_.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::sync::Arc; use crate::build_tools::py_schema_err; use pyo3::intern; @@ -18,7 +19,7 @@ use super::{BuildSerializer, CombinedSerializer, Extra, TypeSerializer}; #[derive(Debug)] pub struct EnumSerializer { class: Py, - serializer: Option>, + serializer: Option>, } impl BuildSerializer for EnumSerializer { @@ -27,21 +28,21 @@ impl BuildSerializer for EnumSerializer { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let sub_type: Option = schema.get_as(intern!(schema.py(), "sub_type"))?; let serializer = match sub_type.as_deref() { - Some("int") => Some(Box::new(IntSerializer::new().into())), - Some("str") => Some(Box::new(StrSerializer::new().into())), - Some("float") => Some(Box::new(FloatSerializer::new(schema.py(), config)?.into())), + Some("int") => Some(IntSerializer::get().clone()), + Some("str") => Some(StrSerializer::get().clone()), + Some("float") => Some(FloatSerializer::get(schema.py(), config)?.clone()), Some(_) => return py_schema_err!("`sub_type` must be one of: 'int', 'str', 'float' or None"), None => None, }; - Ok(Self { + Ok(CombinedSerializer::Enum(Self { class: schema.get_as_req(intern!(schema.py(), "cls"))?, serializer, - } + }) .into()) } } diff --git a/src/serializers/type_serializers/float.rs b/src/serializers/type_serializers/float.rs index 7cfdde52a..f6d30cf39 100644 --- a/src/serializers/type_serializers/float.rs +++ b/src/serializers/type_serializers/float.rs @@ -2,9 +2,11 @@ use pyo3::types::PyDict; use pyo3::{intern, prelude::*, IntoPyObjectExt}; use std::borrow::Cow; +use std::sync::Arc; use serde::Serializer; +use crate::build_tools::LazyLock; use crate::definitions::DefinitionsBuilder; use crate::serializers::config::InfNanMode; use crate::tools::SchemaDict; @@ -21,13 +23,36 @@ pub struct FloatSerializer { inf_nan_mode: InfNanMode, } +static FLOAT_SERIALIZER_NULL: LazyLock> = LazyLock::new(|| { + Arc::new(CombinedSerializer::Float(FloatSerializer { + inf_nan_mode: InfNanMode::Null, + })) +}); + +static FLOAT_SERIALIZER_CONSTANTS: LazyLock> = LazyLock::new(|| { + Arc::new(CombinedSerializer::Float(FloatSerializer { + inf_nan_mode: InfNanMode::Constants, + })) +}); + +static FLOAT_SERIALIZER_STRINGS: LazyLock> = LazyLock::new(|| { + Arc::new(CombinedSerializer::Float(FloatSerializer { + inf_nan_mode: InfNanMode::Strings, + })) +}); + impl FloatSerializer { - pub fn new(py: Python, config: Option<&Bound<'_, PyDict>>) -> PyResult { + pub fn get(py: Python, config: Option<&Bound<'_, PyDict>>) -> PyResult<&'static Arc> { let inf_nan_mode = config .and_then(|c| c.get_as(intern!(py, "ser_json_inf_nan")).transpose()) .transpose()? .unwrap_or_default(); - Ok(Self { inf_nan_mode }) + + match inf_nan_mode { + InfNanMode::Null => Ok(&FLOAT_SERIALIZER_NULL), + InfNanMode::Constants => Ok(&FLOAT_SERIALIZER_CONSTANTS), + InfNanMode::Strings => Ok(&FLOAT_SERIALIZER_STRINGS), + } } } @@ -55,9 +80,9 @@ impl BuildSerializer for FloatSerializer { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { - Self::new(schema.py(), config).map(Into::into) + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { + Self::get(schema.py(), config).cloned() } } diff --git a/src/serializers/type_serializers/format.rs b/src/serializers/type_serializers/format.rs index 6e1e8d9af..51d0dc859 100644 --- a/src/serializers/type_serializers/format.rs +++ b/src/serializers/type_serializers/format.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::sync::Arc; use pyo3::intern; use pyo3::prelude::*; @@ -66,21 +67,21 @@ impl BuildSerializer for FormatSerializer { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let formatting_string: Bound<'_, PyString> = schema.get_as_req(intern!(py, "formatting_string"))?; if formatting_string.is_empty()? { ToStringSerializer::build(schema, config, definitions) } else { - Ok(Self { + Ok(CombinedSerializer::Format(Self { format_func: py .import(intern!(py, "builtins"))? .getattr(intern!(py, "format"))? .unbind(), formatting_string: formatting_string.unbind(), when_used: WhenUsed::new(schema, WhenUsed::JsonUnlessNone)?, - } + }) .into()) } } @@ -171,11 +172,11 @@ impl BuildSerializer for ToStringSerializer { fn build( schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { - Ok(Self { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { + Ok(CombinedSerializer::ToString(Self { when_used: WhenUsed::new(schema, WhenUsed::JsonUnlessNone)?, - } + }) .into()) } } diff --git a/src/serializers/type_serializers/function.rs b/src/serializers/type_serializers/function.rs index d6f6e3c2a..faeb3c4dc 100644 --- a/src/serializers/type_serializers/function.rs +++ b/src/serializers/type_serializers/function.rs @@ -31,8 +31,8 @@ impl BuildSerializer for FunctionBeforeSerializerBuilder { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); // `before` schemas will obviously have type from `schema` since the validator is called second let schema = schema.get_as_req(intern!(py, "schema"))?; @@ -47,8 +47,8 @@ impl BuildSerializer for FunctionAfterSerializerBuilder { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); // While `before` function schemas do not modify the output type (and therefore affect the // serialization), for `after` schemas, there's no way to directly infer what schema should @@ -66,8 +66,8 @@ impl BuildSerializer for FunctionPlainSerializerBuilder { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { super::any::AnySerializer::build(schema, config, definitions) } } @@ -77,9 +77,9 @@ pub struct FunctionPlainSerializer { func: Py, name: String, function_name: String, - return_serializer: Box, + return_serializer: Arc, // fallback serializer - used when when_used decides that this serializer should not be used - fallback_serializer: Option>, + fallback_serializer: Option>, when_used: WhenUsed, is_field_serializer: bool, info_arg: bool, @@ -102,8 +102,8 @@ impl BuildSerializer for FunctionPlainSerializer { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let ser_schema = schema.get_as_req(intern!(py, "serialization"))?; @@ -112,8 +112,8 @@ impl BuildSerializer for FunctionPlainSerializer { let function_name = function_name(&function)?; let return_serializer = match ser_schema.get_as(intern!(py, "return_schema"))? { - Some(s) => Box::new(CombinedSerializer::build(&s, config, definitions)?), - None => Box::new(AnySerializer::build(schema, config, definitions)?), + Some(s) => CombinedSerializer::build(&s, config, definitions)?, + None => AnySerializer::build(schema, config, definitions)?, }; let when_used = WhenUsed::new(&ser_schema, WhenUsed::Always)?; @@ -121,12 +121,12 @@ impl BuildSerializer for FunctionPlainSerializer { WhenUsed::Always => None, _ => { let new_schema = copy_outer_schema(schema)?; - Some(Box::new(CombinedSerializer::build(&new_schema, config, definitions)?)) + Some(CombinedSerializer::build(&new_schema, config, definitions)?) } }; let name = format!("plain_function[{function_name}]"); - Ok(Self { + Ok(CombinedSerializer::Function(Self { func: function.unbind(), function_name, name, @@ -135,7 +135,7 @@ impl BuildSerializer for FunctionPlainSerializer { when_used, is_field_serializer, info_arg, - } + }) .into()) } } @@ -314,8 +314,8 @@ impl BuildSerializer for FunctionWrapSerializerBuilder { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); // While `before` function schemas do not modify the output type (and therefore affect the // serialization), for `wrap` schemas (like `after`), there's no way to directly infer what @@ -346,8 +346,8 @@ impl BuildSerializer for FunctionWrapSerializer { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let ser_schema = schema.get_as_req(intern!(py, "serialization"))?; @@ -369,16 +369,16 @@ impl BuildSerializer for FunctionWrapSerializer { }; let name = format!("wrap_function[{function_name}, {}]", serializer.get_name()); - Ok(Self { - serializer: Arc::new(serializer), + Ok(CombinedSerializer::FunctionWrap(Self { + serializer, func: function.into(), function_name, name, - return_serializer: Arc::new(return_serializer), + return_serializer, when_used: WhenUsed::new(&ser_schema, WhenUsed::Always)?, is_field_serializer, info_arg, - } + }) .into()) } } diff --git a/src/serializers/type_serializers/generator.rs b/src/serializers/type_serializers/generator.rs index 2ff99935f..b140acb2e 100644 --- a/src/serializers/type_serializers/generator.rs +++ b/src/serializers/type_serializers/generator.rs @@ -31,17 +31,17 @@ impl BuildSerializer for GeneratorSerializer { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let item_serializer = match schema.get_as(intern!(py, "items_schema"))? { Some(items_schema) => CombinedSerializer::build(&items_schema, config, definitions)?, None => AnySerializer::build(schema, config, definitions)?, }; - Ok(Self { - item_serializer: Arc::new(item_serializer), + Ok(CombinedSerializer::Generator(Self { + item_serializer, filter: SchemaFilter::from_schema(schema)?, - } + }) .into()) } } diff --git a/src/serializers/type_serializers/json.rs b/src/serializers/type_serializers/json.rs index cd3fd30b4..3f36f5241 100644 --- a/src/serializers/type_serializers/json.rs +++ b/src/serializers/type_serializers/json.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; use std::str::from_utf8; +use std::sync::Arc; use pyo3::intern; use pyo3::prelude::*; @@ -19,7 +20,7 @@ use super::{ #[derive(Debug)] pub struct JsonSerializer { - serializer: Box, + serializer: Arc, } impl BuildSerializer for JsonSerializer { @@ -28,18 +29,15 @@ impl BuildSerializer for JsonSerializer { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let serializer = match schema.get_as(intern!(py, "schema"))? { Some(items_schema) => CombinedSerializer::build(&items_schema, config, definitions)?, None => AnySerializer::build(schema, config, definitions)?, }; - Ok(Self { - serializer: Box::new(serializer), - } - .into()) + Ok(Arc::new(Self { serializer }.into())) } } diff --git a/src/serializers/type_serializers/json_or_python.rs b/src/serializers/type_serializers/json_or_python.rs index 9657a6af1..116a6d309 100644 --- a/src/serializers/type_serializers/json_or_python.rs +++ b/src/serializers/type_serializers/json_or_python.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::sync::Arc; use pyo3::intern; use pyo3::prelude::*; @@ -10,8 +11,8 @@ use crate::tools::SchemaDict; #[derive(Debug)] pub struct JsonOrPythonSerializer { - json: Box, - python: Box, + json: Arc, + python: Arc, name: String, } @@ -21,8 +22,8 @@ impl BuildSerializer for JsonOrPythonSerializer { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let json_schema = schema.get_as_req(intern!(py, "json_schema"))?; let python_schema = schema.get_as_req(intern!(py, "python_schema"))?; @@ -36,12 +37,7 @@ impl BuildSerializer for JsonOrPythonSerializer { json.get_name(), python.get_name(), ); - Ok(Self { - json: Box::new(json), - python: Box::new(python), - name, - } - .into()) + Ok(Arc::new(Self { json, python, name }.into())) } } diff --git a/src/serializers/type_serializers/list.rs b/src/serializers/type_serializers/list.rs index cd0330cef..f2391816d 100644 --- a/src/serializers/type_serializers/list.rs +++ b/src/serializers/type_serializers/list.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::sync::Arc; use pyo3::intern; use pyo3::prelude::*; @@ -18,7 +19,7 @@ use super::{ #[derive(Debug)] pub struct ListSerializer { - item_serializer: Box, + item_serializer: Arc, filter: SchemaFilter, name: String, } @@ -29,20 +30,22 @@ impl BuildSerializer for ListSerializer { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let item_serializer = match schema.get_as(intern!(py, "items_schema"))? { Some(items_schema) => CombinedSerializer::build(&items_schema, config, definitions)?, None => AnySerializer::build(schema, config, definitions)?, }; let name = format!("{}[{}]", Self::EXPECTED_TYPE, item_serializer.get_name()); - Ok(Self { - item_serializer: Box::new(item_serializer), - filter: SchemaFilter::from_schema(schema)?, - name, - } - .into()) + Ok(Arc::new( + Self { + item_serializer, + filter: SchemaFilter::from_schema(schema)?, + name, + } + .into(), + )) } } diff --git a/src/serializers/type_serializers/literal.rs b/src/serializers/type_serializers/literal.rs index 97b6e3b69..5f94b4daf 100644 --- a/src/serializers/type_serializers/literal.rs +++ b/src/serializers/type_serializers/literal.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::sync::Arc; use pyo3::intern; use pyo3::prelude::*; @@ -31,8 +32,8 @@ impl BuildSerializer for LiteralSerializer { fn build( schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let expected: Bound<'_, PyList> = schema.get_as_req(intern!(schema.py(), "expected"))?; if expected.is_empty() { @@ -56,16 +57,18 @@ impl BuildSerializer for LiteralSerializer { } } - Ok(Self { - expected_int, - expected_str, - expected_py: match expected_py.is_empty() { - true => None, - false => Some(expected_py.into()), - }, - name: format!("{}[{}]", Self::EXPECTED_TYPE, repr_args.join(",")), - } - .into()) + Ok(Arc::new( + Self { + expected_int, + expected_str, + expected_py: match expected_py.is_empty() { + true => None, + false => Some(expected_py.into()), + }, + name: format!("{}[{}]", Self::EXPECTED_TYPE, repr_args.join(",")), + } + .into(), + )) } } diff --git a/src/serializers/type_serializers/missing_sentinel.rs b/src/serializers/type_serializers/missing_sentinel.rs index 5c0cff247..82fec8d2e 100644 --- a/src/serializers/type_serializers/missing_sentinel.rs +++ b/src/serializers/type_serializers/missing_sentinel.rs @@ -5,31 +5,34 @@ // core schema is used standalone (e.g. with a Pydantic type adapter), but this isn't // something we explicitly support. -use std::borrow::Cow; +use std::{borrow::Cow, sync::Arc}; use pyo3::prelude::*; use pyo3::types::PyDict; use serde::ser::Error; -use crate::common::missing_sentinel::get_missing_sentinel_object; use crate::definitions::DefinitionsBuilder; use crate::PydanticSerializationUnexpectedValue; +use crate::{build_tools::LazyLock, common::missing_sentinel::get_missing_sentinel_object}; use super::{BuildSerializer, CombinedSerializer, Extra, TypeSerializer}; #[derive(Debug)] pub struct MissingSentinelSerializer {} +static MISSING_SENTINEL_SERIALIZER: LazyLock> = + LazyLock::new(|| Arc::new(MissingSentinelSerializer {}.into())); + impl BuildSerializer for MissingSentinelSerializer { const EXPECTED_TYPE: &'static str = "missing-sentinel"; fn build( _schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { - Ok(Self {}.into()) + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { + Ok(MISSING_SENTINEL_SERIALIZER.clone()) } } diff --git a/src/serializers/type_serializers/model.rs b/src/serializers/type_serializers/model.rs index 3d9045d8f..07b4689a5 100644 --- a/src/serializers/type_serializers/model.rs +++ b/src/serializers/type_serializers/model.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::sync::Arc; use pyo3::intern; use pyo3::prelude::*; @@ -28,8 +29,8 @@ impl BuildSerializer for ModelFieldsBuilder { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let fields_mode = match has_extra(schema, config)? { @@ -85,14 +86,16 @@ impl BuildSerializer for ModelFieldsBuilder { let computed_fields = ComputedFields::new(schema, config, definitions)?; - Ok(GeneralFieldsSerializer::new(fields, fields_mode, extra_serializer, computed_fields).into()) + Ok(Arc::new( + GeneralFieldsSerializer::new(fields, fields_mode, extra_serializer, computed_fields).into(), + )) } } #[derive(Debug)] pub struct ModelSerializer { class: Py, - serializer: Box, + serializer: Arc, has_extra: bool, root_model: bool, name: String, @@ -104,8 +107,8 @@ impl BuildSerializer for ModelSerializer { fn build( schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); // models ignore the parent config and always use the config from this model @@ -113,17 +116,17 @@ impl BuildSerializer for ModelSerializer { let class: Py = 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 root_model = schema.get_as(intern!(py, "root_model"))?.unwrap_or(false); let name = class.bind(py).getattr(intern!(py, "__name__"))?.extract()?; - Ok(Self { + Ok(CombinedSerializer::Model(Self { class, serializer, has_extra: has_extra(schema, config.as_ref())?, root_model, name, - } + }) .into()) } } diff --git a/src/serializers/type_serializers/nullable.rs b/src/serializers/type_serializers/nullable.rs index ab5c2402a..f5c6f61bc 100644 --- a/src/serializers/type_serializers/nullable.rs +++ b/src/serializers/type_serializers/nullable.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::sync::Arc; use pyo3::intern; use pyo3::prelude::*; @@ -11,7 +12,7 @@ use super::{infer_json_key_known, BuildSerializer, CombinedSerializer, Extra, Is #[derive(Debug)] pub struct NullableSerializer { - serializer: Box, + serializer: Arc, } impl BuildSerializer for NullableSerializer { @@ -20,12 +21,12 @@ impl BuildSerializer for NullableSerializer { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let sub_schema = schema.get_as_req(intern!(schema.py(), "schema"))?; - Ok(Self { - serializer: Box::new(CombinedSerializer::build(&sub_schema, config, definitions)?), - } + Ok(CombinedSerializer::Nullable(Self { + serializer: CombinedSerializer::build(&sub_schema, config, definitions)?, + }) .into()) } } diff --git a/src/serializers/type_serializers/other.rs b/src/serializers/type_serializers/other.rs index 8ca67f80d..56dae66ac 100644 --- a/src/serializers/type_serializers/other.rs +++ b/src/serializers/type_serializers/other.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::intern; use pyo3::prelude::*; use pyo3::types::{PyDict, PyList}; @@ -17,8 +19,8 @@ impl BuildSerializer for ChainBuilder { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let last_schema = schema .get_as_req::>(intern!(schema.py(), "steps"))? .iter() @@ -37,8 +39,8 @@ impl BuildSerializer for CustomErrorBuilder { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let sub_schema = schema.get_as_req(intern!(schema.py(), "schema"))?; CombinedSerializer::build(&sub_schema, config, definitions) } @@ -52,8 +54,8 @@ impl BuildSerializer for CallBuilder { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let return_schema = schema.get_as(intern!(schema.py(), "return_schema"))?; match return_schema { Some(return_schema) => CombinedSerializer::build(&return_schema, config, definitions), @@ -70,8 +72,8 @@ impl BuildSerializer for LaxOrStrictBuilder { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let strict_schema = schema.get_as_req(intern!(schema.py(), "strict_schema"))?; CombinedSerializer::build(&strict_schema, config, definitions) } @@ -85,8 +87,8 @@ impl BuildSerializer for ArgumentsBuilder { fn build( _schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { py_schema_err!("`arguments` validators require a custom serializer") } } @@ -101,8 +103,8 @@ macro_rules! any_build_serializer { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { AnySerializer::build(schema, config, definitions) } } diff --git a/src/serializers/type_serializers/set_frozenset.rs b/src/serializers/type_serializers/set_frozenset.rs index 17b57fc01..6387fd2f4 100644 --- a/src/serializers/type_serializers/set_frozenset.rs +++ b/src/serializers/type_serializers/set_frozenset.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::sync::Arc; use pyo3::prelude::*; use pyo3::types::{PyDict, PyFrozenSet, PyList, PySet}; @@ -19,7 +20,7 @@ macro_rules! build_serializer { ($struct_name:ident, $expected_type:literal, $py_type:ty) => { #[derive(Debug)] pub struct $struct_name { - item_serializer: Box, + item_serializer: Arc, name: String, } @@ -29,19 +30,21 @@ macro_rules! build_serializer { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let item_serializer = match schema.get_as(intern!(py, "items_schema"))? { Some(items_schema) => CombinedSerializer::build(&items_schema, config, definitions)?, None => AnySerializer::build(schema, config, definitions)?, }; let name = format!("{}[{}]", Self::EXPECTED_TYPE, item_serializer.get_name()); - Ok(Self { - item_serializer: Box::new(item_serializer), - name, - } - .into()) + Ok(Arc::new( + Self { + item_serializer, + name, + } + .into(), + )) } } diff --git a/src/serializers/type_serializers/simple.rs b/src/serializers/type_serializers/simple.rs index d27dd80f8..d05698b6d 100644 --- a/src/serializers/type_serializers/simple.rs +++ b/src/serializers/type_serializers/simple.rs @@ -3,9 +3,11 @@ use pyo3::types::PyDict; use pyo3::IntoPyObjectExt; use std::borrow::Cow; +use std::sync::Arc; use serde::Serialize; +use crate::build_tools::LazyLock; use crate::PydanticSerializationUnexpectedValue; use crate::{definitions::DefinitionsBuilder, input::Int}; @@ -17,15 +19,17 @@ use super::{ #[derive(Debug)] pub struct NoneSerializer; +static NONE_SERIALIZER: LazyLock> = LazyLock::new(|| Arc::new(NoneSerializer.into())); + impl BuildSerializer for NoneSerializer { const EXPECTED_TYPE: &'static str = "none"; fn build( _schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { - Ok(Self {}.into()) + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { + Ok(NONE_SERIALIZER.clone()) } } @@ -92,8 +96,10 @@ macro_rules! build_simple_serializer { pub struct $struct_name; impl $struct_name { - pub fn new() -> Self { - Self {} + pub fn get() -> &'static std::sync::Arc { + static INSTANCE: $crate::build_tools::LazyLock> = + $crate::build_tools::LazyLock::new(|| std::sync::Arc::new($struct_name.into())); + &INSTANCE } } @@ -103,9 +109,9 @@ macro_rules! build_simple_serializer { fn build( _schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { - Ok(Self::new().into()) + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { + Ok(Self::get().clone()) } } diff --git a/src/serializers/type_serializers/string.rs b/src/serializers/type_serializers/string.rs index 5534f5ea8..be54e9619 100644 --- a/src/serializers/type_serializers/string.rs +++ b/src/serializers/type_serializers/string.rs @@ -1,8 +1,10 @@ use std::borrow::Cow; +use std::sync::Arc; use pyo3::types::{PyDict, PyString}; use pyo3::{prelude::*, IntoPyObjectExt}; +use crate::build_tools::LazyLock; use crate::definitions::DefinitionsBuilder; use super::{ @@ -13,9 +15,11 @@ use super::{ #[derive(Debug)] pub struct StrSerializer; +static STR_SERIALIZER: LazyLock> = LazyLock::new(|| Arc::new(StrSerializer.into())); + impl StrSerializer { - pub fn new() -> Self { - Self {} + pub fn get() -> &'static Arc { + &STR_SERIALIZER } } @@ -25,9 +29,9 @@ impl BuildSerializer for StrSerializer { fn build( _schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { - Ok(Self::new().into()) + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { + Ok(Self::get().clone()) } } diff --git a/src/serializers/type_serializers/timedelta.rs b/src/serializers/type_serializers/timedelta.rs index fcd8278ef..f740bd49c 100644 --- a/src/serializers/type_serializers/timedelta.rs +++ b/src/serializers/type_serializers/timedelta.rs @@ -2,6 +2,7 @@ use pyo3::intern; use pyo3::prelude::*; use pyo3::types::PyDict; use std::borrow::Cow; +use std::sync::Arc; use crate::definitions::DefinitionsBuilder; use crate::input::EitherTimedelta; @@ -23,8 +24,8 @@ impl BuildSerializer for TimeDeltaSerializer { fn build( _schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let temporal_set = config .and_then(|cfg| cfg.contains(intern!(cfg.py(), "ser_json_temporal")).ok()) .unwrap_or(false); @@ -35,7 +36,7 @@ impl BuildSerializer for TimeDeltaSerializer { td_mode.into() }; - Ok(Self { temporal_mode }.into()) + Ok(Arc::new(Self { temporal_mode }.into())) } } diff --git a/src/serializers/type_serializers/tuple.rs b/src/serializers/type_serializers/tuple.rs index 7ff9384b2..835d32544 100644 --- a/src/serializers/type_serializers/tuple.rs +++ b/src/serializers/type_serializers/tuple.rs @@ -3,6 +3,7 @@ use pyo3::prelude::*; use pyo3::types::{PyDict, PyList, PyTuple}; use std::borrow::Cow; use std::iter; +use std::sync::Arc; use serde::ser::SerializeSeq; @@ -19,7 +20,7 @@ use super::{ #[derive(Debug)] pub struct TupleSerializer { - serializers: Vec, + serializers: Vec>, variadic_item_index: Option, filter: SchemaFilter, name: String, @@ -31,28 +32,28 @@ impl BuildSerializer for TupleSerializer { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let items: Bound<'_, PyList> = schema.get_as_req(intern!(py, "items_schema"))?; - let serializers: Vec = items + let serializers: Vec> = items .iter() .map(|item| CombinedSerializer::build(item.downcast()?, config, definitions)) .collect::>()?; - let mut serializer_names = serializers.iter().map(TypeSerializer::get_name).collect::>(); + let mut serializer_names = serializers.iter().map(|v| v.get_name()).collect::>(); let variadic_item_index: Option = schema.get_as(intern!(py, "variadic_item_index"))?; if let Some(variadic_item_index) = variadic_item_index { serializer_names.insert(variadic_item_index + 1, "..."); } let name = format!("tuple[{}]", serializer_names.join(", ")); - Ok(Self { + Ok(CombinedSerializer::Tuple(Self { serializers, variadic_item_index, filter: SchemaFilter::from_schema(schema)?, name, - } + }) .into()) } } diff --git a/src/serializers/type_serializers/typed_dict.rs b/src/serializers/type_serializers/typed_dict.rs index 406b95779..9b1751bc2 100644 --- a/src/serializers/type_serializers/typed_dict.rs +++ b/src/serializers/type_serializers/typed_dict.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::intern; use pyo3::prelude::*; use pyo3::types::{PyDict, PyString}; @@ -20,8 +22,8 @@ impl BuildSerializer for TypedDictBuilder { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let total = @@ -82,6 +84,8 @@ impl BuildSerializer for TypedDictBuilder { let computed_fields = ComputedFields::new(schema, config, definitions)?; - Ok(GeneralFieldsSerializer::new(fields, fields_mode, extra_serializer, computed_fields).into()) + Ok(Arc::new( + GeneralFieldsSerializer::new(fields, fields_mode, extra_serializer, computed_fields).into(), + )) } } diff --git a/src/serializers/type_serializers/union.rs b/src/serializers/type_serializers/union.rs index 9ab1a9f54..2df50a694 100644 --- a/src/serializers/type_serializers/union.rs +++ b/src/serializers/type_serializers/union.rs @@ -4,6 +4,7 @@ use pyo3::prelude::*; use pyo3::types::{PyDict, PyList, PyTuple}; use smallvec::SmallVec; use std::borrow::Cow; +use std::sync::Arc; use crate::build_tools::py_schema_err; use crate::common::union::{Discriminator, SMALL_UNION_THRESHOLD}; @@ -18,7 +19,7 @@ use super::{ #[derive(Debug)] pub struct UnionSerializer { - choices: Vec, + choices: Vec>, name: String, } @@ -28,10 +29,10 @@ impl BuildSerializer for UnionSerializer { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); - let choices: Vec = schema + let choices = schema .get_as_req::>(intern!(py, "choices"))? .iter() .map(|choice| { @@ -41,27 +42,23 @@ impl BuildSerializer for UnionSerializer { }; CombinedSerializer::build(choice.downcast()?, config, definitions) }) - .collect::>>()?; + .collect::>()?; Self::from_choices(choices) } } impl UnionSerializer { - fn from_choices(choices: Vec) -> PyResult { + fn from_choices(choices: Vec>) -> PyResult> { match choices.len() { 0 => py_schema_err!("One or more union choices required"), 1 => Ok(choices.into_iter().next().unwrap()), _ => { - let descr = choices - .iter() - .map(TypeSerializer::get_name) - .collect::>() - .join(", "); - Ok(Self { + let descr = choices.iter().map(|v| v.get_name()).collect::>().join(", "); + Ok(CombinedSerializer::Union(Self { choices, name: format!("Union[{descr}]"), - } + }) .into()) } } @@ -76,7 +73,7 @@ fn union_serialize( // Finally, `Err(err)` if we encountered errors while trying to serialize mut selector: impl FnMut(&CombinedSerializer, &Extra) -> PyResult, extra: &Extra, - choices: &[CombinedSerializer], + choices: &[Arc], retry_with_lax_check: bool, py: Python<'_>, ) -> PyResult> { @@ -182,7 +179,7 @@ impl TypeSerializer for UnionSerializer { } fn retry_with_lax_check(&self) -> bool { - self.choices.iter().any(CombinedSerializer::retry_with_lax_check) + self.choices.iter().any(|c| c.retry_with_lax_check()) } } @@ -190,7 +187,7 @@ impl TypeSerializer for UnionSerializer { pub struct TaggedUnionSerializer { discriminator: Discriminator, lookup: HashMap, - choices: Vec, + choices: Vec>, name: String, } @@ -200,8 +197,8 @@ impl BuildSerializer for TaggedUnionSerializer { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let discriminator = Discriminator::new(py, &schema.get_as_req(intern!(py, "discriminator"))?)?; @@ -216,18 +213,14 @@ impl BuildSerializer for TaggedUnionSerializer { lookup.insert(choice_key.to_string(), idx); } - let descr = choices - .iter() - .map(TypeSerializer::get_name) - .collect::>() - .join(", "); + let descr = choices.iter().map(|s| s.get_name()).collect::>().join(", "); - Ok(Self { + Ok(CombinedSerializer::TaggedUnion(Self { discriminator, lookup, choices, name: format!("TaggedUnion[{descr}]"), - } + }) .into()) } } @@ -287,7 +280,7 @@ impl TypeSerializer for TaggedUnionSerializer { } fn retry_with_lax_check(&self) -> bool { - self.choices.iter().any(CombinedSerializer::retry_with_lax_check) + self.choices.iter().any(|c| c.retry_with_lax_check()) } } diff --git a/src/serializers/type_serializers/url.rs b/src/serializers/type_serializers/url.rs index 1387dbfdc..00600b6f4 100644 --- a/src/serializers/type_serializers/url.rs +++ b/src/serializers/type_serializers/url.rs @@ -1,11 +1,12 @@ use std::borrow::Cow; +use std::sync::Arc; use pyo3::prelude::*; use pyo3::types::PyDict; use pyo3::IntoPyObjectExt; +use crate::build_tools::LazyLock; use crate::definitions::DefinitionsBuilder; - use crate::url::{PyMultiHostUrl, PyUrl}; use super::{ @@ -24,9 +25,11 @@ macro_rules! build_serializer { fn build( _schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { - Ok(Self {}.into()) + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { + static SERIALIZER: LazyLock> = + LazyLock::new(|| Arc::new(CombinedSerializer::from($struct_name {}))); + Ok(SERIALIZER.clone()) } } diff --git a/src/serializers/type_serializers/uuid.rs b/src/serializers/type_serializers/uuid.rs index c8c6a7fb6..f39f3d59f 100644 --- a/src/serializers/type_serializers/uuid.rs +++ b/src/serializers/type_serializers/uuid.rs @@ -1,9 +1,11 @@ use std::borrow::Cow; +use std::sync::Arc; use pyo3::types::PyDict; use pyo3::{intern, prelude::*, IntoPyObjectExt}; use uuid::Uuid; +use crate::build_tools::LazyLock; use crate::definitions::DefinitionsBuilder; use super::{ @@ -21,6 +23,9 @@ pub(crate) fn uuid_to_string(py_uuid: &Bound<'_, PyAny>) -> PyResult { #[derive(Debug)] pub struct UuidSerializer; +static UUID_SERIALIZER: LazyLock> = + LazyLock::new(|| Arc::new(CombinedSerializer::from(UuidSerializer {}))); + impl_py_gc_traverse!(UuidSerializer {}); impl BuildSerializer for UuidSerializer { @@ -29,9 +34,9 @@ impl BuildSerializer for UuidSerializer { fn build( _schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { - Ok(Self {}.into()) + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { + Ok(UUID_SERIALIZER.clone()) } } diff --git a/src/serializers/type_serializers/with_default.rs b/src/serializers/type_serializers/with_default.rs index a4a5c6769..e1f3add22 100644 --- a/src/serializers/type_serializers/with_default.rs +++ b/src/serializers/type_serializers/with_default.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::sync::Arc; use pyo3::intern; use pyo3::prelude::*; @@ -13,7 +14,7 @@ use super::{BuildSerializer, CombinedSerializer, Extra, TypeSerializer}; #[derive(Debug)] pub struct WithDefaultSerializer { default: DefaultType, - serializer: Box, + serializer: Arc, } impl BuildSerializer for WithDefaultSerializer { @@ -22,15 +23,15 @@ impl BuildSerializer for WithDefaultSerializer { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let default = DefaultType::new(schema)?; let sub_schema = schema.get_as_req(intern!(py, "schema"))?; - let serializer = Box::new(CombinedSerializer::build(&sub_schema, config, definitions)?); + let serializer = CombinedSerializer::build(&sub_schema, config, definitions)?; - Ok(Self { default, serializer }.into()) + Ok(Arc::new(Self { default, serializer }.into())) } } diff --git a/src/validators/any.rs b/src/validators/any.rs index 52bb401b4..93b0761db 100644 --- a/src/validators/any.rs +++ b/src/validators/any.rs @@ -1,8 +1,10 @@ +use std::sync::Arc; + use pyo3::prelude::*; use pyo3::types::PyDict; -use crate::errors::ValResult; use crate::input::Input; +use crate::{build_tools::LazyLock, errors::ValResult}; use super::{ validation_state::Exactness, BuildValidator, CombinedValidator, DefinitionsBuilder, ValidationState, Validator, @@ -12,15 +14,17 @@ use super::{ #[derive(Debug, Clone)] pub struct AnyValidator; +static ANY_VALIDATOR: LazyLock> = LazyLock::new(|| Arc::new(AnyValidator.into())); + impl BuildValidator for AnyValidator { const EXPECTED_TYPE: &'static str = "any"; fn build( _schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { - Ok(Self.into()) + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { + Ok(ANY_VALIDATOR.clone()) } } diff --git a/src/validators/arguments.rs b/src/validators/arguments.rs index 737e0b3a2..ad13e6ade 100644 --- a/src/validators/arguments.rs +++ b/src/validators/arguments.rs @@ -1,4 +1,5 @@ use std::str::FromStr; +use std::sync::Arc; use pyo3::intern; use pyo3::prelude::*; @@ -43,7 +44,7 @@ struct Parameter { positional: bool, name: String, kwarg_key: Option>, - validator: CombinedValidator, + validator: Arc, lookup_key_collection: LookupKeyCollection, mode: String, } @@ -52,9 +53,9 @@ struct Parameter { pub struct ArgumentsValidator { parameters: Vec, positional_params_count: usize, - var_args_validator: Option>, + var_args_validator: Option>, var_kwargs_mode: VarKwargsMode, - var_kwargs_validator: Option>, + var_kwargs_validator: Option>, loc_by_alias: bool, extra: ExtraBehavior, validate_by_alias: Option, @@ -67,8 +68,8 @@ impl BuildValidator for ArgumentsValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let arguments_schema: Bound<'_, PyList> = schema.get_as_req(intern!(py, "arguments_schema"))?; @@ -111,8 +112,8 @@ impl BuildValidator for ArgumentsValidator { Err(err) => return py_schema_err!("Parameter '{}':\n {}", name, err), }; - let has_default = match validator { - CombinedValidator::WithDefault(ref v) => { + let has_default = match validator.as_ref() { + CombinedValidator::WithDefault(v) => { if v.omit_on_error() { return py_schema_err!("Parameter '{}': omit_on_error cannot be used with arguments", name); } @@ -146,7 +147,7 @@ impl BuildValidator for ArgumentsValidator { let var_kwargs_mode = VarKwargsMode::from_str(py_var_kwargs_mode.to_str()?)?; let var_kwargs_validator = match schema.get_item(intern!(py, "var_kwargs_schema"))? { - Some(v) => Some(Box::new(build_validator(&v, config, definitions)?)), + Some(v) => Some(build_validator(&v, config, definitions)?), None => None, }; @@ -156,11 +157,11 @@ impl BuildValidator for ArgumentsValidator { ); } - Ok(Self { + Ok(CombinedValidator::Arguments(Self { parameters, positional_params_count, var_args_validator: match schema.get_item(intern!(py, "var_args_schema"))? { - Some(v) => Some(Box::new(build_validator(&v, config, definitions)?)), + Some(v) => Some(build_validator(&v, config, definitions)?), None => None, }, var_kwargs_mode, @@ -169,7 +170,7 @@ impl BuildValidator for ArgumentsValidator { extra: ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Forbid)?, validate_by_alias: schema_or_config_same(schema, config, intern!(py, "validate_by_alias"))?, validate_by_name: schema_or_config_same(schema, config, intern!(py, "validate_by_name"))?, - } + }) .into()) } } diff --git a/src/validators/arguments_v3.rs b/src/validators/arguments_v3.rs index a9c8183b2..8e5781051 100644 --- a/src/validators/arguments_v3.rs +++ b/src/validators/arguments_v3.rs @@ -1,4 +1,5 @@ use std::str::FromStr; +use std::sync::Arc; use pyo3::intern; use pyo3::prelude::*; @@ -52,7 +53,7 @@ struct Parameter { name: String, mode: ParameterMode, lookup_key_collection: LookupKeyCollection, - validator: CombinedValidator, + validator: Arc, } impl Parameter { @@ -80,8 +81,8 @@ impl BuildValidator for ArgumentsV3Validator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let arguments_schema: Bound<'_, PyList> = schema.get_as_req(intern!(py, "arguments_schema"))?; @@ -167,8 +168,8 @@ impl BuildValidator for ArgumentsV3Validator { Err(err) => return py_schema_err!("Parameter '{}':\n {}", name, err), }; - let has_default = match validator { - CombinedValidator::WithDefault(ref v) => { + let has_default = match validator.as_ref() { + CombinedValidator::WithDefault(v) => { if v.omit_on_error() { return py_schema_err!("Parameter '{}': omit_on_error cannot be used with arguments", name); } @@ -204,14 +205,14 @@ impl BuildValidator for ArgumentsV3Validator { }) .count(); - Ok(Self { + Ok(CombinedValidator::ArgumentsV3(Self { parameters, positional_params_count, loc_by_alias: config.get_as(intern!(py, "loc_by_alias"))?.unwrap_or(true), extra: ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Forbid)?, validate_by_alias: schema_or_config_same(schema, config, intern!(py, "validate_by_alias"))?, validate_by_name: schema_or_config_same(schema, config, intern!(py, "validate_by_name"))?, - } + }) .into()) } } diff --git a/src/validators/bool.rs b/src/validators/bool.rs index a0c0454d2..094343791 100644 --- a/src/validators/bool.rs +++ b/src/validators/bool.rs @@ -1,7 +1,9 @@ +use std::sync::Arc; + use pyo3::types::PyDict; use pyo3::{prelude::*, IntoPyObjectExt}; -use crate::build_tools::is_strict; +use crate::build_tools::{is_strict, LazyLock}; use crate::errors::ValResult; use crate::input::Input; @@ -12,18 +14,25 @@ pub struct BoolValidator { strict: bool, } +static STRICT_BOOL_VALIDATOR: LazyLock> = + LazyLock::new(|| Arc::new(BoolValidator { strict: true }.into())); + +static LAX_BOOL_VALIDATOR: LazyLock> = + LazyLock::new(|| Arc::new(BoolValidator { strict: false }.into())); + impl BuildValidator for BoolValidator { const EXPECTED_TYPE: &'static str = "bool"; fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { - Ok(Self { - strict: is_strict(schema, config)?, + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { + if is_strict(schema, config)? { + Ok(STRICT_BOOL_VALIDATOR.clone()) + } else { + Ok(LAX_BOOL_VALIDATOR.clone()) } - .into()) } } diff --git a/src/validators/bytes.rs b/src/validators/bytes.rs index ccc5a1a48..6092b0771 100644 --- a/src/validators/bytes.rs +++ b/src/validators/bytes.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::intern; use pyo3::prelude::*; use pyo3::types::PyDict; @@ -24,18 +26,18 @@ impl BuildValidator for BytesValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let use_constrained = schema.get_item(intern!(py, "max_length"))?.is_some() || schema.get_item(intern!(py, "min_length"))?.is_some(); if use_constrained { BytesConstrainedValidator::build(schema, config) } else { - Ok(Self { + Ok(CombinedValidator::Bytes(Self { strict: is_strict(schema, config)?, bytes_mode: ValBytesMode::from_config(config)?, - } + }) .into()) } } @@ -113,14 +115,14 @@ impl Validator for BytesConstrainedValidator { } impl BytesConstrainedValidator { - fn build(schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>) -> PyResult { + fn build(schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>) -> PyResult> { let py = schema.py(); - Ok(Self { + Ok(CombinedValidator::ConstrainedBytes(Self { strict: is_strict(schema, config)?, bytes_mode: ValBytesMode::from_config(config)?, min_length: schema.get_as(intern!(py, "min_length"))?, max_length: schema.get_as(intern!(py, "max_length"))?, - } + }) .into()) } } diff --git a/src/validators/call.rs b/src/validators/call.rs index 41398ed3e..48509a92f 100644 --- a/src/validators/call.rs +++ b/src/validators/call.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::exceptions::PyTypeError; use pyo3::intern; use pyo3::prelude::*; @@ -15,8 +17,8 @@ use super::{build_validator, BuildValidator, CombinedValidator, DefinitionsBuild #[derive(Debug)] pub struct CallValidator { function: Py, - arguments_validator: Box, - return_validator: Option>, + arguments_validator: Arc, + return_validator: Option>, name: String, } @@ -26,16 +28,16 @@ impl BuildValidator for CallValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let arguments_schema = schema.get_as_req(intern!(py, "arguments_schema"))?; - let arguments_validator = Box::new(build_validator(&arguments_schema, config, definitions)?); + let arguments_validator = build_validator(&arguments_schema, config, definitions)?; let return_schema = schema.get_item(intern!(py, "return_schema"))?; let return_validator = match return_schema { - Some(return_schema) => Some(Box::new(build_validator(&return_schema, config, definitions)?)), + Some(return_schema) => Some(build_validator(&return_schema, config, definitions)?), None => None, }; let function: Bound<'_, PyAny> = schema.get_as_req(intern!(py, "function"))?; @@ -58,12 +60,12 @@ impl BuildValidator for CallValidator { let function_name = function_name.bind(py); let name = format!("{}[{function_name}]", Self::EXPECTED_TYPE); - Ok(Self { + Ok(CombinedValidator::FunctionCall(Self { function: function.unbind(), arguments_validator, return_validator, name, - } + }) .into()) } } diff --git a/src/validators/callable.rs b/src/validators/callable.rs index 2990bf374..fc421af61 100644 --- a/src/validators/callable.rs +++ b/src/validators/callable.rs @@ -1,6 +1,9 @@ +use std::sync::Arc; + use pyo3::prelude::*; use pyo3::types::PyDict; +use crate::build_tools::LazyLock; use crate::errors::{ErrorTypeDefaults, ValError, ValResult}; use crate::input::Input; @@ -10,15 +13,17 @@ use super::{BuildValidator, CombinedValidator, DefinitionsBuilder, ValidationSta #[derive(Debug, Clone)] pub struct CallableValidator; +static CALLABLE_VALIDATOR: LazyLock> = LazyLock::new(|| Arc::new(CallableValidator.into())); + impl BuildValidator for CallableValidator { const EXPECTED_TYPE: &'static str = "callable"; fn build( _schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { - Ok(Self.into()) + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { + Ok(CALLABLE_VALIDATOR.clone()) } } diff --git a/src/validators/chain.rs b/src/validators/chain.rs index 2ba18bd18..b0a6d9979 100644 --- a/src/validators/chain.rs +++ b/src/validators/chain.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::intern; use pyo3::prelude::*; use pyo3::types::{PyDict, PyList}; @@ -12,7 +14,7 @@ use super::{build_validator, BuildValidator, CombinedValidator, DefinitionsBuild #[derive(Debug)] pub struct ChainValidator { - steps: Vec, + steps: Vec>, name: String, } @@ -22,16 +24,16 @@ impl BuildValidator for ChainValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { - let steps: Vec = schema + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { + let steps: Vec> = schema .get_as_req::>(intern!(schema.py(), "steps"))? .iter() .map(|step| build_validator_steps(&step, config, definitions)) - .collect::>>>()? + .collect::>>>>()? .into_iter() .flatten() - .collect::>(); + .collect(); match steps.len() { 0 => py_schema_err!("One or more steps are required for a chain validator"), @@ -40,12 +42,12 @@ impl BuildValidator for ChainValidator { Ok(step) } _ => { - let descr = steps.iter().map(Validator::get_name).collect::>().join(","); + let descr = steps.iter().map(|v| v.get_name()).collect::>().join(","); - Ok(Self { + Ok(CombinedValidator::Chain(Self { steps, name: format!("{}[{descr}]", Self::EXPECTED_TYPE), - } + }) .into()) } } @@ -57,11 +59,11 @@ impl BuildValidator for ChainValidator { fn build_validator_steps( step: &Bound<'_, PyAny>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, -) -> PyResult> { + definitions: &mut DefinitionsBuilder>, +) -> PyResult>> { let validator = build_validator(step, config, definitions)?; - if let CombinedValidator::Chain(chain_validator) = validator { - Ok(chain_validator.steps) + if let CombinedValidator::Chain(chain_validator) = validator.as_ref() { + Ok(chain_validator.steps.clone()) } else { Ok(vec![validator]) } diff --git a/src/validators/complex.rs b/src/validators/complex.rs index f25739f88..2e6cc3c50 100644 --- a/src/validators/complex.rs +++ b/src/validators/complex.rs @@ -1,9 +1,11 @@ +use std::sync::Arc; + use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use pyo3::sync::PyOnceLock; use pyo3::types::{PyComplex, PyDict, PyString, PyType}; -use crate::build_tools::is_strict; +use crate::build_tools::{is_strict, LazyLock}; use crate::errors::{ErrorTypeDefaults, ToErrorValue, ValError, ValResult}; use crate::input::Input; @@ -22,17 +24,24 @@ pub struct ComplexValidator { strict: bool, } +static STRICT_COMPLEX_VALIDATOR: LazyLock> = + LazyLock::new(|| Arc::new(ComplexValidator { strict: true }.into())); + +static LAX_COMPLEX_VALIDATOR: LazyLock> = + LazyLock::new(|| Arc::new(ComplexValidator { strict: false }.into())); + impl BuildValidator for ComplexValidator { const EXPECTED_TYPE: &'static str = "complex"; fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { - Ok(Self { - strict: is_strict(schema, config)?, + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { + if is_strict(schema, config)? { + Ok(STRICT_COMPLEX_VALIDATOR.clone()) + } else { + Ok(LAX_COMPLEX_VALIDATOR.clone()) } - .into()) } } diff --git a/src/validators/custom_error.rs b/src/validators/custom_error.rs index 1ab421596..426a191ed 100644 --- a/src/validators/custom_error.rs +++ b/src/validators/custom_error.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::intern; use pyo3::prelude::*; use pyo3::types::PyDict; @@ -21,7 +23,7 @@ impl CustomError { pub fn build( schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, + _definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); let error_type: String = match schema.get_as(intern!(py, "custom_error_type"))? { @@ -59,7 +61,7 @@ impl CustomError { #[derive(Debug)] pub struct CustomErrorValidator { - validator: Box, + validator: Arc, custom_error: CustomError, name: String, } @@ -70,17 +72,17 @@ impl BuildValidator for CustomErrorValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let custom_error = CustomError::build(schema, config, definitions)?.unwrap(); let schema = schema.get_as_req(intern!(schema.py(), "schema"))?; - let validator = Box::new(build_validator(&schema, config, definitions)?); + let validator = build_validator(&schema, config, definitions)?; let name = format!("{}[{}]", Self::EXPECTED_TYPE, validator.get_name()); - Ok(Self { + Ok(CombinedValidator::CustomError(Self { validator, custom_error, name, - } + }) .into()) } } diff --git a/src/validators/dataclass.rs b/src/validators/dataclass.rs index 4d5a1f93c..dbd797f12 100644 --- a/src/validators/dataclass.rs +++ b/src/validators/dataclass.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::exceptions::PyKeyError; use pyo3::intern; use pyo3::prelude::*; @@ -28,7 +30,7 @@ struct Field { init: bool, init_only: bool, lookup_key_collection: LookupKeyCollection, - validator: CombinedValidator, + validator: Arc, frozen: bool, } @@ -40,7 +42,7 @@ pub struct DataclassArgsValidator { dataclass_name: String, validator_name: String, extra_behavior: ExtraBehavior, - extras_validator: Option>, + extras_validator: Option>, loc_by_alias: bool, validate_by_alias: Option, validate_by_name: Option, @@ -52,14 +54,14 @@ impl BuildValidator for DataclassArgsValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let extra_behavior = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?; let extras_validator = match (schema.get_item(intern!(py, "extras_schema"))?, &extra_behavior) { - (Some(v), ExtraBehavior::Allow) => Some(Box::new(build_validator(&v, config, definitions)?)), + (Some(v), ExtraBehavior::Allow) => Some(build_validator(&v, config, definitions)?), (Some(_), _) => return py_schema_err!("extras_schema can only be used if extra_behavior=allow"), (_, _) => None, }; @@ -82,7 +84,7 @@ impl BuildValidator for DataclassArgsValidator { Err(err) => return py_schema_err!("Field '{}':\n {}", name, err), }; - if let CombinedValidator::WithDefault(ref v) = validator { + if let CombinedValidator::WithDefault(v) = validator.as_ref() { if v.omit_on_error() { return py_schema_err!("Field `{}`: omit_on_error cannot be used with arguments", name); } @@ -116,7 +118,7 @@ impl BuildValidator for DataclassArgsValidator { let dataclass_name: String = schema.get_as_req(intern!(py, "dataclass_name"))?; let validator_name = format!("dataclass-args[{dataclass_name}]"); - Ok(Self { + Ok(CombinedValidator::DataclassArgs(Self { fields, positional_count, init_only_count, @@ -127,7 +129,7 @@ impl BuildValidator for DataclassArgsValidator { loc_by_alias: config.get_as(intern!(py, "loc_by_alias"))?.unwrap_or(true), validate_by_alias: config.get_as(intern!(py, "validate_by_alias"))?, validate_by_name: config.get_as(intern!(py, "validate_by_name"))?, - } + }) .into()) } } @@ -452,7 +454,7 @@ impl Validator for DataclassArgsValidator { #[derive(Debug)] pub struct DataclassValidator { strict: bool, - validator: Box, + validator: Arc, class: Py, generic_origin: Option>, fields: Vec>, @@ -469,8 +471,8 @@ impl BuildValidator for DataclassValidator { fn build( schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); // dataclasses ignore the parent config and always use the config from this dataclasses @@ -494,9 +496,9 @@ impl BuildValidator for DataclassValidator { let fields = schema.get_as_req(intern!(py, "fields"))?; - Ok(Self { + Ok(CombinedValidator::Dataclass(Self { strict: is_strict(schema, config)?, - validator: Box::new(validator), + validator, class: class.into(), generic_origin: generic_origin.map(std::convert::Into::into), fields, @@ -510,7 +512,7 @@ impl BuildValidator for DataclassValidator { name, frozen: schema.get_as(intern!(py, "frozen"))?.unwrap_or(false), slots: schema.get_as(intern!(py, "slots"))?.unwrap_or(false), - } + }) .into()) } } diff --git a/src/validators/date.rs b/src/validators/date.rs index 2cc47814a..4836ec73c 100644 --- a/src/validators/date.rs +++ b/src/validators/date.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::exceptions::PyValueError; use pyo3::intern; use pyo3::prelude::*; @@ -27,13 +29,13 @@ impl BuildValidator for DateValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { - Ok(Self { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { + Ok(CombinedValidator::Date(Self { strict: is_strict(schema, config)?, constraints: DateConstraints::from_py(schema)?, val_temporal_unit: TemporalUnitMode::from_config(config)?, - } + }) .into()) } } diff --git a/src/validators/datetime.rs b/src/validators/datetime.rs index 818028b93..23fdfe6a4 100644 --- a/src/validators/datetime.rs +++ b/src/validators/datetime.rs @@ -5,6 +5,7 @@ use pyo3::sync::PyOnceLock; use pyo3::types::{PyDict, PyString}; use speedate::{DateTime, MicrosecondsPrecisionOverflowBehavior, Time}; use std::cmp::Ordering; +use std::sync::Arc; use strum::EnumMessage; use crate::build_tools::{is_strict, py_schema_error_type}; @@ -46,14 +47,14 @@ impl BuildValidator for DateTimeValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { - Ok(Self { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { + Ok(CombinedValidator::Datetime(Self { strict: is_strict(schema, config)?, constraints: DateTimeConstraints::from_py(schema)?, microseconds_precision: extract_microseconds_precision(schema, config)?, val_temporal_unit: TemporalUnitMode::from_config(config)?, - } + }) .into()) } } diff --git a/src/validators/decimal.rs b/src/validators/decimal.rs index 2c6d308ab..56f0aa766 100644 --- a/src/validators/decimal.rs +++ b/src/validators/decimal.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::exceptions::{PyTypeError, PyValueError}; use pyo3::intern; use pyo3::sync::PyOnceLock; @@ -63,8 +65,8 @@ impl BuildValidator for DecimalValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let allow_inf_nan = schema_or_config_same(schema, config, intern!(py, "allow_inf_nan"))?.unwrap_or(false); @@ -76,7 +78,7 @@ impl BuildValidator for DecimalValidator { )); } - Ok(Self { + Ok(CombinedValidator::Decimal(Self { strict: is_strict(schema, config)?, allow_inf_nan, check_digits: decimal_places.is_some() || max_digits.is_some(), @@ -87,7 +89,7 @@ impl BuildValidator for DecimalValidator { ge: validate_as_decimal(py, schema, intern!(py, "ge"))?, gt: validate_as_decimal(py, schema, intern!(py, "gt"))?, max_digits, - } + }) .into()) } } diff --git a/src/validators/definitions.rs b/src/validators/definitions.rs index a64df3bd5..387d329ab 100644 --- a/src/validators/definitions.rs +++ b/src/validators/definitions.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::intern; use pyo3::prelude::*; use pyo3::types::PyString; @@ -21,8 +23,8 @@ impl BuildValidator for DefinitionsValidatorBuilder { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let schema_definitions: Bound<'_, PyList> = schema.get_as_req(intern!(py, "definitions"))?; @@ -42,11 +44,11 @@ impl BuildValidator for DefinitionsValidatorBuilder { #[derive(Debug, Clone)] pub struct DefinitionRefValidator { - definition: DefinitionRef, + definition: DefinitionRef>, } impl DefinitionRefValidator { - pub fn new(definition: DefinitionRef) -> Self { + pub fn new(definition: DefinitionRef>) -> Self { Self { definition } } } @@ -57,12 +59,12 @@ impl BuildValidator for DefinitionRefValidator { fn build( schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let schema_ref: Bound<'_, PyString> = schema.get_as_req(intern!(schema.py(), "schema_ref"))?; let definition = definitions.get_definition(schema_ref.to_str()?); - Ok(Self::new(definition).into()) + Ok(CombinedValidator::DefinitionRef(Self::new(definition)).into()) } } diff --git a/src/validators/dict.rs b/src/validators/dict.rs index 48729e7ee..b204e1f88 100644 --- a/src/validators/dict.rs +++ b/src/validators/dict.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::intern; use pyo3::prelude::*; use pyo3::types::PyDict; @@ -17,8 +19,8 @@ use super::{build_validator, BuildValidator, CombinedValidator, DefinitionsBuild #[derive(Debug)] pub struct DictValidator { strict: bool, - key_validator: Box, - value_validator: Box, + key_validator: Arc, + value_validator: Arc, min_length: Option, max_length: Option, fail_fast: bool, @@ -31,16 +33,16 @@ impl BuildValidator for DictValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let key_validator = match schema.get_item(intern!(py, "keys_schema"))? { - Some(schema) => Box::new(build_validator(&schema, config, definitions)?), - None => Box::new(AnyValidator::build(schema, config, definitions)?), + Some(schema) => build_validator(&schema, config, definitions)?, + None => AnyValidator::build(schema, config, definitions)?, }; let value_validator = match schema.get_item(intern!(py, "values_schema"))? { - Some(d) => Box::new(build_validator(&d, config, definitions)?), - None => Box::new(AnyValidator::build(schema, config, definitions)?), + Some(d) => build_validator(&d, config, definitions)?, + None => AnyValidator::build(schema, config, definitions)?, }; let name = format!( "{}[{},{}]", @@ -48,7 +50,7 @@ impl BuildValidator for DictValidator { key_validator.get_name(), value_validator.get_name() ); - Ok(Self { + Ok(CombinedValidator::Dict(Self { strict: is_strict(schema, config)?, key_validator, value_validator, @@ -56,7 +58,7 @@ impl BuildValidator for DictValidator { max_length: schema.get_as(intern!(py, "max_length"))?, fail_fast: schema.get_as(intern!(py, "fail_fast"))?.unwrap_or(false), name, - } + }) .into()) } } diff --git a/src/validators/enum_.rs b/src/validators/enum_.rs index 4f3c33287..febb16cb7 100644 --- a/src/validators/enum_.rs +++ b/src/validators/enum_.rs @@ -1,5 +1,6 @@ // Validator for Enums, so named because "enum" is a reserved keyword in Rust. use std::marker::PhantomData; +use std::sync::Arc; use pyo3::exceptions::PyTypeError; use pyo3::intern; @@ -24,8 +25,8 @@ impl BuildValidator for BuildEnumValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let members: Bound = schema.get_as_req(intern!(schema.py(), "members"))?; if members.is_empty() { return py_schema_err!("`members` should have length > 0"); @@ -65,11 +66,11 @@ impl BuildValidator for BuildEnumValidator { let sub_type: Option = schema.get_as(intern!(py, "sub_type"))?; match sub_type.as_deref() { - Some("int") => Ok(CombinedValidator::IntEnum(build!(IntEnumValidator, "int-enum"))), - Some("str") => Ok(CombinedValidator::StrEnum(build!(StrEnumValidator, "str-enum"))), - Some("float") => Ok(CombinedValidator::FloatEnum(build!(FloatEnumValidator, "float-enum"))), + Some("int") => Ok(CombinedValidator::IntEnum(build!(IntEnumValidator, "int-enum")).into()), + Some("str") => Ok(CombinedValidator::StrEnum(build!(StrEnumValidator, "str-enum")).into()), + Some("float") => Ok(CombinedValidator::FloatEnum(build!(FloatEnumValidator, "float-enum")).into()), Some(_) => py_schema_err!("`sub_type` must be one of: 'int', 'str', 'float' or None"), - None => Ok(CombinedValidator::PlainEnum(build!(PlainEnumValidator, "enum"))), + None => Ok(CombinedValidator::PlainEnum(build!(PlainEnumValidator, "enum")).into()), } } } diff --git a/src/validators/float.rs b/src/validators/float.rs index 988c7b889..59ea26801 100644 --- a/src/validators/float.rs +++ b/src/validators/float.rs @@ -1,4 +1,5 @@ use std::cmp::Ordering; +use std::sync::Arc; use pyo3::intern; use pyo3::prelude::*; @@ -19,8 +20,8 @@ impl BuildValidator for FloatBuilder { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let use_constrained = schema.get_item(intern!(py, "multiple_of"))?.is_some() || schema.get_item(intern!(py, "le"))?.is_some() @@ -30,10 +31,10 @@ impl BuildValidator for FloatBuilder { if use_constrained { ConstrainedFloatValidator::build(schema, config, definitions) } else { - Ok(FloatValidator { + Ok(CombinedValidator::Float(FloatValidator { strict: is_strict(schema, config)?, allow_inf_nan: schema_or_config_same(schema, config, intern!(py, "allow_inf_nan"))?.unwrap_or(true), - } + }) .into()) } } @@ -51,13 +52,13 @@ impl BuildValidator for FloatValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); - Ok(Self { + Ok(CombinedValidator::Float(Self { strict: is_strict(schema, config)?, allow_inf_nan: schema_or_config_same(schema, config, intern!(py, "allow_inf_nan"))?.unwrap_or(true), - } + }) .into()) } } @@ -179,10 +180,10 @@ impl BuildValidator for ConstrainedFloatValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); - Ok(Self { + Ok(CombinedValidator::ConstrainedFloat(Self { strict: is_strict(schema, config)?, allow_inf_nan: schema_or_config_same(schema, config, intern!(py, "allow_inf_nan"))?.unwrap_or(true), multiple_of: schema.get_as(intern!(py, "multiple_of"))?, @@ -190,7 +191,7 @@ impl BuildValidator for ConstrainedFloatValidator { lt: schema.get_as(intern!(py, "lt"))?, ge: schema.get_as(intern!(py, "ge"))?, gt: schema.get_as(intern!(py, "gt"))?, - } + }) .into()) } } diff --git a/src/validators/frozenset.rs b/src/validators/frozenset.rs index 18e08b36e..d1fdd6449 100644 --- a/src/validators/frozenset.rs +++ b/src/validators/frozenset.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::types::{PyDict, PyFrozenSet}; use pyo3::{prelude::*, IntoPyObjectExt}; @@ -13,7 +15,7 @@ use super::{BuildValidator, CombinedValidator, DefinitionsBuilder, Validator}; #[derive(Debug)] pub struct FrozenSetValidator { strict: bool, - item_validator: Box, + item_validator: Arc, min_length: Option, max_length: Option, name: String, diff --git a/src/validators/function.rs b/src/validators/function.rs index b280707d3..6b71d82d6 100644 --- a/src/validators/function.rs +++ b/src/validators/function.rs @@ -51,8 +51,8 @@ macro_rules! impl_build { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let validator = build_validator(&schema.get_as_req(intern!(py, "schema"))?, config, definitions)?; let func_info = destructure_function_schema(schema)?; @@ -62,18 +62,20 @@ macro_rules! impl_build { function_name(func_info.function.bind(py))?, validator.get_name() ); - Ok(Self { - validator: Box::new(validator), - func: func_info.function, - config: match config { - Some(c) => c.clone().into(), - None => py.None(), - }, - name, - field_name: func_info.field_name, - info_arg: func_info.info_arg, - } - .into()) + Ok(Arc::new( + Self { + validator, + func: func_info.function, + config: match config { + Some(c) => c.clone().into(), + None => py.None(), + }, + name, + field_name: func_info.field_name, + info_arg: func_info.info_arg, + } + .into(), + )) } } }; @@ -81,7 +83,7 @@ macro_rules! impl_build { #[derive(Debug)] pub struct FunctionBeforeValidator { - validator: Box, + validator: Arc, func: Py, config: Py, name: String, @@ -155,7 +157,7 @@ impl Validator for FunctionBeforeValidator { #[derive(Debug)] pub struct FunctionAfterValidator { - validator: Box, + validator: Arc, func: Py, config: Py, name: String, @@ -242,11 +244,11 @@ impl BuildValidator for FunctionPlainValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let function_info = destructure_function_schema(schema)?; - Ok(Self { + Ok(CombinedValidator::FunctionPlain(Self { func: function_info.function.clone(), config: match config { Some(c) => c.clone().into(), @@ -255,7 +257,7 @@ impl BuildValidator for FunctionPlainValidator { name: format!("function-plain[{}()]", function_name(function_info.function.bind(py))?), field_name: function_info.field_name.clone(), info_arg: function_info.info_arg, - } + }) .into()) } } @@ -307,15 +309,15 @@ impl BuildValidator for FunctionWrapValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let validator = build_validator(&schema.get_as_req(intern!(py, "schema"))?, config, definitions)?; let function_info = destructure_function_schema(schema)?; let hide_input_in_errors: bool = config.get_as(intern!(py, "hide_input_in_errors"))?.unwrap_or(false); let validation_error_cause: bool = config.get_as(intern!(py, "validation_error_cause"))?.unwrap_or(false); - Ok(Self { - validator: Arc::new(validator), + Ok(CombinedValidator::FunctionWrap(Self { + validator, func: function_info.function.clone(), config: match config { Some(c) => c.clone().into(), @@ -326,7 +328,7 @@ impl BuildValidator for FunctionWrapValidator { info_arg: function_info.info_arg, hide_input_in_errors, validation_error_cause, - } + }) .into()) } } diff --git a/src/validators/generator.rs b/src/validators/generator.rs index 22fa9ffec..6dc63a752 100644 --- a/src/validators/generator.rs +++ b/src/validators/generator.rs @@ -33,9 +33,9 @@ impl BuildValidator for GeneratorValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { - let item_validator = get_items_schema(schema, config, definitions)?.map(Arc::new); + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { + let item_validator = get_items_schema(schema, config, definitions)?; let name = match item_validator { Some(ref v) => format!("{}[{}]", Self::EXPECTED_TYPE, v.get_name()), None => format!("{}[any]", Self::EXPECTED_TYPE), @@ -46,14 +46,14 @@ impl BuildValidator for GeneratorValidator { let validation_error_cause: bool = config .get_as(pyo3::intern!(schema.py(), "validation_error_cause"))? .unwrap_or(false); - Ok(Self { + Ok(CombinedValidator::Generator(Self { item_validator, name, min_length: schema.get_as(pyo3::intern!(schema.py(), "min_length"))?, max_length: schema.get_as(pyo3::intern!(schema.py(), "max_length"))?, hide_input_in_errors, validation_error_cause, - } + }) .into()) } } diff --git a/src/validators/int.rs b/src/validators/int.rs index 6bc31fbf1..1d427eb76 100644 --- a/src/validators/int.rs +++ b/src/validators/int.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use num_bigint::BigInt; use pyo3::exceptions::PyValueError; use pyo3::intern; @@ -6,6 +8,7 @@ use pyo3::types::{PyDict, PyString}; use pyo3::IntoPyObjectExt; use crate::build_tools::is_strict; +use crate::build_tools::LazyLock; use crate::errors::{ErrorType, ValError, ValResult}; use crate::input::{Input, Int}; @@ -33,27 +36,33 @@ pub struct IntValidator { strict: bool, } +static STRICT_INT_VALIDATOR: LazyLock> = + LazyLock::new(|| Arc::new(IntValidator { strict: true }.into())); + +static LAX_INT_VALIDATOR: LazyLock> = + LazyLock::new(|| Arc::new(IntValidator { strict: false }.into())); + impl BuildValidator for IntValidator { const EXPECTED_TYPE: &'static str = "int"; fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let use_constrained = schema.get_item(intern!(py, "multiple_of"))?.is_some() || schema.get_item(intern!(py, "le"))?.is_some() || schema.get_item(intern!(py, "lt"))?.is_some() || schema.get_item(intern!(py, "ge"))?.is_some() || schema.get_item(intern!(py, "gt"))?.is_some(); + if use_constrained { ConstrainedIntValidator::build(schema, config) + } else if is_strict(schema, config)? { + Ok(STRICT_INT_VALIDATOR.clone()) } else { - Ok(Self { - strict: is_strict(schema, config)?, - } - .into()) + Ok(LAX_INT_VALIDATOR.clone()) } } } @@ -88,16 +97,16 @@ pub struct ConstrainedIntValidator { } impl ConstrainedIntValidator { - fn build(schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>) -> PyResult { + fn build(schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>) -> PyResult> { let py = schema.py(); - Ok(Self { + Ok(CombinedValidator::ConstrainedInt(Self { strict: is_strict(schema, config)?, multiple_of: validate_as_int(schema, intern!(py, "multiple_of"))?, le: validate_as_int(schema, intern!(py, "le"))?, lt: validate_as_int(schema, intern!(py, "lt"))?, ge: validate_as_int(schema, intern!(py, "ge"))?, gt: validate_as_int(schema, intern!(py, "gt"))?, - } + }) .into()) } } diff --git a/src/validators/is_instance.rs b/src/validators/is_instance.rs index cf51841eb..62d995b54 100644 --- a/src/validators/is_instance.rs +++ b/src/validators/is_instance.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::intern; use pyo3::prelude::*; use pyo3::types::{PyDict, PyType}; @@ -22,8 +24,8 @@ impl BuildValidator for IsInstanceValidator { fn build( schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let cls_key = intern!(py, "cls"); let class = schema.get_as_req(cls_key)?; @@ -36,11 +38,11 @@ impl BuildValidator for IsInstanceValidator { let class_repr = class_repr(schema, &class)?; let name = format!("{}[{class_repr}]", Self::EXPECTED_TYPE); - Ok(Self { + Ok(CombinedValidator::IsInstance(Self { class: class.into(), class_repr, name, - } + }) .into()) } } diff --git a/src/validators/is_subclass.rs b/src/validators/is_subclass.rs index cdcfcfce2..6e6b8598b 100644 --- a/src/validators/is_subclass.rs +++ b/src/validators/is_subclass.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::intern; use pyo3::prelude::*; use pyo3::types::{PyDict, PyType}; @@ -21,8 +23,8 @@ impl BuildValidator for IsSubclassValidator { fn build( schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let class = schema.get_as_req::>(intern!(py, "cls"))?; @@ -31,11 +33,11 @@ impl BuildValidator for IsSubclassValidator { None => class.qualname()?.to_string(), }; let name = format!("{}[{class_repr}]", Self::EXPECTED_TYPE); - Ok(Self { + Ok(CombinedValidator::IsSubclass(Self { class: class.into(), class_repr, name, - } + }) .into()) } } diff --git a/src/validators/json.rs b/src/validators/json.rs index 8391efe74..5b2d6e563 100644 --- a/src/validators/json.rs +++ b/src/validators/json.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::intern; use pyo3::prelude::*; use pyo3::types::PyDict; @@ -14,7 +16,7 @@ use super::{build_validator, BuildValidator, CombinedValidator, DefinitionsBuild #[derive(Debug)] pub struct JsonValidator { - validator: Option>, + validator: Option>, name: String, } @@ -24,14 +26,14 @@ impl BuildValidator for JsonValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let validator = match schema.get_as(intern!(schema.py(), "schema"))? { Some(schema) => { let validator = build_validator(&schema, config, definitions)?; - match validator { + match validator.as_ref() { CombinedValidator::Any(_) => None, - _ => Some(Box::new(validator)), + _ => Some(validator), } } None => None, @@ -41,7 +43,7 @@ impl BuildValidator for JsonValidator { Self::EXPECTED_TYPE, validator.as_ref().map_or("any", |v| v.get_name()) ); - Ok(Self { validator, name }.into()) + Ok(CombinedValidator::Json(Self { validator, name }).into()) } } diff --git a/src/validators/json_or_python.rs b/src/validators/json_or_python.rs index 068feb5c3..6b482ef38 100644 --- a/src/validators/json_or_python.rs +++ b/src/validators/json_or_python.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::intern; use pyo3::prelude::*; use pyo3::types::PyDict; @@ -11,8 +13,8 @@ use super::{build_validator, BuildValidator, CombinedValidator, InputType, Valid #[derive(Debug)] pub struct JsonOrPython { - json: Box, - python: Box, + json: Arc, + python: Arc, name: String, } @@ -22,8 +24,8 @@ impl BuildValidator for JsonOrPython { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let json_schema = schema.get_as_req(intern!(py, "json_schema"))?; let python_schema = schema.get_as_req(intern!(py, "python_schema"))?; @@ -37,12 +39,7 @@ impl BuildValidator for JsonOrPython { json.get_name(), python.get_name(), ); - Ok(Self { - json: Box::new(json), - python: Box::new(python), - name, - } - .into()) + Ok(CombinedValidator::JsonOrPython(Self { json, python, name }).into()) } } diff --git a/src/validators/lax_or_strict.rs b/src/validators/lax_or_strict.rs index 72fbc3713..09d5fef7a 100644 --- a/src/validators/lax_or_strict.rs +++ b/src/validators/lax_or_strict.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::intern; use pyo3::prelude::*; use pyo3::types::PyDict; @@ -14,8 +16,8 @@ use super::{build_validator, BuildValidator, CombinedValidator, DefinitionsBuild #[derive(Debug)] pub struct LaxOrStrictValidator { strict: bool, - lax_validator: Box, - strict_validator: Box, + lax_validator: Arc, + strict_validator: Arc, name: String, } @@ -25,14 +27,14 @@ impl BuildValidator for LaxOrStrictValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let lax_schema = schema.get_as_req(intern!(py, "lax_schema"))?; - let lax_validator = Box::new(build_validator(&lax_schema, config, definitions)?); + let lax_validator = build_validator(&lax_schema, config, definitions)?; let strict_schema = schema.get_as_req(intern!(py, "strict_schema"))?; - let strict_validator = Box::new(build_validator(&strict_schema, config, definitions)?); + let strict_validator = build_validator(&strict_schema, config, definitions)?; let name = format!( "{}[lax={},strict={}]", @@ -40,12 +42,12 @@ impl BuildValidator for LaxOrStrictValidator { lax_validator.get_name(), strict_validator.get_name() ); - Ok(Self { + Ok(CombinedValidator::LaxOrStrict(Self { strict: is_strict(schema, config)?, lax_validator, strict_validator, name, - } + }) .into()) } } diff --git a/src/validators/list.rs b/src/validators/list.rs index fe3fcfa59..0188d73ea 100644 --- a/src/validators/list.rs +++ b/src/validators/list.rs @@ -1,4 +1,4 @@ -use std::sync::OnceLock; +use std::sync::{Arc, OnceLock}; use pyo3::types::PyDict; use pyo3::{prelude::*, IntoPyObjectExt}; @@ -14,7 +14,7 @@ use super::{build_validator, BuildValidator, CombinedValidator, DefinitionsBuild #[derive(Debug)] pub struct ListValidator { strict: bool, - item_validator: Option>, + item_validator: Option>, min_length: Option, max_length: Option, name: OnceLock, @@ -24,12 +24,12 @@ pub struct ListValidator { pub fn get_items_schema( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, -) -> PyResult> { + definitions: &mut DefinitionsBuilder>, +) -> PyResult>> { match schema.get_item(pyo3::intern!(schema.py(), "items_schema"))? { Some(d) => { let validator = build_validator(&d, config, definitions)?; - match validator { + match validator.as_ref() { CombinedValidator::Any(_) => Ok(None), _ => Ok(Some(validator)), } @@ -100,18 +100,18 @@ impl BuildValidator for ListValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); - let item_validator = get_items_schema(schema, config, definitions)?.map(Box::new); - Ok(Self { + let item_validator = get_items_schema(schema, config, definitions)?; + Ok(CombinedValidator::List(Self { strict: crate::build_tools::is_strict(schema, config)?, item_validator, min_length: schema.get_as(pyo3::intern!(py, "min_length"))?, max_length: schema.get_as(pyo3::intern!(py, "max_length"))?, name: OnceLock::new(), fail_fast: schema.get_as(pyo3::intern!(py, "fail_fast"))?.unwrap_or(false), - } + }) .into()) } } diff --git a/src/validators/literal.rs b/src/validators/literal.rs index f55dbd0d3..9c8ffae10 100644 --- a/src/validators/literal.rs +++ b/src/validators/literal.rs @@ -2,6 +2,7 @@ // which can be an int, a string, bytes or an Enum value (including `class Foo(str, Enum)` type enums) use core::fmt::Debug; use std::cell::OnceCell; +use std::sync::Arc; use pyo3::prelude::*; use pyo3::types::{PyDict, PyInt, PyList}; @@ -255,8 +256,8 @@ impl BuildValidator for LiteralValidator { fn build( schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let expected: Bound = schema.get_as_req(intern!(schema.py(), "expected"))?; if expected.is_empty() { return py_schema_err!("`expected` should have length > 0"); @@ -272,7 +273,8 @@ impl BuildValidator for LiteralValidator { lookup, expected_repr, name, - })) + }) + .into()) } } diff --git a/src/validators/missing_sentinel.rs b/src/validators/missing_sentinel.rs index 2094bd0d5..fa897f928 100644 --- a/src/validators/missing_sentinel.rs +++ b/src/validators/missing_sentinel.rs @@ -1,8 +1,10 @@ use core::fmt::Debug; +use std::sync::Arc; use pyo3::prelude::*; use pyo3::types::PyDict; +use crate::build_tools::LazyLock; use crate::common::missing_sentinel::get_missing_sentinel_object; use crate::errors::{ErrorType, ValError, ValResult}; use crate::input::Input; @@ -12,15 +14,18 @@ use super::{BuildValidator, CombinedValidator, DefinitionsBuilder, ValidationSta #[derive(Debug, Clone)] pub struct MissingSentinelValidator {} +static MISSING_SENTINEL_VALIDATOR: LazyLock> = + LazyLock::new(|| CombinedValidator::MissingSentinel(MissingSentinelValidator {}).into()); + impl BuildValidator for MissingSentinelValidator { const EXPECTED_TYPE: &'static str = "missing-sentinel"; fn build( _schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { - Ok(CombinedValidator::MissingSentinel(Self {})) + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { + Ok(MISSING_SENTINEL_VALIDATOR.clone()) } } diff --git a/src/validators/mod.rs b/src/validators/mod.rs index 1b19b6235..0456566ae 100644 --- a/src/validators/mod.rs +++ b/src/validators/mod.rs @@ -1,5 +1,6 @@ use std::fmt::Debug; use std::str::FromStr; +use std::sync::Arc; use enum_dispatch::enum_dispatch; use jiter::{PartialMode, StringCacheMode}; @@ -107,8 +108,8 @@ impl PySome { #[pyclass(module = "pydantic_core._pydantic_core", frozen)] #[derive(Debug)] pub struct SchemaValidator { - validator: CombinedValidator, - definitions: Definitions, + validator: Arc, + definitions: Definitions>, // References to the Python schema and config objects are saved to enable // reconstructing the object for cloudpickle support (see `__reduce__`). py_schema: Py, @@ -499,8 +500,8 @@ pub trait BuildValidator: Sized { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult; + definitions: &mut DefinitionsBuilder>, + ) -> PyResult>; } /// Logic to create a particular validator, called in the `validator_match` macro, then in turn by `build_validator` @@ -508,8 +509,8 @@ fn build_specific_validator( val_type: &str, schema_dict: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, -) -> PyResult { + definitions: &mut DefinitionsBuilder>, +) -> PyResult> { T::build(schema_dict, config, definitions) .map_err(|err| py_schema_error_type!("Error building \"{}\" validator:\n {}", val_type, err)) } @@ -532,25 +533,25 @@ macro_rules! validator_match { pub fn build_validator_base( schema: &Bound<'_, PyAny>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, -) -> PyResult { + definitions: &mut DefinitionsBuilder>, +) -> PyResult> { build_validator_inner(schema, config, definitions, false) } pub fn build_validator( schema: &Bound<'_, PyAny>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, -) -> PyResult { + definitions: &mut DefinitionsBuilder>, +) -> PyResult> { build_validator_inner(schema, config, definitions, true) } fn build_validator_inner( schema: &Bound<'_, PyAny>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, + definitions: &mut DefinitionsBuilder>, use_prebuilt: bool, -) -> PyResult { +) -> PyResult> { let dict = schema.downcast::()?; let py = schema.py(); let type_: Bound<'_, PyString> = dict.get_as_req(intern!(py, "type"))?; @@ -559,7 +560,7 @@ fn build_validator_inner( if use_prebuilt { // if we have a SchemaValidator on the type already, use it if let Ok(Some(prebuilt_validator)) = prebuilt::PrebuiltValidator::try_get_from_schema(type_, dict) { - return Ok(prebuilt_validator); + return Ok(Arc::new(prebuilt_validator)); } } diff --git a/src/validators/model.rs b/src/validators/model.rs index 9fc8f1db4..3385f68b6 100644 --- a/src/validators/model.rs +++ b/src/validators/model.rs @@ -1,4 +1,5 @@ use std::ptr::null_mut; +use std::sync::Arc; use pyo3::exceptions::PyTypeError; use pyo3::types::{PyDict, PySet, PyString, PyTuple, PyType}; @@ -53,7 +54,7 @@ impl Revalidate { #[derive(Debug)] pub struct ModelValidator { revalidate: Revalidate, - validator: Box, + validator: Arc, class: Py, generic_origin: Option>, post_init: Option>, @@ -70,8 +71,8 @@ impl BuildValidator for ModelValidator { fn build( schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { 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"))?; @@ -82,7 +83,7 @@ impl BuildValidator for ModelValidator { let validator = build_validator(&sub_schema, config.as_ref(), definitions)?; let name = class.getattr(intern!(py, "__name__"))?.extract()?; - Ok(Self { + Ok(CombinedValidator::Model(Self { revalidate: Revalidate::from_str( schema_or_config_same::>( schema, @@ -93,7 +94,7 @@ impl BuildValidator for ModelValidator { .map(|s| s.to_str()) .transpose()?, )?, - validator: Box::new(validator), + validator, class: class.into(), generic_origin: generic_origin.map(std::convert::Into::into), post_init: schema.get_as(intern!(py, "post_init"))?, @@ -103,7 +104,7 @@ impl BuildValidator for ModelValidator { undefined: PydanticUndefinedType::new(py).into_any(), // Get the class's `__name__`, not using `class.qualname()` name, - } + }) .into()) } } diff --git a/src/validators/model_fields.rs b/src/validators/model_fields.rs index d17732b47..672cd55ce 100644 --- a/src/validators/model_fields.rs +++ b/src/validators/model_fields.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::exceptions::PyKeyError; use pyo3::intern; use pyo3::prelude::*; @@ -22,7 +24,7 @@ struct Field { name: String, lookup_key_collection: LookupKeyCollection, name_py: Py, - validator: CombinedValidator, + validator: Arc, frozen: bool, } @@ -33,8 +35,8 @@ pub struct ModelFieldsValidator { fields: Vec, model_name: String, extra_behavior: ExtraBehavior, - extras_validator: Option>, - extras_keys_validator: Option>, + extras_validator: Option>, + extras_keys_validator: Option>, strict: bool, from_attributes: bool, loc_by_alias: bool, @@ -48,8 +50,8 @@ impl BuildValidator for ModelFieldsValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let strict = is_strict(schema, config)?; @@ -59,12 +61,12 @@ impl BuildValidator for ModelFieldsValidator { let extra_behavior = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?; let extras_validator = match (schema.get_item(intern!(py, "extras_schema"))?, &extra_behavior) { - (Some(v), ExtraBehavior::Allow) => Some(Box::new(build_validator(&v, config, definitions)?)), + (Some(v), ExtraBehavior::Allow) => Some(build_validator(&v, config, definitions)?), (Some(_), _) => return py_schema_err!("extras_schema can only be used if extra_behavior=allow"), (_, _) => None, }; let extras_keys_validator = match (schema.get_item(intern!(py, "extras_keys_schema"))?, &extra_behavior) { - (Some(v), ExtraBehavior::Allow) => Some(Box::new(build_validator(&v, config, definitions)?)), + (Some(v), ExtraBehavior::Allow) => Some(build_validator(&v, config, definitions)?), (Some(_), _) => return py_schema_err!("extras_keys_schema can only be used if extra_behavior=allow"), (_, _) => None, }; @@ -99,7 +101,7 @@ impl BuildValidator for ModelFieldsValidator { }); } - Ok(Self { + Ok(CombinedValidator::ModelFields(Self { fields, model_name, extra_behavior, @@ -110,7 +112,7 @@ impl BuildValidator for ModelFieldsValidator { loc_by_alias: config.get_as(intern!(py, "loc_by_alias"))?.unwrap_or(true), validate_by_alias: config.get_as(intern!(py, "validate_by_alias"))?, validate_by_name: config.get_as(intern!(py, "validate_by_name"))?, - } + }) .into()) } } diff --git a/src/validators/none.rs b/src/validators/none.rs index e933134e3..9d35eec86 100644 --- a/src/validators/none.rs +++ b/src/validators/none.rs @@ -1,6 +1,9 @@ +use std::sync::Arc; + use pyo3::prelude::*; use pyo3::types::PyDict; +use crate::build_tools::LazyLock; use crate::errors::{ErrorTypeDefaults, ValError, ValResult}; use crate::input::Input; @@ -9,15 +12,17 @@ use super::{BuildValidator, CombinedValidator, DefinitionsBuilder, ValidationSta #[derive(Debug, Clone)] pub struct NoneValidator; +static NONE_VALIDATOR: LazyLock> = LazyLock::new(|| Arc::new(NoneValidator.into())); + impl BuildValidator for NoneValidator { const EXPECTED_TYPE: &'static str = "none"; fn build( _schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { - Ok(Self.into()) + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { + Ok(NONE_VALIDATOR.clone()) } } diff --git a/src/validators/nullable.rs b/src/validators/nullable.rs index b01cbd365..00e702f88 100644 --- a/src/validators/nullable.rs +++ b/src/validators/nullable.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::intern; use pyo3::prelude::*; use pyo3::types::PyDict; @@ -11,7 +13,7 @@ use super::{build_validator, BuildValidator, CombinedValidator, DefinitionsBuild #[derive(Debug)] pub struct NullableValidator { - validator: Box, + validator: Arc, name: String, } @@ -21,12 +23,12 @@ impl BuildValidator for NullableValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let schema = schema.get_as_req(intern!(schema.py(), "schema"))?; - let validator = Box::new(build_validator(&schema, config, definitions)?); + let validator = build_validator(&schema, config, definitions)?; let name = format!("{}[{}]", Self::EXPECTED_TYPE, validator.get_name()); - Ok(Self { validator, name }.into()) + Ok(CombinedValidator::Nullable(Self { validator, name }).into()) } } diff --git a/src/validators/prebuilt.rs b/src/validators/prebuilt.rs index 80a199768..77e6ba758 100644 --- a/src/validators/prebuilt.rs +++ b/src/validators/prebuilt.rs @@ -18,7 +18,7 @@ impl PrebuiltValidator { get_prebuilt(type_, schema, "__pydantic_validator__", |py_any| { let schema_validator = py_any.extract::>()?; if matches!( - schema_validator.get().validator, + schema_validator.get().validator.as_ref(), CombinedValidator::FunctionWrap(_) | CombinedValidator::FunctionAfter(_) ) { return Ok(None); diff --git a/src/validators/set.rs b/src/validators/set.rs index fac8a279a..8c62c6d1a 100644 --- a/src/validators/set.rs +++ b/src/validators/set.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::types::{PyDict, PySet}; use pyo3::{prelude::*, IntoPyObjectExt}; @@ -11,7 +13,7 @@ use super::{BuildValidator, CombinedValidator, DefinitionsBuilder, ValidationSta #[derive(Debug)] pub struct SetValidator { strict: bool, - item_validator: Box, + item_validator: Arc, min_length: Option, max_length: Option, name: String, @@ -23,29 +25,27 @@ macro_rules! set_build { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let item_validator = match schema.get_item(pyo3::intern!(schema.py(), "items_schema"))? { - Some(d) => Box::new(crate::validators::build_validator(&d, config, definitions)?), - None => Box::new(crate::validators::any::AnyValidator::build( - schema, - config, - definitions, - )?), + Some(d) => crate::validators::build_validator(&d, config, definitions)?, + None => crate::validators::any::AnyValidator::build(schema, config, definitions)?, }; let inner_name = item_validator.get_name(); let max_length = schema.get_as(pyo3::intern!(py, "max_length"))?; let name = format!("{}[{}]", Self::EXPECTED_TYPE, inner_name); - Ok(Self { - strict: crate::build_tools::is_strict(schema, config)?, - item_validator, - min_length: schema.get_as(pyo3::intern!(py, "min_length"))?, - max_length, - name, - fail_fast: schema.get_as(pyo3::intern!(py, "fail_fast"))?.unwrap_or(false), - } - .into()) + Ok(Arc::new( + Self { + strict: crate::build_tools::is_strict(schema, config)?, + item_validator, + min_length: schema.get_as(pyo3::intern!(py, "min_length"))?, + max_length, + name, + fail_fast: schema.get_as(pyo3::intern!(py, "fail_fast"))?.unwrap_or(false), + } + .into(), + )) } }; } diff --git a/src/validators/string.rs b/src/validators/string.rs index 308d1ec70..705c4f2b1 100644 --- a/src/validators/string.rs +++ b/src/validators/string.rs @@ -1,9 +1,12 @@ +use std::sync::Arc; + use pyo3::intern; use pyo3::prelude::*; use pyo3::types::{PyDict, PyString}; use pyo3::IntoPyObjectExt; use regex::Regex; +use crate::build_tools::LazyLock; use crate::build_tools::{is_strict, py_schema_error_type, schema_or_config, schema_or_config_same}; use crate::errors::{ErrorType, ValError, ValResult}; use crate::input::Input; @@ -17,23 +20,45 @@ pub struct StrValidator { coerce_numbers_to_str: bool, } +static STRICT_STR_VALIDATOR: LazyLock> = LazyLock::new(|| { + CombinedValidator::Str(StrValidator { + strict: true, + coerce_numbers_to_str: false, + }) + .into() +}); + +static LAX_STR_VALIDATOR: LazyLock> = LazyLock::new(|| { + CombinedValidator::Str(StrValidator { + strict: false, + coerce_numbers_to_str: false, + }) + .into() +}); + impl BuildValidator for StrValidator { const EXPECTED_TYPE: &'static str = "str"; fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let con_str_validator = StrConstrainedValidator::build(schema, config)?; if con_str_validator.has_constraints_set() { - Ok(con_str_validator.into()) + Ok(Arc::new(con_str_validator.into())) + } else if !con_str_validator.coerce_numbers_to_str { + if is_strict(schema, config)? { + Ok(STRICT_STR_VALIDATOR.clone()) + } else { + Ok(LAX_STR_VALIDATOR.clone()) + } } else { - Ok(Self { + Ok(CombinedValidator::Str(StrValidator { strict: con_str_validator.strict, coerce_numbers_to_str: con_str_validator.coerce_numbers_to_str, - } + }) .into()) } } diff --git a/src/validators/time.rs b/src/validators/time.rs index 7601eddff..6fb5037db 100644 --- a/src/validators/time.rs +++ b/src/validators/time.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::exceptions::PyValueError; use pyo3::intern; use pyo3::prelude::*; @@ -27,14 +29,14 @@ impl BuildValidator for TimeValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let s = Self { strict: is_strict(schema, config)?, constraints: TimeConstraints::from_py(schema)?, microseconds_precision: extract_microseconds_precision(schema, config)?, }; - Ok(s.into()) + Ok(Arc::new(s.into())) } } diff --git a/src/validators/timedelta.rs b/src/validators/timedelta.rs index 522b1f252..bf2950df7 100644 --- a/src/validators/timedelta.rs +++ b/src/validators/timedelta.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::exceptions::PyValueError; use pyo3::intern; use pyo3::prelude::*; @@ -44,8 +46,8 @@ impl BuildValidator for TimeDeltaValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let constraints = TimedeltaConstraints { le: get_constraint(schema, intern!(py, "le"))?, @@ -54,7 +56,7 @@ impl BuildValidator for TimeDeltaValidator { gt: get_constraint(schema, intern!(py, "gt"))?, }; - Ok(Self { + Ok(CombinedValidator::Timedelta(Self { strict: is_strict(schema, config)?, constraints: (constraints.le.is_some() || constraints.lt.is_some() @@ -62,7 +64,7 @@ impl BuildValidator for TimeDeltaValidator { || constraints.gt.is_some()) .then_some(constraints), microseconds_precision: extract_microseconds_precision(schema, config)?, - } + }) .into()) } } diff --git a/src/validators/tuple.rs b/src/validators/tuple.rs index 1fa5ecad5..5ede4bad2 100644 --- a/src/validators/tuple.rs +++ b/src/validators/tuple.rs @@ -1,4 +1,5 @@ use std::collections::VecDeque; +use std::sync::Arc; use pyo3::intern; use pyo3::prelude::*; @@ -15,7 +16,7 @@ use super::{build_validator, BuildValidator, CombinedValidator, DefinitionsBuild #[derive(Debug)] pub struct TupleValidator { strict: bool, - validators: Vec, + validators: Vec>, variadic_item_index: Option, min_length: Option, max_length: Option, @@ -28,16 +29,16 @@ impl BuildValidator for TupleValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let items: Bound<'_, PyList> = schema.get_as_req(intern!(py, "items_schema"))?; - let validators: Vec = items + let validators: Vec> = items .iter() .map(|item| build_validator(&item, config, definitions)) .collect::>()?; - let mut validator_names = validators.iter().map(Validator::get_name).collect::>(); + let mut validator_names = validators.iter().map(|v| v.get_name()).collect::>(); let variadic_item_index: Option = schema.get_as(intern!(py, "variadic_item_index"))?; // FIXME add friendly schema error if item out of bounds if let Some(variadic_item_index) = variadic_item_index { @@ -45,7 +46,7 @@ impl BuildValidator for TupleValidator { } let name = format!("tuple[{}]", validator_names.join(", ")); - Ok(Self { + Ok(CombinedValidator::Tuple(Self { strict: is_strict(schema, config)?, validators, variadic_item_index, @@ -53,7 +54,7 @@ impl BuildValidator for TupleValidator { max_length: schema.get_as(intern!(py, "max_length"))?, name, fail_fast: schema.get_as(intern!(py, "fail_fast"))?.unwrap_or(false), - } + }) .into()) } } @@ -69,7 +70,7 @@ impl TupleValidator { state: &mut ValidationState<'_, 'py>, output: &mut Vec>, errors: &mut Vec, - item_validators: &[CombinedValidator], + item_validators: &[Arc], collection_iter: &mut NextCountingIterator>, actual_length: Option, fail_fast: bool, diff --git a/src/validators/typed_dict.rs b/src/validators/typed_dict.rs index f2276a517..fdec72143 100644 --- a/src/validators/typed_dict.rs +++ b/src/validators/typed_dict.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::intern; use pyo3::prelude::*; use pyo3::types::{PyDict, PyString, PyType}; @@ -23,7 +25,7 @@ struct TypedDictField { lookup_key_collection: LookupKeyCollection, name_py: Py, required: bool, - validator: CombinedValidator, + validator: Arc, } impl_py_gc_traverse!(TypedDictField { validator }); @@ -32,7 +34,7 @@ impl_py_gc_traverse!(TypedDictField { validator }); pub struct TypedDictValidator { fields: Vec, extra_behavior: ExtraBehavior, - extras_validator: Option>, + extras_validator: Option>, strict: bool, loc_by_alias: bool, validate_by_alias: Option, @@ -46,8 +48,8 @@ impl BuildValidator for TypedDictValidator { fn build( schema: &Bound<'_, PyDict>, _config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); // typed dicts ignore the parent config and always use the config from this TypedDict @@ -62,7 +64,7 @@ impl BuildValidator for TypedDictValidator { let extra_behavior = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?; let extras_validator = match (schema.get_item(intern!(py, "extras_schema"))?, &extra_behavior) { - (Some(v), ExtraBehavior::Allow) => Some(Box::new(build_validator(&v, config, definitions)?)), + (Some(v), ExtraBehavior::Allow) => Some(build_validator(&v, config, definitions)?), (Some(_), _) => return py_schema_err!("extras_schema can only be used if extra_behavior=allow"), (_, _) => None, }; @@ -93,7 +95,7 @@ impl BuildValidator for TypedDictValidator { let required = match field_info.get_as::(intern!(py, "required"))? { Some(required) => { if required { - if let CombinedValidator::WithDefault(ref val) = validator { + if let CombinedValidator::WithDefault(ref val) = validator.as_ref() { if val.has_default() { return py_schema_err!( "Field '{}': a required field cannot have a default value", @@ -108,7 +110,7 @@ impl BuildValidator for TypedDictValidator { }; if required { - if let CombinedValidator::WithDefault(ref val) = validator { + if let CombinedValidator::WithDefault(ref val) = validator.as_ref() { if val.omit_on_error() { return py_schema_err!( "Field '{}': 'on_error = omit' cannot be set for required fields", @@ -129,7 +131,7 @@ impl BuildValidator for TypedDictValidator { required, }); } - Ok(Self { + Ok(CombinedValidator::TypedDict(Self { fields, extra_behavior, extras_validator, @@ -138,7 +140,7 @@ impl BuildValidator for TypedDictValidator { validate_by_alias: config.get_as(intern!(py, "validate_by_alias"))?, validate_by_name: config.get_as(intern!(py, "validate_by_name"))?, cls_name, - } + }) .into()) } } diff --git a/src/validators/union.rs b/src/validators/union.rs index 27af13267..200e2b339 100644 --- a/src/validators/union.rs +++ b/src/validators/union.rs @@ -1,5 +1,6 @@ use std::fmt::Write; use std::str::FromStr; +use std::sync::Arc; use crate::py_gc::PyGcTraverse; use pyo3::prelude::*; @@ -41,7 +42,7 @@ impl FromStr for UnionMode { #[derive(Debug)] pub struct UnionValidator { mode: UnionMode, - choices: Vec<(CombinedValidator, Option)>, + choices: Vec<(Arc, Option)>, custom_error: Option, name: String, } @@ -52,10 +53,10 @@ impl BuildValidator for UnionValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); - let choices: Vec<(CombinedValidator, Option)> = schema + let choices: Vec<(Arc, Option)> = schema .get_as_req::>(intern!(py, "choices"))? .iter() .map(|choice| { @@ -70,7 +71,7 @@ impl BuildValidator for UnionValidator { }; Ok((build_validator(&choice, config, definitions)?, label)) }) - .collect::)>>>()?; + .collect::>()?; let auto_collapse = || schema.get_as_req(intern!(py, "auto_collapse")).unwrap_or(true); let mode = schema @@ -86,12 +87,12 @@ impl BuildValidator for UnionValidator { .collect::>() .join(","); - Ok(Self { + Ok(CombinedValidator::Union(Self { mode, choices, custom_error: CustomError::build(schema, config, definitions)?, name: format!("{}[{descr}]", Self::EXPECTED_TYPE), - } + }) .into()) } } @@ -282,7 +283,7 @@ impl<'a> MaybeErrors<'a> { #[derive(Debug)] pub struct TaggedUnionValidator { discriminator: Discriminator, - lookup: LiteralLookup, + lookup: LiteralLookup>, from_attributes: bool, custom_error: Option, tags_repr: String, @@ -296,8 +297,8 @@ impl BuildValidator for TaggedUnionValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let discriminator = Discriminator::new(py, &schema.get_as_req(intern!(py, "discriminator"))?)?; let discriminator_repr = discriminator.to_string_py(py)?; @@ -328,7 +329,7 @@ impl BuildValidator for TaggedUnionValidator { let key = intern!(py, "from_attributes"); let from_attributes = schema_or_config(schema, config, key, key)?.unwrap_or(true); - Ok(Self { + Ok(CombinedValidator::TaggedUnion(Self { discriminator, lookup, from_attributes, @@ -336,7 +337,7 @@ impl BuildValidator for TaggedUnionValidator { tags_repr, discriminator_repr, name: format!("{}[{descr}]", Self::EXPECTED_TYPE), - } + }) .into()) } } diff --git a/src/validators/url.rs b/src/validators/url.rs index 28d200342..bcd6e8786 100644 --- a/src/validators/url.rs +++ b/src/validators/url.rs @@ -1,6 +1,7 @@ use std::cell::RefCell; use std::iter::Peekable; use std::str::Chars; +use std::sync::Arc; use pyo3::intern; use pyo3::prelude::*; @@ -43,11 +44,11 @@ impl BuildValidator for UrlValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let (allowed_schemes, name) = get_allowed_schemes(schema, Self::EXPECTED_TYPE)?; - Ok(Self { + Ok(CombinedValidator::Url(Self { strict: is_strict(schema, config)?, max_length: schema.get_as(intern!(schema.py(), "max_length"))?, host_required: schema.get_as(intern!(schema.py(), "host_required"))?.unwrap_or(false), @@ -56,7 +57,7 @@ impl BuildValidator for UrlValidator { default_path: schema.get_as(intern!(schema.py(), "default_path"))?, allowed_schemes, name, - } + }) .into()) } } @@ -199,8 +200,8 @@ impl BuildValidator for MultiHostUrlValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let (allowed_schemes, name) = get_allowed_schemes(schema, Self::EXPECTED_TYPE)?; let default_host: Option = schema.get_as(intern!(schema.py(), "default_host"))?; @@ -209,7 +210,7 @@ impl BuildValidator for MultiHostUrlValidator { return py_schema_err!("default_host cannot contain a comma, see pydantic-core#326"); } } - Ok(Self { + Ok(CombinedValidator::MultiHostUrl(Self { strict: is_strict(schema, config)?, max_length: schema.get_as(intern!(schema.py(), "max_length"))?, allowed_schemes, @@ -218,7 +219,7 @@ impl BuildValidator for MultiHostUrlValidator { default_port: schema.get_as(intern!(schema.py(), "default_port"))?, default_path: schema.get_as(intern!(schema.py(), "default_path"))?, name, - } + }) .into()) } } diff --git a/src/validators/uuid.rs b/src/validators/uuid.rs index 03ff27864..25b80c55a 100644 --- a/src/validators/uuid.rs +++ b/src/validators/uuid.rs @@ -1,4 +1,5 @@ use std::str::from_utf8; +use std::sync::Arc; use pyo3::intern; use pyo3::prelude::*; @@ -80,15 +81,15 @@ impl BuildValidator for UuidValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - _definitions: &mut DefinitionsBuilder, - ) -> PyResult { + _definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); // Note(lig): let's keep this conversion through the Version enum just for the sake of validation let version = schema.get_as::(intern!(py, "version"))?.map(Version::from); - Ok(Self { + Ok(CombinedValidator::Uuid(Self { strict: is_strict(schema, config)?, version: version.map(usize::from), - } + }) .into()) } } diff --git a/src/validators/with_default.rs b/src/validators/with_default.rs index dde8dca77..c097f40d0 100644 --- a/src/validators/with_default.rs +++ b/src/validators/with_default.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use pyo3::intern; use pyo3::prelude::*; use pyo3::sync::PyOnceLock; @@ -88,7 +90,7 @@ enum OnError { pub struct WithDefaultValidator { default: DefaultType, on_error: OnError, - validator: Box, + validator: Arc, validate_default: bool, copy_default: bool, name: String, @@ -101,8 +103,8 @@ impl BuildValidator for WithDefaultValidator { fn build( schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>, - definitions: &mut DefinitionsBuilder, - ) -> PyResult { + definitions: &mut DefinitionsBuilder>, + ) -> PyResult> { let py = schema.py(); let default = DefaultType::new(schema)?; let on_error = match schema @@ -125,7 +127,7 @@ impl BuildValidator for WithDefaultValidator { }; let sub_schema = schema.get_as_req(intern!(schema.py(), "schema"))?; - let validator = Box::new(build_validator(&sub_schema, config, definitions)?); + let validator = build_validator(&sub_schema, config, definitions)?; let copy_default = if let DefaultType::Default(default_obj) = &default { default_obj.bind(py).hash().is_err() @@ -135,7 +137,7 @@ impl BuildValidator for WithDefaultValidator { let name = format!("{}[{}]", Self::EXPECTED_TYPE, validator.get_name()); - Ok(Self { + Ok(CombinedValidator::WithDefault(Self { default, on_error, validator, @@ -143,7 +145,7 @@ impl BuildValidator for WithDefaultValidator { copy_default, name, undefined: PydanticUndefinedType::new(py).into_any(), - } + }) .into()) } }