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::attach(|py| {
41 /// let err = py
42 /// .run(c"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 .cast::<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 types::{any::PyAnyMethods, dict::PyDictMethods, traceback::PyTracebackMethods, PyDict},
86 PyErr, Python,
87 };
88
89 #[test]
90 fn format_traceback() {
91 Python::attach(|py| {
92 let err = py
93 .run(c"raise Exception('banana')", None, None)
94 .expect_err("raising should have given us an error");
95
96 assert_eq!(
97 err.traceback(py).unwrap().format().unwrap(),
98 "Traceback (most recent call last):\n File \"<string>\", line 1, in <module>\n"
99 );
100 })
101 }
102
103 #[test]
104 fn test_err_from_value() {
105 Python::attach(|py| {
106 let locals = PyDict::new(py);
107 // Produce an error from python so that it has a traceback
108 py.run(
109 cr"
110try:
111 raise ValueError('raised exception')
112except Exception as e:
113 err = e
114",
115 None,
116 Some(&locals),
117 )
118 .unwrap();
119 let err = PyErr::from_value(locals.get_item("err").unwrap().unwrap());
120 let traceback = err.value(py).getattr("__traceback__").unwrap();
121 assert!(err.traceback(py).unwrap().is(&traceback));
122 })
123 }
124
125 #[test]
126 fn test_err_into_py() {
127 Python::attach(|py| {
128 let locals = PyDict::new(py);
129 // Produce an error from python so that it has a traceback
130 py.run(
131 cr"
132def f():
133 raise ValueError('raised exception')
134",
135 None,
136 Some(&locals),
137 )
138 .unwrap();
139 let f = locals.get_item("f").unwrap().unwrap();
140 let err = f.call0().unwrap_err();
141 let traceback = err.traceback(py).unwrap();
142 let err_object = err.clone_ref(py).into_pyobject(py).unwrap();
143
144 assert!(err_object.getattr("__traceback__").unwrap().is(&traceback));
145 })
146 }
147}