use crate::conversion::IntoPyObject;
use crate::ffi_ptr_ext::FfiPtrExt;
#[cfg(feature = "experimental-inspect")]
use crate::inspect::PyStaticExpr;
use crate::instance::Bound;
#[cfg(Py_3_11)]
use crate::intern;
use crate::panic::PanicException;
use crate::py_result_ext::PyResultExt;
use crate::type_object::PyTypeInfo;
use crate::types::any::PyAnyMethods;
#[cfg(Py_3_11)]
use crate::types::PyString;
use crate::types::{
string::PyStringMethods, traceback::PyTracebackMethods, typeobject::PyTypeMethods, PyTraceback,
PyType,
};
use crate::{exceptions::PyBaseException, ffi};
use crate::{BoundObject, Py, PyAny, Python};
use err_state::{PyErrState, PyErrStateLazyFnOutput, PyErrStateNormalized};
use std::convert::Infallible;
use std::ffi::CStr;
mod cast_error;
mod downcast_error;
mod err_state;
mod impls;
pub use cast_error::{CastError, CastIntoError};
#[allow(deprecated)]
pub use downcast_error::{DowncastError, DowncastIntoError};
pub struct PyErr {
state: PyErrState,
}
#[cfg(feature = "nightly")]
unsafe impl crate::marker::Ungil for PyErr {}
pub type PyResult<T> = Result<T, PyErr>;
pub trait PyErrArguments: Send + Sync {
fn arguments(self, py: Python<'_>) -> Py<PyAny>;
}
impl<T> PyErrArguments for T
where
T: for<'py> IntoPyObject<'py> + Send + Sync,
{
fn arguments(self, py: Python<'_>) -> Py<PyAny> {
match self.into_pyobject(py) {
Ok(obj) => obj.into_any().unbind(),
Err(e) => panic!("Converting PyErr arguments failed: {}", e.into()),
}
}
}
impl PyErr {
#[inline]
pub fn new<T, A>(args: A) -> PyErr
where
T: PyTypeInfo,
A: PyErrArguments + Send + Sync + 'static,
{
PyErr::from_state(PyErrState::lazy(Box::new(move |py| {
PyErrStateLazyFnOutput {
ptype: T::type_object(py).into(),
pvalue: args.arguments(py),
}
})))
}
pub fn from_type<A>(ty: Bound<'_, PyType>, args: A) -> PyErr
where
A: PyErrArguments + Send + Sync + 'static,
{
PyErr::from_state(PyErrState::lazy_arguments(ty.unbind().into_any(), args))
}
pub fn from_value(obj: Bound<'_, PyAny>) -> PyErr {
let state = match obj.cast_into::<PyBaseException>() {
Ok(obj) => PyErrState::normalized(PyErrStateNormalized::new(obj)),
Err(err) => {
let obj = err.into_inner();
let py = obj.py();
PyErrState::lazy_arguments(obj.unbind(), py.None())
}
};
PyErr::from_state(state)
}
pub fn get_type<'py>(&self, py: Python<'py>) -> Bound<'py, PyType> {
self.normalized(py).ptype(py)
}
pub fn value<'py>(&self, py: Python<'py>) -> &Bound<'py, PyBaseException> {
self.normalized(py).pvalue.bind(py)
}
pub fn into_value(self, py: Python<'_>) -> Py<PyBaseException> {
let normalized = self.normalized(py);
let exc = normalized.pvalue.clone_ref(py);
if let Some(tb) = normalized.ptraceback(py) {
unsafe {
ffi::PyException_SetTraceback(exc.as_ptr(), tb.as_ptr());
}
}
exc
}
pub fn traceback<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyTraceback>> {
self.normalized(py).ptraceback(py)
}
#[inline]
pub fn occurred(_: Python<'_>) -> bool {
unsafe { !ffi::PyErr_Occurred().is_null() }
}
pub fn take(py: Python<'_>) -> Option<PyErr> {
let state = PyErrStateNormalized::take(py)?;
if PanicException::is_exact_type_of(state.pvalue.bind(py)) {
Self::print_panic_and_unwind(py, state)
}
Some(PyErr::from_state(PyErrState::normalized(state)))
}
#[cold]
fn print_panic_and_unwind(py: Python<'_>, state: PyErrStateNormalized) -> ! {
let msg: String = state
.pvalue
.bind(py)
.str()
.map(|py_str| py_str.to_string_lossy().into())
.unwrap_or_else(|_| String::from("Unwrapped panic from Python code"));
eprintln!("--- PyO3 is resuming a panic after fetching a PanicException from Python. ---");
eprintln!("Python stack trace below:");
PyErrState::normalized(state).restore(py);
unsafe {
ffi::PyErr_PrintEx(0);
}
std::panic::resume_unwind(Box::new(msg))
}
#[cfg_attr(debug_assertions, track_caller)]
#[inline]
pub fn fetch(py: Python<'_>) -> PyErr {
PyErr::take(py).unwrap_or_else(failed_to_fetch)
}
pub fn new_type<'py>(
py: Python<'py>,
name: &CStr,
doc: Option<&CStr>,
base: Option<&Bound<'py, PyType>>,
dict: Option<Py<PyAny>>,
) -> PyResult<Py<PyType>> {
let base: *mut ffi::PyObject = match base {
None => std::ptr::null_mut(),
Some(obj) => obj.as_ptr(),
};
let dict: *mut ffi::PyObject = match dict {
None => std::ptr::null_mut(),
Some(obj) => obj.as_ptr(),
};
let doc_ptr = match doc.as_ref() {
Some(c) => c.as_ptr(),
None => std::ptr::null(),
};
unsafe {
ffi::PyErr_NewExceptionWithDoc(name.as_ptr(), doc_ptr, base, dict)
.assume_owned_or_err(py)
.cast_into_unchecked()
}
.map(Bound::unbind)
}
pub fn display(&self, py: Python<'_>) {
#[cfg(Py_3_12)]
unsafe {
ffi::PyErr_DisplayException(self.value(py).as_ptr())
}
#[cfg(not(Py_3_12))]
unsafe {
let traceback = self.traceback(py);
let type_bound = self.get_type(py);
ffi::PyErr_Display(
type_bound.as_ptr(),
self.value(py).as_ptr(),
traceback
.as_ref()
.map_or(std::ptr::null_mut(), |traceback| traceback.as_ptr()),
)
}
}
pub fn print(&self, py: Python<'_>) {
self.clone_ref(py).restore(py);
unsafe { ffi::PyErr_PrintEx(0) }
}
pub fn print_and_set_sys_last_vars(&self, py: Python<'_>) {
self.clone_ref(py).restore(py);
unsafe { ffi::PyErr_PrintEx(1) }
}
pub fn matches<'py, T>(&self, py: Python<'py>, exc: T) -> Result<bool, T::Error>
where
T: IntoPyObject<'py>,
{
Ok(self.is_instance(py, &exc.into_pyobject(py)?.into_any().as_borrowed()))
}
#[inline]
pub fn is_instance(&self, py: Python<'_>, ty: &Bound<'_, PyAny>) -> bool {
let type_bound = self.get_type(py);
(unsafe { ffi::PyErr_GivenExceptionMatches(type_bound.as_ptr(), ty.as_ptr()) }) != 0
}
#[inline]
pub fn is_instance_of<T>(&self, py: Python<'_>) -> bool
where
T: PyTypeInfo,
{
self.is_instance(py, &T::type_object(py))
}
#[inline]
pub fn restore(self, py: Python<'_>) {
self.state.restore(py)
}
#[inline]
pub fn write_unraisable(self, py: Python<'_>, obj: Option<&Bound<'_, PyAny>>) {
self.restore(py);
unsafe { ffi::PyErr_WriteUnraisable(obj.map_or(std::ptr::null_mut(), Bound::as_ptr)) }
}
pub fn warn<'py>(
py: Python<'py>,
category: &Bound<'py, PyAny>,
message: &CStr,
stacklevel: i32,
) -> PyResult<()> {
error_on_minusone(py, unsafe {
ffi::PyErr_WarnEx(
category.as_ptr(),
message.as_ptr(),
stacklevel as ffi::Py_ssize_t,
)
})
}
pub fn warn_explicit<'py>(
py: Python<'py>,
category: &Bound<'py, PyAny>,
message: &CStr,
filename: &CStr,
lineno: i32,
module: Option<&CStr>,
registry: Option<&Bound<'py, PyAny>>,
) -> PyResult<()> {
let module_ptr = match module {
None => std::ptr::null_mut(),
Some(s) => s.as_ptr(),
};
let registry: *mut ffi::PyObject = match registry {
None => std::ptr::null_mut(),
Some(obj) => obj.as_ptr(),
};
error_on_minusone(py, unsafe {
ffi::PyErr_WarnExplicit(
category.as_ptr(),
message.as_ptr(),
filename.as_ptr(),
lineno,
module_ptr,
registry,
)
})
}
#[inline]
pub fn clone_ref(&self, py: Python<'_>) -> PyErr {
PyErr::from_state(PyErrState::normalized(self.normalized(py).clone_ref(py)))
}
pub fn cause(&self, py: Python<'_>) -> Option<PyErr> {
use crate::ffi_ptr_ext::FfiPtrExt;
let obj =
unsafe { ffi::PyException_GetCause(self.value(py).as_ptr()).assume_owned_or_opt(py) };
#[cfg(GraalPy)]
if let Some(cause) = &obj {
if cause.is_none() {
return None;
}
}
obj.map(Self::from_value)
}
pub fn set_cause(&self, py: Python<'_>, cause: Option<Self>) {
let value = self.value(py);
let cause = cause.map(|err| err.into_value(py));
unsafe {
ffi::PyException_SetCause(
value.as_ptr(),
cause.map_or(std::ptr::null_mut(), Py::into_ptr),
);
}
}
#[cfg(Py_3_11)]
pub fn add_note<N: for<'py> IntoPyObject<'py, Target = PyString>>(
&self,
py: Python<'_>,
note: N,
) -> PyResult<()> {
self.value(py)
.call_method1(intern!(py, "add_note"), (note,))?;
Ok(())
}
#[inline]
fn from_state(state: PyErrState) -> PyErr {
PyErr { state }
}
#[inline]
fn normalized(&self, py: Python<'_>) -> &PyErrStateNormalized {
self.state.as_normalized(py)
}
}
#[cold]
#[cfg_attr(debug_assertions, track_caller)]
fn failed_to_fetch() -> PyErr {
const FAILED_TO_FETCH: &str = "attempted to fetch exception but none was set";
if cfg!(debug_assertions) {
panic!("{}", FAILED_TO_FETCH)
} else {
crate::exceptions::PySystemError::new_err(FAILED_TO_FETCH)
}
}
impl std::fmt::Debug for PyErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
Python::attach(|py| {
f.debug_struct("PyErr")
.field("type", &self.get_type(py))
.field("value", self.value(py))
.field(
"traceback",
&self.traceback(py).map(|tb| match tb.format() {
Ok(s) => s,
Err(err) => {
err.write_unraisable(py, Some(&tb));
format!("<unformattable {tb:?}>")
}
}),
)
.finish()
})
}
}
impl std::fmt::Display for PyErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Python::attach(|py| {
let value = self.value(py);
let type_name = value.get_type().qualname().map_err(|_| std::fmt::Error)?;
write!(f, "{type_name}")?;
if let Ok(s) = value.str() {
write!(f, ": {}", &s.to_string_lossy())
} else {
write!(f, ": <exception str() failed>")
}
})
}
}
impl std::error::Error for PyErr {}
impl<'py> IntoPyObject<'py> for PyErr {
type Target = PyBaseException;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = PyBaseException::TYPE_HINT;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
Ok(self.into_value(py).into_bound(py))
}
}
impl<'py> IntoPyObject<'py> for &PyErr {
type Target = PyBaseException;
type Output = Bound<'py, Self::Target>;
type Error = Infallible;
#[cfg(feature = "experimental-inspect")]
const OUTPUT_TYPE: PyStaticExpr = PyErr::OUTPUT_TYPE;
#[inline]
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
self.clone_ref(py).into_pyobject(py)
}
}
pub trait ToPyErr {}
impl<'py, T> std::convert::From<Bound<'py, T>> for PyErr
where
T: ToPyErr,
{
#[inline]
fn from(err: Bound<'py, T>) -> PyErr {
PyErr::from_value(err.into_any())
}
}
#[inline]
pub(crate) fn error_on_minusone<T: SignedInteger>(py: Python<'_>, result: T) -> PyResult<()> {
if result != T::MINUS_ONE {
Ok(())
} else {
Err(PyErr::fetch(py))
}
}
pub(crate) trait SignedInteger: Eq {
const MINUS_ONE: Self;
}
macro_rules! impl_signed_integer {
($t:ty) => {
impl SignedInteger for $t {
const MINUS_ONE: Self = -1;
}
};
}
impl_signed_integer!(i8);
impl_signed_integer!(i16);
impl_signed_integer!(i32);
impl_signed_integer!(i64);
impl_signed_integer!(i128);
impl_signed_integer!(isize);
#[cfg(test)]
mod tests {
use super::PyErrState;
use crate::exceptions::{self, PyTypeError, PyValueError};
use crate::impl_::pyclass::{value_of, IsSend, IsSync};
use crate::test_utils::assert_warnings;
use crate::{PyErr, PyTypeInfo, Python};
#[test]
fn no_error() {
assert!(Python::attach(PyErr::take).is_none());
}
#[test]
fn set_valueerror() {
Python::attach(|py| {
let err: PyErr = exceptions::PyValueError::new_err("some exception message");
assert!(err.is_instance_of::<exceptions::PyValueError>(py));
err.restore(py);
assert!(PyErr::occurred(py));
let err = PyErr::fetch(py);
assert!(err.is_instance_of::<exceptions::PyValueError>(py));
assert_eq!(err.to_string(), "ValueError: some exception message");
})
}
#[test]
fn invalid_error_type() {
Python::attach(|py| {
let err: PyErr = PyErr::new::<crate::types::PyString, _>(());
assert!(err.is_instance_of::<exceptions::PyTypeError>(py));
err.restore(py);
let err = PyErr::fetch(py);
assert!(err.is_instance_of::<exceptions::PyTypeError>(py));
assert_eq!(
err.to_string(),
"TypeError: exceptions must derive from BaseException"
);
})
}
#[test]
fn set_typeerror() {
Python::attach(|py| {
let err: PyErr = exceptions::PyTypeError::new_err(());
err.restore(py);
assert!(PyErr::occurred(py));
drop(PyErr::fetch(py));
});
}
#[test]
#[should_panic(expected = "new panic")]
fn fetching_panic_exception_resumes_unwind() {
use crate::panic::PanicException;
Python::attach(|py| {
let err: PyErr = PanicException::new_err("new panic");
err.restore(py);
assert!(PyErr::occurred(py));
let _ = PyErr::fetch(py);
});
}
#[test]
#[should_panic(expected = "new panic")]
#[cfg(not(Py_3_12))]
fn fetching_normalized_panic_exception_resumes_unwind() {
use crate::panic::PanicException;
Python::attach(|py| {
let err: PyErr = PanicException::new_err("new panic");
let _ = err.normalized(py);
err.restore(py);
assert!(PyErr::occurred(py));
let _ = PyErr::fetch(py);
});
}
#[test]
fn err_debug() {
Python::attach(|py| {
let err = py
.run(c"raise Exception('banana')", None, None)
.expect_err("raising should have given us an error");
let debug_str = format!("{err:?}");
assert!(debug_str.starts_with("PyErr { "));
assert!(debug_str.ends_with(" }"));
let mut fields = debug_str["PyErr { ".len()..debug_str.len() - 2].splitn(3, ", ");
assert_eq!(fields.next().unwrap(), "type: <class 'Exception'>");
assert_eq!(fields.next().unwrap(), "value: Exception('banana')");
assert_eq!(
fields.next().unwrap(),
"traceback: Some(\"Traceback (most recent call last):\\n File \\\"<string>\\\", line 1, in <module>\\n\")"
);
assert!(fields.next().is_none());
});
}
#[test]
fn err_display() {
Python::attach(|py| {
let err = py
.run(c"raise Exception('banana')", None, None)
.expect_err("raising should have given us an error");
assert_eq!(err.to_string(), "Exception: banana");
});
}
#[test]
fn test_pyerr_send_sync() {
assert!(value_of!(IsSend, PyErr));
assert!(value_of!(IsSync, PyErr));
assert!(value_of!(IsSend, PyErrState));
assert!(value_of!(IsSync, PyErrState));
}
#[test]
fn test_pyerr_matches() {
Python::attach(|py| {
let err = PyErr::new::<PyValueError, _>("foo");
assert!(err.matches(py, PyValueError::type_object(py)).unwrap());
assert!(err
.matches(
py,
(PyValueError::type_object(py), PyTypeError::type_object(py))
)
.unwrap());
assert!(!err.matches(py, PyTypeError::type_object(py)).unwrap());
let err: PyErr = PyErr::from_type(crate::types::PyString::type_object(py), "foo");
assert!(err.matches(py, PyTypeError::type_object(py)).unwrap());
})
}
#[test]
fn test_pyerr_cause() {
Python::attach(|py| {
let err = py
.run(c"raise Exception('banana')", None, None)
.expect_err("raising should have given us an error");
assert!(err.cause(py).is_none());
let err = py
.run(
c"raise Exception('banana') from Exception('apple')",
None,
None,
)
.expect_err("raising should have given us an error");
let cause = err
.cause(py)
.expect("raising from should have given us a cause");
assert_eq!(cause.to_string(), "Exception: apple");
err.set_cause(py, None);
assert!(err.cause(py).is_none());
let new_cause = exceptions::PyValueError::new_err("orange");
err.set_cause(py, Some(new_cause));
let cause = err
.cause(py)
.expect("set_cause should have given us a cause");
assert_eq!(cause.to_string(), "ValueError: orange");
});
}
#[test]
fn warnings() {
use crate::types::any::PyAnyMethods;
Python::attach(|py| {
let cls = py.get_type::<exceptions::PyUserWarning>();
let warnings = py.import("warnings").unwrap();
warnings.call_method0("resetwarnings").unwrap();
assert_warnings!(
py,
{ PyErr::warn(py, &cls, c"I am warning you", 0).unwrap() },
[(exceptions::PyUserWarning, "I am warning you")]
);
warnings
.call_method1("simplefilter", ("error", &cls))
.unwrap();
PyErr::warn(py, &cls, c"I am warning you", 0).unwrap_err();
warnings.call_method0("resetwarnings").unwrap();
warnings
.call_method1("filterwarnings", ("error", "", &cls, "pyo3test"))
.unwrap();
assert_warnings!(
py,
{ PyErr::warn(py, &cls, c"I am warning you", 0).unwrap() },
[(exceptions::PyUserWarning, "I am warning you")]
);
let err = PyErr::warn_explicit(
py,
&cls,
c"I am warning you",
c"pyo3test.py",
427,
None,
None,
)
.unwrap_err();
assert!(err
.value(py)
.getattr("args")
.unwrap()
.get_item(0)
.unwrap()
.eq("I am warning you")
.unwrap());
warnings.call_method0("resetwarnings").unwrap();
});
}
#[test]
#[cfg(Py_3_11)]
fn test_add_note() {
use crate::types::any::PyAnyMethods;
Python::attach(|py| {
let err = PyErr::new::<exceptions::PyValueError, _>("original error");
err.add_note(py, "additional context").unwrap();
let notes = err.value(py).getattr("__notes__").unwrap();
assert_eq!(notes.len().unwrap(), 1);
assert_eq!(
notes.get_item(0).unwrap().extract::<String>().unwrap(),
"additional context"
);
});
}
}