pyo3/types/
traceback.rs

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