1 | use crate::panic::PanicException; |
2 | use crate::type_object::PyTypeInfo; |
3 | use crate::types::{PyTraceback, PyType}; |
4 | use crate::{ |
5 | exceptions::{self, PyBaseException}, |
6 | ffi, |
7 | }; |
8 | use crate::{IntoPy, Py, PyAny, PyObject, Python, ToPyObject}; |
9 | use std::borrow::Cow; |
10 | use std::cell::UnsafeCell; |
11 | use std::ffi::CString; |
12 | |
13 | mod err_state; |
14 | mod impls; |
15 | |
16 | pub use err_state::PyErrArguments; |
17 | use err_state::{PyErrState, PyErrStateLazyFnOutput, PyErrStateNormalized}; |
18 | |
19 | /// Represents a Python exception. |
20 | /// |
21 | /// To avoid needing access to [`Python`] in `Into` conversions to create `PyErr` (thus improving |
22 | /// compatibility with `?` and other Rust errors) this type supports creating exceptions instances |
23 | /// in a lazy fashion, where the full Python object for the exception is created only when needed. |
24 | /// |
25 | /// Accessing the contained exception in any way, such as with [`value`](PyErr::value), |
26 | /// [`get_type`](PyErr::get_type), or [`is_instance`](PyErr::is_instance) will create the full |
27 | /// exception object if it was not already created. |
28 | pub struct PyErr { |
29 | // Safety: can only hand out references when in the "normalized" state. Will never change |
30 | // after normalization. |
31 | // |
32 | // The state is temporarily removed from the PyErr during normalization, to avoid |
33 | // concurrent modifications. |
34 | state: UnsafeCell<Option<PyErrState>>, |
35 | } |
36 | |
37 | // The inner value is only accessed through ways that require proving the gil is held |
38 | #[cfg (feature = "nightly" )] |
39 | unsafe impl crate::marker::Ungil for PyErr {} |
40 | unsafe impl Send for PyErr {} |
41 | unsafe impl Sync for PyErr {} |
42 | |
43 | /// Represents the result of a Python call. |
44 | pub type PyResult<T> = Result<T, PyErr>; |
45 | |
46 | /// Error that indicates a failure to convert a PyAny to a more specific Python type. |
47 | #[derive (Debug)] |
48 | pub struct PyDowncastError<'a> { |
49 | from: &'a PyAny, |
50 | to: Cow<'static, str>, |
51 | } |
52 | |
53 | impl<'a> PyDowncastError<'a> { |
54 | /// Create a new `PyDowncastError` representing a failure to convert the object |
55 | /// `from` into the type named in `to`. |
56 | pub fn new(from: &'a PyAny, to: impl Into<Cow<'static, str>>) -> Self { |
57 | PyDowncastError { |
58 | from, |
59 | to: to.into(), |
60 | } |
61 | } |
62 | } |
63 | |
64 | impl PyErr { |
65 | /// Creates a new PyErr of type `T`. |
66 | /// |
67 | /// `args` can be: |
68 | /// * a tuple: the exception instance will be created using the equivalent to the Python |
69 | /// expression `T(*tuple)` |
70 | /// * any other value: the exception instance will be created using the equivalent to the Python |
71 | /// expression `T(value)` |
72 | /// |
73 | /// This exception instance will be initialized lazily. This avoids the need for the Python GIL |
74 | /// to be held, but requires `args` to be `Send` and `Sync`. If `args` is not `Send` or `Sync`, |
75 | /// consider using [`PyErr::from_value`] instead. |
76 | /// |
77 | /// If `T` does not inherit from `BaseException`, then a `TypeError` will be returned. |
78 | /// |
79 | /// If calling T's constructor with `args` raises an exception, that exception will be returned. |
80 | /// |
81 | /// # Examples |
82 | /// |
83 | /// ``` |
84 | /// use pyo3::prelude::*; |
85 | /// use pyo3::exceptions::PyTypeError; |
86 | /// |
87 | /// #[pyfunction] |
88 | /// fn always_throws() -> PyResult<()> { |
89 | /// Err(PyErr::new::<PyTypeError, _>("Error message" )) |
90 | /// } |
91 | /// # |
92 | /// # Python::with_gil(|py| { |
93 | /// # let fun = pyo3::wrap_pyfunction!(always_throws, py).unwrap(); |
94 | /// # let err = fun.call0().expect_err("called a function that should always return an error but the return value was Ok" ); |
95 | /// # assert!(err.is_instance_of::<PyTypeError>(py)) |
96 | /// # }); |
97 | /// ``` |
98 | /// |
99 | /// In most cases, you can use a concrete exception's constructor instead: |
100 | /// |
101 | /// ``` |
102 | /// use pyo3::prelude::*; |
103 | /// use pyo3::exceptions::PyTypeError; |
104 | /// |
105 | /// #[pyfunction] |
106 | /// fn always_throws() -> PyResult<()> { |
107 | /// Err(PyTypeError::new_err("Error message" )) |
108 | /// } |
109 | /// # |
110 | /// # Python::with_gil(|py| { |
111 | /// # let fun = pyo3::wrap_pyfunction!(always_throws, py).unwrap(); |
112 | /// # let err = fun.call0().expect_err("called a function that should always return an error but the return value was Ok" ); |
113 | /// # assert!(err.is_instance_of::<PyTypeError>(py)) |
114 | /// # }); |
115 | /// ``` |
116 | #[inline ] |
117 | pub fn new<T, A>(args: A) -> PyErr |
118 | where |
119 | T: PyTypeInfo, |
120 | A: PyErrArguments + Send + Sync + 'static, |
121 | { |
122 | PyErr::from_state(PyErrState::Lazy(Box::new(move |py| { |
123 | PyErrStateLazyFnOutput { |
124 | ptype: T::type_object(py).into(), |
125 | pvalue: args.arguments(py), |
126 | } |
127 | }))) |
128 | } |
129 | |
130 | /// Constructs a new PyErr from the given Python type and arguments. |
131 | /// |
132 | /// `ty` is the exception type; usually one of the standard exceptions |
133 | /// like `exceptions::PyRuntimeError`. |
134 | /// |
135 | /// `args` is either a tuple or a single value, with the same meaning as in [`PyErr::new`]. |
136 | /// |
137 | /// If `ty` does not inherit from `BaseException`, then a `TypeError` will be returned. |
138 | /// |
139 | /// If calling `ty` with `args` raises an exception, that exception will be returned. |
140 | pub fn from_type<A>(ty: &PyType, args: A) -> PyErr |
141 | where |
142 | A: PyErrArguments + Send + Sync + 'static, |
143 | { |
144 | PyErr::from_state(PyErrState::lazy(ty, args)) |
145 | } |
146 | |
147 | /// Creates a new PyErr. |
148 | /// |
149 | /// If `obj` is a Python exception object, the PyErr will contain that object. |
150 | /// |
151 | /// If `obj` is a Python exception type object, this is equivalent to `PyErr::from_type(obj, ())`. |
152 | /// |
153 | /// Otherwise, a `TypeError` is created. |
154 | /// |
155 | /// # Examples |
156 | /// ```rust |
157 | /// use pyo3::prelude::*; |
158 | /// use pyo3::exceptions::PyTypeError; |
159 | /// use pyo3::types::{PyType, PyString}; |
160 | /// |
161 | /// Python::with_gil(|py| { |
162 | /// // Case #1: Exception object |
163 | /// let err = PyErr::from_value(PyTypeError::new_err("some type error" ).value(py)); |
164 | /// assert_eq!(err.to_string(), "TypeError: some type error" ); |
165 | /// |
166 | /// // Case #2: Exception type |
167 | /// let err = PyErr::from_value(PyType::new::<PyTypeError>(py)); |
168 | /// assert_eq!(err.to_string(), "TypeError: " ); |
169 | /// |
170 | /// // Case #3: Invalid exception value |
171 | /// let err = PyErr::from_value(PyString::new(py, "foo" ).into()); |
172 | /// assert_eq!( |
173 | /// err.to_string(), |
174 | /// "TypeError: exceptions must derive from BaseException" |
175 | /// ); |
176 | /// }); |
177 | /// ``` |
178 | pub fn from_value(obj: &PyAny) -> PyErr { |
179 | let state = if let Ok(obj) = obj.downcast::<PyBaseException>() { |
180 | PyErrState::normalized(obj) |
181 | } else { |
182 | // Assume obj is Type[Exception]; let later normalization handle if this |
183 | // is not the case |
184 | PyErrState::lazy(obj, obj.py().None()) |
185 | }; |
186 | |
187 | PyErr::from_state(state) |
188 | } |
189 | |
190 | /// Returns the type of this exception. |
191 | /// |
192 | /// # Examples |
193 | /// ```rust |
194 | /// use pyo3::{exceptions::PyTypeError, types::PyType, PyErr, Python}; |
195 | /// |
196 | /// Python::with_gil(|py| { |
197 | /// let err: PyErr = PyTypeError::new_err(("some type error" ,)); |
198 | /// assert!(err.get_type(py).is(PyType::new::<PyTypeError>(py))); |
199 | /// }); |
200 | /// ``` |
201 | pub fn get_type<'py>(&'py self, py: Python<'py>) -> &'py PyType { |
202 | self.normalized(py).ptype(py) |
203 | } |
204 | |
205 | /// Returns the value of this exception. |
206 | /// |
207 | /// # Examples |
208 | /// |
209 | /// ```rust |
210 | /// use pyo3::{exceptions::PyTypeError, PyErr, Python}; |
211 | /// |
212 | /// Python::with_gil(|py| { |
213 | /// let err: PyErr = PyTypeError::new_err(("some type error" ,)); |
214 | /// assert!(err.is_instance_of::<PyTypeError>(py)); |
215 | /// assert_eq!(err.value(py).to_string(), "some type error" ); |
216 | /// }); |
217 | /// ``` |
218 | pub fn value<'py>(&'py self, py: Python<'py>) -> &'py PyBaseException { |
219 | self.normalized(py).pvalue.as_ref(py) |
220 | } |
221 | |
222 | /// Consumes self to take ownership of the exception value contained in this error. |
223 | pub fn into_value(self, py: Python<'_>) -> Py<PyBaseException> { |
224 | // NB technically this causes one reference count increase and decrease in quick succession |
225 | // on pvalue, but it's probably not worth optimizing this right now for the additional code |
226 | // complexity. |
227 | let normalized = self.normalized(py); |
228 | let exc = normalized.pvalue.clone_ref(py); |
229 | if let Some(tb) = normalized.ptraceback(py) { |
230 | unsafe { |
231 | ffi::PyException_SetTraceback(exc.as_ptr(), tb.as_ptr()); |
232 | } |
233 | } |
234 | exc |
235 | } |
236 | |
237 | /// Returns the traceback of this exception object. |
238 | /// |
239 | /// # Examples |
240 | /// ```rust |
241 | /// use pyo3::{exceptions::PyTypeError, Python}; |
242 | /// |
243 | /// Python::with_gil(|py| { |
244 | /// let err = PyTypeError::new_err(("some type error" ,)); |
245 | /// assert!(err.traceback(py).is_none()); |
246 | /// }); |
247 | /// ``` |
248 | pub fn traceback<'py>(&'py self, py: Python<'py>) -> Option<&'py PyTraceback> { |
249 | self.normalized(py).ptraceback(py) |
250 | } |
251 | |
252 | /// Gets whether an error is present in the Python interpreter's global state. |
253 | #[inline ] |
254 | pub fn occurred(_: Python<'_>) -> bool { |
255 | unsafe { !ffi::PyErr_Occurred().is_null() } |
256 | } |
257 | |
258 | /// Takes the current error from the Python interpreter's global state and clears the global |
259 | /// state. If no error is set, returns `None`. |
260 | /// |
261 | /// If the error is a `PanicException` (which would have originated from a panic in a pyo3 |
262 | /// callback) then this function will resume the panic. |
263 | /// |
264 | /// Use this function when it is not known if an error should be present. If the error is |
265 | /// expected to have been set, for example from [`PyErr::occurred`] or by an error return value |
266 | /// from a C FFI function, use [`PyErr::fetch`]. |
267 | pub fn take(py: Python<'_>) -> Option<PyErr> { |
268 | Self::_take(py) |
269 | } |
270 | |
271 | #[cfg (not(Py_3_12))] |
272 | fn _take(py: Python<'_>) -> Option<PyErr> { |
273 | let (ptype, pvalue, ptraceback) = unsafe { |
274 | let mut ptype: *mut ffi::PyObject = std::ptr::null_mut(); |
275 | let mut pvalue: *mut ffi::PyObject = std::ptr::null_mut(); |
276 | let mut ptraceback: *mut ffi::PyObject = std::ptr::null_mut(); |
277 | ffi::PyErr_Fetch(&mut ptype, &mut pvalue, &mut ptraceback); |
278 | |
279 | // Convert to Py immediately so that any references are freed by early return. |
280 | let ptype = PyObject::from_owned_ptr_or_opt(py, ptype); |
281 | let pvalue = PyObject::from_owned_ptr_or_opt(py, pvalue); |
282 | let ptraceback = PyObject::from_owned_ptr_or_opt(py, ptraceback); |
283 | |
284 | // A valid exception state should always have a non-null ptype, but the other two may be |
285 | // null. |
286 | let ptype = match ptype { |
287 | Some(ptype) => ptype, |
288 | None => { |
289 | debug_assert!( |
290 | pvalue.is_none(), |
291 | "Exception type was null but value was not null" |
292 | ); |
293 | debug_assert!( |
294 | ptraceback.is_none(), |
295 | "Exception type was null but traceback was not null" |
296 | ); |
297 | return None; |
298 | } |
299 | }; |
300 | |
301 | (ptype, pvalue, ptraceback) |
302 | }; |
303 | |
304 | if ptype.as_ptr() == PanicException::type_object_raw(py).cast() { |
305 | let msg = pvalue |
306 | .as_ref() |
307 | .and_then(|obj| obj.as_ref(py).str().ok()) |
308 | .map(|py_str| py_str.to_string_lossy().into()) |
309 | .unwrap_or_else(|| String::from("Unwrapped panic from Python code" )); |
310 | |
311 | let state = PyErrState::FfiTuple { |
312 | ptype, |
313 | pvalue, |
314 | ptraceback, |
315 | }; |
316 | Self::print_panic_and_unwind(py, state, msg) |
317 | } |
318 | |
319 | Some(PyErr::from_state(PyErrState::FfiTuple { |
320 | ptype, |
321 | pvalue, |
322 | ptraceback, |
323 | })) |
324 | } |
325 | |
326 | #[cfg (Py_3_12)] |
327 | fn _take(py: Python<'_>) -> Option<PyErr> { |
328 | let state = PyErrStateNormalized::take(py)?; |
329 | let pvalue = state.pvalue.as_ref(py); |
330 | if pvalue.get_type().as_ptr() == PanicException::type_object_raw(py).cast() { |
331 | let msg: String = pvalue |
332 | .str() |
333 | .map(|py_str| py_str.to_string_lossy().into()) |
334 | .unwrap_or_else(|_| String::from("Unwrapped panic from Python code" )); |
335 | Self::print_panic_and_unwind(py, PyErrState::Normalized(state), msg) |
336 | } |
337 | |
338 | Some(PyErr::from_state(PyErrState::Normalized(state))) |
339 | } |
340 | |
341 | fn print_panic_and_unwind(py: Python<'_>, state: PyErrState, msg: String) -> ! { |
342 | eprintln!("--- PyO3 is resuming a panic after fetching a PanicException from Python. ---" ); |
343 | eprintln!("Python stack trace below:" ); |
344 | |
345 | state.restore(py); |
346 | |
347 | unsafe { |
348 | ffi::PyErr_PrintEx(0); |
349 | } |
350 | |
351 | std::panic::resume_unwind(Box::new(msg)) |
352 | } |
353 | |
354 | /// Equivalent to [PyErr::take], but when no error is set: |
355 | /// - Panics in debug mode. |
356 | /// - Returns a `SystemError` in release mode. |
357 | /// |
358 | /// This behavior is consistent with Python's internal handling of what happens when a C return |
359 | /// value indicates an error occurred but the global error state is empty. (A lack of exception |
360 | /// should be treated as a bug in the code which returned an error code but did not set an |
361 | /// exception.) |
362 | /// |
363 | /// Use this function when the error is expected to have been set, for example from |
364 | /// [PyErr::occurred] or by an error return value from a C FFI function. |
365 | #[cfg_attr (all(debug_assertions, track_caller), track_caller)] |
366 | #[inline ] |
367 | pub fn fetch(py: Python<'_>) -> PyErr { |
368 | const FAILED_TO_FETCH: &str = "attempted to fetch exception but none was set" ; |
369 | match PyErr::take(py) { |
370 | Some(err) => err, |
371 | #[cfg (debug_assertions)] |
372 | None => panic!("{}" , FAILED_TO_FETCH), |
373 | #[cfg (not(debug_assertions))] |
374 | None => exceptions::PySystemError::new_err(FAILED_TO_FETCH), |
375 | } |
376 | } |
377 | |
378 | /// Creates a new exception type with the given name and docstring. |
379 | /// |
380 | /// - `base` can be an existing exception type to subclass, or a tuple of classes. |
381 | /// - `dict` specifies an optional dictionary of class variables and methods. |
382 | /// - `doc` will be the docstring seen by python users. |
383 | /// |
384 | /// |
385 | /// # Errors |
386 | /// |
387 | /// This function returns an error if `name` is not of the form `<module>.<ExceptionName>`. |
388 | /// |
389 | /// # Panics |
390 | /// |
391 | /// This function will panic if `name` or `doc` cannot be converted to [`CString`]s. |
392 | pub fn new_type( |
393 | py: Python<'_>, |
394 | name: &str, |
395 | doc: Option<&str>, |
396 | base: Option<&PyType>, |
397 | dict: Option<PyObject>, |
398 | ) -> PyResult<Py<PyType>> { |
399 | let base: *mut ffi::PyObject = match base { |
400 | None => std::ptr::null_mut(), |
401 | Some(obj) => obj.as_ptr(), |
402 | }; |
403 | |
404 | let dict: *mut ffi::PyObject = match dict { |
405 | None => std::ptr::null_mut(), |
406 | Some(obj) => obj.as_ptr(), |
407 | }; |
408 | |
409 | let null_terminated_name = |
410 | CString::new(name).expect("Failed to initialize nul terminated exception name" ); |
411 | |
412 | let null_terminated_doc = |
413 | doc.map(|d| CString::new(d).expect("Failed to initialize nul terminated docstring" )); |
414 | |
415 | let null_terminated_doc_ptr = match null_terminated_doc.as_ref() { |
416 | Some(c) => c.as_ptr(), |
417 | None => std::ptr::null(), |
418 | }; |
419 | |
420 | let ptr = unsafe { |
421 | ffi::PyErr_NewExceptionWithDoc( |
422 | null_terminated_name.as_ptr(), |
423 | null_terminated_doc_ptr, |
424 | base, |
425 | dict, |
426 | ) |
427 | }; |
428 | |
429 | unsafe { Py::from_owned_ptr_or_err(py, ptr) } |
430 | } |
431 | |
432 | /// Prints a standard traceback to `sys.stderr`. |
433 | pub fn display(&self, py: Python<'_>) { |
434 | #[cfg (Py_3_12)] |
435 | unsafe { |
436 | ffi::PyErr_DisplayException(self.value(py).as_ptr()) |
437 | } |
438 | |
439 | #[cfg (not(Py_3_12))] |
440 | unsafe { |
441 | ffi::PyErr_Display( |
442 | self.get_type(py).as_ptr(), |
443 | self.value(py).as_ptr(), |
444 | self.traceback(py) |
445 | .map_or(std::ptr::null_mut(), |traceback| traceback.as_ptr()), |
446 | ) |
447 | } |
448 | } |
449 | |
450 | /// Calls `sys.excepthook` and then prints a standard traceback to `sys.stderr`. |
451 | pub fn print(&self, py: Python<'_>) { |
452 | self.clone_ref(py).restore(py); |
453 | unsafe { ffi::PyErr_PrintEx(0) } |
454 | } |
455 | |
456 | /// Calls `sys.excepthook` and then prints a standard traceback to `sys.stderr`. |
457 | /// |
458 | /// Additionally sets `sys.last_{type,value,traceback,exc}` attributes to this exception. |
459 | pub fn print_and_set_sys_last_vars(&self, py: Python<'_>) { |
460 | self.clone_ref(py).restore(py); |
461 | unsafe { ffi::PyErr_PrintEx(1) } |
462 | } |
463 | |
464 | /// Returns true if the current exception matches the exception in `exc`. |
465 | /// |
466 | /// If `exc` is a class object, this also returns `true` when `self` is an instance of a subclass. |
467 | /// If `exc` is a tuple, all exceptions in the tuple (and recursively in subtuples) are searched for a match. |
468 | pub fn matches<T>(&self, py: Python<'_>, exc: T) -> bool |
469 | where |
470 | T: ToPyObject, |
471 | { |
472 | self.is_instance(py, exc.to_object(py).as_ref(py)) |
473 | } |
474 | |
475 | /// Returns true if the current exception is instance of `T`. |
476 | #[inline ] |
477 | pub fn is_instance(&self, py: Python<'_>, ty: &PyAny) -> bool { |
478 | (unsafe { ffi::PyErr_GivenExceptionMatches(self.get_type(py).as_ptr(), ty.as_ptr()) }) != 0 |
479 | } |
480 | |
481 | /// Returns true if the current exception is instance of `T`. |
482 | #[inline ] |
483 | pub fn is_instance_of<T>(&self, py: Python<'_>) -> bool |
484 | where |
485 | T: PyTypeInfo, |
486 | { |
487 | self.is_instance(py, T::type_object(py)) |
488 | } |
489 | |
490 | /// Writes the error back to the Python interpreter's global state. |
491 | /// This is the opposite of `PyErr::fetch()`. |
492 | #[inline ] |
493 | pub fn restore(self, py: Python<'_>) { |
494 | self.state |
495 | .into_inner() |
496 | .expect("PyErr state should never be invalid outside of normalization" ) |
497 | .restore(py) |
498 | } |
499 | |
500 | /// Reports the error as unraisable. |
501 | /// |
502 | /// This calls `sys.unraisablehook()` using the current exception and obj argument. |
503 | /// |
504 | /// This method is useful to report errors in situations where there is no good mechanism |
505 | /// to report back to the Python land. In Python this is used to indicate errors in |
506 | /// background threads or destructors which are protected. In Rust code this is commonly |
507 | /// useful when you are calling into a Python callback which might fail, but there is no |
508 | /// obvious way to handle this error other than logging it. |
509 | /// |
510 | /// Calling this method has the benefit that the error goes back into a standardized callback |
511 | /// in Python which for instance allows unittests to ensure that no unraisable error |
512 | /// actually happend by hooking `sys.unraisablehook`. |
513 | /// |
514 | /// Example: |
515 | /// ```rust |
516 | /// # use pyo3::prelude::*; |
517 | /// # use pyo3::exceptions::PyRuntimeError; |
518 | /// # fn failing_function() -> PyResult<()> { Err(PyRuntimeError::new_err("foo" )) } |
519 | /// # fn main() -> PyResult<()> { |
520 | /// Python::with_gil(|py| { |
521 | /// match failing_function() { |
522 | /// Err(pyerr) => pyerr.write_unraisable(py, None), |
523 | /// Ok(..) => { /* do something here */ } |
524 | /// } |
525 | /// Ok(()) |
526 | /// }) |
527 | /// # } |
528 | #[inline ] |
529 | pub fn write_unraisable(self, py: Python<'_>, obj: Option<&PyAny>) { |
530 | self.restore(py); |
531 | unsafe { ffi::PyErr_WriteUnraisable(obj.map_or(std::ptr::null_mut(), |x| x.as_ptr())) } |
532 | } |
533 | |
534 | /// Issues a warning message. |
535 | /// |
536 | /// May return an `Err(PyErr)` if warnings-as-errors is enabled. |
537 | /// |
538 | /// Equivalent to `warnings.warn()` in Python. |
539 | /// |
540 | /// The `category` should be one of the `Warning` classes available in |
541 | /// [`pyo3::exceptions`](crate::exceptions), or a subclass. The Python |
542 | /// object can be retrieved using [`Python::get_type()`]. |
543 | /// |
544 | /// Example: |
545 | /// ```rust |
546 | /// # use pyo3::prelude::*; |
547 | /// # fn main() -> PyResult<()> { |
548 | /// Python::with_gil(|py| { |
549 | /// let user_warning = py.get_type::<pyo3::exceptions::PyUserWarning>(); |
550 | /// PyErr::warn(py, user_warning, "I am warning you" , 0)?; |
551 | /// Ok(()) |
552 | /// }) |
553 | /// # } |
554 | /// ``` |
555 | pub fn warn(py: Python<'_>, category: &PyAny, message: &str, stacklevel: i32) -> PyResult<()> { |
556 | let message = CString::new(message)?; |
557 | error_on_minusone(py, unsafe { |
558 | ffi::PyErr_WarnEx( |
559 | category.as_ptr(), |
560 | message.as_ptr(), |
561 | stacklevel as ffi::Py_ssize_t, |
562 | ) |
563 | }) |
564 | } |
565 | |
566 | /// Issues a warning message, with more control over the warning attributes. |
567 | /// |
568 | /// May return a `PyErr` if warnings-as-errors is enabled. |
569 | /// |
570 | /// Equivalent to `warnings.warn_explicit()` in Python. |
571 | /// |
572 | /// The `category` should be one of the `Warning` classes available in |
573 | /// [`pyo3::exceptions`](crate::exceptions), or a subclass. |
574 | pub fn warn_explicit( |
575 | py: Python<'_>, |
576 | category: &PyAny, |
577 | message: &str, |
578 | filename: &str, |
579 | lineno: i32, |
580 | module: Option<&str>, |
581 | registry: Option<&PyAny>, |
582 | ) -> PyResult<()> { |
583 | let message = CString::new(message)?; |
584 | let filename = CString::new(filename)?; |
585 | let module = module.map(CString::new).transpose()?; |
586 | let module_ptr = match module { |
587 | None => std::ptr::null_mut(), |
588 | Some(s) => s.as_ptr(), |
589 | }; |
590 | let registry: *mut ffi::PyObject = match registry { |
591 | None => std::ptr::null_mut(), |
592 | Some(obj) => obj.as_ptr(), |
593 | }; |
594 | error_on_minusone(py, unsafe { |
595 | ffi::PyErr_WarnExplicit( |
596 | category.as_ptr(), |
597 | message.as_ptr(), |
598 | filename.as_ptr(), |
599 | lineno, |
600 | module_ptr, |
601 | registry, |
602 | ) |
603 | }) |
604 | } |
605 | |
606 | /// Clone the PyErr. This requires the GIL, which is why PyErr does not implement Clone. |
607 | /// |
608 | /// # Examples |
609 | /// ```rust |
610 | /// use pyo3::{exceptions::PyTypeError, PyErr, Python}; |
611 | /// Python::with_gil(|py| { |
612 | /// let err: PyErr = PyTypeError::new_err(("some type error" ,)); |
613 | /// let err_clone = err.clone_ref(py); |
614 | /// assert!(err.get_type(py).is(err_clone.get_type(py))); |
615 | /// assert!(err.value(py).is(err_clone.value(py))); |
616 | /// match err.traceback(py) { |
617 | /// None => assert!(err_clone.traceback(py).is_none()), |
618 | /// Some(tb) => assert!(err_clone.traceback(py).unwrap().is(tb)), |
619 | /// } |
620 | /// }); |
621 | /// ``` |
622 | #[inline ] |
623 | pub fn clone_ref(&self, py: Python<'_>) -> PyErr { |
624 | PyErr::from_state(PyErrState::Normalized(self.normalized(py).clone())) |
625 | } |
626 | |
627 | /// Return the cause (either an exception instance, or None, set by `raise ... from ...`) |
628 | /// associated with the exception, as accessible from Python through `__cause__`. |
629 | pub fn cause(&self, py: Python<'_>) -> Option<PyErr> { |
630 | let value = self.value(py); |
631 | let obj = |
632 | unsafe { py.from_owned_ptr_or_opt::<PyAny>(ffi::PyException_GetCause(value.as_ptr())) }; |
633 | obj.map(Self::from_value) |
634 | } |
635 | |
636 | /// Set the cause associated with the exception, pass `None` to clear it. |
637 | pub fn set_cause(&self, py: Python<'_>, cause: Option<Self>) { |
638 | let value = self.value(py); |
639 | let cause = cause.map(|err| err.into_value(py)); |
640 | unsafe { |
641 | // PyException_SetCause _steals_ a reference to cause, so must use .into_ptr() |
642 | ffi::PyException_SetCause( |
643 | value.as_ptr(), |
644 | cause.map_or(std::ptr::null_mut(), Py::into_ptr), |
645 | ); |
646 | } |
647 | } |
648 | |
649 | #[inline ] |
650 | fn from_state(state: PyErrState) -> PyErr { |
651 | PyErr { |
652 | state: UnsafeCell::new(Some(state)), |
653 | } |
654 | } |
655 | |
656 | #[inline ] |
657 | fn normalized(&self, py: Python<'_>) -> &PyErrStateNormalized { |
658 | if let Some(PyErrState::Normalized(n)) = unsafe { |
659 | // Safety: self.state will never be written again once normalized. |
660 | &*self.state.get() |
661 | } { |
662 | return n; |
663 | } |
664 | |
665 | self.make_normalized(py) |
666 | } |
667 | |
668 | #[cold ] |
669 | fn make_normalized(&self, py: Python<'_>) -> &PyErrStateNormalized { |
670 | // This process is safe because: |
671 | // - Access is guaranteed not to be concurrent thanks to `Python` GIL token |
672 | // - Write happens only once, and then never will change again. |
673 | // - State is set to None during the normalization process, so that a second |
674 | // concurrent normalization attempt will panic before changing anything. |
675 | |
676 | let state = unsafe { |
677 | (*self.state.get()) |
678 | .take() |
679 | .expect("Cannot normalize a PyErr while already normalizing it." ) |
680 | }; |
681 | |
682 | unsafe { |
683 | let self_state = &mut *self.state.get(); |
684 | *self_state = Some(PyErrState::Normalized(state.normalize(py))); |
685 | match self_state { |
686 | Some(PyErrState::Normalized(n)) => n, |
687 | _ => unreachable!(), |
688 | } |
689 | } |
690 | } |
691 | } |
692 | |
693 | impl std::fmt::Debug for PyErr { |
694 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { |
695 | Python::with_gil(|py: Python<'_>| { |
696 | f&mut DebugStruct<'_, '_>.debug_struct("PyErr" ) |
697 | .field("type" , self.get_type(py)) |
698 | .field("value" , self.value(py)) |
699 | .field(name:"traceback" , &self.traceback(py)) |
700 | .finish() |
701 | }) |
702 | } |
703 | } |
704 | |
705 | impl std::fmt::Display for PyErr { |
706 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
707 | Python::with_gil(|py: Python<'_>| { |
708 | let value: &PyBaseException = self.value(py); |
709 | let type_name: &str = value.get_type().name().map_err(|_| std::fmt::Error)?; |
710 | write!(f, " {}" , type_name)?; |
711 | if let Ok(s: &PyString) = value.str() { |
712 | write!(f, ": {}" , &s.to_string_lossy()) |
713 | } else { |
714 | write!(f, ": <exception str() failed>" ) |
715 | } |
716 | }) |
717 | } |
718 | } |
719 | |
720 | impl std::error::Error for PyErr {} |
721 | |
722 | impl IntoPy<PyObject> for PyErr { |
723 | fn into_py(self, py: Python<'_>) -> PyObject { |
724 | self.into_value(py).into() |
725 | } |
726 | } |
727 | |
728 | impl ToPyObject for PyErr { |
729 | fn to_object(&self, py: Python<'_>) -> PyObject { |
730 | self.clone_ref(py).into_py(py) |
731 | } |
732 | } |
733 | |
734 | impl<'a> IntoPy<PyObject> for &'a PyErr { |
735 | fn into_py(self, py: Python<'_>) -> PyObject { |
736 | self.clone_ref(py).into_py(py) |
737 | } |
738 | } |
739 | |
740 | struct PyDowncastErrorArguments { |
741 | from: Py<PyType>, |
742 | to: Cow<'static, str>, |
743 | } |
744 | |
745 | impl PyErrArguments for PyDowncastErrorArguments { |
746 | fn arguments(self, py: Python<'_>) -> PyObject { |
747 | formatString!( |
748 | "' {}' object cannot be converted to ' {}'" , |
749 | self.from |
750 | .as_ref(py) |
751 | .name() |
752 | .unwrap_or("<failed to extract type name>" ), |
753 | self.to |
754 | ) |
755 | .to_object(py) |
756 | } |
757 | } |
758 | |
759 | /// Convert `PyDowncastError` to Python `TypeError`. |
760 | impl<'a> std::convert::From<PyDowncastError<'a>> for PyErr { |
761 | fn from(err: PyDowncastError<'_>) -> PyErr { |
762 | let args: PyDowncastErrorArguments = PyDowncastErrorArguments { |
763 | from: err.from.get_type().into(), |
764 | to: err.to, |
765 | }; |
766 | |
767 | exceptions::PyTypeError::new_err(args) |
768 | } |
769 | } |
770 | |
771 | impl<'a> std::error::Error for PyDowncastError<'a> {} |
772 | |
773 | impl<'a> std::fmt::Display for PyDowncastError<'a> { |
774 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { |
775 | write!( |
776 | f, |
777 | "' {}' object cannot be converted to ' {}'" , |
778 | self.from.get_type().name().map_err(|_| std::fmt::Error)?, |
779 | self.to |
780 | ) |
781 | } |
782 | } |
783 | |
784 | pub fn panic_after_error(_py: Python<'_>) -> ! { |
785 | unsafe { |
786 | ffi::PyErr_Print(); |
787 | } |
788 | panic!("Python API call failed" ); |
789 | } |
790 | |
791 | /// Returns Ok if the error code is not -1. |
792 | #[inline ] |
793 | pub(crate) fn error_on_minusone<T: SignedInteger>(py: Python<'_>, result: T) -> PyResult<()> { |
794 | if result != T::MINUS_ONE { |
795 | Ok(()) |
796 | } else { |
797 | Err(PyErr::fetch(py)) |
798 | } |
799 | } |
800 | |
801 | pub(crate) trait SignedInteger: Eq { |
802 | const MINUS_ONE: Self; |
803 | } |
804 | |
805 | macro_rules! impl_signed_integer { |
806 | ($t:ty) => { |
807 | impl SignedInteger for $t { |
808 | const MINUS_ONE: Self = -1; |
809 | } |
810 | }; |
811 | } |
812 | |
813 | impl_signed_integer!(i8); |
814 | impl_signed_integer!(i16); |
815 | impl_signed_integer!(i32); |
816 | impl_signed_integer!(i64); |
817 | impl_signed_integer!(i128); |
818 | impl_signed_integer!(isize); |
819 | |
820 | #[cfg (test)] |
821 | mod tests { |
822 | use super::PyErrState; |
823 | use crate::exceptions::{self, PyTypeError, PyValueError}; |
824 | use crate::tests::common::CatchWarnings; |
825 | use crate::{PyErr, PyTypeInfo, Python}; |
826 | |
827 | #[test ] |
828 | fn no_error() { |
829 | assert!(Python::with_gil(PyErr::take).is_none()); |
830 | } |
831 | |
832 | #[test ] |
833 | fn set_valueerror() { |
834 | Python::with_gil(|py| { |
835 | let err: PyErr = exceptions::PyValueError::new_err("some exception message" ); |
836 | assert!(err.is_instance_of::<exceptions::PyValueError>(py)); |
837 | err.restore(py); |
838 | assert!(PyErr::occurred(py)); |
839 | let err = PyErr::fetch(py); |
840 | assert!(err.is_instance_of::<exceptions::PyValueError>(py)); |
841 | assert_eq!(err.to_string(), "ValueError: some exception message" ); |
842 | }) |
843 | } |
844 | |
845 | #[test ] |
846 | fn invalid_error_type() { |
847 | Python::with_gil(|py| { |
848 | let err: PyErr = PyErr::new::<crate::types::PyString, _>(()); |
849 | assert!(err.is_instance_of::<exceptions::PyTypeError>(py)); |
850 | err.restore(py); |
851 | let err = PyErr::fetch(py); |
852 | |
853 | assert!(err.is_instance_of::<exceptions::PyTypeError>(py)); |
854 | assert_eq!( |
855 | err.to_string(), |
856 | "TypeError: exceptions must derive from BaseException" |
857 | ); |
858 | }) |
859 | } |
860 | |
861 | #[test ] |
862 | fn set_typeerror() { |
863 | Python::with_gil(|py| { |
864 | let err: PyErr = exceptions::PyTypeError::new_err(()); |
865 | err.restore(py); |
866 | assert!(PyErr::occurred(py)); |
867 | drop(PyErr::fetch(py)); |
868 | }); |
869 | } |
870 | |
871 | #[test ] |
872 | #[should_panic (expected = "new panic" )] |
873 | fn fetching_panic_exception_resumes_unwind() { |
874 | use crate::panic::PanicException; |
875 | |
876 | Python::with_gil(|py| { |
877 | let err: PyErr = PanicException::new_err("new panic" ); |
878 | err.restore(py); |
879 | assert!(PyErr::occurred(py)); |
880 | |
881 | // should resume unwind |
882 | let _ = PyErr::fetch(py); |
883 | }); |
884 | } |
885 | |
886 | #[test ] |
887 | #[should_panic (expected = "new panic" )] |
888 | #[cfg (not(Py_3_12))] |
889 | fn fetching_normalized_panic_exception_resumes_unwind() { |
890 | use crate::panic::PanicException; |
891 | |
892 | Python::with_gil(|py| { |
893 | let err: PyErr = PanicException::new_err("new panic" ); |
894 | // Restoring an error doesn't normalize it before Python 3.12, |
895 | // so we have to explicitly test this case. |
896 | let _ = err.normalized(py); |
897 | err.restore(py); |
898 | assert!(PyErr::occurred(py)); |
899 | |
900 | // should resume unwind |
901 | let _ = PyErr::fetch(py); |
902 | }); |
903 | } |
904 | |
905 | #[test ] |
906 | fn err_debug() { |
907 | // Debug representation should be like the following (without the newlines): |
908 | // PyErr { |
909 | // type: <class 'Exception'>, |
910 | // value: Exception('banana'), |
911 | // traceback: Some(<traceback object at 0x..)" |
912 | // } |
913 | |
914 | Python::with_gil(|py| { |
915 | let err = py |
916 | .run("raise Exception('banana')" , None, None) |
917 | .expect_err("raising should have given us an error" ); |
918 | |
919 | let debug_str = format!(" {:?}" , err); |
920 | assert!(debug_str.starts_with("PyErr { " )); |
921 | assert!(debug_str.ends_with(" }" )); |
922 | |
923 | // strip "PyErr { " and " }" |
924 | let mut fields = debug_str["PyErr { " .len()..debug_str.len() - 2].split(", " ); |
925 | |
926 | assert_eq!(fields.next().unwrap(), "type: <class 'Exception'>" ); |
927 | assert_eq!(fields.next().unwrap(), "value: Exception('banana')" ); |
928 | |
929 | let traceback = fields.next().unwrap(); |
930 | assert!(traceback.starts_with("traceback: Some(<traceback object at 0x" )); |
931 | assert!(traceback.ends_with(">)" )); |
932 | |
933 | assert!(fields.next().is_none()); |
934 | }); |
935 | } |
936 | |
937 | #[test ] |
938 | fn err_display() { |
939 | Python::with_gil(|py| { |
940 | let err = py |
941 | .run("raise Exception('banana')" , None, None) |
942 | .expect_err("raising should have given us an error" ); |
943 | assert_eq!(err.to_string(), "Exception: banana" ); |
944 | }); |
945 | } |
946 | |
947 | #[test ] |
948 | fn test_pyerr_send_sync() { |
949 | fn is_send<T: Send>() {} |
950 | fn is_sync<T: Sync>() {} |
951 | |
952 | is_send::<PyErr>(); |
953 | is_sync::<PyErr>(); |
954 | |
955 | is_send::<PyErrState>(); |
956 | is_sync::<PyErrState>(); |
957 | } |
958 | |
959 | #[test ] |
960 | fn test_pyerr_matches() { |
961 | Python::with_gil(|py| { |
962 | let err = PyErr::new::<PyValueError, _>("foo" ); |
963 | assert!(err.matches(py, PyValueError::type_object(py))); |
964 | |
965 | assert!(err.matches( |
966 | py, |
967 | (PyValueError::type_object(py), PyTypeError::type_object(py)) |
968 | )); |
969 | |
970 | assert!(!err.matches(py, PyTypeError::type_object(py))); |
971 | |
972 | // String is not a valid exception class, so we should get a TypeError |
973 | let err: PyErr = PyErr::from_type(crate::types::PyString::type_object(py), "foo" ); |
974 | assert!(err.matches(py, PyTypeError::type_object(py))); |
975 | }) |
976 | } |
977 | |
978 | #[test ] |
979 | fn test_pyerr_cause() { |
980 | Python::with_gil(|py| { |
981 | let err = py |
982 | .run("raise Exception('banana')" , None, None) |
983 | .expect_err("raising should have given us an error" ); |
984 | assert!(err.cause(py).is_none()); |
985 | |
986 | let err = py |
987 | .run( |
988 | "raise Exception('banana') from Exception('apple')" , |
989 | None, |
990 | None, |
991 | ) |
992 | .expect_err("raising should have given us an error" ); |
993 | let cause = err |
994 | .cause(py) |
995 | .expect("raising from should have given us a cause" ); |
996 | assert_eq!(cause.to_string(), "Exception: apple" ); |
997 | |
998 | err.set_cause(py, None); |
999 | assert!(err.cause(py).is_none()); |
1000 | |
1001 | let new_cause = exceptions::PyValueError::new_err("orange" ); |
1002 | err.set_cause(py, Some(new_cause)); |
1003 | let cause = err |
1004 | .cause(py) |
1005 | .expect("set_cause should have given us a cause" ); |
1006 | assert_eq!(cause.to_string(), "ValueError: orange" ); |
1007 | }); |
1008 | } |
1009 | |
1010 | #[test ] |
1011 | fn warnings() { |
1012 | // Note: although the warning filter is interpreter global, keeping the |
1013 | // GIL locked should prevent effects to be visible to other testing |
1014 | // threads. |
1015 | Python::with_gil(|py| { |
1016 | let cls = py.get_type::<exceptions::PyUserWarning>(); |
1017 | |
1018 | // Reset warning filter to default state |
1019 | let warnings = py.import("warnings" ).unwrap(); |
1020 | warnings.call_method0("resetwarnings" ).unwrap(); |
1021 | |
1022 | // First, test the warning is emitted |
1023 | assert_warnings!( |
1024 | py, |
1025 | { PyErr::warn(py, cls, "I am warning you" , 0).unwrap() }, |
1026 | [(exceptions::PyUserWarning, "I am warning you" )] |
1027 | ); |
1028 | |
1029 | // Test with raising |
1030 | warnings |
1031 | .call_method1("simplefilter" , ("error" , cls)) |
1032 | .unwrap(); |
1033 | PyErr::warn(py, cls, "I am warning you" , 0).unwrap_err(); |
1034 | |
1035 | // Test with error for an explicit module |
1036 | warnings.call_method0("resetwarnings" ).unwrap(); |
1037 | warnings |
1038 | .call_method1("filterwarnings" , ("error" , "" , cls, "pyo3test" )) |
1039 | .unwrap(); |
1040 | |
1041 | // This has the wrong module and will not raise, just be emitted |
1042 | assert_warnings!( |
1043 | py, |
1044 | { PyErr::warn(py, cls, "I am warning you" , 0).unwrap() }, |
1045 | [(exceptions::PyUserWarning, "I am warning you" )] |
1046 | ); |
1047 | |
1048 | let err = |
1049 | PyErr::warn_explicit(py, cls, "I am warning you" , "pyo3test.py" , 427, None, None) |
1050 | .unwrap_err(); |
1051 | assert!(err |
1052 | .value(py) |
1053 | .getattr("args" ) |
1054 | .unwrap() |
1055 | .get_item(0) |
1056 | .unwrap() |
1057 | .eq("I am warning you" ) |
1058 | .unwrap()); |
1059 | |
1060 | // Finally, reset filter again |
1061 | warnings.call_method0("resetwarnings" ).unwrap(); |
1062 | }); |
1063 | } |
1064 | } |
1065 | |