File vortex.diff of Package mingw-python-pydantic-core
diff --git a/Cargo.toml b/Cargo.toml
index a7e86ab..01586c4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -27,7 +27,7 @@ rust-version = "1.75"
[dependencies]
# TODO it would be very nice to remove the "py-clone" feature as it can panic,
# but needs a bit of work to make sure it's not used in the codebase
-pyo3 = { version = "0.26", features = ["generate-import-lib", "num-bigint", "py-clone"] }
+pyo3 = { version = "0.27", features = ["generate-import-lib", "num-bigint", "py-clone"] }
regex = "1.12.2"
strum = { version = "0.27", features = ["derive"] }
strum_macros = "0.27"
@@ -44,7 +44,7 @@ base64 = "0.22.1"
num-bigint = "0.4.6"
num-traits = "0.2.19"
uuid = "1.18.1"
-jiter = { version = "0.11.1", features = ["python"] }
+jiter = { version = "0.12.0", features = ["python"] }
hex = "0.4.3"
percent-encoding = "2.3.2"
@@ -69,12 +69,12 @@ debug = true
strip = false
[dev-dependencies]
-pyo3 = { version = "0.26", features = ["auto-initialize"] }
+pyo3 = { version = "0.27", features = ["auto-initialize"] }
[build-dependencies]
version_check = "0.9.5"
# used where logic has to be version/distribution specific, e.g. pypy
-pyo3-build-config = { version = "0.26" }
+pyo3-build-config = { version = "0.27" }
[lints.clippy]
dbg_macro = "warn"
diff --git a/src/build_tools.rs b/src/build_tools.rs
index 2ccb827..1644c2b 100644
--- a/src/build_tools.rs
+++ b/src/build_tools.rs
@@ -14,6 +14,7 @@ use crate::input::InputType;
use crate::tools::SchemaDict;
use crate::ValidationError;
+/*
pub fn schema_or_config<'py, T>(
schema: &Bound<'py, PyDict>,
config: Option<&Bound<'py, PyDict>>,
@@ -31,7 +32,28 @@ where
},
}
}
+*/
+pub fn schema_or_config<'py, T>(
+ schema: &Bound<'py, PyDict>,
+ config: Option<&Bound<'py, PyDict>>,
+ schema_key: &Bound<'py, PyString>,
+ config_key: &Bound<'py, PyString>,
+) -> PyResult<Option<T>>
+where
+ T: for<'a> FromPyObject<'a, 'py>,
+ for<'a> <T as FromPyObject<'a, 'py>>::Error: Into<PyErr>,
+{
+ match schema.get_as(schema_key)? {
+ Some(v) => Ok(Some(v)),
+ None => match config {
+ Some(config) => config.get_as(config_key),
+ None => Ok(None),
+ },
+ }
+}
+
+/*
pub fn schema_or_config_same<'py, T>(
schema: &Bound<'py, PyDict>,
config: Option<&Bound<'py, PyDict>>,
@@ -42,6 +64,20 @@ where
{
schema_or_config(schema, config, key, key)
}
+*/
+
+pub fn schema_or_config_same<'py, T>(
+ schema: &Bound<'py, PyDict>,
+ config: Option<&Bound<'py, PyDict>>,
+ key: &Bound<'py, PyString>,
+) -> PyResult<Option<T>>
+where
+ T: for<'a> FromPyObject<'a, 'py>,
+ for<'a> <T as FromPyObject<'a, 'py>>::Error: Into<PyErr>,
+{
+ schema_or_config(schema, config, key, key)
+}
+
pub fn is_strict(schema: &Bound<'_, PyDict>, config: Option<&Bound<'_, PyDict>>) -> PyResult<bool> {
let py = schema.py();
@@ -185,6 +221,7 @@ pub enum ExtraBehavior {
Ignore,
}
+/*
impl ExtraBehavior {
pub fn from_schema_or_config(
py: Python,
@@ -206,6 +243,29 @@ impl ExtraBehavior {
Ok(res)
}
}
+*/
+
+impl ExtraBehavior {
+ pub fn from_schema_or_config<'py>(
+ py: Python<'py>,
+ schema: &Bound<'py, PyDict>,
+ config: Option<&Bound<'py, PyDict>>,
+ default: Self,
+ ) -> PyResult<Self> {
+ let extra_behavior: Option<String> = schema_or_config(
+ schema,
+ config,
+ intern!(py, "extra_behavior"),
+ intern!(py, "extra_fields_behavior"),
+ )?;
+
+ Ok(match extra_behavior.as_deref() {
+ Some(s) => Self::from_str(s)?,
+ None => default,
+ })
+ }
+}
+
impl FromStr for ExtraBehavior {
type Err = PyErr;
diff --git a/src/common/prebuilt.rs b/src/common/prebuilt.rs
index e66e2c3..141c7c8 100644
--- a/src/common/prebuilt.rs
+++ b/src/common/prebuilt.rs
@@ -1,9 +1,10 @@
use pyo3::intern;
+use pyo3::exceptions::PyKeyError;
use pyo3::prelude::*;
use pyo3::types::{PyAny, PyDict, PyType};
-use crate::tools::SchemaDict;
+/*
pub fn get_prebuilt<T>(
type_: &str,
schema: &Bound<'_, PyDict>,
@@ -42,3 +43,47 @@ pub fn get_prebuilt<T>(
let prebuilt: Bound<'_, PyAny> = class_dict.get_item(prebuilt_attr_name)?;
extractor(prebuilt)
}
+*/
+
+pub fn get_prebuilt<'py, T>(
+ type_: &str,
+ schema: &Bound<'py, PyDict>,
+ prebuilt_attr_name: &str,
+ extractor: impl FnOnce(Bound<'py, PyAny>) -> PyResult<Option<T>>,
+) -> PyResult<Option<T>> {
+ let py = schema.py();
+
+ // we can only use prebuilt validators/serializers from models and Pydantic dataclasses.
+ // However, we don't want to use a prebuilt structure from dataclasses if we have a `generic_origin`
+ // as this means the dataclass was parametrized (so a generic alias instance), and `cls` in the
+ // core schema is still the (unparametrized) class, meaning we would fetch the wrong validator/serializer.
+ if !matches!(type_, "model" | "dataclass")
+ || (type_ == "dataclass" && schema.contains(intern!(py, "generic_origin"))?)
+ {
+ return Ok(None);
+ }
+
+ // Avoid SchemaDict::get_as_req::<Bound<PyType>>() here; extract as PyAny then downcast.
+ let cls_any = schema
+ .get_item(intern!(py, "cls"))?
+ .ok_or_else(|| PyKeyError::new_err("cls"))?;
+ let class: Bound<'py, PyType> = cls_any.downcast::<PyType>()?.to_owned();
+
+ // Note: we NEED to use the __dict__ here (and perform get_item calls rather than getattr)
+ // because we don't want to fetch prebuilt validators from parent classes.
+ // We don't downcast here because __dict__ on a class is a readonly mappingproxy,
+ // so we can just leave it as is and do get_item checks.
+ let class_dict = class.getattr(intern!(py, "__dict__"))?;
+
+ let is_complete: bool = class_dict
+ .get_item(intern!(py, "__pydantic_complete__"))
+ .is_ok_and(|b| b.extract().unwrap_or(false));
+
+ if !is_complete {
+ return Ok(None);
+ }
+
+ // Retrieve the prebuilt validator / serializer if available
+ let prebuilt: Bound<'py, PyAny> = class_dict.get_item(prebuilt_attr_name)?;
+ extractor(prebuilt)
+}
diff --git a/src/errors/types.rs b/src/errors/types.rs
index 1b821e2..ee4341c 100644
--- a/src/errors/types.rs
+++ b/src/errors/types.rs
@@ -42,6 +42,7 @@ pub fn list_all_errors(py: Python<'_>) -> PyResult<Bound<'_, PyList>> {
PyList::new(py, errors)
}
+/*
fn field_from_context<'py, T: FromPyObject<'py>>(
context: Option<&Bound<'py, PyDict>>,
field_name: &str,
@@ -55,7 +56,37 @@ fn field_from_context<'py, T: FromPyObject<'py>>(
.extract::<T>()
.map_err(|_| py_error_type!(PyTypeError; "{}: '{}' context value must be a {}", enum_name, field_name, type_name_fn()))
}
+*/
+fn field_from_context<'py, T>(
+ context: Option<&Bound<'py, PyDict>>,
+ field_name: &str,
+ enum_name: &str,
+ type_name_fn: fn() -> &'static str,
+) -> PyResult<T>
+where
+ T: for<'a> FromPyObject<'a, 'py>,
+ for<'a> <T as FromPyObject<'a, 'py>>::Error: Into<PyErr>,
+{
+ context
+ .ok_or_else(|| py_error_type!(PyTypeError; "{}: '{}' required in context", enum_name, field_name))?
+ .get_item(field_name)?
+ .ok_or_else(|| py_error_type!(PyTypeError; "{}: '{}' required in context", enum_name, field_name))?
+ .extract::<T>()
+ .map_err(Into::into)
+ .map_err(|_| {
+ py_error_type!(
+ PyTypeError;
+ "{}: '{}' context value must be a {}",
+ enum_name,
+ field_name,
+ type_name_fn()
+ )
+ })
+}
+
+
+/*
fn cow_field_from_context<'py, T: FromPyObject<'py>, B: ToOwned<Owned = T> + ?Sized + 'static>(
context: Option<&Bound<'py, PyDict>>,
field_name: &str,
@@ -67,6 +98,25 @@ fn cow_field_from_context<'py, T: FromPyObject<'py>, B: ToOwned<Owned = T> + ?Si
})?;
Ok(Cow::Owned(res))
}
+*/
+
+fn cow_field_from_context<'py, T, B>(
+ context: Option<&Bound<'py, PyDict>>,
+ field_name: &str,
+ enum_name: &str,
+ _type_name_fn: fn() -> &'static str,
+) -> PyResult<Cow<'static, B>>
+where
+ T: for<'a> FromPyObject<'a, 'py>,
+ for<'a> <T as FromPyObject<'a, 'py>>::Error: Into<PyErr>,
+ B: ToOwned<Owned = T> + ?Sized + 'static,
+{
+ let res: T = field_from_context(context, field_name, enum_name, || {
+ type_name::<T>().split("::").last().unwrap()
+ })?;
+ Ok(Cow::Owned(res))
+}
+
macro_rules! basic_error_default {
(
@@ -799,6 +849,7 @@ impl From<Int> for Number {
}
}
+/*
impl FromPyObject<'_> for Number {
fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult<Self> {
if let Some(int) = extract_i64(obj) {
@@ -812,6 +863,30 @@ impl FromPyObject<'_> for Number {
}
}
}
+*/
+
+use pyo3::Borrowed;
+
+impl<'py> FromPyObject<'_, 'py> for Number {
+ type Error = PyErr;
+
+ fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {
+ // Keep your existing logic by working with a Bound<PyAny>
+ let obj: &Bound<'py, PyAny> = &*obj;
+
+ if let Some(int) = extract_i64(obj) {
+ Ok(Number::Int(int))
+ } else if let Ok(float) = obj.extract::<f64>() {
+ Ok(Number::Float(float))
+ } else if let Ok(string) = obj.extract::<String>() {
+ Ok(Number::String(string))
+ } else {
+ py_err!(PyTypeError; "Expected int or float or String, got {}", obj.get_type())
+ }
+ }
+}
+
+
impl fmt::Display for Number {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
diff --git a/src/errors/validation_exception.rs b/src/errors/validation_exception.rs
index 5216acc..f750905 100644
--- a/src/errors/validation_exception.rs
+++ b/src/errors/validation_exception.rs
@@ -441,6 +441,7 @@ impl From<PyLineError> for ValLineError {
}
}
+/*
impl TryFrom<&Bound<'_, PyAny>> for PyLineError {
type Error = PyErr;
@@ -477,6 +478,49 @@ impl TryFrom<&Bound<'_, PyAny>> for PyLineError {
})
}
}
+*/
+
+impl<'py> TryFrom<&Bound<'py, PyAny>> for PyLineError {
+ type Error = PyErr;
+
+ fn try_from(value: &Bound<'py, PyAny>) -> PyResult<Self> {
+ let dict = value.downcast::<PyDict>()?;
+ let py = value.py();
+
+ let type_raw = dict
+ .get_item(intern!(py, "type"))?
+ .ok_or_else(|| PyKeyError::new_err("type"))?;
+
+ let error_type = if let Ok(type_str) = type_raw.downcast::<PyString>() {
+ let context: Option<Bound<'py, PyDict>> = match dict.get_item(intern!(py, "ctx"))? {
+ Some(ctx_any) => Some(ctx_any.downcast::<PyDict>()?.to_owned()),
+ None => None,
+ };
+
+ ErrorType::new(py, type_str.to_str()?, context)?
+ } else if let Ok(custom_error) = type_raw.extract::<PydanticCustomError>() {
+ ErrorType::new_custom_error(py, custom_error)
+ } else {
+ return Err(PyTypeError::new_err(
+ "`type` should be a `str` or `PydanticCustomError`",
+ ));
+ };
+
+ let location = Location::try_from(dict.get_item("loc")?.as_ref())?;
+
+ let input_value = match dict.get_item("input")? {
+ Some(i) => i.unbind(),
+ None => py.None(),
+ };
+
+ Ok(Self {
+ error_type,
+ location,
+ input_value,
+ })
+ }
+}
+
impl PyLineError {
pub fn from_val_line_error(py: Python, error: ValLineError) -> PyResult<Self> {
diff --git a/src/input/return_enums.rs b/src/input/return_enums.rs
index 526d2c9..bfef47d 100644
--- a/src/input/return_enums.rs
+++ b/src/input/return_enums.rs
@@ -706,6 +706,7 @@ impl Rem for &Int {
}
}
+/*
impl FromPyObject<'_> for Int {
fn extract_bound(obj: &Bound<'_, PyAny>) -> PyResult<Self> {
match extract_int(obj) {
@@ -714,6 +715,23 @@ impl FromPyObject<'_> for Int {
}
}
}
+*/
+
+use pyo3::Borrowed;
+
+impl<'py> FromPyObject<'_, 'py> for Int {
+ type Error = PyErr;
+
+ fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {
+ let obj: &Bound<'py, PyAny> = &*obj;
+
+ match extract_int(obj) {
+ Some(i) => Ok(i),
+ None => py_err!(PyTypeError; "Expected int, got {}", obj.get_type()),
+ }
+ }
+}
+
#[derive(Clone)]
pub enum EitherComplex<'py> {
diff --git a/src/serializers/computed_fields.rs b/src/serializers/computed_fields.rs
index 890d4a8..f1997c5 100644
--- a/src/serializers/computed_fields.rs
+++ b/src/serializers/computed_fields.rs
@@ -1,5 +1,6 @@
use std::sync::Arc;
+use pyo3::exceptions::PyKeyError;
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList, PyString};
use pyo3::{intern, PyTraverseError, PyVisit};
@@ -21,6 +22,8 @@ use super::errors::py_err_se_err;
pub(super) struct ComputedFields(Vec<ComputedField>);
impl ComputedFields {
+
+ /*
pub fn new(
schema: &Bound<'_, PyDict>,
config: Option<&Bound<'_, PyDict>>,
@@ -37,6 +40,30 @@ impl ComputedFields {
Ok(None)
}
}
+ */
+
+ pub fn new<'py>(
+ schema: &Bound<'py, PyDict>,
+ config: Option<&Bound<'py, PyDict>>,
+ definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
+ ) -> PyResult<Option<Self>> {
+ let py = schema.py();
+
+ let computed_fields_any: Option<Bound<'py, PyAny>> =
+ schema.get_item(intern!(py, "computed_fields"))?;
+
+ let Some(computed_fields_any) = computed_fields_any else {
+ return Ok(None);
+ };
+
+ let computed_fields_list: &Bound<'py, PyList> = computed_fields_any.downcast::<PyList>()?;
+ let computed_fields = computed_fields_list
+ .iter()
+ .map(|field| ComputedField::new(&field, config, definitions))
+ .collect::<PyResult<Vec<_>>>()?;
+
+ Ok(Some(Self(computed_fields)))
+ }
pub fn len(&self) -> usize {
self.0.len()
@@ -162,6 +189,7 @@ struct ComputedField {
serialize_by_alias: Option<bool>,
}
+/*
impl ComputedField {
pub fn new(
schema: &Bound<'_, PyAny>,
@@ -187,6 +215,60 @@ impl ComputedField {
})
}
}
+*/
+
+impl ComputedField {
+ pub fn new<'py>(
+ schema: &Bound<'py, PyAny>,
+ config: Option<&Bound<'py, PyDict>>,
+ definitions: &mut DefinitionsBuilder<Arc<CombinedSerializer>>,
+ ) -> PyResult<Self> {
+ let py = schema.py();
+ let schema: &Bound<'py, PyDict> = schema.downcast::<PyDict>()?;
+
+ // property_name: required str
+ let property_name_any = schema
+ .get_item(intern!(py, "property_name"))?
+ .ok_or_else(|| PyKeyError::new_err("property_name"))?;
+ let property_name: &Bound<'py, PyString> = property_name_any.downcast::<PyString>()?;
+
+ // return_schema: required dict (or whatever CombinedSerializer::build expects; keep as PyAny if it accepts that)
+ let return_schema_any = schema
+ .get_item(intern!(py, "return_schema"))?
+ .ok_or_else(|| PyKeyError::new_err("return_schema"))?;
+
+ let return_schema: &Bound<'_, PyDict> = return_schema_any.downcast::<PyDict>()?;
+
+ let serializer = CombinedSerializer::build(return_schema, config, definitions)
+ .map_err(|e| py_schema_error_type!("Computed field `{}`:\n {}", property_name, e))?;
+
+ // alias: optional str; default to property_name
+ let alias: Bound<'py, PyString> = match schema.get_item(intern!(py, "alias"))? {
+ Some(alias_any) => alias_any.downcast::<PyString>()?.to_owned(),
+ None => property_name.to_owned(),
+ };
+
+ let serialize_by_alias: Option<bool> = match config {
+ Some(c) => match c.get_item(intern!(py, "serialize_by_alias"))? {
+ Some(v) => Some(v.extract::<bool>().unwrap_or(false)),
+ None => None,
+ },
+ None => None,
+ };
+
+
+ Ok(Self {
+ property_name: property_name.to_str()?.to_owned(),
+ property_name_py: property_name.to_owned().into(),
+ serializer,
+ alias: alias.to_str()?.to_owned(),
+ alias_py: alias.into(),
+ serialize_by_alias,
+ })
+ }
+}
+
+
impl_py_gc_traverse!(ComputedField { serializer });
diff --git a/src/serializers/config.rs b/src/serializers/config.rs
index 69d03ea..954e892 100644
--- a/src/serializers/config.rs
+++ b/src/serializers/config.rs
@@ -92,6 +92,7 @@ macro_rules! serialization_mode {
}
}
+ /*
impl FromConfig for $name {
fn from_config(config: Option<&Bound<'_, PyDict>>) -> PyResult<Self> {
let Some(config_dict) = config else {
@@ -101,6 +102,24 @@ macro_rules! serialization_mode {
raw_mode.map_or_else(|| Ok(Self::default()), |raw| Self::from_str(raw.to_str()?))
}
}
+ */
+
+ impl FromConfig for $name {
+ fn from_config(config: Option<&Bound<'_, PyDict>>) -> PyResult<Self> {
+ let Some(config_dict) = config else {
+ return Ok(Self::default());
+ };
+ let py = config_dict.py();
+
+ let raw_mode: Option<String> = match config_dict.get_item(intern!(py, $config_key))? {
+ Some(v) => Some(v.extract::<String>()?),
+ None => None,
+ };
+
+ raw_mode.map_or_else(|| Ok(Self::default()), |raw| Self::from_str(&raw))
+ }
+ }
+
};
}
@@ -347,8 +366,21 @@ pub fn utf8_py_error(py: Python, err: Utf8Error, data: &[u8]) -> PyErr {
}
}
+/*
impl FromPyObject<'_> for InfNanMode {
fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
Self::from_str(ob.downcast::<PyString>()?.to_str()?)
}
}
+*/
+
+use pyo3::Borrowed;
+
+impl<'py> FromPyObject<'_, 'py> for InfNanMode {
+ type Error = PyErr;
+
+ fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {
+ let ob: &Bound<'py, PyAny> = &*ob;
+ Self::from_str(ob.downcast::<PyString>()?.to_str()?)
+ }
+}
diff --git a/src/serializers/extra.rs b/src/serializers/extra.rs
index 2e43954..e57f74f 100644
--- a/src/serializers/extra.rs
+++ b/src/serializers/extra.rs
@@ -403,6 +403,7 @@ pub enum WarningsMode {
Error,
}
+/*
impl<'py> FromPyObject<'py> for WarningsMode {
fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<WarningsMode> {
if let Ok(bool_mode) = ob.downcast::<PyBool>() {
@@ -423,6 +424,35 @@ impl<'py> FromPyObject<'py> for WarningsMode {
}
}
}
+*/
+
+use pyo3::Borrowed;
+
+impl<'py> FromPyObject<'_, 'py> for WarningsMode {
+ type Error = PyErr;
+
+ fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {
+ let ob: &Bound<'py, PyAny> = &*ob;
+
+ if let Ok(bool_mode) = ob.downcast::<PyBool>() {
+ Ok(bool_mode.is_true().into())
+ } else if let Ok(str_mode) = ob.extract::<&str>() {
+ match str_mode {
+ "none" => Ok(Self::None),
+ "warn" => Ok(Self::Warn),
+ "error" => Ok(Self::Error),
+ _ => Err(PyValueError::new_err(
+ "Invalid warnings parameter, should be `'none'`, `'warn'`, `'error'` or a `bool`",
+ )),
+ }
+ } else {
+ Err(PyTypeError::new_err(
+ "Invalid warnings parameter, should be `'none'`, `'warn'`, `'error'` or a `bool`",
+ ))
+ }
+ }
+}
+
impl From<bool> for WarningsMode {
fn from(mode: bool) -> Self {
diff --git a/src/serializers/filter.rs b/src/serializers/filter.rs
index 444033a..5659038 100644
--- a/src/serializers/filter.rs
+++ b/src/serializers/filter.rs
@@ -61,6 +61,7 @@ fn map_negative_indices<'py>(
type NextFilters<'py> = Option<(Option<Bound<'py, PyAny>>, Option<Bound<'py, PyAny>>)>;
impl SchemaFilter<usize> {
+ /*
pub fn from_schema(schema: &Bound<'_, PyDict>) -> PyResult<Self> {
let py = schema.py();
match schema.get_as::<Bound<'_, PyDict>>(intern!(py, "serialization"))? {
@@ -72,6 +73,23 @@ impl SchemaFilter<usize> {
None => Ok(SchemaFilter::default()),
}
}
+ */
+
+ pub fn from_schema<'py>(schema: &Bound<'py, PyDict>) -> PyResult<Self> {
+ let py = schema.py();
+
+ let ser_any: Option<Bound<'py, PyAny>> = schema.get_item(intern!(py, "serialization"))?;
+ match ser_any {
+ Some(ser_any) => {
+ let ser: &Bound<'py, PyDict> = ser_any.downcast::<PyDict>()?;
+
+ let include = Self::build_set_ints(ser.get_item(intern!(py, "include"))?)?;
+ let exclude = Self::build_set_ints(ser.get_item(intern!(py, "exclude"))?)?;
+ Ok(Self { include, exclude })
+ }
+ None => Ok(SchemaFilter::default()),
+ }
+ }
fn build_set_ints(v: Option<Bound<'_, PyAny>>) -> PyResult<Option<AHashSet<usize>>> {
match v {
diff --git a/src/serializers/infer.rs b/src/serializers/infer.rs
index 1f6f468..d1e71d1 100644
--- a/src/serializers/infer.rs
+++ b/src/serializers/infer.rs
@@ -618,7 +618,11 @@ pub(crate) fn call_pydantic_serializer<'py, T, E: From<PyErr>>(
) -> Result<T, E> {
let py = value.py();
let py_serializer = value.getattr(intern!(py, "__pydantic_serializer__"))?;
- let extracted_serializer: PyRef<SchemaSerializer> = py_serializer.extract()?;
+ //let extracted_serializer: PyRef<SchemaSerializer> = py_serializer.extract()?;
+
+ let extracted_serializer: PyRef<'_, SchemaSerializer> = py_serializer
+ .extract::<PyRef<'_, SchemaSerializer>>()
+ .map_err(|e| e.into())?;
let mut state = SerializationState {
warnings: state.warnings.clone(),
rec_guard: state.rec_guard.clone(),
diff --git a/src/serializers/shared.rs b/src/serializers/shared.rs
index f8a0201..505dd60 100644
--- a/src/serializers/shared.rs
+++ b/src/serializers/shared.rs
@@ -4,7 +4,7 @@ use std::fmt::Debug;
use std::io::{self, Write};
use std::sync::Arc;
-use pyo3::exceptions::PyTypeError;
+use pyo3::exceptions::{PyKeyError, PyTypeError};
use pyo3::sync::PyOnceLock;
use pyo3::types::{PyDict, PyString};
use pyo3::{intern, PyTraverseError, PyVisit};
@@ -174,9 +174,21 @@ impl CombinedSerializer {
let py = schema.py();
let type_key = intern!(py, "type");
- if let Some(ser_schema) = schema.get_as::<Bound<'_, PyDict>>(intern!(py, "serialization"))? {
- let op_ser_type: Option<Bound<'_, PyString>> = ser_schema.get_as(type_key)?;
- match op_ser_type.as_ref().map(|py_str| py_str.to_str()).transpose()? {
+ let ser_schema: Option<Bound<'_, PyDict>> = match schema.get_item(intern!(py, "serialization"))? {
+ Some(v) => Some(v.downcast::<PyDict>()?.to_owned()),
+ None => None,
+ };
+
+ if let Some(ser_schema) = ser_schema.as_ref() {
+ //if let Some(ser_schema) = schema.get_as::<Bound<'_, PyDict>>(intern!(py, "serialization"))? {
+ let op_ser_type: Option<String> = match ser_schema.get_item(type_key)? {
+ Some(v) => Some(v.extract::<String>()?),
+ None => None,
+ };
+
+ match op_ser_type.as_deref() {
+ //let op_ser_type: Option<Bound<'_, PyString>> = ser_schema.get_as(type_key)?;
+ //match op_ser_type.as_ref().map(|py_str| py_str.to_str()).transpose()? {
Some("function-plain") => {
// `function-plain` is a special case, not included in `find_serializer` since it means
// something different in `schema.type`
@@ -210,24 +222,29 @@ impl CombinedSerializer {
Some(ser_type) => {
// otherwise if `schema.serialization.type` is defined, use that with `find_serializer`
// instead of `schema.type`. In this case it's an error if a serializer isn't found.
- return Self::find_serializer(ser_type, &ser_schema, config, definitions);
+ //return Self::find_serializer(ser_type, &ser_schema, config, definitions);
+ return Self::find_serializer(ser_type, ser_schema, config, definitions);
}
};
}
- let type_: Bound<'_, PyString> = schema.get_as_req(type_key)?;
- let type_ = type_.to_str()?;
+ //let type_: Bound<'_, PyString> = schema.get_as_req(type_key)?;
+ //let type_ = type_.to_str()?;
+ let type_: String = match schema.get_item(type_key)? {
+ Some(v) => v.extract::<String>()?,
+ None => return Err(PyKeyError::new_err("type")),
+ };
if use_prebuilt {
// if we have a SchemaValidator on the type already, use it
if let Ok(Some(prebuilt_serializer)) =
- super::prebuilt::PrebuiltSerializer::try_get_from_schema(type_, schema)
+ super::prebuilt::PrebuiltSerializer::try_get_from_schema(&type_, schema)
{
return Ok(Arc::new(prebuilt_serializer));
}
}
- Self::find_serializer(type_, schema, config, definitions)
+ Self::find_serializer(&type_, schema, config, definitions)
}
/// Main recursive way to call serializers, supports possible recursive type inference by
diff --git a/src/tools.rs b/src/tools.rs
index 8ce929c..10f18c0 100644
--- a/src/tools.rs
+++ b/src/tools.rs
@@ -11,6 +11,7 @@ use crate::input::Int;
use crate::PydanticUndefinedType;
use jiter::{cached_py_string, StringCacheMode};
+/*
pub trait SchemaDict<'py> {
fn get_as<T>(&self, key: &Bound<'py, PyString>) -> PyResult<Option<T>>
where
@@ -20,7 +21,21 @@ pub trait SchemaDict<'py> {
where
T: FromPyObject<'py>;
}
+*/
+pub trait SchemaDict<'py> {
+ fn get_as<T>(&self, key: &Bound<'py, PyString>) -> PyResult<Option<T>>
+ where
+ T: for<'a> FromPyObject<'a, 'py>,
+ for<'a> <T as FromPyObject<'a, 'py>>::Error: Into<PyErr>;
+
+ fn get_as_req<T>(&self, key: &Bound<'py, PyString>) -> PyResult<T>
+ where
+ T: for<'a> FromPyObject<'a, 'py>,
+ for<'a> <T as FromPyObject<'a, 'py>>::Error: Into<PyErr>;
+}
+
+/*
impl<'py> SchemaDict<'py> for Bound<'py, PyDict> {
fn get_as<T>(&self, key: &Bound<'py, PyString>) -> PyResult<Option<T>>
where
@@ -42,7 +57,33 @@ impl<'py> SchemaDict<'py> for Bound<'py, PyDict> {
}
}
}
+*/
+
+impl<'py> SchemaDict<'py> for Bound<'py, PyDict> {
+ fn get_as<T>(&self, key: &Bound<'py, PyString>) -> PyResult<Option<T>>
+ where
+ T: for<'a> FromPyObject<'a, 'py>,
+ for<'a> <T as FromPyObject<'a, 'py>>::Error: Into<PyErr>,
+ {
+ match self.get_item(key)? {
+ Some(t) => t.extract().map(Some).map_err(Into::into),
+ None => Ok(None),
+ }
+ }
+
+ fn get_as_req<T>(&self, key: &Bound<'py, PyString>) -> PyResult<T>
+ where
+ T: for<'a> FromPyObject<'a, 'py>,
+ for<'a> <T as FromPyObject<'a, 'py>>::Error: Into<PyErr>,
+ {
+ match self.get_item(key)? {
+ Some(t) => t.extract().map_err(Into::into),
+ None => py_err!(PyKeyError; "{}", key),
+ }
+ }
+}
+/*
impl<'py> SchemaDict<'py> for Option<&Bound<'py, PyDict>> {
fn get_as<T>(&self, key: &Bound<'py, PyString>) -> PyResult<Option<T>>
where
@@ -65,6 +106,33 @@ impl<'py> SchemaDict<'py> for Option<&Bound<'py, PyDict>> {
}
}
}
+*/
+
+impl<'py> SchemaDict<'py> for Option<&Bound<'py, PyDict>> {
+ fn get_as<T>(&self, key: &Bound<'py, PyString>) -> PyResult<Option<T>>
+ where
+ T: for<'a> FromPyObject<'a, 'py>,
+ for<'a> <T as FromPyObject<'a, 'py>>::Error: Into<PyErr>,
+ {
+ match self {
+ Some(d) => d.get_as(key),
+ None => Ok(None),
+ }
+ }
+
+ #[cfg_attr(has_coverage_attribute, coverage(off))]
+ fn get_as_req<T>(&self, key: &Bound<'py, PyString>) -> PyResult<T>
+ where
+ T: for<'a> FromPyObject<'a, 'py>,
+ for<'a> <T as FromPyObject<'a, 'py>>::Error: Into<PyErr>,
+ {
+ match self {
+ Some(d) => d.get_as_req(key),
+ None => py_err!(PyKeyError; "{}", key),
+ }
+ }
+}
+
macro_rules! py_error_type {
($error_type:ty; $msg:expr) => {
diff --git a/src/url.rs b/src/url.rs
index 6963e65..ae1e885 100644
--- a/src/url.rs
+++ b/src/url.rs
@@ -602,6 +602,7 @@ impl UrlHostParts {
}
}
+/*
impl FromPyObject<'_> for UrlHostParts {
fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
let py = ob.py();
@@ -614,6 +615,28 @@ impl FromPyObject<'_> for UrlHostParts {
})
}
}
+*/
+
+use pyo3::Borrowed;
+
+impl<'py> FromPyObject<'_, 'py> for UrlHostParts {
+ type Error = PyErr;
+
+ fn extract(ob: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {
+ let ob: &Bound<'py, PyAny> = &*ob;
+
+ let py = ob.py();
+ let dict = ob.downcast::<PyDict>()?;
+
+ Ok(UrlHostParts {
+ username: dict.get_as(intern!(py, "username"))?,
+ password: dict.get_as(intern!(py, "password"))?,
+ host: dict.get_as(intern!(py, "host"))?,
+ port: dict.get_as(intern!(py, "port"))?,
+ })
+ }
+}
+
fn host_to_dict<'a>(py: Python<'a>, lib_url: &Url) -> PyResult<Bound<'a, PyDict>> {
let dict = PyDict::new(py);
diff --git a/src/validators/uuid.rs b/src/validators/uuid.rs
index 25b80c5..9fc67a8 100644
--- a/src/validators/uuid.rs
+++ b/src/validators/uuid.rs
@@ -28,7 +28,7 @@ const UUID_IS_SAFE: &str = "is_safe";
static UUID_TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
fn import_type(py: Python, module: &str, attr: &str) -> PyResult<Py<PyType>> {
- py.import(module)?.getattr(attr)?.extract()
+ py.import(module)?.getattr(attr)?.extract().map_err(Into::into)
}
fn get_uuid_type(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {