1use crate::err::{error_on_minusone, PyResult};
2use crate::platform::prelude::*;
3use crate::types::{any::PyAnyMethods, string::PyStringMethods, PyString};
4use crate::{ffi, Bound, PyAny};
5#[cfg(RustPython)]
6use crate::{
7 sync::PyOnceLock,
8 types::{PyType, PyTypeMethods},
9 Py,
10};
11#[cfg(all(not(Py_LIMITED_API), not(PyPy), not(GraalPy)))]
12use crate::{types::PyFrame, PyTypeCheck, Python};
13
14#[repr(transparent)]
22pub struct PyTraceback(PyAny);
23
24#[cfg(not(RustPython))]
25pyobject_native_type_core!(
26 PyTraceback,
27 pyobject_native_static_type_object!(ffi::PyTraceBack_Type),
28 "builtins",
29 "traceback",
30 #checkfunction=ffi::PyTraceBack_Check
31);
32
33#[cfg(RustPython)]
34pyobject_native_type_core!(
35 PyTraceback,
36 |py| {
37 static TYPE: PyOnceLock<Py<PyType>> = PyOnceLock::new();
38 TYPE.import(py, "types", "TracebackType").unwrap().as_type_ptr()
39 },
40 "builtins",
41 "traceback",
42 #checkfunction=ffi::PyTraceBack_Check
43);
44
45impl PyTraceback {
46 #[cfg(all(not(Py_LIMITED_API), not(PyPy), not(GraalPy)))]
51 pub fn new<'py>(
52 py: Python<'py>,
53 next: Option<Bound<'py, PyTraceback>>,
54 frame: Bound<'py, PyFrame>,
55 instruction_index: i32,
56 line_number: i32,
57 ) -> PyResult<Bound<'py, PyTraceback>> {
58 unsafe {
59 Ok(PyTraceback::classinfo_object(py)
60 .call1((next, frame, instruction_index, line_number))?
61 .cast_into_unchecked())
62 }
63 }
64}
65
66#[doc(alias = "PyTraceback")]
72pub trait PyTracebackMethods<'py>: crate::sealed::Sealed {
73 fn format(&self) -> PyResult<String>;
105}
106
107impl<'py> PyTracebackMethods<'py> for Bound<'py, PyTraceback> {
108 fn format(&self) -> PyResult<String> {
109 let py = self.py();
110 let string_io = py
111 .import(intern!(py, "io"))?
112 .getattr(intern!(py, "StringIO"))?
113 .call0()?;
114 let result = unsafe { ffi::PyTraceBack_Print(self.as_ptr(), string_io.as_ptr()) };
115 error_on_minusone(py, result)?;
116 let formatted = string_io
117 .getattr(intern!(py, "getvalue"))?
118 .call0()?
119 .cast::<PyString>()?
120 .to_cow()?
121 .into_owned();
122 Ok(formatted)
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use crate::IntoPyObject;
130 use crate::{
131 types::{dict::PyDictMethods, PyDict},
132 PyErr, Python,
133 };
134
135 #[test]
136 fn format_traceback() {
137 Python::attach(|py| {
138 let err = py
139 .run(c"raise Exception('banana')", None, None)
140 .expect_err("raising should have given us an error");
141
142 assert_eq!(
143 err.traceback(py).unwrap().format().unwrap(),
144 "Traceback (most recent call last):\n File \"<string>\", line 1, in <module>\n"
145 );
146 })
147 }
148
149 #[test]
150 fn test_err_from_value() {
151 Python::attach(|py| {
152 let locals = PyDict::new(py);
153 py.run(
155 cr"
156try:
157 raise ValueError('raised exception')
158except Exception as e:
159 err = e
160",
161 None,
162 Some(&locals),
163 )
164 .unwrap();
165 let err = PyErr::from_value(locals.get_item("err").unwrap().unwrap());
166 let traceback = err.value(py).getattr("__traceback__").unwrap();
167 assert!(err.traceback(py).unwrap().is(&traceback));
168 })
169 }
170
171 #[test]
172 fn test_err_into_py() {
173 Python::attach(|py| {
174 let locals = PyDict::new(py);
175 py.run(
177 cr"
178def f():
179 raise ValueError('raised exception')
180",
181 None,
182 Some(&locals),
183 )
184 .unwrap();
185 let f = locals.get_item("f").unwrap().unwrap();
186 let err = f.call0().unwrap_err();
187 let traceback = err.traceback(py).unwrap();
188 let err_object = err.clone_ref(py).into_pyobject(py).unwrap();
189
190 assert!(err_object.getattr("__traceback__").unwrap().is(&traceback));
191 })
192 }
193
194 #[test]
195 #[cfg(all(not(Py_LIMITED_API), not(PyPy), not(GraalPy)))]
196 fn test_create_traceback() {
197 Python::attach(|py| {
198 let traceback = PyTraceback::new(
199 py,
200 None,
201 PyFrame::new(py, c"file2.py", c"func2", 20).unwrap(),
202 0,
203 20,
204 )
205 .unwrap();
206 let traceback = PyTraceback::new(
207 py,
208 Some(traceback),
209 PyFrame::new(py, c"file1.py", c"func1", 10).unwrap(),
210 0,
211 10,
212 )
213 .unwrap();
214 assert_eq!(
215 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"
216 );
217 })
218 }
219}