pyo3/types/
traceback.rs

1use crate::err::{error_on_minusone, PyResult};
2use crate::types::{any::PyAnyMethods, string::PyStringMethods, PyString};
3#[cfg(feature = "gil-refs")]
4use crate::PyNativeType;
5use crate::{ffi, Bound, PyAny};
6
7/// Represents a Python traceback.
8///
9/// Values of this type are accessed via PyO3's smart pointers, e.g. as
10/// [`Py<PyTraceback>`][crate::Py] or [`Bound<'py, PyTraceback>`][Bound].
11///
12/// For APIs available on traceback objects, see the [`PyTracebackMethods`] trait which is implemented for
13/// [`Bound<'py, PyTraceback>`][Bound].
14#[repr(transparent)]
15pub struct PyTraceback(PyAny);
16
17pyobject_native_type_core!(
18    PyTraceback,
19    pyobject_native_static_type_object!(ffi::PyTraceBack_Type),
20    #checkfunction=ffi::PyTraceBack_Check
21);
22
23#[cfg(feature = "gil-refs")]
24impl PyTraceback {
25    /// Formats the traceback as a string.
26    ///
27    /// This does not include the exception type and value. The exception type and value can be
28    /// formatted using the `Display` implementation for `PyErr`.
29    ///
30    /// # Example
31    ///
32    /// The following code formats a Python traceback and exception pair from Rust:
33    ///
34    /// ```rust
35    /// # use pyo3::{Python, PyResult, prelude::PyTracebackMethods};
36    /// # let result: PyResult<()> =
37    /// Python::with_gil(|py| {
38    ///     let err = py
39    ///         .run_bound("raise Exception('banana')", None, None)
40    ///         .expect_err("raise will create a Python error");
41    ///
42    ///     let traceback = err.traceback_bound(py).expect("raised exception will have a traceback");
43    ///     assert_eq!(
44    ///         format!("{}{}", traceback.format()?, err),
45    ///         "\
46    /// Traceback (most recent call last):
47    ///   File \"<string>\", line 1, in <module>
48    /// Exception: banana\
49    /// "
50    ///     );
51    ///     Ok(())
52    /// })
53    /// # ;
54    /// # result.expect("example failed");
55    /// ```
56    pub fn format(&self) -> PyResult<String> {
57        self.as_borrowed().format()
58    }
59}
60
61/// Implementation of functionality for [`PyTraceback`].
62///
63/// These methods are defined for the `Bound<'py, PyTraceback>` smart pointer, so to use method call
64/// syntax these methods are separated into a trait, because stable Rust does not yet support
65/// `arbitrary_self_types`.
66#[doc(alias = "PyTraceback")]
67pub trait PyTracebackMethods<'py>: crate::sealed::Sealed {
68    /// Formats the traceback as a string.
69    ///
70    /// This does not include the exception type and value. The exception type and value can be
71    /// formatted using the `Display` implementation for `PyErr`.
72    ///
73    /// # Example
74    ///
75    /// The following code formats a Python traceback and exception pair from Rust:
76    ///
77    /// ```rust
78    /// # use pyo3::{Python, PyResult, prelude::PyTracebackMethods};
79    /// # let result: PyResult<()> =
80    /// Python::with_gil(|py| {
81    ///     let err = py
82    ///         .run_bound("raise Exception('banana')", None, None)
83    ///         .expect_err("raise will create a Python error");
84    ///
85    ///     let traceback = err.traceback_bound(py).expect("raised exception will have a traceback");
86    ///     assert_eq!(
87    ///         format!("{}{}", traceback.format()?, err),
88    ///         "\
89    /// Traceback (most recent call last):
90    ///   File \"<string>\", line 1, in <module>
91    /// Exception: banana\
92    /// "
93    ///     );
94    ///     Ok(())
95    /// })
96    /// # ;
97    /// # result.expect("example failed");
98    /// ```
99    fn format(&self) -> PyResult<String>;
100}
101
102impl<'py> PyTracebackMethods<'py> for Bound<'py, PyTraceback> {
103    fn format(&self) -> PyResult<String> {
104        let py = self.py();
105        let string_io = py
106            .import_bound(intern!(py, "io"))?
107            .getattr(intern!(py, "StringIO"))?
108            .call0()?;
109        let result = unsafe { ffi::PyTraceBack_Print(self.as_ptr(), string_io.as_ptr()) };
110        error_on_minusone(py, result)?;
111        let formatted = string_io
112            .getattr(intern!(py, "getvalue"))?
113            .call0()?
114            .downcast::<PyString>()?
115            .to_cow()?
116            .into_owned();
117        Ok(formatted)
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use crate::{
124        types::{any::PyAnyMethods, dict::PyDictMethods, traceback::PyTracebackMethods, PyDict},
125        IntoPy, PyErr, Python,
126    };
127
128    #[test]
129    fn format_traceback() {
130        Python::with_gil(|py| {
131            let err = py
132                .run_bound("raise Exception('banana')", None, None)
133                .expect_err("raising should have given us an error");
134
135            assert_eq!(
136                err.traceback_bound(py).unwrap().format().unwrap(),
137                "Traceback (most recent call last):\n  File \"<string>\", line 1, in <module>\n"
138            );
139        })
140    }
141
142    #[test]
143    fn test_err_from_value() {
144        Python::with_gil(|py| {
145            let locals = PyDict::new_bound(py);
146            // Produce an error from python so that it has a traceback
147            py.run_bound(
148                r"
149try:
150    raise ValueError('raised exception')
151except Exception as e:
152    err = e
153",
154                None,
155                Some(&locals),
156            )
157            .unwrap();
158            let err = PyErr::from_value_bound(locals.get_item("err").unwrap().unwrap());
159            let traceback = err.value_bound(py).getattr("__traceback__").unwrap();
160            assert!(err.traceback_bound(py).unwrap().is(&traceback));
161        })
162    }
163
164    #[test]
165    fn test_err_into_py() {
166        Python::with_gil(|py| {
167            let locals = PyDict::new_bound(py);
168            // Produce an error from python so that it has a traceback
169            py.run_bound(
170                r"
171def f():
172    raise ValueError('raised exception')
173",
174                None,
175                Some(&locals),
176            )
177            .unwrap();
178            let f = locals.get_item("f").unwrap().unwrap();
179            let err = f.call0().unwrap_err();
180            let traceback = err.traceback_bound(py).unwrap();
181            let err_object = err.clone_ref(py).into_py(py).into_bound(py);
182
183            assert!(err_object.getattr("__traceback__").unwrap().is(&traceback));
184        })
185    }
186}