implement `PyTracebackMethods`

This commit is contained in:
Icxolu 2024-01-26 22:20:24 +01:00
parent f449fc0fc1
commit f86053e2c2
7 changed files with 101 additions and 25 deletions

View File

@ -479,7 +479,7 @@ class House(object):
}
Err(e) => {
house
.call_method1("__exit__", (e.get_type(py), e.value(py), e.traceback(py)))
.call_method1("__exit__", (e.get_type(py), e.value(py), e.traceback_bound(py)))
.unwrap();
}
}

View File

@ -2,7 +2,7 @@ use crate::{
exceptions::{PyBaseException, PyTypeError},
ffi,
types::{PyTraceback, PyType},
IntoPy, Py, PyAny, PyObject, PyTypeInfo, Python,
Bound, IntoPy, Py, PyAny, PyObject, PyTypeInfo, Python,
};
#[derive(Clone)]
@ -26,15 +26,21 @@ impl PyErrStateNormalized {
}
#[cfg(not(Py_3_12))]
pub(crate) fn ptraceback<'py>(&'py self, py: Python<'py>) -> Option<&'py PyTraceback> {
pub(crate) fn ptraceback<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyTraceback>> {
self.ptraceback
.as_ref()
.map(|traceback| traceback.as_ref(py))
.map(|traceback| traceback.bind(py).clone())
}
#[cfg(Py_3_12)]
pub(crate) fn ptraceback<'py>(&'py self, py: Python<'py>) -> Option<&'py PyTraceback> {
unsafe { py.from_owned_ptr_or_opt(ffi::PyException_GetTraceback(self.pvalue.as_ptr())) }
pub(crate) fn ptraceback<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyTraceback>> {
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::types::any::PyAnyMethods;
unsafe {
ffi::PyException_GetTraceback(self.pvalue.as_ptr())
.assume_owned_or_opt(py)
.map(|b| b.downcast_into_unchecked())
}
}
#[cfg(Py_3_12)]

View File

@ -272,6 +272,18 @@ impl PyErr {
exc
}
/// Deprecated form of [`PyErr::traceback_bound`].
#[cfg_attr(
not(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())
}
/// Returns the traceback of this exception object.
///
/// # Examples
@ -280,10 +292,10 @@ impl PyErr {
///
/// Python::with_gil(|py| {
/// let err = PyTypeError::new_err(("some type error",));
/// assert!(err.traceback(py).is_none());
/// assert!(err.traceback_bound(py).is_none());
/// });
/// ```
pub fn traceback<'py>(&'py self, py: Python<'py>) -> Option<&'py PyTraceback> {
pub fn traceback_bound<'py>(&self, py: Python<'py>) -> Option<Bound<'py, PyTraceback>> {
self.normalized(py).ptraceback(py)
}
@ -476,10 +488,16 @@ impl PyErr {
#[cfg(not(Py_3_12))]
unsafe {
// keep the bound `traceback` alive for entire duration of
// PyErr_Display. if we inline this, the `Bound` will be dropped
// after the argument got evaluated, leading to call with a dangling
// pointer.
let traceback = self.traceback_bound(py);
ffi::PyErr_Display(
self.get_type(py).as_ptr(),
self.value(py).as_ptr(),
self.traceback(py)
traceback
.as_ref()
.map_or(std::ptr::null_mut(), |traceback| traceback.as_ptr()),
)
}
@ -658,15 +676,15 @@ impl PyErr {
///
/// # Examples
/// ```rust
/// use pyo3::{exceptions::PyTypeError, PyErr, Python};
/// use pyo3::{exceptions::PyTypeError, PyErr, Python, prelude::PyAnyMethods};
/// Python::with_gil(|py| {
/// let err: PyErr = PyTypeError::new_err(("some type error",));
/// let err_clone = err.clone_ref(py);
/// assert!(err.get_type(py).is(err_clone.get_type(py)));
/// assert!(err.value(py).is(err_clone.value(py)));
/// match err.traceback(py) {
/// None => assert!(err_clone.traceback(py).is_none()),
/// Some(tb) => assert!(err_clone.traceback(py).unwrap().is(tb)),
/// match err.traceback_bound(py) {
/// None => assert!(err_clone.traceback_bound(py).is_none()),
/// Some(tb) => assert!(err_clone.traceback_bound(py).unwrap().is(&tb)),
/// }
/// });
/// ```
@ -747,7 +765,7 @@ impl std::fmt::Debug for PyErr {
f.debug_struct("PyErr")
.field("type", self.get_type(py))
.field("value", self.value(py))
.field("traceback", &self.traceback(py))
.field("traceback", &self.traceback_bound(py))
.finish()
})
}

View File

@ -100,6 +100,7 @@ macro_rules! import_exception {
impl $name {
fn type_object_raw(py: $crate::Python<'_>) -> *mut $crate::ffi::PyTypeObject {
use $crate::sync::GILOnceCell;
use $crate::prelude::PyTracebackMethods;
static TYPE_OBJECT: GILOnceCell<$crate::Py<$crate::types::PyType>> =
GILOnceCell::new();
@ -109,7 +110,7 @@ macro_rules! import_exception {
.import(stringify!($module))
.unwrap_or_else(|err| {
let traceback = err
.traceback(py)
.traceback_bound(py)
.map(|tb| tb.format().expect("raised exception will have a traceback"))
.unwrap_or_default();
::std::panic!("Can not import module {}: {}\n{}", stringify!($module), err, traceback);

View File

@ -38,4 +38,5 @@ pub use crate::types::module::PyModuleMethods;
pub use crate::types::sequence::PySequenceMethods;
pub use crate::types::set::PySetMethods;
pub use crate::types::string::PyStringMethods;
pub use crate::types::traceback::PyTracebackMethods;
pub use crate::types::tuple::PyTupleMethods;

View File

@ -304,6 +304,6 @@ pub(crate) mod sequence;
pub(crate) mod set;
mod slice;
pub(crate) mod string;
mod traceback;
pub(crate) mod traceback;
pub(crate) mod tuple;
mod typeobject;

View File

@ -1,7 +1,7 @@
use crate::err::{error_on_minusone, PyResult};
use crate::ffi;
use crate::types::PyString;
use crate::PyAny;
use crate::{ffi, Bound};
use crate::{PyAny, PyNativeType};
/// Represents a Python traceback.
#[repr(transparent)]
@ -24,14 +24,14 @@ impl PyTraceback {
/// The following code formats a Python traceback and exception pair from Rust:
///
/// ```rust
/// # use pyo3::{Python, PyResult};
/// # use pyo3::{Python, PyResult, prelude::PyTracebackMethods};
/// # let result: PyResult<()> =
/// Python::with_gil(|py| {
/// let err = py
/// .run("raise Exception('banana')", None, None)
/// .expect_err("raise will create a Python error");
///
/// let traceback = err.traceback(py).expect("raised exception will have a traceback");
/// let traceback = err.traceback_bound(py).expect("raised exception will have a traceback");
/// assert_eq!(
/// format!("{}{}", traceback.format()?, err),
/// "\
@ -46,6 +46,53 @@ impl PyTraceback {
/// # result.expect("example failed");
/// ```
pub fn format(&self) -> PyResult<String> {
self.as_borrowed().format()
}
}
/// Implementation of functionality for [`PyTraceback`].
///
/// These methods are defined for the `Bound<'py, PyTraceback>` smart pointer, so to use method call
/// syntax these methods are separated into a trait, because stable Rust does not yet support
/// `arbitrary_self_types`.
#[doc(alias = "PyTraceback")]
pub trait PyTracebackMethods<'py> {
/// Formats the traceback as a string.
///
/// This does not include the exception type and value. The exception type and value can be
/// formatted using the `Display` implementation for `PyErr`.
///
/// # Example
///
/// The following code formats a Python traceback and exception pair from Rust:
///
/// ```rust
/// # use pyo3::{Python, PyResult, prelude::PyTracebackMethods};
/// # let result: PyResult<()> =
/// Python::with_gil(|py| {
/// let err = py
/// .run("raise Exception('banana')", None, None)
/// .expect_err("raise will create a Python error");
///
/// let traceback = err.traceback_bound(py).expect("raised exception will have a traceback");
/// assert_eq!(
/// format!("{}{}", traceback.format()?, err),
/// "\
/// Traceback (most recent call last):
/// File \"<string>\", line 1, in <module>
/// Exception: banana\
/// "
/// );
/// Ok(())
/// })
/// # ;
/// # result.expect("example failed");
/// ```
fn format(&self) -> PyResult<String>;
}
impl<'py> PyTracebackMethods<'py> for Bound<'py, PyTraceback> {
fn format(&self) -> PyResult<String> {
let py = self.py();
let string_io = py
.import(intern!(py, "io"))?
@ -65,7 +112,10 @@ impl PyTraceback {
#[cfg(test)]
mod tests {
use crate::{prelude::*, types::PyDict};
use crate::{
prelude::*,
types::{traceback::PyTracebackMethods, PyDict},
};
#[test]
fn format_traceback() {
@ -75,7 +125,7 @@ mod tests {
.expect_err("raising should have given us an error");
assert_eq!(
err.traceback(py).unwrap().format().unwrap(),
err.traceback_bound(py).unwrap().format().unwrap(),
"Traceback (most recent call last):\n File \"<string>\", line 1, in <module>\n"
);
})
@ -99,7 +149,7 @@ except Exception as e:
.unwrap();
let err = PyErr::from_value(locals.get_item("err").unwrap().unwrap());
let traceback = err.value(py).getattr("__traceback__").unwrap();
assert!(err.traceback(py).unwrap().is(traceback));
assert!(err.traceback_bound(py).unwrap().is(traceback));
})
}
@ -119,10 +169,10 @@ def f():
.unwrap();
let f = locals.get_item("f").unwrap().unwrap();
let err = f.call0().unwrap_err();
let traceback = err.traceback(py).unwrap();
let traceback = err.traceback_bound(py).unwrap();
let err_object = err.clone_ref(py).into_py(py).into_ref(py);
assert!(err_object.getattr("__traceback__").unwrap().is(traceback));
assert!(err_object.getattr("__traceback__").unwrap().is(&traceback));
})
}
}