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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 15 additions & 35 deletions src/argument_markers.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use pyo3::basic::CompareOp;
use pyo3::exceptions::PyNotImplementedError;
use pyo3::prelude::*;
use pyo3::sync::GILOnceCell;
Expand All @@ -13,45 +12,26 @@ pub struct ArgsKwargs {
pub(crate) kwargs: Option<Py<PyDict>>,
}

impl ArgsKwargs {
fn eq(&self, py: Python, other: &Self) -> PyResult<bool> {
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<Bound<'_, PyDict>>) -> 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<bool> {
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),
}
}

Expand Down Expand Up @@ -82,16 +62,16 @@ impl PydanticUndefinedType {
#[staticmethod]
pub fn new(py: Python) -> Py<Self> {
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 {
"PydanticUndefined"
}

fn __copy__(&self, py: Python) -> Py<Self> {
UNDEFINED_CELL.get(py).unwrap().clone()
UNDEFINED_CELL.get(py).unwrap().clone_ref(py)
}

#[pyo3(signature = (_memo, /))]
Expand Down
11 changes: 9 additions & 2 deletions src/build_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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::<PyResult<_>>()
{
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));
Expand Down
10 changes: 9 additions & 1 deletion src/errors/line_error.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::convert::Infallible;

use pyo3::exceptions::PyTypeError;
use pyo3::prelude::*;
use pyo3::DowncastError;
Expand Down Expand Up @@ -61,6 +63,12 @@ impl From<Vec<ValLineError>> for ValError {
}
}

impl From<Infallible> 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)])
Expand Down Expand Up @@ -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>),
Expand Down
2 changes: 1 addition & 1 deletion src/errors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
11 changes: 3 additions & 8 deletions src/errors/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -766,7 +766,7 @@ impl ErrorType {
}
}

#[derive(Clone, Debug)]
#[derive(Clone, Debug, IntoPyObject, IntoPyObjectRef)]
pub enum Number {
Int(i64),
BigInt(BigInt),
Expand Down Expand Up @@ -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()
}
}
80 changes: 45 additions & 35 deletions src/errors/validation_exception.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -57,12 +57,19 @@ impl ValidationError {
) -> PyErr {
match error {
ValError::LineErrors(raw_errors) => {
let line_errors: Vec<PyLineError> = 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) {
Expand Down Expand Up @@ -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:
Expand All @@ -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);
}
Expand All @@ -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:
Expand All @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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()?;
Expand All @@ -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))
}
}
Expand Down Expand Up @@ -418,16 +430,6 @@ pub struct PyLineError {
input_value: PyObject,
}

impl IntoPy<PyLineError> 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<PyLineError> for ValLineError {
/// Used to extract line errors from a validation error for wrap functions
fn from(other: PyLineError) -> ValLineError {
Expand Down Expand Up @@ -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(),
};

Expand All @@ -477,18 +479,26 @@ impl TryFrom<&Bound<'_, PyAny>> for PyLineError {
}

impl PyLineError {
pub fn from_val_line_error(py: Python, error: ValLineError) -> PyResult<Self> {
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<PyObject> {
) -> PyResult<Bound<'py, PyDict>> {
let dict = PyDict::new(py);
dict.set_item("type", self.error_type.type_string())?;
dict.set_item("loc", self.location.to_object(py))?;
Expand All @@ -511,7 +521,7 @@ impl PyLineError {
}
}
}
Ok(dict.into_py(py))
Ok(dict)
}

fn pretty(
Expand Down
Loading
Loading