Merge pull request #3763 from Icxolu/slice-traceback

implement `PyTracebackMethods` and `PySliceMethods`
This commit is contained in:
David Hewitt 2024-01-27 12:42:37 +00:00 committed by GitHub
commit f09ad1e28f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 162 additions and 36 deletions

View File

@ -479,7 +479,7 @@ class House(object):
} }
Err(e) => { Err(e) => {
house 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(); .unwrap();
} }
} }

View File

@ -2,7 +2,7 @@ use crate::{
exceptions::{PyBaseException, PyTypeError}, exceptions::{PyBaseException, PyTypeError},
ffi, ffi,
types::{PyTraceback, PyType}, types::{PyTraceback, PyType},
IntoPy, Py, PyAny, PyObject, PyTypeInfo, Python, Bound, IntoPy, Py, PyAny, PyObject, PyTypeInfo, Python,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -26,15 +26,21 @@ impl PyErrStateNormalized {
} }
#[cfg(not(Py_3_12))] #[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 self.ptraceback
.as_ref() .as_ref()
.map(|traceback| traceback.as_ref(py)) .map(|traceback| traceback.bind(py).clone())
} }
#[cfg(Py_3_12)] #[cfg(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>> {
unsafe { py.from_owned_ptr_or_opt(ffi::PyException_GetTraceback(self.pvalue.as_ptr())) } 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)] #[cfg(Py_3_12)]

View File

@ -272,6 +272,18 @@ impl PyErr {
exc 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. /// Returns the traceback of this exception object.
/// ///
/// # Examples /// # Examples
@ -280,10 +292,10 @@ impl PyErr {
/// ///
/// Python::with_gil(|py| { /// Python::with_gil(|py| {
/// let err = PyTypeError::new_err(("some type error",)); /// 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) self.normalized(py).ptraceback(py)
} }
@ -476,10 +488,16 @@ impl PyErr {
#[cfg(not(Py_3_12))] #[cfg(not(Py_3_12))]
unsafe { 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( ffi::PyErr_Display(
self.get_type(py).as_ptr(), self.get_type(py).as_ptr(),
self.value(py).as_ptr(), self.value(py).as_ptr(),
self.traceback(py) traceback
.as_ref()
.map_or(std::ptr::null_mut(), |traceback| traceback.as_ptr()), .map_or(std::ptr::null_mut(), |traceback| traceback.as_ptr()),
) )
} }
@ -658,15 +676,15 @@ impl PyErr {
/// ///
/// # Examples /// # Examples
/// ```rust /// ```rust
/// use pyo3::{exceptions::PyTypeError, PyErr, Python}; /// use pyo3::{exceptions::PyTypeError, PyErr, Python, prelude::PyAnyMethods};
/// Python::with_gil(|py| { /// Python::with_gil(|py| {
/// let err: PyErr = PyTypeError::new_err(("some type error",)); /// let err: PyErr = PyTypeError::new_err(("some type error",));
/// let err_clone = err.clone_ref(py); /// let err_clone = err.clone_ref(py);
/// assert!(err.get_type(py).is(err_clone.get_type(py))); /// assert!(err.get_type(py).is(err_clone.get_type(py)));
/// assert!(err.value(py).is(err_clone.value(py))); /// assert!(err.value(py).is(err_clone.value(py)));
/// match err.traceback(py) { /// match err.traceback_bound(py) {
/// None => assert!(err_clone.traceback(py).is_none()), /// None => assert!(err_clone.traceback_bound(py).is_none()),
/// Some(tb) => assert!(err_clone.traceback(py).unwrap().is(tb)), /// 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") f.debug_struct("PyErr")
.field("type", self.get_type(py)) .field("type", self.get_type(py))
.field("value", self.value(py)) .field("value", self.value(py))
.field("traceback", &self.traceback(py)) .field("traceback", &self.traceback_bound(py))
.finish() .finish()
}) })
} }

View File

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

View File

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

View File

