From 39435c236f011262011fd275b0eac310a0273e4f Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Tue, 17 Dec 2024 15:35:49 +0000 Subject: [PATCH] remove uses of IntoPy (#1578) --- src/argument_markers.rs | 50 ++---- src/build_tools.rs | 11 +- src/errors/line_error.rs | 10 +- src/errors/mod.rs | 2 +- src/errors/types.rs | 11 +- src/errors/validation_exception.rs | 80 +++++---- src/input/datetime.rs | 157 ++++++++++-------- src/input/return_enums.rs | 93 +++-------- src/serializers/computed_fields.rs | 4 +- src/serializers/config.rs | 18 +- src/serializers/fields.rs | 2 +- src/serializers/infer.rs | 79 +++++---- src/serializers/shared.rs | 9 +- src/serializers/type_serializers/bytes.rs | 8 +- src/serializers/type_serializers/complex.rs | 10 +- src/serializers/type_serializers/dataclass.rs | 4 +- .../type_serializers/datetime_etc.rs | 5 +- src/serializers/type_serializers/dict.rs | 5 +- src/serializers/type_serializers/enum_.rs | 2 +- src/serializers/type_serializers/float.rs | 2 +- src/serializers/type_serializers/format.rs | 8 +- src/serializers/type_serializers/function.rs | 36 ++-- src/serializers/type_serializers/generator.rs | 5 +- src/serializers/type_serializers/list.rs | 3 +- src/serializers/type_serializers/nullable.rs | 2 +- .../type_serializers/set_frozenset.rs | 6 +- src/serializers/type_serializers/simple.rs | 4 +- src/serializers/type_serializers/string.rs | 8 +- src/serializers/type_serializers/timedelta.rs | 6 +- src/serializers/type_serializers/tuple.rs | 4 +- src/serializers/type_serializers/url.rs | 5 +- src/serializers/type_serializers/uuid.rs | 6 +- src/url.rs | 20 +-- src/validators/arguments.rs | 2 +- src/validators/bool.rs | 4 +- src/validators/bytes.rs | 5 +- src/validators/complex.rs | 2 +- src/validators/dataclass.rs | 6 +- src/validators/date.rs | 3 +- src/validators/datetime.rs | 11 +- src/validators/float.rs | 5 +- src/validators/frozenset.rs | 4 +- src/validators/function.rs | 4 +- src/validators/generator.rs | 10 +- src/validators/int.rs | 5 +- src/validators/list.rs | 6 +- src/validators/mod.rs | 14 +- src/validators/model.rs | 6 +- src/validators/set.rs | 4 +- src/validators/string.rs | 10 +- src/validators/time.rs | 3 +- src/validators/timedelta.rs | 2 +- src/validators/tuple.rs | 2 +- src/validators/url.rs | 29 ++-- src/validators/with_default.rs | 4 +- 55 files changed, 405 insertions(+), 411 deletions(-) diff --git a/src/argument_markers.rs b/src/argument_markers.rs index 23cd74f41..f22a32c78 100644 --- a/src/argument_markers.rs +++ b/src/argument_markers.rs @@ -1,4 +1,3 @@ -use pyo3::basic::CompareOp; use pyo3::exceptions::PyNotImplementedError; use pyo3::prelude::*; use pyo3::sync::GILOnceCell; @@ -13,45 +12,26 @@ pub struct ArgsKwargs { pub(crate) kwargs: Option>, } -impl ArgsKwargs { - fn eq(&self, py: Python, other: &Self) -> PyResult { - if self.args.bind(py).eq(other.args.bind(py))? { - match (&self.kwargs, &other.kwargs) { - (Some(d1), Some(d2)) => d1.bind(py).eq(d2.bind(py)), - (None, None) => Ok(true), - _ => Ok(false), - } - } else { - Ok(false) - } - } -} - #[pymethods] impl ArgsKwargs { #[new] #[pyo3(signature = (args, kwargs = None))] - fn py_new(py: Python, args: &Bound<'_, PyTuple>, kwargs: Option<&Bound<'_, PyDict>>) -> Self { + fn py_new(args: Bound<'_, PyTuple>, kwargs: Option>) -> Self { Self { - args: args.into_py(py), - kwargs: match kwargs { - Some(d) if !d.is_empty() => Some(d.to_owned().unbind()), - _ => None, - }, + args: args.unbind(), + kwargs: kwargs.filter(|d| !d.is_empty()).map(Bound::unbind), } } - fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> PyObject { - match op { - CompareOp::Eq => match self.eq(py, other) { - Ok(b) => b.into_py(py), - Err(e) => e.into_py(py), - }, - CompareOp::Ne => match self.eq(py, other) { - Ok(b) => (!b).into_py(py), - Err(e) => e.into_py(py), - }, - _ => py.NotImplemented(), + fn __eq__(&self, py: Python, other: &Self) -> PyResult { + if !self.args.bind(py).eq(&other.args)? { + return Ok(false); + } + + match (&self.kwargs, &other.kwargs) { + (Some(d1), Some(d2)) => d1.bind(py).eq(d2), + (None, None) => Ok(true), + _ => Ok(false), } } @@ -82,8 +62,8 @@ impl PydanticUndefinedType { #[staticmethod] pub fn new(py: Python) -> Py { UNDEFINED_CELL - .get_or_init(py, || PydanticUndefinedType {}.into_py(py).extract(py).unwrap()) - .clone() + .get_or_init(py, || Py::new(py, PydanticUndefinedType {}).unwrap()) + .clone_ref(py) } fn __repr__(&self) -> &'static str { @@ -91,7 +71,7 @@ impl PydanticUndefinedType { } fn __copy__(&self, py: Python) -> Py { - UNDEFINED_CELL.get(py).unwrap().clone() + UNDEFINED_CELL.get(py).unwrap().clone_ref(py) } #[pyo3(signature = (_memo, /))] diff --git a/src/build_tools.rs b/src/build_tools.rs index 000ffff34..9ada03e49 100644 --- a/src/build_tools.rs +++ b/src/build_tools.rs @@ -6,7 +6,7 @@ use pyo3::prelude::*; use pyo3::types::{PyDict, PyList, PyString}; use pyo3::{intern, FromPyObject, PyErrArguments}; -use crate::errors::ValError; +use crate::errors::{PyLineError, ValError}; use crate::input::InputType; use crate::tools::SchemaDict; use crate::ValidationError; @@ -85,7 +85,14 @@ impl SchemaError { pub fn from_val_error(py: Python, error: ValError) -> PyErr { match error { ValError::LineErrors(raw_errors) => { - let line_errors = raw_errors.into_iter().map(|e| e.into_py(py)).collect(); + let line_errors = match raw_errors + .into_iter() + .map(|e| PyLineError::from_val_line_error(py, e)) + .collect::>() + { + Ok(errors) => errors, + Err(err) => return err, + }; let validation_error = ValidationError::new(line_errors, "Schema".to_object(py), InputType::Python, false); let schema_error = SchemaError(SchemaErrorEnum::ValidationError(validation_error)); diff --git a/src/errors/line_error.rs b/src/errors/line_error.rs index 03e770cc3..ec8348556 100644 --- a/src/errors/line_error.rs +++ b/src/errors/line_error.rs @@ -1,3 +1,5 @@ +use std::convert::Infallible; + use pyo3::exceptions::PyTypeError; use pyo3::prelude::*; use pyo3::DowncastError; @@ -61,6 +63,12 @@ impl From> for ValError { } } +impl From for ValError { + fn from(infallible: Infallible) -> Self { + match infallible {} + } +} + impl ValError { pub fn new(error_type: ErrorType, input: impl ToErrorValue) -> ValError { Self::LineErrors(vec![ValLineError::new(error_type, input)]) @@ -156,7 +164,7 @@ impl ValLineError { } #[cfg_attr(debug_assertions, derive(Debug))] -#[derive(Clone)] +#[derive(Clone, IntoPyObject)] pub enum InputValue { Python(PyObject), Json(JsonValue<'static>), diff --git a/src/errors/mod.rs b/src/errors/mod.rs index 6cf38b186..c80f402bb 100644 --- a/src/errors/mod.rs +++ b/src/errors/mod.rs @@ -9,7 +9,7 @@ mod value_exception; pub use self::line_error::{InputValue, ToErrorValue, ValError, ValLineError, ValResult}; pub use self::location::LocItem; pub use self::types::{list_all_errors, ErrorType, ErrorTypeDefaults, Number}; -pub use self::validation_exception::ValidationError; +pub use self::validation_exception::{PyLineError, ValidationError}; pub use self::value_exception::{PydanticCustomError, PydanticKnownError, PydanticOmit, PydanticUseDefault}; pub fn py_err_string(py: Python, err: PyErr) -> String { diff --git a/src/errors/types.rs b/src/errors/types.rs index 8e36ec9f5..54d4c9c8f 100644 --- a/src/errors/types.rs +++ b/src/errors/types.rs @@ -3,9 +3,9 @@ use std::borrow::Cow; use std::fmt; use pyo3::exceptions::{PyKeyError, PyTypeError}; -use pyo3::prelude::*; use pyo3::sync::GILOnceCell; use pyo3::types::{PyDict, PyList}; +use pyo3::{prelude::*, IntoPyObjectExt}; use ahash::AHashMap; use num_bigint::BigInt; @@ -766,7 +766,7 @@ impl ErrorType { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, IntoPyObject, IntoPyObjectRef)] pub enum Number { Int(i64), BigInt(BigInt), @@ -826,11 +826,6 @@ impl fmt::Display for Number { } impl ToPyObject for Number { fn to_object(&self, py: Python<'_>) -> PyObject { - match self { - Self::Int(i) => i.into_py(py), - Self::BigInt(i) => i.clone().into_py(py), - Self::Float(f) => f.into_py(py), - Self::String(s) => s.into_py(py), - } + self.into_py_any(py).unwrap() } } diff --git a/src/errors/validation_exception.rs b/src/errors/validation_exception.rs index acbce0d7f..89e082d02 100644 --- a/src/errors/validation_exception.rs +++ b/src/errors/validation_exception.rs @@ -7,7 +7,7 @@ use pyo3::ffi::{self, c_str}; use pyo3::intern; use pyo3::prelude::*; use pyo3::sync::GILOnceCell; -use pyo3::types::{PyDict, PyList, PyString, PyType}; +use pyo3::types::{PyDict, PyList, PyString, PyTuple, PyType}; use serde::ser::{Error, SerializeMap, SerializeSeq}; use serde::{Serialize, Serializer}; @@ -57,12 +57,19 @@ impl ValidationError { ) -> PyErr { match error { ValError::LineErrors(raw_errors) => { - let line_errors: Vec = match outer_location { + let line_errors = match outer_location { Some(outer_location) => raw_errors .into_iter() - .map(|e| e.with_outer_location(outer_location.clone()).into_py(py)) + .map(|e| PyLineError::from_val_line_error(py, e.with_outer_location(outer_location.clone()))) + .collect(), + None => raw_errors + .into_iter() + .map(|e| PyLineError::from_val_line_error(py, e)) .collect(), - None => raw_errors.into_iter().map(|e| e.into_py(py)).collect(), + }; + let line_errors = match line_errors { + Ok(errors) => errors, + Err(err) => return err, }; let validation_error = Self::new(line_errors, title, input_type, hide_input); match Py::new(py, validation_error) { @@ -117,15 +124,17 @@ impl ValidationError { context: _, } = &line_error.error_type { - let note: PyObject = if let Location::Empty = &line_error.location { - "Pydantic: cause of loc: root".into_py(py) + let note = if let Location::Empty = &line_error.location { + PyString::new(py, "Pydantic: cause of loc: root") } else { - format!( - "Pydantic: cause of loc: {}", - // Location formats with a newline at the end, hence the trim() - line_error.location.to_string().trim() + PyString::new( + py, + &format!( + "Pydantic: cause of loc: {}", + // Location formats with a newline at the end, hence the trim() + line_error.location.to_string().trim() + ), ) - .into_py(py) }; // Notes only support 3.11 upwards: @@ -144,7 +153,7 @@ impl ValidationError { { use pyo3::exceptions::PyUserWarning; - let wrapped = PyUserWarning::new_err((note,)); + let wrapped = PyUserWarning::new_err((note.unbind(),)); wrapped.set_cause(py, Some(PyErr::from_value(err.clone_ref(py).into_bound(py)))); user_py_errs.push(wrapped); } @@ -159,7 +168,7 @@ impl ValidationError { #[cfg(Py_3_11)] let cause = { use pyo3::exceptions::PyBaseExceptionGroup; - Some(PyBaseExceptionGroup::new_err((title, user_py_errs)).into_py(py)) + Some(PyBaseExceptionGroup::new_err((title, user_py_errs)).into_value(py)) }; // Pre 3.11 ExceptionGroup support, use the python backport instead: @@ -170,7 +179,7 @@ impl ValidationError { match py.import("exceptiongroup") { Ok(py_mod) => match py_mod.getattr("ExceptionGroup") { Ok(group_cls) => match group_cls.call1((title, user_py_errs)) { - Ok(group_instance) => Some(group_instance.into_py(py)), + Ok(group_instance) => Some(group_instance), Err(_) => None, }, Err(_) => None, @@ -308,10 +317,13 @@ impl ValidationError { return py.None(); } e.as_dict(py, url_prefix, include_context, self.input_type, include_input) - .unwrap_or_else(|err| { - iteration_error = Some(err); - py.None() - }) + .map_or_else( + |err| { + iteration_error = Some(err); + py.None() + }, + Into::into, + ) }), )?; if let Some(err) = iteration_error { @@ -379,7 +391,7 @@ impl ValidationError { self.__repr__(py) } - fn __reduce__<'py>(slf: &Bound<'py, Self>) -> PyResult<(Bound<'py, PyAny>, PyObject)> { + fn __reduce__<'py>(slf: &Bound<'py, Self>) -> PyResult<(Bound<'py, PyAny>, Bound<'py, PyTuple>)> { let py = slf.py(); let callable = slf.getattr("from_exception_data")?; let borrow = slf.try_borrow()?; @@ -389,7 +401,7 @@ impl ValidationError { borrow.input_type.into_pyobject(py)?, borrow.hide_input, ) - .into_py(slf.py()); + .into_pyobject(slf.py())?; Ok((callable, args)) } } @@ -418,16 +430,6 @@ pub struct PyLineError { input_value: PyObject, } -impl IntoPy for ValLineError { - fn into_py(self, py: Python<'_>) -> PyLineError { - PyLineError { - error_type: self.error_type, - location: self.location, - input_value: self.input_value.to_object(py), - } - } -} - impl From for ValLineError { /// Used to extract line errors from a validation error for wrap functions fn from(other: PyLineError) -> ValLineError { @@ -464,7 +466,7 @@ impl TryFrom<&Bound<'_, PyAny>> for PyLineError { let location = Location::try_from(dict.get_item("loc")?.as_ref())?; let input_value = match dict.get_item("input")? { - Some(i) => i.into_py(py), + Some(i) => i.unbind(), None => py.None(), }; @@ -477,18 +479,26 @@ impl TryFrom<&Bound<'_, PyAny>> for PyLineError { } impl PyLineError { + pub fn from_val_line_error(py: Python, error: ValLineError) -> PyResult { + Ok(Self { + error_type: error.error_type, + location: error.location, + input_value: error.input_value.into_pyobject(py)?.unbind(), + }) + } + fn get_error_url(&self, url_prefix: &str) -> String { format!("{url_prefix}{}", self.error_type.type_string()) } - pub fn as_dict( + pub fn as_dict<'py>( &self, - py: Python, + py: Python<'py>, url_prefix: Option<&str>, include_context: bool, input_type: InputType, include_input: bool, - ) -> PyResult { + ) -> PyResult> { let dict = PyDict::new(py); dict.set_item("type", self.error_type.type_string())?; dict.set_item("loc", self.location.to_object(py))?; @@ -511,7 +521,7 @@ impl PyLineError { } } } - Ok(dict.into_py(py)) + Ok(dict) } fn pretty( diff --git a/src/input/datetime.rs b/src/input/datetime.rs index 33a09dcb7..6e4093207 100644 --- a/src/input/datetime.rs +++ b/src/input/datetime.rs @@ -3,7 +3,9 @@ use pyo3::prelude::*; use pyo3::exceptions::PyValueError; use pyo3::pyclass::CompareOp; +use pyo3::types::PyTuple; use pyo3::types::{PyDate, PyDateTime, PyDelta, PyDeltaAccess, PyDict, PyTime, PyTzInfo}; +use pyo3::IntoPyObjectExt; use speedate::MicrosecondsPrecisionOverflowBehavior; use speedate::{Date, DateTime, Duration, ParseError, Time, TimeConfig}; use std::borrow::Cow; @@ -19,9 +21,9 @@ use crate::errors::{ErrorType, ValError, ValResult}; use crate::tools::py_err; #[cfg_attr(debug_assertions, derive(Debug))] -pub enum EitherDate<'a> { +pub enum EitherDate<'py> { Raw(Date), - Py(Bound<'a, PyDate>), + Py(Bound<'py, PyDate>), } impl From for EitherDate<'_> { @@ -30,8 +32,8 @@ impl From for EitherDate<'_> { } } -impl<'a> From> for EitherDate<'a> { - fn from(date: Bound<'a, PyDate>) -> Self { +impl<'py> From> for EitherDate<'py> { + fn from(date: Bound<'py, PyDate>) -> Self { Self::Py(date) } } @@ -45,6 +47,19 @@ pub fn pydate_as_date(py_date: &Bound<'_, PyAny>) -> PyResult { }) } +impl<'py> IntoPyObject<'py> for EitherDate<'py> { + type Target = PyDate; + type Output = Bound<'py, PyDate>; + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> PyResult { + match self { + Self::Raw(date) => PyDate::new(py, date.year.into(), date.month, date.day), + Self::Py(date) => Ok(date), + } + } +} + impl EitherDate<'_> { pub fn as_raw(&self) -> PyResult { match self { @@ -52,20 +67,12 @@ impl EitherDate<'_> { Self::Py(py_date) => pydate_as_date(py_date), } } - - pub fn try_into_py(self, py: Python<'_>) -> PyResult { - let date = match self { - Self::Py(date) => Ok(date), - Self::Raw(date) => PyDate::new(py, date.year.into(), date.month, date.day), - }?; - Ok(date.into_py(py)) - } } #[cfg_attr(debug_assertions, derive(Debug))] -pub enum EitherTime<'a> { +pub enum EitherTime<'py> { Raw(Time), - Py(Bound<'a, PyTime>), + Py(Bound<'py, PyTime>), } impl From