Skip to main content

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