@ -1,6 +1,8 @@
use crate::err::{PyErr, PyResult}; use crate::err::{PyErr, PyResult};
use crate::ffi::{self, Py_ssize_t}; use crate::ffi::{self, Py_ssize_t};
use crate::{PyAny, PyObject, Python, ToPyObject}; use crate::ffi_ptr_ext::FfiPtrExt;
use crate::types::any::PyAnyMethods;
use crate::{Bound, PyAny, PyNativeType, PyObject, Python, ToPyObject};
use std::os::raw::c_long; use std::os::raw::c_long;
/// Represents a Python `slice`. /// Represents a Python `slice`.
@ -42,23 +44,52 @@ impl PySliceIndices {
} }
impl PySlice { impl PySlice {
/// Constructs a new slice with the given elements. /// Deprecated form of `PySlice::new_bound`.
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PySlice::new` will be replaced by `PySlice::new_bound` in a future PyO3 version"
)
)]
pub fn new(py: Python<'_>, start: isize, stop: isize, step: isize) -> &PySlice { pub fn new(py: Python<'_>, start: isize, stop: isize, step: isize) -> &PySlice {
Self::new_bound(py, start, stop, step).into_gil_ref()
}
/// Constructs a new slice with the given elements.
pub fn new_bound(py: Python<'_>, start: isize, stop: isize, step: isize) -> Bound<'_, PySlice> {
unsafe { unsafe {
let ptr = ffi::PySlice_New( ffi::PySlice_New(
ffi::PyLong_FromSsize_t(start), ffi::PyLong_FromSsize_t(start),
ffi::PyLong_FromSsize_t(stop), ffi::PyLong_FromSsize_t(stop),
ffi::PyLong_FromSsize_t(step), ffi::PyLong_FromSsize_t(step),
); )
.assume_owned(py)
.downcast_into_unchecked()
}
}
/// Deprecated form of `PySlice::full_bound`.
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PySlice::full` will be replaced by `PySlice::full_bound` in a future PyO3 version"
)
)]
pub fn full(py: Python<'_>) -> &PySlice {
unsafe {
let ptr = ffi::PySlice_New(ffi::Py_None(), ffi::Py_None(), ffi::Py_None());
py.from_owned_ptr(ptr) py.from_owned_ptr(ptr)
} }
} }
/// Constructs a new full slice that is equivalent to `::`. /// Constructs a new full slice that is equivalent to `::`.
pub fn full(py: Python<'_>) -> &PySlice { pub fn full_bound(py: Python<'_>) -> Bound<'_, PySlice> {
unsafe { unsafe {
let ptr = ffi::PySlice_New(ffi::Py_None(), ffi::Py_None(), ffi::Py_None()); ffi::PySlice_New(ffi::Py_None(), ffi::Py_None(), ffi::Py_None())
py.from_owned_ptr(ptr) .assume_owned(py)
.downcast_into_unchecked()
} }
} }
@ -67,6 +98,25 @@ impl PySlice {
/// slice in its `slicelength` member. /// slice in its `slicelength` member.
#[inline] #[inline]
pub fn indices(&self, length: c_long) -> PyResult<PySliceIndices> { pub fn indices(&self, length: c_long) -> PyResult<PySliceIndices> {
self.as_borrowed().indices(length)
}
}
/// Implementation of functionality for [`PySlice`].
///
/// These methods are defined for the `Bound<'py, PyTuple>` 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 = "PySlice")]
pub trait PySliceMethods<'py> {
/// Retrieves the start, stop, and step indices from the slice object,
/// assuming a sequence of length `length`, and stores the length of the
/// slice in its `slicelength` member.
fn indices(&self, length: c_long) -> PyResult<PySliceIndices>;
}
impl<'py> PySliceMethods<'py> for Bound<'py, PySlice> {
fn indices(&self, length: c_long) -> PyResult<PySliceIndices> {
// non-negative Py_ssize_t should always fit into Rust usize // non-negative Py_ssize_t should always fit into Rust usize
unsafe { unsafe {
let mut slicelength: isize = 0; let mut slicelength: isize = 0;
@ -97,7 +147,7 @@ impl PySlice {
impl ToPyObject for PySliceIndices { impl ToPyObject for PySliceIndices {
fn to_object(&self, py: Python<'_>) -> PyObject { fn to_object(&self, py: Python<'_>) -> PyObject {
PySlice::new(py, self.start, self.stop, self.step).into() PySlice::new_bound(py, self.start, self.stop, self.step).into()
} }
} }
@ -108,7 +158,7 @@ mod tests {
#[test] #[test]
fn test_py_slice_new() { fn test_py_slice_new() {
Python::with_gil(|py| { Python::with_gil(|py| {
let slice = PySlice::new(py, isize::MIN, isize::MAX, 1); let slice = PySlice::new_bound(py, isize::MIN, isize::MAX, 1);
assert_eq!( assert_eq!(
slice.getattr("start").unwrap().extract::<isize>().unwrap(), slice.getattr("start").unwrap().extract::<isize>().unwrap(),
isize::MIN isize::MIN
@ -127,7 +177,7 @@ mod tests {
#[test] #[test]
fn test_py_slice_full() { fn test_py_slice_full() {
Python::with_gil(|py| { Python::with_gil(|py| {
let slice = PySlice::full(py); let slice = PySlice::full_bound(py);
assert!(slice.getattr("start").unwrap().is_none(),); assert!(slice.getattr("start").unwrap().is_none(),);
assert!(slice.getattr("stop").unwrap().is_none(),); assert!(slice.getattr("stop").unwrap().is_none(),);
assert!(slice.getattr("step").unwrap().is_none(),); assert!(slice.getattr("step").unwrap().is_none(),);

View File

@ -1,7 +1,7 @@
use crate::err::{error_on_minusone, PyResult}; use crate::err::{error_on_minusone, PyResult};
use crate::ffi;
use crate::types::PyString; use crate::types::PyString;
use crate::PyAny; use crate::{ffi, Bound};
use crate::{PyAny, PyNativeType};
/// Represents a Python traceback. /// Represents a Python traceback.
#[repr(transparent)] #[repr(transparent)]
@ -24,14 +24,14 @@ impl PyTraceback {
/// The following code formats a Python traceback and exception pair from Rust: /// The following code formats a Python traceback and exception pair from Rust:
/// ///
/// ```rust /// ```rust
/// # use pyo3::{Python, PyResult}; /// # use pyo3::{Python, PyResult, prelude::PyTracebackMethods};
/// # let result: PyResult<()> = /// # let result: PyResult<()> =
/// Python::with_gil(|py| { /// Python::with_gil(|py| {
/// let err = py /// let err = py
/// .run("raise Exception('banana')", None, None) /// .run("raise Exception('banana')", None, None)
/// .expect_err("raise will create a Python error"); /// .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!( /// assert_eq!(
/// format!("{}{}", traceback.format()?, err), /// format!("{}{}", traceback.format()?, err),
/// "\ /// "\
@ -46,6 +46,53 @@ impl PyTraceback {
/// # result.expect("example failed"); /// # result.expect("example failed");
/// ``` /// ```
pub fn format(&self) -> PyResult<String> { 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 py = self.py();
let string_io = py let string_io = py
.import(intern!(py, "io"))? .import(intern!(py, "io"))?
@ -65,7 +112,10 @@ impl PyTraceback {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{prelude::*, types::PyDict}; use crate::{
prelude::*,
types::{traceback::PyTracebackMethods, PyDict},
};
#[test] #[test]
fn format_traceback() { fn format_traceback() {
@ -75,7 +125,7 @@ mod tests {
.expect_err("raising should have given us an error"); .expect_err("raising should have given us an error");
assert_eq!( 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" "Traceback (most recent call last):\n File \"<string>\", line 1, in <module>\n"
); );
}) })
@ -99,7 +149,7 @@ except Exception as e:
.unwrap(); .unwrap();
let err = PyErr::from_value(locals.get_item("err").unwrap().unwrap()); let err = PyErr::from_value(locals.get_item("err").unwrap().unwrap());
let traceback = err.value(py).getattr("__traceback__").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(); .unwrap();
let f = locals.get_item("f").unwrap().unwrap(); let f = locals.get_item("f").unwrap().unwrap();
let err = f.call0().unwrap_err(); 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); 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));
}) })
} }
} }

View File

@ -5,8 +5,8 @@ error[E0277]: the trait bound `PyErr: From<MyError>` is not satisfied
| ^^^^^^^^^^^^^ the trait `From<MyError>` is not implemented for `PyErr` | ^^^^^^^^^^^^^ the trait `From<MyError>` is not implemented for `PyErr`
| |
= help: the following other types implement trait `From<T>`: = help: the following other types implement trait `From<T>`:
<PyErr as From<PyBorrowError>>
<PyErr as From<std::io::Error>> <PyErr as From<std::io::Error>>
<PyErr as From<PyBorrowError>>
<PyErr as From<PyBorrowMutError>> <PyErr as From<PyBorrowMutError>>
<PyErr as From<PyDowncastError<'a>>> <PyErr as From<PyDowncastError<'a>>>
<PyErr as From<DowncastError<'_, '_>>> <PyErr as From<DowncastError<'_, '_>>>