diff --git a/src/build_tools.rs b/src/build_tools.rs index 2ccb82728..0d40a0767 100644 --- a/src/build_tools.rs +++ b/src/build_tools.rs @@ -1,3 +1,4 @@ +use std::convert::Infallible; use std::error::Error; use std::fmt; use std::ops::Deref; @@ -9,6 +10,7 @@ use pyo3::prelude::*; use pyo3::types::{PyDict, PyList, PyString}; use pyo3::{intern, FromPyObject, PyErrArguments}; +use crate::config::CoreConfig; use crate::errors::{PyLineError, ValError}; use crate::input::InputType; use crate::tools::SchemaDict; @@ -43,9 +45,14 @@ where schema_or_config(schema, config, key, key) } -pub fn is_strict(schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>) -> PyResult { +pub fn is_strict(schema: &Bound<'_, PyDict>, config: &CoreConfig) -> PyResult { let py = schema.py(); - Ok(schema_or_config_same(schema, config, intern!(py, "strict"))?.unwrap_or(false)) + let is_strict = schema + .get_as(intern!(py, "strict"))? + .flatten() + .or(config.strict) + .unwrap_or(false); + Ok(is_strict) } enum SchemaErrorEnum { @@ -189,21 +196,36 @@ impl ExtraBehavior { pub fn from_schema_or_config( py: Python, schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, default: Self, ) -> PyResult { - let extra_behavior = schema_or_config::>>( - schema, - config, - intern!(py, "extra_behavior"), - intern!(py, "extra_fields_behavior"), - )? - .flatten(); - let res = match extra_behavior.as_ref().map(|s| s.to_str()).transpose()? { - Some(s) => Self::from_str(s)?, - None => default, + let extra_behavior = schema.get_as(intern!(py, "extra_behavior"))?.flatten(); + let extra_behavior = extra_behavior.or(config.extra_fields_behavior); + Ok(extra_behavior.unwrap_or(default)) + } +} + +impl FromPyObject<'_> for ExtraBehavior { + fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { + let s: &str = ob.extract()?; + Self::from_str(s) + } +} + +impl<'py> IntoPyObject<'py> for ExtraBehavior { + type Target = PyString; + + type Output = Borrowed<'py, 'py, PyString>; + + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result { + let s = match self { + Self::Allow => intern!(py, "allow"), + Self::Forbid => intern!(py, "forbid"), + Self::Ignore => intern!(py, "ignore"), }; - Ok(res) + Ok(s.as_borrowed()) } } diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 000000000..c97bc21b8 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,55 @@ +use std::convert::Infallible; + +use pyo3::{ + intern, + types::{PyAnyMethods, PyString}, + Borrowed, Bound, FromPyObject, IntoPyObject, PyAny, PyResult, Python, +}; + +use crate::{ + build_tools::{py_schema_error_type, ExtraBehavior}, + validators::{Revalidate, TemporalUnitMode, ValBytesMode}, +}; + +#[derive(FromPyObject, IntoPyObject, Default, Clone, Debug)] +pub struct CoreConfig { + pub loc_by_alias: Option, + pub extra_fields_behavior: Option, + pub validate_by_alias: Option, + pub validate_by_name: Option, + pub val_json_bytes: Option, + pub val_temporal_unit: Option, + pub revalidate_instances: Option, + pub microseconds_precision: Option, + pub strict: Option, + pub allow_inf_nan: Option, +} + +/// Wrapper around the speedate config value to add Python conversions +#[derive(Debug, Clone, Copy, PartialEq)] +struct MicrosecondsPrecisionOverflowBehavior(pub speedate::MicrosecondsPrecisionOverflowBehavior); + +impl FromPyObject<'_> for MicrosecondsPrecisionOverflowBehavior { + fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { + let s: &str = ob.extract()?; + s.parse().map(MicrosecondsPrecisionOverflowBehavior).map_err(|_| { + py_schema_error_type!("Invalid `microseconds_precision`, must be one of \"truncate\" or \"error\"") + }) + } +} + +impl<'py> IntoPyObject<'py> for MicrosecondsPrecisionOverflowBehavior { + type Target = PyString; + + type Output = Borrowed<'py, 'py, PyString>; + + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result { + let s = match self.0 { + speedate::MicrosecondsPrecisionOverflowBehavior::Truncate => intern!(py, "truncate"), + speedate::MicrosecondsPrecisionOverflowBehavior::Error => intern!(py, "error"), + }; + Ok(s.as_borrowed()) + } +} diff --git a/src/lib.rs b/src/lib.rs index ad4654177..b701117be 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ mod py_gc; mod argument_markers; mod build_tools; mod common; +mod config; mod definitions; mod errors; mod input; diff --git a/src/serializers/type_serializers/dataclass.rs b/src/serializers/type_serializers/dataclass.rs index 6b9f34923..68cbae8bd 100644 --- a/src/serializers/type_serializers/dataclass.rs +++ b/src/serializers/type_serializers/dataclass.rs @@ -8,6 +8,7 @@ use ahash::AHashMap; use serde::ser::SerializeMap; use crate::build_tools::{py_schema_error_type, ExtraBehavior}; +use crate::config::CoreConfig; use crate::definitions::DefinitionsBuilder; use crate::tools::SchemaDict; @@ -29,10 +30,16 @@ impl BuildSerializer for DataclassArgsBuilder { ) -> PyResult> { let py = schema.py(); + let typed_config: CoreConfig = match config { + Some(config) => config.extract()?, + None => CoreConfig::default(), + }; + let fields_list: Bound<'_, PyList> = schema.get_as_req(intern!(py, "fields"))?; let mut fields: AHashMap = AHashMap::with_capacity(fields_list.len()); - let fields_mode = match ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)? { + let fields_mode = match ExtraBehavior::from_schema_or_config(py, schema, &typed_config, ExtraBehavior::Ignore)? + { ExtraBehavior::Allow => FieldsMode::TypedDictAllow, _ => FieldsMode::SimpleDict, }; diff --git a/src/serializers/type_serializers/model.rs b/src/serializers/type_serializers/model.rs index 07b4689a5..7533e29ed 100644 --- a/src/serializers/type_serializers/model.rs +++ b/src/serializers/type_serializers/model.rs @@ -15,6 +15,7 @@ use super::{ }; use crate::build_tools::py_schema_err; use crate::build_tools::{py_schema_error_type, ExtraBehavior}; +use crate::config::CoreConfig; use crate::definitions::DefinitionsBuilder; use crate::serializers::errors::PydanticSerializationUnexpectedValue; use crate::tools::SchemaDict; @@ -133,7 +134,11 @@ impl BuildSerializer for ModelSerializer { fn has_extra(schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>) -> PyResult { let py = schema.py(); - let extra_behaviour = ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)?; + let typed_config: CoreConfig = match config { + Some(config) => config.extract()?, + None => CoreConfig::default(), + }; + let extra_behaviour = ExtraBehavior::from_schema_or_config(py, schema, &typed_config, ExtraBehavior::Ignore)?; Ok(matches!(extra_behaviour, ExtraBehavior::Allow)) } diff --git a/src/serializers/type_serializers/typed_dict.rs b/src/serializers/type_serializers/typed_dict.rs index 9b1751bc2..6d8e46850 100644 --- a/src/serializers/type_serializers/typed_dict.rs +++ b/src/serializers/type_serializers/typed_dict.rs @@ -8,6 +8,7 @@ use ahash::AHashMap; use crate::build_tools::py_schema_err; use crate::build_tools::{py_schema_error_type, schema_or_config, ExtraBehavior}; +use crate::config::CoreConfig; use crate::definitions::DefinitionsBuilder; use crate::tools::SchemaDict; @@ -29,7 +30,13 @@ impl BuildSerializer for TypedDictBuilder { let total = schema_or_config(schema, config, intern!(py, "total"), intern!(py, "typed_dict_total"))?.unwrap_or(true); - let fields_mode = match ExtraBehavior::from_schema_or_config(py, schema, config, ExtraBehavior::Ignore)? { + let typed_config: CoreConfig = match config { + Some(config) => config.extract()?, + None => CoreConfig::default(), + }; + + let fields_mode = match ExtraBehavior::from_schema_or_config(py, schema, &typed_config, ExtraBehavior::Ignore)? + { ExtraBehavior::Allow => FieldsMode::TypedDictAllow, _ => FieldsMode::SimpleDict, }; diff --git a/src/validators/any.rs b/src/validators/any.rs index 93b0761db..27b9ee77b 100644 --- a/src/validators/any.rs +++ b/src/validators/any.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use pyo3::prelude::*; use pyo3::types::PyDict; +use crate::config::CoreConfig; use crate::input::Input; use crate::{build_tools::LazyLock, errors::ValResult}; @@ -21,7 +22,7 @@ impl BuildValidator for AnyValidator { fn build( _schema: &Bound<'_, PyDict>, - _config: Option<&Bound<'_, PyDict>>, + _config: &CoreConfig, _definitions: &mut DefinitionsBuilder>, ) -> PyResult> { Ok(ANY_VALIDATOR.clone()) diff --git a/src/validators/arguments.rs b/src/validators/arguments.rs index ad13e6ade..259eb806d 100644 --- a/src/validators/arguments.rs +++ b/src/validators/arguments.rs @@ -9,7 +9,8 @@ use ahash::AHashSet; use pyo3::IntoPyObjectExt; use crate::build_tools::py_schema_err; -use crate::build_tools::{schema_or_config_same, ExtraBehavior}; +use crate::build_tools::ExtraBehavior; +use crate::config::CoreConfig; use crate::errors::{ErrorTypeDefaults, ValError, ValLineError, ValResult}; use crate::input::{Arguments, BorrowInput, Input, KeywordArgs, PositionalArgs, ValidationMatch}; use crate::lookup_key::LookupKeyCollection; @@ -67,7 +68,7 @@ impl BuildValidator for ArgumentsValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); @@ -166,10 +167,14 @@ impl BuildValidator for ArgumentsValidator { }, var_kwargs_mode, var_kwargs_validator, - loc_by_alias: config.get_as(intern!(py, "loc_by_alias"))?.unwrap_or(true), + loc_by_alias: config.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"))?, + validate_by_alias: schema + .get_as(intern!(py, "validate_by_alias"))? + .or(config.validate_by_alias), + validate_by_name: schema + .get_as(intern!(py, "validate_by_name"))? + .or(config.validate_by_name), }) .into()) } diff --git a/src/validators/arguments_v3.rs b/src/validators/arguments_v3.rs index 8e5781051..240955c2f 100644 --- a/src/validators/arguments_v3.rs +++ b/src/validators/arguments_v3.rs @@ -9,7 +9,8 @@ use ahash::AHashSet; use pyo3::IntoPyObjectExt; use crate::build_tools::py_schema_err; -use crate::build_tools::{schema_or_config_same, ExtraBehavior}; +use crate::build_tools::ExtraBehavior; +use crate::config::CoreConfig; use crate::errors::LocItem; use crate::errors::{ErrorTypeDefaults, ValError, ValLineError, ValResult}; use crate::input::ConsumeIterator; @@ -80,7 +81,7 @@ impl BuildValidator for ArgumentsV3Validator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); @@ -208,10 +209,14 @@ impl BuildValidator for ArgumentsV3Validator { Ok(CombinedValidator::ArgumentsV3(Self { parameters, positional_params_count, - loc_by_alias: config.get_as(intern!(py, "loc_by_alias"))?.unwrap_or(true), + loc_by_alias: config.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"))?, + validate_by_alias: schema + .get_as(intern!(py, "validate_by_alias"))? + .or(config.validate_by_alias), + validate_by_name: schema + .get_as(intern!(py, "validate_by_name"))? + .or(config.validate_by_name), }) .into()) } diff --git a/src/validators/bool.rs b/src/validators/bool.rs index 094343791..aa43f1e2f 100644 --- a/src/validators/bool.rs +++ b/src/validators/bool.rs @@ -4,6 +4,7 @@ use pyo3::types::PyDict; use pyo3::{prelude::*, IntoPyObjectExt}; use crate::build_tools::{is_strict, LazyLock}; +use crate::config::CoreConfig; use crate::errors::ValResult; use crate::input::Input; @@ -25,7 +26,7 @@ impl BuildValidator for BoolValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, _definitions: &mut DefinitionsBuilder>, ) -> PyResult> { if is_strict(schema, config)? { diff --git a/src/validators/bytes.rs b/src/validators/bytes.rs index 6092b0771..512ba1ce6 100644 --- a/src/validators/bytes.rs +++ b/src/validators/bytes.rs @@ -6,6 +6,7 @@ use pyo3::types::PyDict; use pyo3::IntoPyObjectExt; use crate::build_tools::is_strict; +use crate::config::CoreConfig; use crate::errors::{ErrorType, ValError, ValResult}; use crate::input::Input; @@ -25,7 +26,7 @@ impl BuildValidator for BytesValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, _definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); @@ -36,7 +37,7 @@ impl BuildValidator for BytesValidator { } else { Ok(CombinedValidator::Bytes(Self { strict: is_strict(schema, config)?, - bytes_mode: ValBytesMode::from_config(config)?, + bytes_mode: ValBytesMode::from_config(config), }) .into()) } @@ -115,11 +116,11 @@ impl Validator for BytesConstrainedValidator { } impl BytesConstrainedValidator { - fn build(schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>) -> PyResult> { + fn build(schema: &Bound<'_, PyDict>, config: &CoreConfig) -> PyResult> { let py = schema.py(); Ok(CombinedValidator::ConstrainedBytes(Self { strict: is_strict(schema, config)?, - bytes_mode: ValBytesMode::from_config(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"))?, }) diff --git a/src/validators/call.rs b/src/validators/call.rs index 48509a92f..fb4c782ca 100644 --- a/src/validators/call.rs +++ b/src/validators/call.rs @@ -6,6 +6,7 @@ use pyo3::prelude::*; use pyo3::types::PyString; use pyo3::types::{PyDict, PyTuple}; +use crate::config::CoreConfig; use crate::errors::ValResult; use crate::input::Input; @@ -27,7 +28,7 @@ impl BuildValidator for CallValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); diff --git a/src/validators/callable.rs b/src/validators/callable.rs index fc421af61..bf6aaddcb 100644 --- a/src/validators/callable.rs +++ b/src/validators/callable.rs @@ -4,6 +4,7 @@ use pyo3::prelude::*; use pyo3::types::PyDict; use crate::build_tools::LazyLock; +use crate::config::CoreConfig; use crate::errors::{ErrorTypeDefaults, ValError, ValResult}; use crate::input::Input; @@ -20,7 +21,7 @@ impl BuildValidator for CallableValidator { fn build( _schema: &Bound<'_, PyDict>, - _config: Option<&Bound<'_, PyDict>>, + _config: &CoreConfig, _definitions: &mut DefinitionsBuilder>, ) -> PyResult> { Ok(CALLABLE_VALIDATOR.clone()) diff --git a/src/validators/chain.rs b/src/validators/chain.rs index b0a6d9979..decb3f040 100644 --- a/src/validators/chain.rs +++ b/src/validators/chain.rs @@ -5,6 +5,7 @@ use pyo3::prelude::*; use pyo3::types::{PyDict, PyList}; use crate::build_tools::py_schema_err; +use crate::config::CoreConfig; use crate::errors::ValResult; use crate::input::Input; use crate::tools::SchemaDict; @@ -23,7 +24,7 @@ impl BuildValidator for ChainValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let steps: Vec> = schema @@ -58,7 +59,7 @@ impl BuildValidator for ChainValidator { // to be flattened into `steps` above fn build_validator_steps( step: &Bound<'_, PyAny>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult>> { let validator = build_validator(step, config, definitions)?; diff --git a/src/validators/complex.rs b/src/validators/complex.rs index 2e6cc3c50..6953c7d3e 100644 --- a/src/validators/complex.rs +++ b/src/validators/complex.rs @@ -6,6 +6,7 @@ use pyo3::sync::PyOnceLock; use pyo3::types::{PyComplex, PyDict, PyString, PyType}; use crate::build_tools::{is_strict, LazyLock}; +use crate::config::CoreConfig; use crate::errors::{ErrorTypeDefaults, ToErrorValue, ValError, ValResult}; use crate::input::Input; @@ -34,7 +35,7 @@ impl BuildValidator for ComplexValidator { const EXPECTED_TYPE: &'static str = "complex"; fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, _definitions: &mut DefinitionsBuilder>, ) -> PyResult> { if is_strict(schema, config)? { diff --git a/src/validators/config.rs b/src/validators/config.rs index 9d7c24770..045ad59ad 100644 --- a/src/validators/config.rs +++ b/src/validators/config.rs @@ -1,15 +1,16 @@ use std::borrow::Cow; +use std::convert::Infallible; use std::str::FromStr; use crate::build_tools::py_schema_err; +use crate::config::CoreConfig; use crate::errors::ErrorType; use crate::input::EitherBytes; use crate::serializers::BytesMode; -use crate::tools::SchemaDict; use base64::engine::general_purpose::GeneralPurpose; use base64::engine::{DecodePaddingMode, GeneralPurposeConfig}; use base64::{alphabet, DecodeError, Engine}; -use pyo3::types::{PyDict, PyString}; +use pyo3::types::PyString; use pyo3::{intern, prelude::*}; use speedate::TimestampUnit; @@ -47,17 +48,33 @@ impl FromStr for TemporalUnitMode { } } -impl TemporalUnitMode { - pub fn from_config(config: Option<&Bound<'_, PyDict>>) -> PyResult { - let Some(config_dict) = config else { - return Ok(Self::default()); +impl FromPyObject<'_> for TemporalUnitMode { + fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { + let s: &str = ob.extract()?; + Self::from_str(s) + } +} + +impl<'py> IntoPyObject<'py> for TemporalUnitMode { + type Target = PyString; + + type Output = Borrowed<'py, 'py, PyString>; + + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result { + let s = match self { + Self::Seconds => intern!(py, "seconds"), + Self::Milliseconds => intern!(py, "milliseconds"), + Self::Infer => intern!(py, "infer"), }; - let raw_mode = config_dict.get_as::>(intern!(config_dict.py(), "val_temporal_unit"))?; - let temporal_unit = raw_mode.map_or_else( - || Ok(TemporalUnitMode::default()), - |raw| TemporalUnitMode::from_str(&raw.to_cow()?), - )?; - Ok(temporal_unit) + Ok(s.as_borrowed()) + } +} + +impl TemporalUnitMode { + pub fn from_config(config: &CoreConfig) -> Self { + config.val_temporal_unit.unwrap_or_default() } } @@ -76,14 +93,40 @@ pub struct ValBytesMode { pub ser: BytesMode, } -impl ValBytesMode { - pub fn from_config(config: Option<&Bound<'_, PyDict>>) -> PyResult { - let Some(config_dict) = config else { - return Ok(Self::default()); +impl FromPyObject<'_> for ValBytesMode { + fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { + let s: &str = ob.extract()?; + let ser = BytesMode::from_str(s)?; + Ok(Self { ser }) + } +} + +impl<'py> IntoPyObject<'py> for ValBytesMode { + type Target = PyString; + + type Output = Borrowed<'py, 'py, PyString>; + + type Error = Infallible; + + fn into_pyobject(self, py: Python<'py>) -> Result { + let s = match self.ser { + BytesMode::Utf8 => intern!(py, "utf-8"), + BytesMode::Base64 => intern!(py, "base64"), + BytesMode::Hex => intern!(py, "hex"), }; - let raw_mode = config_dict.get_as::>(intern!(config_dict.py(), "val_json_bytes"))?; - let ser_mode = raw_mode.map_or_else(|| Ok(BytesMode::default()), |raw| BytesMode::from_str(&raw.to_cow()?))?; - Ok(Self { ser: ser_mode }) + Ok(s.as_borrowed()) + } +} + +impl ValBytesMode { + pub fn from_config(config: &CoreConfig) -> Self { + config.val_json_bytes.unwrap_or_default() + // let Some(config_dict) = config else { + // return Ok(Self::default()); + // }; + // let raw_mode = config_dict.get_as::>(intern!(config_dict.py(), "val_json_bytes"))?; + // let ser_mode = raw_mode.map_or_else(|| Ok(BytesMode::default()), |raw| BytesMode::from_str(&raw.to_cow()?))?; + // Ok(Self { ser: ser_mode }) } pub fn deserialize_string<'py>(self, s: &str) -> Result, ErrorType> { diff --git a/src/validators/custom_error.rs b/src/validators/custom_error.rs index 426a191ed..831a10275 100644 --- a/src/validators/custom_error.rs +++ b/src/validators/custom_error.rs @@ -5,6 +5,7 @@ use pyo3::prelude::*; use pyo3::types::PyDict; use crate::build_tools::py_schema_err; +use crate::config::CoreConfig; use crate::errors::ToErrorValue; use crate::errors::{ErrorType, PydanticCustomError, PydanticKnownError, ValError, ValResult}; use crate::input::Input; @@ -22,7 +23,7 @@ pub enum CustomError { impl CustomError { pub fn build( schema: &Bound<'_, PyDict>, - _config: Option<&Bound<'_, PyDict>>, + _config: &CoreConfig, _definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); @@ -71,7 +72,7 @@ impl BuildValidator for CustomErrorValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let custom_error = CustomError::build(schema, config, definitions)?.unwrap(); diff --git a/src/validators/dataclass.rs b/src/validators/dataclass.rs index dbd797f12..0b549d873 100644 --- a/src/validators/dataclass.rs +++ b/src/validators/dataclass.rs @@ -9,7 +9,8 @@ use ahash::AHashSet; use pyo3::IntoPyObjectExt; use crate::build_tools::py_schema_err; -use crate::build_tools::{is_strict, schema_or_config_same, ExtraBehavior}; +use crate::build_tools::{is_strict, ExtraBehavior}; +use crate::config::CoreConfig; use crate::errors::{ErrorType, ErrorTypeDefaults, ValError, ValLineError, ValResult}; use crate::input::{ input_as_python_instance, Arguments, BorrowInput, Input, InputType, KeywordArgs, PositionalArgs, ValidationMatch, @@ -53,7 +54,7 @@ impl BuildValidator for DataclassArgsValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); @@ -126,9 +127,9 @@ impl BuildValidator for DataclassArgsValidator { validator_name, extra_behavior, extras_validator, - 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"))?, + loc_by_alias: config.loc_by_alias.unwrap_or(true), + validate_by_alias: config.validate_by_alias, + validate_by_name: config.validate_by_name, }) .into()) } @@ -470,14 +471,13 @@ impl BuildValidator for DataclassValidator { fn build( schema: &Bound<'_, PyDict>, - _config: Option<&Bound<'_, PyDict>>, + _config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); // dataclasses ignore the parent config and always use the config from this dataclasses - let config = schema.get_as(intern!(py, "config"))?; - let config = config.as_ref(); + let config = schema.get_as(intern!(py, "config"))?.unwrap_or_default(); let class = schema.get_as_req::>(intern!(py, "cls"))?; let generic_origin: Option> = schema.get_as(intern!(py, "generic_origin"))?; @@ -486,7 +486,7 @@ impl BuildValidator for DataclassValidator { Err(_) => class.getattr(intern!(py, "__name__"))?.extract()?, }; let sub_schema = schema.get_as_req(intern!(py, "schema"))?; - let validator = build_validator(&sub_schema, config, definitions)?; + let validator = build_validator(&sub_schema, &config, definitions)?; let post_init = if schema.get_as::(intern!(py, "post_init"))?.unwrap_or(false) { Some(intern!(py, "__post_init__").clone().unbind()) @@ -497,18 +497,16 @@ impl BuildValidator for DataclassValidator { let fields = schema.get_as_req(intern!(py, "fields"))?; Ok(CombinedValidator::Dataclass(Self { - strict: is_strict(schema, config)?, + strict: is_strict(schema, &config)?, validator, class: class.into(), generic_origin: generic_origin.map(std::convert::Into::into), fields, post_init, - revalidate: Revalidate::from_str( - schema_or_config_same::>(schema, config, intern!(py, "revalidate_instances"))? - .as_ref() - .map(|s| s.to_str()) - .transpose()?, - )?, + revalidate: schema + .get_as(intern!(py, "revalidate_instances"))? + .or(config.revalidate_instances) + .unwrap_or(Revalidate::Never), name, frozen: schema.get_as(intern!(py, "frozen"))?.unwrap_or(false), slots: schema.get_as(intern!(py, "slots"))?.unwrap_or(false), diff --git a/src/validators/date.rs b/src/validators/date.rs index 4836ec73c..a29099c04 100644 --- a/src/validators/date.rs +++ b/src/validators/date.rs @@ -8,6 +8,7 @@ use speedate::{Date, Time}; use strum::EnumMessage; use crate::build_tools::{is_strict, py_schema_error_type}; +use crate::config::CoreConfig; use crate::errors::{ErrorType, ErrorTypeDefaults, ValError, ValResult}; use crate::input::{EitherDate, Input}; @@ -28,13 +29,13 @@ impl BuildValidator for DateValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, _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)?, + val_temporal_unit: TemporalUnitMode::from_config(config), }) .into()) } diff --git a/src/validators/datetime.rs b/src/validators/datetime.rs index 23fdfe6a4..388fc1f90 100644 --- a/src/validators/datetime.rs +++ b/src/validators/datetime.rs @@ -8,8 +8,9 @@ use std::cmp::Ordering; use std::sync::Arc; use strum::EnumMessage; +use crate::build_tools::py_schema_err; use crate::build_tools::{is_strict, py_schema_error_type}; -use crate::build_tools::{py_schema_err, schema_or_config_same}; +use crate::config::CoreConfig; use crate::errors::ToErrorValue; use crate::errors::{py_err_string, ErrorType, ErrorTypeDefaults, ValError, ValResult}; use crate::input::{EitherDateTime, Input}; @@ -29,16 +30,21 @@ pub struct DateTimeValidator { pub(crate) fn extract_microseconds_precision( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, ) -> PyResult { - schema_or_config_same(schema, config, intern!(schema.py(), "microseconds_precision"))? - .map_or( - Ok(speedate::MicrosecondsPrecisionOverflowBehavior::Truncate), - |v: Bound<'_, PyString>| v.to_str().unwrap().parse(), - ) - .map_err(|_| { - py_schema_error_type!("Invalid `microseconds_precision`, must be one of \"truncate\" or \"error\"") - }) + let value = schema + .get_as(intern!(schema.py(), "microseconds_precision"))? + .or(config.microseconds_precision) + .map_or(speedate::MicrosecondsPrecisionOverflowBehavior::Truncate, |v| v.0); + Ok(value) + // schema_or_config_same(schema, config, intern!(schema.py(), "microseconds_precision"))? + // .map_or( + // Ok(speedate::MicrosecondsPrecisionOverflowBehavior::Truncate), + // |v: Bound<'_, PyString>| v.to_str().unwrap().parse(), + // ) + // .map_err(|_| { + // py_schema_error_type!("Invalid `microseconds_precision`, must be one of \"truncate\" or \"error\"") + // }) } impl BuildValidator for DateTimeValidator { @@ -46,14 +52,14 @@ impl BuildValidator for DateTimeValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, _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)?, + val_temporal_unit: TemporalUnitMode::from_config(config), }) .into()) } diff --git a/src/validators/decimal.rs b/src/validators/decimal.rs index 56f0aa766..a7a782b66 100644 --- a/src/validators/decimal.rs +++ b/src/validators/decimal.rs @@ -6,7 +6,8 @@ use pyo3::sync::PyOnceLock; use pyo3::types::{IntoPyDict, PyDict, PyString, PyTuple, PyType}; use pyo3::{prelude::*, PyTypeInfo}; -use crate::build_tools::{is_strict, schema_or_config_same}; +use crate::build_tools::is_strict; +use crate::config::CoreConfig; use crate::errors::ErrorType; use crate::errors::ValResult; use crate::errors::{ErrorTypeDefaults, Number}; @@ -64,12 +65,15 @@ impl BuildValidator for DecimalValidator { const EXPECTED_TYPE: &'static str = "decimal"; fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, _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); + let allow_inf_nan = schema + .get_as(intern!(py, "allow_inf_nan"))? + .or(config.allow_inf_nan) + .unwrap_or(false); let decimal_places = schema.get_as(intern!(py, "decimal_places"))?; let max_digits = schema.get_as(intern!(py, "max_digits"))?; if allow_inf_nan && (decimal_places.is_some() || max_digits.is_some()) { diff --git a/src/validators/definitions.rs b/src/validators/definitions.rs index 387d329ab..d572bbcac 100644 --- a/src/validators/definitions.rs +++ b/src/validators/definitions.rs @@ -5,6 +5,7 @@ use pyo3::prelude::*; use pyo3::types::PyString; use pyo3::types::{PyDict, PyList}; +use crate::config::CoreConfig; use crate::definitions::DefinitionRef; use crate::errors::{ErrorTypeDefaults, ValError, ValResult}; use crate::input::Input; @@ -22,7 +23,7 @@ impl BuildValidator for DefinitionsValidatorBuilder { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); @@ -58,7 +59,7 @@ impl BuildValidator for DefinitionRefValidator { fn build( schema: &Bound<'_, PyDict>, - _config: Option<&Bound<'_, PyDict>>, + _config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let schema_ref: Bound<'_, PyString> = schema.get_as_req(intern!(schema.py(), "schema_ref"))?; diff --git a/src/validators/dict.rs b/src/validators/dict.rs index b204e1f88..56dca4c72 100644 --- a/src/validators/dict.rs +++ b/src/validators/dict.rs @@ -5,6 +5,7 @@ use pyo3::prelude::*; use pyo3::types::PyDict; use crate::build_tools::is_strict; +use crate::config::CoreConfig; use crate::errors::{LocItem, ValError, ValLineError, ValResult}; use crate::input::BorrowInput; use crate::input::ConsumeIterator; @@ -32,7 +33,7 @@ impl BuildValidator for DictValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); diff --git a/src/validators/enum_.rs b/src/validators/enum_.rs index febb16cb7..3284fd387 100644 --- a/src/validators/enum_.rs +++ b/src/validators/enum_.rs @@ -8,6 +8,7 @@ use pyo3::prelude::*; use pyo3::types::{PyDict, PyFloat, PyInt, PyList, PyString, PyType}; use crate::build_tools::{is_strict, py_schema_err}; +use crate::config::CoreConfig; use crate::errors::{ErrorType, ValError, ValResult}; use crate::input::{Input, InputType}; use crate::tools::{safe_repr, SchemaDict}; @@ -24,7 +25,7 @@ impl BuildValidator for BuildEnumValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, _definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let members: Bound = schema.get_as_req(intern!(schema.py(), "members"))?; diff --git a/src/validators/float.rs b/src/validators/float.rs index 59ea26801..4c974b4c0 100644 --- a/src/validators/float.rs +++ b/src/validators/float.rs @@ -6,7 +6,8 @@ use pyo3::prelude::*; use pyo3::types::PyDict; use pyo3::IntoPyObjectExt; -use crate::build_tools::{is_strict, schema_or_config_same}; +use crate::build_tools::is_strict; +use crate::config::CoreConfig; use crate::errors::{ErrorType, ErrorTypeDefaults, ValError, ValResult}; use crate::input::Input; use crate::tools::SchemaDict; @@ -19,7 +20,7 @@ impl BuildValidator for FloatBuilder { const EXPECTED_TYPE: &'static str = "float"; fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); @@ -33,7 +34,10 @@ impl BuildValidator for FloatBuilder { } else { 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), + allow_inf_nan: schema + .get_as(intern!(py, "allow_inf_nan"))? + .or(config.allow_inf_nan) + .unwrap_or(true), }) .into()) } @@ -51,13 +55,16 @@ impl BuildValidator for FloatValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, _definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); 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), + allow_inf_nan: schema + .get_as(intern!(py, "allow_inf_nan"))? + .or(config.allow_inf_nan) + .unwrap_or(true), }) .into()) } @@ -179,13 +186,16 @@ impl BuildValidator for ConstrainedFloatValidator { const EXPECTED_TYPE: &'static str = "float"; fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, _definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); 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), + allow_inf_nan: schema + .get_as(intern!(py, "allow_inf_nan"))? + .or(config.allow_inf_nan) + .unwrap_or(true), multiple_of: schema.get_as(intern!(py, "multiple_of"))?, le: schema.get_as(intern!(py, "le"))?, lt: schema.get_as(intern!(py, "lt"))?, diff --git a/src/validators/function.rs b/src/validators/function.rs index 6b71d82d6..d41faa138 100644 --- a/src/validators/function.rs +++ b/src/validators/function.rs @@ -1,10 +1,11 @@ use std::sync::Arc; use pyo3::exceptions::{PyAssertionError, PyValueError}; -use pyo3::prelude::*; use pyo3::types::{PyAny, PyDict, PyString}; use pyo3::{intern, PyTraverseError, PyVisit}; +use pyo3::{prelude::*, IntoPyObjectExt}; +use crate::config::CoreConfig; use crate::errors::{ ErrorType, PydanticCustomError, PydanticKnownError, PydanticOmit, ToErrorValue, ValError, ValResult, ValidationError, @@ -50,7 +51,7 @@ macro_rules! impl_build { const EXPECTED_TYPE: &'static str = $name; fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); @@ -66,10 +67,7 @@ macro_rules! impl_build { Self { validator, func: func_info.function, - config: match config { - Some(c) => c.clone().into(), - None => py.None(), - }, + config: config.into_py_any(schema.py())?, name, field_name: func_info.field_name, info_arg: func_info.info_arg, @@ -243,17 +241,14 @@ impl BuildValidator for FunctionPlainValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, _definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); let function_info = destructure_function_schema(schema)?; Ok(CombinedValidator::FunctionPlain(Self { func: function_info.function.clone(), - config: match config { - Some(c) => c.clone().into(), - None => py.None(), - }, + config: config.into_py_any(schema.py())?, name: format!("function-plain[{}()]", function_name(function_info.function.bind(py))?), field_name: function_info.field_name.clone(), info_arg: function_info.info_arg, @@ -308,7 +303,7 @@ impl BuildValidator for FunctionWrapValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); diff --git a/src/validators/generator.rs b/src/validators/generator.rs index 6dc63a752..9a9c6e081 100644 --- a/src/validators/generator.rs +++ b/src/validators/generator.rs @@ -32,7 +32,7 @@ impl BuildValidator for GeneratorValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let item_validator = get_items_schema(schema, config, definitions)?; diff --git a/src/validators/int.rs b/src/validators/int.rs index 1d427eb76..1619407ad 100644 --- a/src/validators/int.rs +++ b/src/validators/int.rs @@ -47,7 +47,7 @@ impl BuildValidator for IntValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, _definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); @@ -97,7 +97,7 @@ pub struct ConstrainedIntValidator { } impl ConstrainedIntValidator { - fn build(schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>) -> PyResult> { + fn build(schema: &Bound<'_, PyDict>, config: &CoreConfig) -> PyResult> { let py = schema.py(); Ok(CombinedValidator::ConstrainedInt(Self { strict: is_strict(schema, config)?, diff --git a/src/validators/is_instance.rs b/src/validators/is_instance.rs index 62d995b54..36d90d132 100644 --- a/src/validators/is_instance.rs +++ b/src/validators/is_instance.rs @@ -23,7 +23,7 @@ impl BuildValidator for IsInstanceValidator { fn build( schema: &Bound<'_, PyDict>, - _config: Option<&Bound<'_, PyDict>>, + _config: &CoreConfig, _definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); diff --git a/src/validators/is_subclass.rs b/src/validators/is_subclass.rs index 6e6b8598b..703875739 100644 --- a/src/validators/is_subclass.rs +++ b/src/validators/is_subclass.rs @@ -22,7 +22,7 @@ impl BuildValidator for IsSubclassValidator { fn build( schema: &Bound<'_, PyDict>, - _config: Option<&Bound<'_, PyDict>>, + _config: &CoreConfig, _definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); diff --git a/src/validators/json.rs b/src/validators/json.rs index 5b2d6e563..4db1eec37 100644 --- a/src/validators/json.rs +++ b/src/validators/json.rs @@ -25,7 +25,7 @@ impl BuildValidator for JsonValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let validator = match schema.get_as(intern!(schema.py(), "schema"))? { diff --git a/src/validators/json_or_python.rs b/src/validators/json_or_python.rs index 6b482ef38..115add8a2 100644 --- a/src/validators/json_or_python.rs +++ b/src/validators/json_or_python.rs @@ -23,7 +23,7 @@ impl BuildValidator for JsonOrPython { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); diff --git a/src/validators/lax_or_strict.rs b/src/validators/lax_or_strict.rs index 09d5fef7a..88ecdd420 100644 --- a/src/validators/lax_or_strict.rs +++ b/src/validators/lax_or_strict.rs @@ -26,7 +26,7 @@ impl BuildValidator for LaxOrStrictValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); diff --git a/src/validators/list.rs b/src/validators/list.rs index 0188d73ea..09b127883 100644 --- a/src/validators/list.rs +++ b/src/validators/list.rs @@ -23,7 +23,7 @@ pub struct ListValidator { pub fn get_items_schema( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult>> { match schema.get_item(pyo3::intern!(schema.py(), "items_schema"))? { @@ -99,7 +99,7 @@ impl BuildValidator for ListValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); diff --git a/src/validators/literal.rs b/src/validators/literal.rs index 9c8ffae10..fc6e362e1 100644 --- a/src/validators/literal.rs +++ b/src/validators/literal.rs @@ -255,7 +255,7 @@ impl BuildValidator for LiteralValidator { fn build( schema: &Bound<'_, PyDict>, - _config: Option<&Bound<'_, PyDict>>, + _config: &CoreConfig, _definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let expected: Bound = schema.get_as_req(intern!(schema.py(), "expected"))?; diff --git a/src/validators/missing_sentinel.rs b/src/validators/missing_sentinel.rs index fa897f928..54ab19f54 100644 --- a/src/validators/missing_sentinel.rs +++ b/src/validators/missing_sentinel.rs @@ -22,7 +22,7 @@ impl BuildValidator for MissingSentinelValidator { fn build( _schema: &Bound<'_, PyDict>, - _config: Option<&Bound<'_, PyDict>>, + _config: &CoreConfig, _definitions: &mut DefinitionsBuilder>, ) -> PyResult> { Ok(MISSING_SENTINEL_VALIDATOR.clone()) diff --git a/src/validators/mod.rs b/src/validators/mod.rs index 7f6a5bdd7..8f0b97617 100644 --- a/src/validators/mod.rs +++ b/src/validators/mod.rs @@ -11,6 +11,7 @@ use pyo3::{intern, PyTraverseError, PyVisit}; use pyo3::{prelude::*, IntoPyObjectExt}; use crate::build_tools::{py_schema_err, py_schema_error_type, ExtraBehavior}; +use crate::config::CoreConfig; use crate::definitions::{Definitions, DefinitionsBuilder}; use crate::errors::{LocItem, ValError, ValResult, ValidationError}; use crate::input::{Input, InputType, StringMapping}; @@ -18,6 +19,7 @@ use crate::py_gc::PyGcTraverse; use crate::recursion_guard::RecursionState; use crate::tools::SchemaDict; pub(crate) use config::{TemporalUnitMode, ValBytesMode}; +pub(crate) use model::Revalidate; mod any; mod arguments; @@ -125,10 +127,10 @@ pub struct SchemaValidator { impl SchemaValidator { #[new] #[pyo3(signature = (schema, config=None))] - pub fn py_new(py: Python, schema: &Bound<'_, PyAny>, config: Option<&Bound<'_, PyDict>>) -> PyResult { + pub fn py_new(py: Python, schema: &Bound<'_, PyAny>, config: Option) -> PyResult { let mut definitions_builder = DefinitionsBuilder::new(); - let validator = build_validator_base(schema, config, &mut definitions_builder)?; + let validator = build_validator_base(schema, config.as_ref(), &mut definitions_builder)?; let definitions = definitions_builder.finish()?; let py_schema = schema.clone().unbind(); let py_config = match config { @@ -499,7 +501,7 @@ pub trait BuildValidator: Sized { /// to return other validators, see `string.rs`, `int.rs`, `float.rs` and `function.rs` for examples fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult>; } @@ -508,7 +510,7 @@ pub trait BuildValidator: Sized { fn build_specific_validator( val_type: &str, schema_dict: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { T::build(schema_dict, config, definitions) @@ -532,7 +534,7 @@ macro_rules! validator_match { // when unpickling: pub fn build_validator_base( schema: &Bound<'_, PyAny>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { build_validator_inner(schema, config, definitions, false) @@ -540,7 +542,7 @@ pub fn build_validator_base( pub fn build_validator( schema: &Bound<'_, PyAny>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { build_validator_inner(schema, config, definitions, true) @@ -548,7 +550,7 @@ pub fn build_validator( fn build_validator_inner( schema: &Bound<'_, PyAny>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, use_prebuilt: bool, ) -> PyResult> { diff --git a/src/validators/model.rs b/src/validators/model.rs index 3385f68b6..abd191cd1 100644 --- a/src/validators/model.rs +++ b/src/validators/model.rs @@ -25,20 +25,19 @@ const DUNDER_MODEL_EXTRA_KEY: &str = "__pydantic_extra__"; const DUNDER_MODEL_PRIVATE_KEY: &str = "__pydantic_private__"; #[derive(Debug, Clone)] -pub(super) enum Revalidate { +pub enum Revalidate { Always, Never, SubclassInstances, } impl Revalidate { - pub fn from_str(s: Option<&str>) -> PyResult { + pub fn from_str(s: &str) -> PyResult { match s { - None => Ok(Self::Never), - Some("always") => Ok(Self::Always), - Some("never") => Ok(Self::Never), - Some("subclass-instances") => Ok(Self::SubclassInstances), - Some(s) => py_schema_err!("Invalid revalidate_instances value: {}", s), + "always" => Ok(Self::Always), + "never" => Ok(Self::Never), + "subclass-instances" => Ok(Self::SubclassInstances), + s => py_schema_err!("Invalid revalidate_instances value: {}", s), } } @@ -51,6 +50,28 @@ impl Revalidate { } } +impl FromPyObject<'_> for Revalidate { + fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { + let s: &str = ob.extract()?; + Self::from_str(s) + } +} + +impl<'py> IntoPyObject<'py> for Revalidate { + type Target = PyString; + type Output = Borrowed<'py, 'py, PyString>; + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> PyResult { + let s = match self { + Revalidate::Always => intern!(py, "always"), + Revalidate::Never => intern!(py, "never"), + Revalidate::SubclassInstances => intern!(py, "subclass-instances"), + }; + Ok(s.as_borrowed()) + } +} + #[derive(Debug)] pub struct ModelValidator { revalidate: Revalidate, @@ -70,7 +91,7 @@ impl BuildValidator for ModelValidator { fn build( schema: &Bound<'_, PyDict>, - _config: Option<&Bound<'_, PyDict>>, + _config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); diff --git a/src/validators/model_fields.rs b/src/validators/model_fields.rs index 672cd55ce..9d73dfa93 100644 --- a/src/validators/model_fields.rs +++ b/src/validators/model_fields.rs @@ -49,7 +49,7 @@ impl BuildValidator for ModelFieldsValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); diff --git a/src/validators/none.rs b/src/validators/none.rs index 9d35eec86..8c0c20920 100644 --- a/src/validators/none.rs +++ b/src/validators/none.rs @@ -19,7 +19,7 @@ impl BuildValidator for NoneValidator { fn build( _schema: &Bound<'_, PyDict>, - _config: Option<&Bound<'_, PyDict>>, + _config: &CoreConfig, _definitions: &mut DefinitionsBuilder>, ) -> PyResult> { Ok(NONE_VALIDATOR.clone()) diff --git a/src/validators/nullable.rs b/src/validators/nullable.rs index 00e702f88..f8d95cd22 100644 --- a/src/validators/nullable.rs +++ b/src/validators/nullable.rs @@ -22,7 +22,7 @@ impl BuildValidator for NullableValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let schema = schema.get_as_req(intern!(schema.py(), "schema"))?; diff --git a/src/validators/set.rs b/src/validators/set.rs index 8c62c6d1a..ff246fbf0 100644 --- a/src/validators/set.rs +++ b/src/validators/set.rs @@ -24,7 +24,7 @@ macro_rules! set_build { () => { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); diff --git a/src/validators/string.rs b/src/validators/string.rs index 705c4f2b1..26ffc052c 100644 --- a/src/validators/string.rs +++ b/src/validators/string.rs @@ -41,7 +41,7 @@ impl BuildValidator for StrValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, _definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let con_str_validator = StrConstrainedValidator::build(schema, config)?; @@ -178,7 +178,7 @@ impl Validator for StrConstrainedValidator { } impl StrConstrainedValidator { - fn build(schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>) -> PyResult { + fn build(schema: &Bound<'_, PyDict>, config: &CoreConfig) -> PyResult { let py = schema.py(); let pattern = schema diff --git a/src/validators/time.rs b/src/validators/time.rs index 6fb5037db..e71a1b010 100644 --- a/src/validators/time.rs +++ b/src/validators/time.rs @@ -28,7 +28,7 @@ impl BuildValidator for TimeValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, _definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let s = Self { diff --git a/src/validators/timedelta.rs b/src/validators/timedelta.rs index bf2950df7..3f744104d 100644 --- a/src/validators/timedelta.rs +++ b/src/validators/timedelta.rs @@ -45,7 +45,7 @@ impl BuildValidator for TimeDeltaValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, _definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); diff --git a/src/validators/tuple.rs b/src/validators/tuple.rs index 5ede4bad2..6db2e7d53 100644 --- a/src/validators/tuple.rs +++ b/src/validators/tuple.rs @@ -28,7 +28,7 @@ impl BuildValidator for TupleValidator { const EXPECTED_TYPE: &'static str = "tuple"; fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); diff --git a/src/validators/typed_dict.rs b/src/validators/typed_dict.rs index fdec72143..f2b7d6f95 100644 --- a/src/validators/typed_dict.rs +++ b/src/validators/typed_dict.rs @@ -47,7 +47,7 @@ impl BuildValidator for TypedDictValidator { fn build( schema: &Bound<'_, PyDict>, - _config: Option<&Bound<'_, PyDict>>, + _config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); diff --git a/src/validators/union.rs b/src/validators/union.rs index 200e2b339..7f2cb9a6d 100644 --- a/src/validators/union.rs +++ b/src/validators/union.rs @@ -52,7 +52,7 @@ impl BuildValidator for UnionValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); @@ -296,7 +296,7 @@ impl BuildValidator for TaggedUnionValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); diff --git a/src/validators/url.rs b/src/validators/url.rs index 799d18722..5fdadb0ad 100644 --- a/src/validators/url.rs +++ b/src/validators/url.rs @@ -97,7 +97,7 @@ static SIMPLE_URL_VALIDATOR_STRICT_PRESERVE_EMPTY_PATH: LazyLock, config: Option<&Bound<'_, PyDict>>) -> PyResult { +fn get_preserve_empty_path(schema: &Bound<'_, PyDict>, config: &CoreConfig) -> PyResult { schema_or_config( schema, config, @@ -112,7 +112,7 @@ impl BuildValidator for UrlValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, _definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let (allowed_schemes, name) = get_allowed_schemes(schema, Self::EXPECTED_TYPE)?; @@ -357,7 +357,7 @@ impl BuildValidator for MultiHostUrlValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, _definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let (allowed_schemes, name) = get_allowed_schemes(schema, Self::EXPECTED_TYPE)?; diff --git a/src/validators/uuid.rs b/src/validators/uuid.rs index 25b80c55a..ef607a2a2 100644 --- a/src/validators/uuid.rs +++ b/src/validators/uuid.rs @@ -80,7 +80,7 @@ impl BuildValidator for UuidValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, _definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py(); diff --git a/src/validators/with_default.rs b/src/validators/with_default.rs index c097f40d0..fc94174e9 100644 --- a/src/validators/with_default.rs +++ b/src/validators/with_default.rs @@ -102,7 +102,7 @@ impl BuildValidator for WithDefaultValidator { fn build( schema: &Bound<'_, PyDict>, - config: Option<&Bound<'_, PyDict>>, + config: &CoreConfig, definitions: &mut DefinitionsBuilder>, ) -> PyResult> { let py = schema.py();