use crate::instance::Bound;
use crate::panic::PanicException;
use crate::type_object::PyTypeInfo;
use crate::types::any::PyAnyMethods;
use crate::types::{string::PyStringMethods, typeobject::PyTypeMethods, PyTraceback, PyType};
#[cfg(feature = "gil-refs")]
use crate::PyNativeType;
use crate::{
exceptions::{self, PyBaseException},
ffi,
};
use crate::{Borrowed, IntoPy, Py, PyAny, PyObject, Python, ToPyObject};
use std::borrow::Cow;
use std::cell::UnsafeCell;
use std::ffi::CString;
mod err_state;
mod impls;
pub use err_state::PyErrArguments;
use err_state::{PyErrState, PyErrStateLazyFnOutput, PyErrStateNormalized};
pub struct PyErr {
state: UnsafeCell<Option<PyErrState>>,
}
#[cfg(feature = "nightly")]
unsafe impl crate::marker::Ungil for PyErr {}
unsafe impl Send for PyErr {}
unsafe impl Sync for PyErr {}
pub type PyResult<T> = Result<T, PyErr>;
#[derive(Debug)]
#[cfg(feature = "gil-refs")]
pub struct PyDowncastError<'a> {
from: &'a PyAny,
to: Cow<'static, str>,
}
#[cfg(feature = "gil-refs")]
impl<'a> PyDowncastError<'a> {
pub fn new(from: &'a PyAny, to: impl Into<Cow<'static, str>>) -> Self {
PyDowncastError {
from,
to: to.into(),
}
}
pub(crate) fn from_downcast_err(DowncastError { from, to }: DowncastError<'a, 'a>) -> Self {
#[allow(deprecated)]
let from = unsafe { from.py().from_borrowed_ptr(from.as_ptr()) };
Self { from, to }
}
}
#[derive(Debug)]
pub struct DowncastError<'a, 'py> {
from: Borrowed<'a, 'py, PyAny>,
to: Cow<'static, str>,
}
impl<'a, 'py> DowncastError<'a, 'py> {
pub fn new(from: &'a Bound<'py, PyAny>, to: impl Into<Cow<'static, str>>) -> Self {
DowncastError {
from: from.as_borrowed(),
to: to.into(),
}
}
#[cfg(not(feature = "gil-refs"))]
pub(crate) fn new_from_borrowed(
from: Borrowed<'a, 'py, PyAny>,
to: impl Into<Cow<'static, str>>,
) -> Self {
DowncastError {
from,
to: to.into(),
}
}
}
#[derive(Debug)]
pub struct DowncastIntoError<'py> {
from: Bound<'py, PyAny>,
to: Cow<'static, str>,
}
impl<'py> DowncastIntoError<'py> {
pub fn new(from: Bound<'py, PyAny>, to: impl Into<Cow<'static, str>>) -> Self {
DowncastIntoError {
from,
to: to.into(),
}
}
pub fn into_inner(self) -> Bound<'py, PyAny> {
self.from
}
}
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_bound(py).into(),
pvalue: args.arguments(py),
}
})))
}
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`PyErr::from_type` will be replaced by `PyErr::from_type_bound` in a future PyO3 version"
)]
pub fn from_type<A>(ty: &PyType, args: A) -> PyErr
where
A: PyErrArguments + Send + Sync + 'static,
{
PyErr::from_state(PyErrState::lazy(ty.into(), args))
}
pub fn from_type_bound<A>(ty: Bound<'_, PyType>, args: A) -> PyErr
where
A: PyErrArguments + Send + Sync + 'static,
{
PyErr::from_state(PyErrState::lazy(ty.unbind().into_any(), args))
}
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`PyErr::from_value` will be replaced by `PyErr::from_value_bound` in a future PyO3 version"
)]
pub fn from_value(obj: &PyAny) -> PyErr {
PyErr::from_value_bound(obj.as_borrowed().to_owned())
}
pub fn from_value_bound(obj: Bound<'_, PyAny>) -> PyErr {
let state = match obj.downcast_into::<PyBaseException>() {
Ok(obj) => PyErrState::normalized(obj),
Err(err) => {
let obj = err.into_inner();
let py = obj.py();
PyErrState::lazy(obj.into_py(py), py.None())
}
};
PyErr::from_state(state)
}
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`PyErr::get_type` will be replaced by `PyErr::get_type_bound` in a future PyO3 version"
)]
pub fn get_type<'py>(&'py self, py: Python<'py>) -> &'py PyType {
self.get_type_bound(py).into_gil_ref()
}
pub fn get_type_bound<'py>(&self, py: Python<'py>) -> Bound<'py, PyType> {
self.normalized(py).ptype(py)
}
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`PyErr::value` will be replaced by `PyErr::value_bound` in a future PyO3 version"
)]
pub fn value<'py>(&'py self, py: Python<'py>) -> &'py PyBaseException {
self.value_bound(py).as_gil_ref()
}
pub fn value_bound<'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
}
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`PyErr::traceback` will be replaced by `PyErr::traceback_bound` in a future PyO3 version"
)]
pub fn traceback<'py>(&'py self, py: Python<'py>) -> Option<&'py PyTraceback> {
self.normalized(py).ptraceback(py).map(|b| b.into_gil_ref())
}
pub fn traceback_bound<'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> {
Self::_take(py)
}
#[cfg(not(Py_3_12))]
fn _take(py: Python<'_>) -> Option<PyErr> {
let (ptype, pvalue, ptraceback) = unsafe {
let mut ptype: *mut ffi::PyObject = std::ptr::null_mut();
let mut pvalue: *mut ffi::PyObject = std::ptr::null_mut();
let mut ptraceback: *mut ffi::PyObject = std::ptr::null_mut();
ffi::PyErr_Fetch(&mut ptype, &mut pvalue, &mut ptraceback);
let ptype = PyObject::from_owned_ptr_or_opt(py, ptype);
let pvalue = PyObject::from_owned_ptr_or_opt(py, pvalue);
let ptraceback = PyObject::from_owned_ptr_or_opt(py, ptraceback);
let ptype = match ptype {
Some(ptype) => ptype,
None => {
debug_assert!(
pvalue.is_none(),
"Exception type was null but value was not null"
);
debug_assert!(
ptraceback.is_none(),
"Exception type was null but traceback was not null"
);
return None;
}
};
(ptype, pvalue, ptraceback)
};
if ptype.as_ptr() == PanicException::type_object_raw(py).cast() {
let msg = pvalue
.as_ref()
.and_then(|obj| obj.bind(py).str().ok())
.map(|py_str| py_str.to_string_lossy().into())
.unwrap_or_else(|| String::from("Unwrapped panic from Python code"));
let state = PyErrState::FfiTuple {
ptype,
pvalue,
ptraceback,
};
Self::print_panic_and_unwind(py, state, msg)
}
Some(PyErr::from_state(PyErrState::FfiTuple {
ptype,
pvalue,
ptraceback,
}))
}
#[cfg(Py_3_12)]
fn _take(py: Python<'_>) -> Option<PyErr> {
let state = PyErrStateNormalized::take(py)?;
let pvalue = state.pvalue.bind(py);
if pvalue.get_type().as_ptr() == PanicException::type_object_raw(py).cast() {
let msg: String = pvalue
.str()
.map(|py_str| py_str.to_string_lossy().into())
.unwrap_or_else(|_| String::from("Unwrapped panic from Python code"));
Self::print_panic_and_unwind(py, PyErrState::Normalized(state), msg)
}
Some(PyErr::from_state(PyErrState::Normalized(state)))
}
fn print_panic_and_unwind(py: Python<'_>, state: PyErrState, msg: String) -> ! {
eprintln!("--- PyO3 is resuming a panic after fetching a PanicException from Python. ---");
eprintln!("Python stack trace below:");
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 {
const FAILED_TO_FETCH: &str = "attempted to fetch exception but none was set";
match PyErr::take(py) {
Some(err) => err,
#[cfg(debug_assertions)]
None => panic!("{}", FAILED_TO_FETCH),
#[cfg(not(debug_assertions))]
None => exceptions::PySystemError::new_err(FAILED_TO_FETCH),
}
}
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`PyErr::new_type` will be replaced by `PyErr::new_type_bound` in a future PyO3 version"
)]
pub fn new_type(
py: Python<'_>,
name: &str,
doc: Option<&str>,
base: Option<&PyType>,
dict: Option<PyObject>,
) -> PyResult<Py<PyType>> {
Self::new_type_bound(
py,
name,
doc,
base.map(PyNativeType::as_borrowed).as_deref(),
dict,
)
}
pub fn new_type_bound<'py>(
py: Python<'py>,
name: &str,
doc: Option<&str>,
base: Option<&Bound<'py, PyType>>,
dict: Option<PyObject>,
) -> 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 null_terminated_name =
CString::new(name).expect("Failed to initialize nul terminated exception name");
let null_terminated_doc =
doc.map(|d| CString::new(d).expect("Failed to initialize nul terminated docstring"));
let null_terminated_doc_ptr = match null_terminated_doc.as_ref() {
Some(c) => c.as_ptr(),
None => std::ptr::null(),
};
let ptr = unsafe {
ffi::PyErr_NewExceptionWithDoc(
null_terminated_name.as_ptr(),
null_terminated_doc_ptr,
base,
dict,
)
};
unsafe { Py::from_owned_ptr_or_err(py, ptr) }
}
pub fn display(&self, py: Python<'_>) {
#[cfg(Py_3_12)]
unsafe {
ffi::PyErr_DisplayException(self.value_bound(py).as_ptr())
}
#[cfg(not(Py_3_12))]
unsafe {
let traceback = self.traceback_bound(py);
let type_bound = self.get_type_bound(py);
ffi::PyErr_Display(
type_bound.as_ptr(),
self.value_bound(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<T>(&self, py: Python<'_>, exc: T) -> bool
where
T: ToPyObject,
{
self.is_instance_bound(py, exc.to_object(py).bind(py))
}
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`PyErr::is_instance` will be replaced by `PyErr::is_instance_bound` in a future PyO3 version"
)]
#[inline]
pub fn is_instance(&self, py: Python<'_>, ty: &PyAny) -> bool {
self.is_instance_bound(py, &ty.as_borrowed())
}
#[inline]
pub fn is_instance_bound(&self, py: Python<'_>, ty: &Bound<'_, PyAny>) -> bool {
let type_bound = self.get_type_bound(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_bound(py, &T::type_object_bound(py))
}
#[inline]
pub fn restore(self, py: Python<'_>) {
self.state
.into_inner()
.expect("PyErr state should never be invalid outside of normalization")
.restore(py)
}
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`PyErr::write_unraisable` will be replaced by `PyErr::write_unraisable_bound` in a future PyO3 version"
)]
#[inline]
pub fn write_unraisable(self, py: Python<'_>, obj: Option<&PyAny>) {
self.write_unraisable_bound(py, obj.map(PyAny::as_borrowed).as_deref())
}
#[inline]
pub fn write_unraisable_bound(self, py: Python<'_>, obj: Option<&Bound<'_, PyAny>>) {
self.restore(py);
unsafe { ffi::PyErr_WriteUnraisable(obj.map_or(std::ptr::null_mut(), Bound::as_ptr)) }
}
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`PyErr::warn` will be replaced by `PyErr::warn_bound` in a future PyO3 version"
)]
pub fn warn(py: Python<'_>, category: &PyAny, message: &str, stacklevel: i32) -> PyResult<()> {
Self::warn_bound(py, &category.as_borrowed(), message, stacklevel)
}
pub fn warn_bound<'py>(
py: Python<'py>,
category: &Bound<'py, PyAny>,
message: &str,
stacklevel: i32,
) -> PyResult<()> {
let message = CString::new(message)?;
error_on_minusone(py, unsafe {
ffi::PyErr_WarnEx(
category.as_ptr(),
message.as_ptr(),
stacklevel as ffi::Py_ssize_t,
)
})
}
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`PyErr::warn_explicit` will be replaced by `PyErr::warn_explicit_bound` in a future PyO3 version"
)]
pub fn warn_explicit(
py: Python<'_>,
category: &PyAny,
message: &str,
filename: &str,
lineno: i32,
module: Option<&str>,
registry: Option<&PyAny>,
) -> PyResult<()> {
Self::warn_explicit_bound(
py,
&category.as_borrowed(),
message,
filename,
lineno,
module,
registry.map(PyNativeType::as_borrowed).as_deref(),
)
}
pub fn warn_explicit_bound<'py>(
py: Python<'py>,
category: &Bound<'py, PyAny>,
message: &str,
filename: &str,
lineno: i32,
module: Option<&str>,
registry: Option<&Bound<'py, PyAny>>,
) -> PyResult<()> {
let message = CString::new(message)?;
let filename = CString::new(filename)?;
let module = module.map(CString::new).transpose()?;
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_bound(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_bound)
}
pub fn set_cause(&self, py: Python<'_>, cause: Option<Self>) {
let value = self.value_bound(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),
);
}
}
#[inline]
fn from_state(state: PyErrState) -> PyErr {
PyErr {
state: UnsafeCell::new(Some(state)),
}
}
#[inline]
fn normalized(&self, py: Python<'_>) -> &PyErrStateNormalized {
if let Some(PyErrState::Normalized(n)) = unsafe {
&*self.state.get()
} {
return n;
}
self.make_normalized(py)
}
#[cold]
fn make_normalized(&self, py: Python<'_>) -> &PyErrStateNormalized {
let state = unsafe {
(*self.state.get())
.take()
.expect("Cannot normalize a PyErr while already normalizing it.")
};
unsafe {
let self_state = &mut *self.state.get();
*self_state = Some(PyErrState::Normalized(state.normalize(py)));
match self_state {
Some(PyErrState::Normalized(n)) => n,
_ => unreachable!(),
}
}
}
}
impl std::fmt::Debug for PyErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
Python::with_gil(|py| {
f.debug_struct("PyErr")
.field("type", &self.get_type_bound(py))
.field("value", self.value_bound(py))
.field("traceback", &self.traceback_bound(py))
.finish()
})
}
}
impl std::fmt::Display for PyErr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Python::with_gil(|py| {
let value = self.value_bound(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 IntoPy<PyObject> for PyErr {
fn into_py(self, py: Python<'_>) -> PyObject {
self.into_value(py).into()
}
}
impl ToPyObject for PyErr {
fn to_object(&self, py: Python<'_>) -> PyObject {
self.clone_ref(py).into_py(py)
}
}
impl<'a> IntoPy<PyObject> for &'a PyErr {
fn into_py(self, py: Python<'_>) -> PyObject {
self.clone_ref(py).into_py(py)
}
}
struct PyDowncastErrorArguments {
from: Py<PyType>,
to: Cow<'static, str>,
}
impl PyErrArguments for PyDowncastErrorArguments {
fn arguments(self, py: Python<'_>) -> PyObject {
const FAILED_TO_EXTRACT: Cow<'_, str> = Cow::Borrowed("<failed to extract type name>");
let from = self.from.bind(py).qualname();
let from = match &from {
Ok(qn) => qn.to_cow().unwrap_or(FAILED_TO_EXTRACT),
Err(_) => FAILED_TO_EXTRACT,
};
format!("'{}' object cannot be converted to '{}'", from, self.to).to_object(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_bound(err.into_any())
}
}
#[cfg(feature = "gil-refs")]
impl<'a> std::convert::From<PyDowncastError<'a>> for PyErr {
fn from(err: PyDowncastError<'_>) -> PyErr {
let args = PyDowncastErrorArguments {
from: err.from.get_type().into(),
to: err.to,
};
exceptions::PyTypeError::new_err(args)
}
}
#[cfg(feature = "gil-refs")]
impl<'a> std::error::Error for PyDowncastError<'a> {}
#[cfg(feature = "gil-refs")]
impl<'a> std::fmt::Display for PyDowncastError<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
display_downcast_error(f, &self.from.as_borrowed(), &self.to)
}
}
impl std::convert::From<DowncastError<'_, '_>> for PyErr {
fn from(err: DowncastError<'_, '_>) -> PyErr {
let args = PyDowncastErrorArguments {
from: err.from.get_type().into(),
to: err.to,
};
exceptions::PyTypeError::new_err(args)
}
}
impl std::error::Error for DowncastError<'_, '_> {}
impl std::fmt::Display for DowncastError<'_, '_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
display_downcast_error(f, &self.from, &self.to)
}
}
impl std::convert::From<DowncastIntoError<'_>> for PyErr {
fn from(err: DowncastIntoError<'_>) -> PyErr {
let args = PyDowncastErrorArguments {
from: err.from.get_type().into(),
to: err.to,
};
exceptions::PyTypeError::new_err(args)
}
}
impl std::error::Error for DowncastIntoError<'_> {}
impl std::fmt::Display for DowncastIntoError<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
display_downcast_error(f, &self.from, &self.to)
}
}
fn display_downcast_error(
f: &mut std::fmt::Formatter<'_>,
from: &Bound<'_, PyAny>,
to: &str,
) -> std::fmt::Result {
write!(
f,
"'{}' object cannot be converted to '{}'",
from.get_type().qualname().map_err(|_| std::fmt::Error)?,
to
)
}
#[track_caller]
pub fn panic_after_error(_py: Python<'_>) -> ! {
unsafe {
ffi::PyErr_Print();
}
panic!("Python API call failed");
}
#[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::{PyErr, PyTypeInfo, Python};
#[test]
fn no_error() {
assert!(Python::with_gil(PyErr::take).is_none());
}
#[test]
fn set_valueerror() {
Python::with_gil(|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::with_gil(|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::with_gil(|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::with_gil(|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::with_gil(|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::with_gil(|py| {
let err = py
.run_bound("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].split(", ");
assert_eq!(fields.next().unwrap(), "type: <class 'Exception'>");
assert_eq!(fields.next().unwrap(), "value: Exception('banana')");
let traceback = fields.next().unwrap();
assert!(traceback.starts_with("traceback: Some(<traceback object at 0x"));
assert!(traceback.ends_with(">)"));
assert!(fields.next().is_none());
});
}
#[test]
fn err_display() {
Python::with_gil(|py| {
let err = py
.run_bound("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() {
fn is_send<T: Send>() {}
fn is_sync<T: Sync>() {}
is_send::<PyErr>();
is_sync::<PyErr>();
is_send::<PyErrState>();
is_sync::<PyErrState>();
}
#[test]
fn test_pyerr_matches() {
Python::with_gil(|py| {
let err = PyErr::new::<PyValueError, _>("foo");
assert!(err.matches(py, PyValueError::type_object_bound(py)));
assert!(err.matches(
py,
(
PyValueError::type_object_bound(py),
PyTypeError::type_object_bound(py)
)
));
assert!(!err.matches(py, PyTypeError::type_object_bound(py)));
let err: PyErr =
PyErr::from_type_bound(crate::types::PyString::type_object_bound(py), "foo");
assert!(err.matches(py, PyTypeError::type_object_bound(py)));
})
}
#[test]
fn test_pyerr_cause() {
Python::with_gil(|py| {
let err = py
.run_bound("raise Exception('banana')", None, None)
.expect_err("raising should have given us an error");
assert!(err.cause(py).is_none());
let err = py
.run_bound(
"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::with_gil(|py| {
let cls = py.get_type_bound::<exceptions::PyUserWarning>();
let warnings = py.import_bound("warnings").unwrap();
warnings.call_method0("resetwarnings").unwrap();
assert_warnings!(
py,
{ PyErr::warn_bound(py, &cls, "I am warning you", 0).unwrap() },
[(exceptions::PyUserWarning, "I am warning you")]
);
warnings
.call_method1("simplefilter", ("error", &cls))
.unwrap();
PyErr::warn_bound(py, &cls, "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_bound(py, &cls, "I am warning you", 0).unwrap() },
[(exceptions::PyUserWarning, "I am warning you")]
);
let err = PyErr::warn_explicit_bound(
py,
&cls,
"I am warning you",
"pyo3test.py",
427,
None,
None,
)
.unwrap_err();
assert!(err
.value_bound(py)
.getattr("args")
.unwrap()
.get_item(0)
.unwrap()
.eq("I am warning you")
.unwrap());
warnings.call_method0("resetwarnings").unwrap();
});
}
}