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