1use crate::err::{error_on_minusone, PyResult};
2use crate::ffi;
3use crate::types::PyString;
4use crate::PyAny;
5
6/// Represents a Python traceback.
7#[repr(transparent)]
8pub struct PyTraceback(PyAny);
9
10pyobject_native_type_core!(
11 PyTraceback,
12 pyobject_native_static_type_object!(ffi::PyTraceBack_Type),
13 #checkfunction=ffi::PyTraceBack_Check
14);
15
16impl PyTraceback {
17 /// Formats the traceback as a string.
18 ///
19 /// This does not include the exception type and value. The exception type and value can be
20 /// formatted using the `Display` implementation for `PyErr`.
21 ///
22 /// # Example
23 ///
24 /// The following code formats a Python traceback and exception pair from Rust:
25 ///
26 /// ```rust
27 /// # use pyo3::{Python, PyResult};
28 /// # let result: PyResult<()> =
29 /// Python::with_gil(|py| {
30 /// let err = py
31 /// .run("raise Exception('banana')", None, None)
32 /// .expect_err("raise will create a Python error");
33 ///
34 /// let traceback = err.traceback(py).expect("raised exception will have a traceback");
35 /// assert_eq!(
36 /// format!("{}{}", traceback.format()?, err),
37 /// "\
38 /// Traceback (most recent call last):
39 /// File \"<string>\", line 1, in <module>
40 /// Exception: banana\
41 /// "
42 /// );
43 /// Ok(())
44 /// })
45 /// # ;
46 /// # result.expect("example failed");
47 /// ```
48 pub fn format(&self) -> PyResult<String> {
49 let py = self.py();
50 let string_io = py
51 .import(intern!(py, "io"))?
52 .getattr(intern!(py, "StringIO"))?
53 .call0()?;
54 let result = unsafe { ffi::PyTraceBack_Print(self.as_ptr(), string_io.as_ptr()) };
55 error_on_minusone(py, result)?;
56 let formatted = string_io
57 .getattr(intern!(py, "getvalue"))?
58 .call0()?
59 .downcast::<PyString>()?
60 .to_str()?
61 .to_owned();
62 Ok(formatted)
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use crate::{prelude::*, types::PyDict};
69
70 #[test]
71 fn format_traceback() {
72 Python::with_gil(|py| {
73 let err = py
74 .run("raise Exception('banana')", None, None)
75 .expect_err("raising should have given us an error");
76
77 assert_eq!(
78 err.traceback(py).unwrap().format().unwrap(),
79 "Traceback (most recent call last):\n File \"<string>\", line 1, in <module>\n"
80 );
81 })
82 }
83
84 #[test]
85 fn test_err_from_value() {
86 Python::with_gil(|py| {
87 let locals = PyDict::new(py);
88 // Produce an error from python so that it has a traceback
89 py.run(
90 r"
91try:
92 raise ValueError('raised exception')
93except Exception as e:
94 err = e
95",
96 None,
97 Some(locals),
98 )
99 .unwrap();
100 let err = PyErr::from_value(locals.get_item("err").unwrap().unwrap());
101 let traceback = err.value(py).getattr("__traceback__").unwrap();
102 assert!(err.traceback(py).unwrap().is(traceback));
103 })
104 }
105
106 #[test]
107 fn test_err_into_py() {
108 Python::with_gil(|py| {
109 let locals = PyDict::new(py);
110 // Produce an error from python so that it has a traceback
111 py.run(
112 r"
113def f():
114 raise ValueError('raised exception')
115",
116 None,
117 Some(locals),
118 )
119 .unwrap();
120 let f = locals.get_item("f").unwrap().unwrap();
121 let err = f.call0().unwrap_err();
122 let traceback = err.traceback(py).unwrap();
123 let err_object = err.clone_ref(py).into_py(py).into_ref(py);
124
125 assert!(err_object.getattr("__traceback__").unwrap().is(traceback));
126 })
127 }
128}
129