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#[cfg(all(not(Py_LIMITED_API), not(PyPy), not(GraalPy)))]
5use crate::{types::PyFrame, PyTypeCheck, Python};
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    "builtins",
21    "traceback",
22    #checkfunction=ffi::PyTraceBack_Check
23);
24
25impl PyTraceback {
26    /// Creates a new traceback object from the given frame.
27    ///
28    /// The `next` is the next traceback in the direction of where the exception was raised
29    /// or `None` if this is the last frame in the traceback.
30    #[cfg(all(not(Py_LIMITED_API), not(PyPy), not(GraalPy)))]
31    pub fn new<'py>(
32        py: Python<'py>,
33        next: Option<Bound<'py, PyTraceback>>,
34        frame: Bound<'py, PyFrame>,
35        instruction_index: i32,
36        line_number: i32,
37    ) -> PyResult<Bound<'py, PyTraceback>> {
38        unsafe {
39            Ok(PyTraceback::classinfo_object(py)
40                .call1((next, frame, instruction_index, line_number))?
41                .cast_into_unchecked())
42        }
43    }
44}
45
46/// Implementation of functionality for [`PyTraceback`].
47///
48/// These methods are defined for the `Bound<'py, PyTraceback>` smart pointer, so to use method call
49/// syntax these methods are separated into a trait, because stable Rust does not yet support
50/// `arbitrary_self_types`.
51#[doc(alias = "PyTraceback")]
52pub trait PyTracebackMethods<'py>: crate::sealed::Sealed {
53    /// Formats the traceback as a string.
54    ///
55    /// This does not include the exception type and value. The exception type and value can be
56    /// formatted using the `Display` implementation for `PyErr`.
57    ///
58    /// # Example
59    ///
60    /// The following code formats a Python traceback and exception pair from Rust:
61    ///
62    /// ```rust
63    /// # use pyo3::{Python, PyResult, prelude::PyTracebackMethods, ffi::c_str};
64    /// # let result: PyResult<()> =
65    /// Python::attach(|py| {
66    ///     let err = py
67    ///         .run(c"raise Exception('banana')", None, None)
68    ///         .expect_err("raise will create a Python error");
69    ///
70    ///     let traceback = err.traceback(py).expect("raised exception will have a traceback");
71    ///     assert_eq!(
72    ///         format!("{}{}", traceback.format()?, err),
73    ///         "\
74    /// Traceback (most recent call last):
75    ///   File \"<string>\", line 1, in <module>
76    /// Exception: banana\
77    /// "
78    ///     );
79    ///     Ok(())
80    /// })
81    /// # ;
82    /// # result.expect("example failed");
83    /// ```
84    fn format(&self) -> PyResult<String>;
85}
86
87impl<'py> PyTracebackMethods<'py> for Bound<'py, PyTraceback> {
88    fn format(&self) -> PyResult<String> {
89        let py = self.py();
90        let string_io = py
91            .import(intern!(py, "io"))?
92            .getattr(intern!(py, "StringIO"))?
93            .call0()?;
94        let result = unsafe { ffi::PyTraceBack_Print(self.as_ptr(), string_io.as_ptr()) };
95        error_on_minusone(py, result)?;
96        let formatted = string_io
97            .getattr(intern!(py, "getvalue"))?
98            .call0()?
99            .cast::<PyString>()?
100            .to_cow()?
101            .into_owned();
102        Ok(formatted)
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use crate::IntoPyObject;
110    use crate::{
111        types::{dict::PyDictMethods, PyDict},
112        PyErr, Python,
113    };
114
115    #[test]
116    fn format_traceback() {
117        Python::attach(|py| {
118            let err = py
119                .run(c"raise Exception('banana')", None, None)
120                .expect_err("raising should have given us an error");
121
122            assert_eq!(
123                err.traceback(py).unwrap().format().unwrap(),
124                "Traceback (most recent call last):\n  File \"<string>\", line 1, in <module>\n"
125            );
126        })
127    }
128
129    #[test]
130    fn test_err_from_value() {
131        Python::attach(|py| {
132            let locals = PyDict::new(py);
133            // Produce an error from python so that it has a traceback
134            py.run(
135                cr"
136try:
137    raise ValueError('raised exception')
138except Exception as e:
139    err = e
140",
141                None,
142                Some(&locals),
143            )
144            .unwrap();
145            let err = PyErr::from_value(locals.get_item("err").unwrap().unwrap());
146            let traceback = err.value(py).getattr("__traceback__").unwrap();
147            assert!(err.traceback(py).unwrap().is(&traceback));
148        })
149    }
150
151    #[test]
152    fn test_err_into_py() {
153        Python::attach(|py| {
154            let locals = PyDict::new(py);
155            // Produce an error from python so that it has a traceback
156            py.run(
157                cr"
158def f():
159    raise ValueError('raised exception')
160",
161                None,
162                Some(&locals),
163            )
164            .unwrap();
165            let f = locals.get_item("f").unwrap().unwrap();
166            let err = f.call0().unwrap_err();
167            let traceback = err.traceback(py).unwrap();
168            let err_object = err.clone_ref(py).into_pyobject(py).unwrap();
169
170            assert!(err_object.getattr("__traceback__").unwrap().is(&traceback));
171        })
172    }
173
174    #[test]
175    #[cfg(all(not(Py_LIMITED_API), not(PyPy), not(GraalPy)))]
176    fn test_create_traceback() {
177        Python::attach(|py| {
178            let traceback = PyTraceback::new(
179                py,
180                None,
181                PyFrame::new(py, c"file2.py", c"func2", 20).unwrap(),
182                0,
183                20,
184            )
185            .unwrap();
186            let traceback = PyTraceback::new(
187                py,
188                Some(traceback),
189                PyFrame::new(py, c"file1.py", c"func1", 10).unwrap(),
190                0,
191                10,
192            )
193            .unwrap();
194            assert_eq!(
195                traceback.format().unwrap(), "Traceback (most recent call last):\n  File \"file1.py\", line 10, in func1\n  File \"file2.py\", line 20, in func2\n"
196            );
197        })
198    }
199}