| 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 | |