1 | use crate::err::{error_on_minusone, PyResult}; |
2 | use crate::types::{any::PyAnyMethods, string::PyStringMethods, PyString}; |
3 | use 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)] |
13 | pub struct PyTraceback(PyAny); |
14 | |
15 | pyobject_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" )] |
27 | pub 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::with_gil(|py| { |
41 | /// let err = py |
42 | /// .run(c_str!("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 | |
62 | impl<'py> PyTracebackMethods<'py> for Bound<'py, PyTraceback> { |
63 | fn format(&self) -> PyResult<String> { |
64 | let py: Python<'_> = self.py(); |
65 | let string_io: Bound<'py, PyAny> = pyBound<'py, PyAny> |
66 | .import(intern!(py, "io" ))? |
67 | .getattr(attr_name:intern!(py, "StringIO" ))? |
68 | .call0()?; |
69 | let result = unsafe { ffi::PyTraceBack_Print(self.as_ptr(), arg2:string_io.as_ptr()) }; |
70 | error_on_minusone(py, result)?; |
71 | let formatted: String = string_ioCow<'_, str> |
72 | .getattr(attr_name:intern!(py, "getvalue" ))? |
73 | .call0()? |
74 | .downcast::<PyString>()? |
75 | .to_cow()? |
76 | .into_owned(); |
77 | Ok(formatted) |
78 | } |
79 | } |
80 | |
81 | #[cfg (test)] |
82 | mod tests { |
83 | use crate::IntoPyObject; |
84 | use crate::{ |
85 | ffi, |
86 | types::{any::PyAnyMethods, dict::PyDictMethods, traceback::PyTracebackMethods, PyDict}, |
87 | PyErr, Python, |
88 | }; |
89 | |
90 | #[test ] |
91 | fn format_traceback() { |
92 | Python::with_gil(|py| { |
93 | let err = py |
94 | .run(ffi::c_str!("raise Exception('banana')" ), None, None) |
95 | .expect_err("raising should have given us an error" ); |
96 | |
97 | assert_eq!( |
98 | err.traceback(py).unwrap().format().unwrap(), |
99 | "Traceback (most recent call last): \n File \"<string> \", line 1, in <module> \n" |
100 | ); |
101 | }) |
102 | } |
103 | |
104 | #[test ] |
105 | fn test_err_from_value() { |
106 | Python::with_gil(|py| { |
107 | let locals = PyDict::new(py); |
108 | // Produce an error from python so that it has a traceback |
109 | py.run( |
110 | ffi::c_str!( |
111 | r" |
112 | try: |
113 | raise ValueError('raised exception') |
114 | except Exception as e: |
115 | err = e |
116 | " |
117 | ), |
118 | None, |
119 | Some(&locals), |
120 | ) |
121 | .unwrap(); |
122 | let err = PyErr::from_value(locals.get_item("err" ).unwrap().unwrap()); |
123 | let traceback = err.value(py).getattr("__traceback__" ).unwrap(); |
124 | assert!(err.traceback(py).unwrap().is(&traceback)); |
125 | }) |
126 | } |
127 | |
128 | #[test ] |
129 | fn test_err_into_py() { |
130 | Python::with_gil(|py| { |
131 | let locals = PyDict::new(py); |
132 | // Produce an error from python so that it has a traceback |
133 | py.run( |
134 | ffi::c_str!( |
135 | r" |
136 | def f(): |
137 | raise ValueError('raised exception') |
138 | " |
139 | ), |
140 | None, |
141 | Some(&locals), |
142 | ) |
143 | .unwrap(); |
144 | let f = locals.get_item("f" ).unwrap().unwrap(); |
145 | let err = f.call0().unwrap_err(); |
146 | let traceback = err.traceback(py).unwrap(); |
147 | let err_object = err.clone_ref(py).into_pyobject(py).unwrap(); |
148 | |
149 | assert!(err_object.getattr("__traceback__" ).unwrap().is(&traceback)); |
150 | }) |
151 | } |
152 | } |
153 | |