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#[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 #[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#[doc(alias = "PyTraceback")]
52pub trait PyTracebackMethods<'py>: crate::sealed::Sealed {
53 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 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 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}