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}