use std::any::Any; use std::panic::{AssertUnwindSafe, catch_unwind}; use pyo3::exceptions::PyValueError; use pyo3::panic::PanicException; use pyo3::prelude::*; use serde::Serialize; use serde::de::DeserializeOwned; pub fn from_py(value: &Bound<'_, PyAny>) -> PyResult where T: DeserializeOwned, { pythonize::depythonize(value).map_err(|error| PyValueError::new_err(error.to_string())) } pub fn from_py_preserving_errors(value: &Bound<'_, PyAny>) -> PyResult where T: DeserializeOwned, { pythonize::depythonize(value).map_err(PyErr::from) } pub fn to_py(py: Python<'_>, value: &T) -> PyResult> where T: Serialize + ?Sized, { pythonize::pythonize(py, value) .map(Bound::unbind) .map_err(|error| PyValueError::new_err(error.to_string())) } pub fn to_py_preserving_errors(py: Python<'_>, value: &T) -> PyResult> where T: Serialize + ?Sized, { pythonize::pythonize(py, value) .map(Bound::unbind) .map_err(PyErr::from) } pub struct Pythonized(pub T); impl<'py, T> IntoPyObject<'py> for Pythonized where T: Serialize, { type Target = PyAny; type Output = Bound<'py, PyAny>; type Error = PyErr; fn into_pyobject(self, py: Python<'py>) -> PyResult { catch_unwind(AssertUnwindSafe(|| pythonize::pythonize(py, &self.0))) .map_err(panic_to_pyerr)? .map_err(|error| PyValueError::new_err(error.to_string())) } } pub fn panic_to_pyerr(payload: Box) -> PyErr { let message = payload .downcast_ref::() .map(String::as_str) .or_else(|| payload.downcast_ref::<&str>().copied()) .unwrap_or("panic from Rust code"); PanicException::new_err(message.to_string()) } #[cfg(test)] mod tests { use serde::Serializer; use super::*; struct PanickingSerializer; impl Serialize for PanickingSerializer { fn serialize(&self, _serializer: S) -> Result where S: Serializer, { panic!("serializer panicked") } } #[test] fn pythonized_converts_on_the_attached_thread() { Python::initialize(); Python::attach(|py| { let value: Vec = Pythonized(vec![1, 2, 3]) .into_pyobject(py) .and_then(|value| value.extract()) .expect("value should convert"); assert_eq!(value, vec![1, 2, 3]); }); } #[test] fn pythonized_maps_serializer_panics_to_a_base_exception() { Python::initialize(); Python::attach(|py| { let error = Pythonized(PanickingSerializer) .into_pyobject(py) .expect_err("serializer panic should become a Python exception"); assert!(error.is_instance_of::(py)); assert_eq!(error.to_string(), "PanicException: serializer panicked"); }); } #[test] fn depythonize_preserves_python_exception_identity_and_traceback() { Python::initialize(); Python::attach(|py| { let locals = pyo3::types::PyDict::new(py); py.run( pyo3::ffi::c_str!( r#" failure = LookupError('conversion failed') cause = ValueError('cause') class Broken: def __index__(self): raise failure from cause value = Broken() "# ), Some(&locals), Some(&locals), ) .unwrap(); let value = locals.get_item("value").unwrap().unwrap(); let legacy_error = from_py::(&value).unwrap_err(); assert!(legacy_error.is_instance_of::(py)); assert!( !legacy_error .value(py) .is(locals.get_item("failure").unwrap().unwrap()) ); let error = from_py_preserving_errors::(&value).unwrap_err(); assert!( error .value(py) .is(locals.get_item("failure").unwrap().unwrap()) ); assert!( error .cause(py) .unwrap() .value(py) .is(locals.get_item("cause").unwrap().unwrap()) ); assert!(error.traceback(py).is_some()); }); } }