pyo3/
exceptions.rs

1//! Exception and warning types defined by Python.
2//!
3//! The structs in this module represent Python's built-in exceptions and
4//! warnings, while the modules comprise structs representing errors defined in
5//! Python code.
6//!
7//! The latter are created with the
8//! [`import_exception`](crate::import_exception) macro, which you can use
9//! yourself to import Python classes that are ultimately derived from
10//! `BaseException`.
11
12use crate::{ffi, Bound, PyResult, Python};
13use std::ffi::CStr;
14use std::ops;
15
16/// The boilerplate to convert between a Rust type and a Python exception.
17#[doc(hidden)]
18#[macro_export]
19macro_rules! impl_exception_boilerplate {
20    ($name: ident) => {
21        // FIXME https://github.com/PyO3/pyo3/issues/3903
22        #[allow(unknown_lints, non_local_definitions)]
23        #[cfg(feature = "gil-refs")]
24        impl ::std::convert::From<&$name> for $crate::PyErr {
25            #[inline]
26            fn from(err: &$name) -> $crate::PyErr {
27                #[allow(deprecated)]
28                $crate::PyErr::from_value(err)
29            }
30        }
31
32        $crate::impl_exception_boilerplate_bound!($name);
33
34        #[cfg(feature = "gil-refs")]
35        impl ::std::error::Error for $name {
36            fn source(&self) -> ::std::option::Option<&(dyn ::std::error::Error + 'static)> {
37                unsafe {
38                    #[allow(deprecated)]
39                    let cause: &$crate::exceptions::PyBaseException = self
40                        .py()
41                        .from_owned_ptr_or_opt($crate::ffi::PyException_GetCause(self.as_ptr()))?;
42
43                    ::std::option::Option::Some(cause)
44                }
45            }
46        }
47
48        impl $crate::ToPyErr for $name {}
49    };
50}
51
52#[doc(hidden)]
53#[macro_export]
54macro_rules! impl_exception_boilerplate_bound {
55    ($name: ident) => {
56        impl $name {
57            /// Creates a new [`PyErr`] of this type.
58            ///
59            /// [`PyErr`]: https://docs.rs/pyo3/latest/pyo3/struct.PyErr.html "PyErr in pyo3"
60            #[inline]
61            #[allow(dead_code)]
62            pub fn new_err<A>(args: A) -> $crate::PyErr
63            where
64                A: $crate::PyErrArguments + ::std::marker::Send + ::std::marker::Sync + 'static,
65            {
66                $crate::PyErr::new::<$name, A>(args)
67            }
68        }
69    };
70}
71
72/// Defines a Rust type for an exception defined in Python code.
73///
74/// # Syntax
75///
76/// ```import_exception!(module, MyError)```
77///
78/// * `module` is the name of the containing module.
79/// * `MyError` is the name of the new exception type.
80///
81/// # Examples
82/// ```
83/// use pyo3::import_exception;
84/// use pyo3::types::IntoPyDict;
85/// use pyo3::Python;
86///
87/// import_exception!(socket, gaierror);
88///
89/// Python::with_gil(|py| {
90///     let ctx = [("gaierror", py.get_type_bound::<gaierror>())].into_py_dict_bound(py);
91///     pyo3::py_run!(py, *ctx, "import socket; assert gaierror is socket.gaierror");
92/// });
93///
94/// ```
95#[macro_export]
96macro_rules! import_exception {
97    ($module: expr, $name: ident) => {
98        /// A Rust type representing an exception defined in Python code.
99        ///
100        /// This type was created by the [`pyo3::import_exception!`] macro - see its documentation
101        /// for more information.
102        ///
103        /// [`pyo3::import_exception!`]: https://docs.rs/pyo3/latest/pyo3/macro.import_exception.html "import_exception in pyo3"
104        #[repr(transparent)]
105        #[allow(non_camel_case_types)] // E.g. `socket.herror`
106        pub struct $name($crate::PyAny);
107
108        $crate::impl_exception_boilerplate!($name);
109
110        $crate::pyobject_native_type_core!(
111            $name,
112            $name::type_object_raw,
113            #module=::std::option::Option::Some(stringify!($module))
114        );
115
116        impl $name {
117            fn type_object_raw(py: $crate::Python<'_>) -> *mut $crate::ffi::PyTypeObject {
118                use $crate::types::PyTypeMethods;
119                static TYPE_OBJECT: $crate::impl_::exceptions::ImportedExceptionTypeObject =
120                    $crate::impl_::exceptions::ImportedExceptionTypeObject::new(stringify!($module), stringify!($name));
121                TYPE_OBJECT.get(py).as_type_ptr()
122            }
123        }
124    };
125}
126
127/// Variant of [`import_exception`](crate::import_exception) that does not emit code needed to
128/// use the imported exception type as a GIL Ref.
129///
130/// This is useful only during migration as a way to avoid generating needless code.
131#[macro_export]
132macro_rules! import_exception_bound {
133    ($module: expr, $name: ident) => {
134        /// A Rust type representing an exception defined in Python code.
135        ///
136        /// This type was created by the [`pyo3::import_exception_bound!`] macro - see its documentation
137        /// for more information.
138        ///
139        /// [`pyo3::import_exception_bound!`]: https://docs.rs/pyo3/latest/pyo3/macro.import_exception.html "import_exception in pyo3"
140        #[repr(transparent)]
141        #[allow(non_camel_case_types)] // E.g. `socket.herror`
142        pub struct $name($crate::PyAny);
143
144        $crate::impl_exception_boilerplate_bound!($name);
145
146        // FIXME remove this: was necessary while `PyTypeInfo` requires `HasPyGilRef`,
147        // should change in 0.22.
148        #[cfg(feature = "gil-refs")]
149        unsafe impl $crate::type_object::HasPyGilRef for $name {
150            type AsRefTarget = $crate::PyAny;
151        }
152
153        $crate::pyobject_native_type_info!(
154            $name,
155            $name::type_object_raw,
156            ::std::option::Option::Some(stringify!($module))
157        );
158
159        impl $crate::types::DerefToPyAny for $name {}
160
161        impl $name {
162            fn type_object_raw(py: $crate::Python<'_>) -> *mut $crate::ffi::PyTypeObject {
163                use $crate::types::PyTypeMethods;
164                static TYPE_OBJECT: $crate::impl_::exceptions::ImportedExceptionTypeObject =
165                    $crate::impl_::exceptions::ImportedExceptionTypeObject::new(
166                        stringify!($module),
167                        stringify!($name),
168                    );
169                TYPE_OBJECT.get(py).as_type_ptr()
170            }
171        }
172    };
173}
174
175/// Defines a new exception type.
176///
177/// # Syntax
178///
179/// * `module` is the name of the containing module.
180/// * `name` is the name of the new exception type.
181/// * `base` is the base class of `MyError`, usually [`PyException`].
182/// * `doc` (optional) is the docstring visible to users (with `.__doc__` and `help()`) and
183///
184/// accompanies your error type in your crate's documentation.
185///
186/// # Examples
187///
188/// ```
189/// use pyo3::prelude::*;
190/// use pyo3::create_exception;
191/// use pyo3::exceptions::PyException;
192///
193/// create_exception!(my_module, MyError, PyException, "Some description.");
194///
195/// #[pyfunction]
196/// fn raise_myerror() -> PyResult<()> {
197///     let err = MyError::new_err("Some error happened.");
198///     Err(err)
199/// }
200///
201/// #[pymodule]
202/// fn my_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
203///     m.add("MyError", m.py().get_type_bound::<MyError>())?;
204///     m.add_function(wrap_pyfunction!(raise_myerror, m)?)?;
205///     Ok(())
206/// }
207/// # fn main() -> PyResult<()> {
208/// #     Python::with_gil(|py| -> PyResult<()> {
209/// #         let fun = wrap_pyfunction_bound!(raise_myerror, py)?;
210/// #         let locals = pyo3::types::PyDict::new_bound(py);
211/// #         locals.set_item("MyError", py.get_type_bound::<MyError>())?;
212/// #         locals.set_item("raise_myerror", fun)?;
213/// #
214/// #         py.run_bound(
215/// # "try:
216/// #     raise_myerror()
217/// # except MyError as e:
218/// #     assert e.__doc__ == 'Some description.'
219/// #     assert str(e) == 'Some error happened.'",
220/// #             None,
221/// #             Some(&locals),
222/// #         )?;
223/// #
224/// #         Ok(())
225/// #     })
226/// # }
227/// ```
228///
229/// Python code can handle this exception like any other exception:
230///
231/// ```python
232/// from my_module import MyError, raise_myerror
233///
234/// try:
235///     raise_myerror()
236/// except MyError as e:
237///     assert e.__doc__ == 'Some description.'
238///     assert str(e) == 'Some error happened.'
239/// ```
240///
241#[macro_export]
242macro_rules! create_exception {
243    ($module: expr, $name: ident, $base: ty) => {
244        #[repr(transparent)]
245        #[allow(non_camel_case_types)] // E.g. `socket.herror`
246        pub struct $name($crate::PyAny);
247
248        $crate::impl_exception_boilerplate!($name);
249
250        $crate::create_exception_type_object!($module, $name, $base, ::std::option::Option::None);
251    };
252    ($module: expr, $name: ident, $base: ty, $doc: expr) => {
253        #[repr(transparent)]
254        #[allow(non_camel_case_types)] // E.g. `socket.herror`
255        #[doc = $doc]
256        pub struct $name($crate::PyAny);
257
258        $crate::impl_exception_boilerplate!($name);
259
260        $crate::create_exception_type_object!(
261            $module,
262            $name,
263            $base,
264            ::std::option::Option::Some($doc)
265        );
266    };
267}
268
269/// `impl PyTypeInfo for $name` where `$name` is an
270/// exception newly defined in Rust code.
271#[doc(hidden)]
272#[macro_export]
273macro_rules! create_exception_type_object {
274    ($module: expr, $name: ident, $base: ty, $doc: expr) => {
275        $crate::pyobject_native_type_core!(
276            $name,
277            $name::type_object_raw,
278            #module=::std::option::Option::Some(stringify!($module))
279        );
280
281        impl $name {
282            fn type_object_raw(py: $crate::Python<'_>) -> *mut $crate::ffi::PyTypeObject {
283                use $crate::sync::GILOnceCell;
284                static TYPE_OBJECT: GILOnceCell<$crate::Py<$crate::types::PyType>> =
285                    GILOnceCell::new();
286
287                TYPE_OBJECT
288                    .get_or_init(py, ||
289                        $crate::PyErr::new_type_bound(
290                            py,
291                            concat!(stringify!($module), ".", stringify!($name)),
292                            $doc,
293                            ::std::option::Option::Some(&py.get_type_bound::<$base>()),
294                            ::std::option::Option::None,
295                        ).expect("Failed to initialize new exception type.")
296                ).as_ptr() as *mut $crate::ffi::PyTypeObject
297            }
298        }
299    };
300}
301
302macro_rules! impl_native_exception (
303    ($name:ident, $exc_name:ident, $doc:expr, $layout:path $(, #checkfunction=$checkfunction:path)?) => (
304        #[doc = $doc]
305        #[allow(clippy::upper_case_acronyms)]
306        pub struct $name($crate::PyAny);
307
308        $crate::impl_exception_boilerplate!($name);
309        $crate::pyobject_native_type!($name, $layout, |_py| unsafe { $crate::ffi::$exc_name as *mut $crate::ffi::PyTypeObject } $(, #checkfunction=$checkfunction)?);
310    );
311    ($name:ident, $exc_name:ident, $doc:expr) => (
312        impl_native_exception!($name, $exc_name, $doc, $crate::ffi::PyBaseExceptionObject);
313    )
314);
315
316#[cfg(windows)]
317macro_rules! impl_windows_native_exception (
318    ($name:ident, $exc_name:ident, $doc:expr, $layout:path) => (
319        #[cfg(windows)]
320        #[doc = $doc]
321        #[allow(clippy::upper_case_acronyms)]
322        pub struct $name($crate::PyAny);
323
324        $crate::impl_exception_boilerplate!($name);
325        $crate::pyobject_native_type!($name, $layout, |_py| unsafe { $crate::ffi::$exc_name as *mut $crate::ffi::PyTypeObject });
326    );
327    ($name:ident, $exc_name:ident, $doc:expr) => (
328        impl_windows_native_exception!($name, $exc_name, $doc, $crate::ffi::PyBaseExceptionObject);
329    )
330);
331
332macro_rules! native_doc(
333    ($name: literal, $alt: literal) => (
334        concat!(
335"Represents Python's [`", $name, "`](https://docs.python.org/3/library/exceptions.html#", $name, ") exception.
336
337", $alt
338        )
339    );
340    ($name: literal) => (
341        concat!(
342"
343Represents Python's [`", $name, "`](https://docs.python.org/3/library/exceptions.html#", $name, ") exception.
344
345# Example: Raising ", $name, " from Rust
346
347This exception can be sent to Python code by converting it into a
348[`PyErr`](crate::PyErr), where Python code can then catch it.
349```
350use pyo3::prelude::*;
351use pyo3::exceptions::Py", $name, ";
352
353#[pyfunction]
354fn always_throws() -> PyResult<()> {
355    let message = \"I'm ", $name ,", and I was raised from Rust.\";
356    Err(Py", $name, "::new_err(message))
357}
358#
359# Python::with_gil(|py| {
360#     let fun = pyo3::wrap_pyfunction_bound!(always_throws, py).unwrap();
361#     let err = fun.call0().expect_err(\"called a function that should always return an error but the return value was Ok\");
362#     assert!(err.is_instance_of::<Py", $name, ">(py))
363# });
364```
365
366Python code:
367 ```python
368 from my_module import always_throws
369
370try:
371    always_throws()
372except ", $name, " as e:
373    print(f\"Caught an exception: {e}\")
374```
375
376# Example: Catching ", $name, " in Rust
377
378```
379use pyo3::prelude::*;
380use pyo3::exceptions::Py", $name, ";
381
382Python::with_gil(|py| {
383    let result: PyResult<()> = py.run_bound(\"raise ", $name, "\", None, None);
384
385    let error_type = match result {
386        Ok(_) => \"Not an error\",
387        Err(error) if error.is_instance_of::<Py", $name, ">(py) => \"" , $name, "\",
388        Err(_) => \"Some other error\",
389    };
390
391    assert_eq!(error_type, \"", $name, "\");
392});
393```
394"
395        )
396    );
397);
398
399impl_native_exception!(
400    PyBaseException,
401    PyExc_BaseException,
402    native_doc!("BaseException"),
403    ffi::PyBaseExceptionObject,
404    #checkfunction=ffi::PyExceptionInstance_Check
405);
406impl_native_exception!(PyException, PyExc_Exception, native_doc!("Exception"));
407impl_native_exception!(
408    PyStopAsyncIteration,
409    PyExc_StopAsyncIteration,
410    native_doc!("StopAsyncIteration")
411);
412impl_native_exception!(
413    PyStopIteration,
414    PyExc_StopIteration,
415    native_doc!("StopIteration"),
416    ffi::PyStopIterationObject
417);
418impl_native_exception!(
419    PyGeneratorExit,
420    PyExc_GeneratorExit,
421    native_doc!("GeneratorExit")
422);
423impl_native_exception!(
424    PyArithmeticError,
425    PyExc_ArithmeticError,
426    native_doc!("ArithmeticError")
427);
428impl_native_exception!(PyLookupError, PyExc_LookupError, native_doc!("LookupError"));
429
430impl_native_exception!(
431    PyAssertionError,
432    PyExc_AssertionError,
433    native_doc!("AssertionError")
434);
435impl_native_exception!(
436    PyAttributeError,
437    PyExc_AttributeError,
438    native_doc!("AttributeError")
439);
440impl_native_exception!(PyBufferError, PyExc_BufferError, native_doc!("BufferError"));
441impl_native_exception!(PyEOFError, PyExc_EOFError, native_doc!("EOFError"));
442impl_native_exception!(
443    PyFloatingPointError,
444    PyExc_FloatingPointError,
445    native_doc!("FloatingPointError")
446);
447#[cfg(not(any(PyPy, GraalPy)))]
448impl_native_exception!(
449    PyOSError,
450    PyExc_OSError,
451    native_doc!("OSError"),
452    ffi::PyOSErrorObject
453);
454#[cfg(any(PyPy, GraalPy))]
455impl_native_exception!(PyOSError, PyExc_OSError, native_doc!("OSError"));
456impl_native_exception!(PyImportError, PyExc_ImportError, native_doc!("ImportError"));
457
458impl_native_exception!(
459    PyModuleNotFoundError,
460    PyExc_ModuleNotFoundError,
461    native_doc!("ModuleNotFoundError")
462);
463
464impl_native_exception!(PyIndexError, PyExc_IndexError, native_doc!("IndexError"));
465impl_native_exception!(PyKeyError, PyExc_KeyError, native_doc!("KeyError"));
466impl_native_exception!(
467    PyKeyboardInterrupt,
468    PyExc_KeyboardInterrupt,
469    native_doc!("KeyboardInterrupt")
470);
471impl_native_exception!(PyMemoryError, PyExc_MemoryError, native_doc!("MemoryError"));
472impl_native_exception!(PyNameError, PyExc_NameError, native_doc!("NameError"));
473impl_native_exception!(
474    PyOverflowError,
475    PyExc_OverflowError,
476    native_doc!("OverflowError")
477);
478impl_native_exception!(
479    PyRuntimeError,
480    PyExc_RuntimeError,
481    native_doc!("RuntimeError")
482);
483impl_native_exception!(
484    PyRecursionError,
485    PyExc_RecursionError,
486    native_doc!("RecursionError")
487);
488impl_native_exception!(
489    PyNotImplementedError,
490    PyExc_NotImplementedError,
491    native_doc!("NotImplementedError")
492);
493#[cfg(not(any(PyPy, GraalPy)))]
494impl_native_exception!(
495    PySyntaxError,
496    PyExc_SyntaxError,
497    native_doc!("SyntaxError"),
498    ffi::PySyntaxErrorObject
499);
500#[cfg(any(PyPy, GraalPy))]
501impl_native_exception!(PySyntaxError, PyExc_SyntaxError, native_doc!("SyntaxError"));
502impl_native_exception!(
503    PyReferenceError,
504    PyExc_ReferenceError,
505    native_doc!("ReferenceError")
506);
507impl_native_exception!(PySystemError, PyExc_SystemError, native_doc!("SystemError"));
508#[cfg(not(any(PyPy, GraalPy)))]
509impl_native_exception!(
510    PySystemExit,
511    PyExc_SystemExit,
512    native_doc!("SystemExit"),
513    ffi::PySystemExitObject
514);
515#[cfg(any(PyPy, GraalPy))]
516impl_native_exception!(PySystemExit, PyExc_SystemExit, native_doc!("SystemExit"));
517impl_native_exception!(PyTypeError, PyExc_TypeError, native_doc!("TypeError"));
518impl_native_exception!(
519    PyUnboundLocalError,
520    PyExc_UnboundLocalError,
521    native_doc!("UnboundLocalError")
522);
523#[cfg(not(any(PyPy, GraalPy)))]
524impl_native_exception!(
525    PyUnicodeError,
526    PyExc_UnicodeError,
527    native_doc!("UnicodeError"),
528    ffi::PyUnicodeErrorObject
529);
530#[cfg(any(PyPy, GraalPy))]
531impl_native_exception!(
532    PyUnicodeError,
533    PyExc_UnicodeError,
534    native_doc!("UnicodeError")
535);
536// these four errors need arguments, so they're too annoying to write tests for using macros...
537impl_native_exception!(
538    PyUnicodeDecodeError,
539    PyExc_UnicodeDecodeError,
540    native_doc!("UnicodeDecodeError", "")
541);
542impl_native_exception!(
543    PyUnicodeEncodeError,
544    PyExc_UnicodeEncodeError,
545    native_doc!("UnicodeEncodeError", "")
546);
547impl_native_exception!(
548    PyUnicodeTranslateError,
549    PyExc_UnicodeTranslateError,
550    native_doc!("UnicodeTranslateError", "")
551);
552#[cfg(Py_3_11)]
553impl_native_exception!(
554    PyBaseExceptionGroup,
555    PyExc_BaseExceptionGroup,
556    native_doc!("BaseExceptionGroup", "")
557);
558impl_native_exception!(PyValueError, PyExc_ValueError, native_doc!("ValueError"));
559impl_native_exception!(
560    PyZeroDivisionError,
561    PyExc_ZeroDivisionError,
562    native_doc!("ZeroDivisionError")
563);
564
565impl_native_exception!(
566    PyBlockingIOError,
567    PyExc_BlockingIOError,
568    native_doc!("BlockingIOError")
569);
570impl_native_exception!(
571    PyBrokenPipeError,
572    PyExc_BrokenPipeError,
573    native_doc!("BrokenPipeError")
574);
575impl_native_exception!(
576    PyChildProcessError,
577    PyExc_ChildProcessError,
578    native_doc!("ChildProcessError")
579);
580impl_native_exception!(
581    PyConnectionError,
582    PyExc_ConnectionError,
583    native_doc!("ConnectionError")
584);
585impl_native_exception!(
586    PyConnectionAbortedError,
587    PyExc_ConnectionAbortedError,
588    native_doc!("ConnectionAbortedError")
589);
590impl_native_exception!(
591    PyConnectionRefusedError,
592    PyExc_ConnectionRefusedError,
593    native_doc!("ConnectionRefusedError")
594);
595impl_native_exception!(
596    PyConnectionResetError,
597    PyExc_ConnectionResetError,
598    native_doc!("ConnectionResetError")
599);
600impl_native_exception!(
601    PyFileExistsError,
602    PyExc_FileExistsError,
603    native_doc!("FileExistsError")
604);
605impl_native_exception!(
606    PyFileNotFoundError,
607    PyExc_FileNotFoundError,
608    native_doc!("FileNotFoundError")
609);
610impl_native_exception!(
611    PyInterruptedError,
612    PyExc_InterruptedError,
613    native_doc!("InterruptedError")
614);
615impl_native_exception!(
616    PyIsADirectoryError,
617    PyExc_IsADirectoryError,
618    native_doc!("IsADirectoryError")
619);
620impl_native_exception!(
621    PyNotADirectoryError,
622    PyExc_NotADirectoryError,
623    native_doc!("NotADirectoryError")
624);
625impl_native_exception!(
626    PyPermissionError,
627    PyExc_PermissionError,
628    native_doc!("PermissionError")
629);
630impl_native_exception!(
631    PyProcessLookupError,
632    PyExc_ProcessLookupError,
633    native_doc!("ProcessLookupError")
634);
635impl_native_exception!(
636    PyTimeoutError,
637    PyExc_TimeoutError,
638    native_doc!("TimeoutError")
639);
640
641impl_native_exception!(
642    PyEnvironmentError,
643    PyExc_EnvironmentError,
644    native_doc!("EnvironmentError")
645);
646impl_native_exception!(PyIOError, PyExc_IOError, native_doc!("IOError"));
647
648#[cfg(windows)]
649impl_windows_native_exception!(
650    PyWindowsError,
651    PyExc_WindowsError,
652    native_doc!("WindowsError")
653);
654
655impl PyUnicodeDecodeError {
656    /// Deprecated form of [`PyUnicodeDecodeError::new_bound`].
657    #[cfg(feature = "gil-refs")]
658    #[deprecated(
659        since = "0.21.0",
660        note = "`PyUnicodeDecodeError::new` will be replaced by `PyUnicodeDecodeError::new_bound` in a future PyO3 version"
661    )]
662    pub fn new<'p>(
663        py: Python<'p>,
664        encoding: &CStr,
665        input: &[u8],
666        range: ops::Range<usize>,
667        reason: &CStr,
668    ) -> PyResult<&'p PyUnicodeDecodeError> {
669        Ok(PyUnicodeDecodeError::new_bound(py, encoding, input, range, reason)?.into_gil_ref())
670    }
671
672    /// Creates a Python `UnicodeDecodeError`.
673    pub fn new_bound<'p>(
674        py: Python<'p>,
675        encoding: &CStr,
676        input: &[u8],
677        range: ops::Range<usize>,
678        reason: &CStr,
679    ) -> PyResult<Bound<'p, PyUnicodeDecodeError>> {
680        use crate::ffi_ptr_ext::FfiPtrExt;
681        use crate::py_result_ext::PyResultExt;
682        unsafe {
683            ffi::PyUnicodeDecodeError_Create(
684                encoding.as_ptr(),
685                input.as_ptr().cast(),
686                input.len() as ffi::Py_ssize_t,
687                range.start as ffi::Py_ssize_t,
688                range.end as ffi::Py_ssize_t,
689                reason.as_ptr(),
690            )
691            .assume_owned_or_err(py)
692        }
693        .downcast_into()
694    }
695
696    /// Deprecated form of [`PyUnicodeDecodeError::new_utf8_bound`].
697    #[cfg(feature = "gil-refs")]
698    #[deprecated(
699        since = "0.21.0",
700        note = "`PyUnicodeDecodeError::new_utf8` will be replaced by `PyUnicodeDecodeError::new_utf8_bound` in a future PyO3 version"
701    )]
702    pub fn new_utf8<'p>(
703        py: Python<'p>,
704        input: &[u8],
705        err: std::str::Utf8Error,
706    ) -> PyResult<&'p PyUnicodeDecodeError> {
707        Ok(PyUnicodeDecodeError::new_utf8_bound(py, input, err)?.into_gil_ref())
708    }
709
710    /// Creates a Python `UnicodeDecodeError` from a Rust UTF-8 decoding error.
711    ///
712    /// # Examples
713    ///
714    /// ```
715    /// #![cfg_attr(invalid_from_utf8_lint, allow(invalid_from_utf8))]
716    /// use pyo3::prelude::*;
717    /// use pyo3::exceptions::PyUnicodeDecodeError;
718    ///
719    /// # fn main() -> PyResult<()> {
720    /// Python::with_gil(|py| {
721    ///     let invalid_utf8 = b"fo\xd8o";
722    ///     let err = std::str::from_utf8(invalid_utf8).expect_err("should be invalid utf8");
723    ///     let decode_err = PyUnicodeDecodeError::new_utf8_bound(py, invalid_utf8, err)?;
724    ///     assert_eq!(
725    ///         decode_err.to_string(),
726    ///         "'utf-8' codec can't decode byte 0xd8 in position 2: invalid utf-8"
727    ///     );
728    ///     Ok(())
729    /// })
730    /// # }
731    pub fn new_utf8_bound<'p>(
732        py: Python<'p>,
733        input: &[u8],
734        err: std::str::Utf8Error,
735    ) -> PyResult<Bound<'p, PyUnicodeDecodeError>> {
736        let pos = err.valid_up_to();
737        PyUnicodeDecodeError::new_bound(
738            py,
739            ffi::c_str!("utf-8"),
740            input,
741            pos..(pos + 1),
742            ffi::c_str!("invalid utf-8"),
743        )
744    }
745}
746
747impl_native_exception!(PyWarning, PyExc_Warning, native_doc!("Warning"));
748impl_native_exception!(PyUserWarning, PyExc_UserWarning, native_doc!("UserWarning"));
749impl_native_exception!(
750    PyDeprecationWarning,
751    PyExc_DeprecationWarning,
752    native_doc!("DeprecationWarning")
753);
754impl_native_exception!(
755    PyPendingDeprecationWarning,
756    PyExc_PendingDeprecationWarning,
757    native_doc!("PendingDeprecationWarning")
758);
759impl_native_exception!(
760    PySyntaxWarning,
761    PyExc_SyntaxWarning,
762    native_doc!("SyntaxWarning")
763);
764impl_native_exception!(
765    PyRuntimeWarning,
766    PyExc_RuntimeWarning,
767    native_doc!("RuntimeWarning")
768);
769impl_native_exception!(
770    PyFutureWarning,
771    PyExc_FutureWarning,
772    native_doc!("FutureWarning")
773);
774impl_native_exception!(
775    PyImportWarning,
776    PyExc_ImportWarning,
777    native_doc!("ImportWarning")
778);
779impl_native_exception!(
780    PyUnicodeWarning,
781    PyExc_UnicodeWarning,
782    native_doc!("UnicodeWarning")
783);
784impl_native_exception!(
785    PyBytesWarning,
786    PyExc_BytesWarning,
787    native_doc!("BytesWarning")
788);
789impl_native_exception!(
790    PyResourceWarning,
791    PyExc_ResourceWarning,
792    native_doc!("ResourceWarning")
793);
794
795#[cfg(Py_3_10)]
796impl_native_exception!(
797    PyEncodingWarning,
798    PyExc_EncodingWarning,
799    native_doc!("EncodingWarning")
800);
801
802#[cfg(test)]
803macro_rules! test_exception {
804    ($exc_ty:ident $(, |$py:tt| $constructor:expr )?) => {
805        #[allow(non_snake_case)]
806        #[test]
807        fn $exc_ty () {
808            use super::$exc_ty;
809
810            $crate::Python::with_gil(|py| {
811                use $crate::types::PyAnyMethods;
812                let err: $crate::PyErr = {
813                    None
814                    $(
815                        .or(Some({ let $py = py; $constructor }))
816                    )?
817                        .unwrap_or($exc_ty::new_err("a test exception"))
818                };
819
820                assert!(err.is_instance_of::<$exc_ty>(py));
821
822                let value = err.value_bound(py).as_any().downcast::<$exc_ty>().unwrap();
823
824                #[cfg(feature = "gil-refs")]
825                {
826                    use std::error::Error;
827                    let value = value.as_gil_ref();
828                    assert!(value.source().is_none());
829
830                    err.set_cause(py, Some($crate::exceptions::PyValueError::new_err("a cause")));
831                    assert!(value.source().is_some());
832                }
833
834                assert!($crate::PyErr::from(value.clone()).is_instance_of::<$exc_ty>(py));
835            })
836        }
837    };
838}
839
840/// Exceptions defined in Python's [`asyncio`](https://docs.python.org/3/library/asyncio.html)
841/// module.
842pub mod asyncio {
843    import_exception!(asyncio, CancelledError);
844    import_exception!(asyncio, InvalidStateError);
845    import_exception!(asyncio, TimeoutError);
846    import_exception!(asyncio, IncompleteReadError);
847    import_exception!(asyncio, LimitOverrunError);
848    import_exception!(asyncio, QueueEmpty);
849    import_exception!(asyncio, QueueFull);
850
851    #[cfg(test)]
852    mod tests {
853        test_exception!(CancelledError);
854        test_exception!(InvalidStateError);
855        test_exception!(TimeoutError);
856        test_exception!(IncompleteReadError, |_| IncompleteReadError::new_err((
857            "partial", "expected"
858        )));
859        test_exception!(LimitOverrunError, |_| LimitOverrunError::new_err((
860            "message", "consumed"
861        )));
862        test_exception!(QueueEmpty);
863        test_exception!(QueueFull);
864    }
865}
866
867/// Exceptions defined in Python's [`socket`](https://docs.python.org/3/library/socket.html)
868/// module.
869pub mod socket {
870    import_exception!(socket, herror);
871    import_exception!(socket, gaierror);
872    import_exception!(socket, timeout);
873
874    #[cfg(test)]
875    mod tests {
876        test_exception!(herror);
877        test_exception!(gaierror);
878        test_exception!(timeout);
879    }
880}
881
882#[cfg(test)]
883mod tests {
884    use super::*;
885    use crate::types::any::PyAnyMethods;
886    use crate::types::{IntoPyDict, PyDict};
887    use crate::PyErr;
888    #[cfg(feature = "gil-refs")]
889    use crate::PyNativeType;
890
891    import_exception_bound!(socket, gaierror);
892    import_exception_bound!(email.errors, MessageError);
893
894    #[test]
895    fn test_check_exception() {
896        Python::with_gil(|py| {
897            let err: PyErr = gaierror::new_err(());
898            let socket = py
899                .import_bound("socket")
900                .map_err(|e| e.display(py))
901                .expect("could not import socket");
902
903            let d = PyDict::new_bound(py);
904            d.set_item("socket", socket)
905                .map_err(|e| e.display(py))
906                .expect("could not setitem");
907
908            d.set_item("exc", err)
909                .map_err(|e| e.display(py))
910                .expect("could not setitem");
911
912            py.run_bound("assert isinstance(exc, socket.gaierror)", None, Some(&d))
913                .map_err(|e| e.display(py))
914                .expect("assertion failed");
915        });
916    }
917
918    #[test]
919    fn test_check_exception_nested() {
920        Python::with_gil(|py| {
921            let err: PyErr = MessageError::new_err(());
922            let email = py
923                .import_bound("email")
924                .map_err(|e| e.display(py))
925                .expect("could not import email");
926
927            let d = PyDict::new_bound(py);
928            d.set_item("email", email)
929                .map_err(|e| e.display(py))
930                .expect("could not setitem");
931            d.set_item("exc", err)
932                .map_err(|e| e.display(py))
933                .expect("could not setitem");
934
935            py.run_bound(
936                "assert isinstance(exc, email.errors.MessageError)",
937                None,
938                Some(&d),
939            )
940            .map_err(|e| e.display(py))
941            .expect("assertion failed");
942        });
943    }
944
945    #[test]
946    fn custom_exception() {
947        create_exception!(mymodule, CustomError, PyException);
948
949        Python::with_gil(|py| {
950            let error_type = py.get_type_bound::<CustomError>();
951            let ctx = [("CustomError", error_type)].into_py_dict_bound(py);
952            let type_description: String = py
953                .eval_bound("str(CustomError)", None, Some(&ctx))
954                .unwrap()
955                .extract()
956                .unwrap();
957            assert_eq!(type_description, "<class 'mymodule.CustomError'>");
958            py.run_bound(
959                "assert CustomError('oops').args == ('oops',)",
960                None,
961                Some(&ctx),
962            )
963            .unwrap();
964            py.run_bound("assert CustomError.__doc__ is None", None, Some(&ctx))
965                .unwrap();
966        });
967    }
968
969    #[test]
970    fn custom_exception_dotted_module() {
971        create_exception!(mymodule.exceptions, CustomError, PyException);
972        Python::with_gil(|py| {
973            let error_type = py.get_type_bound::<CustomError>();
974            let ctx = [("CustomError", error_type)].into_py_dict_bound(py);
975            let type_description: String = py
976                .eval_bound("str(CustomError)", None, Some(&ctx))
977                .unwrap()
978                .extract()
979                .unwrap();
980            assert_eq!(
981                type_description,
982                "<class 'mymodule.exceptions.CustomError'>"
983            );
984        });
985    }
986
987    #[test]
988    fn custom_exception_doc() {
989        create_exception!(mymodule, CustomError, PyException, "Some docs");
990
991        Python::with_gil(|py| {
992            let error_type = py.get_type_bound::<CustomError>();
993            let ctx = [("CustomError", error_type)].into_py_dict_bound(py);
994            let type_description: String = py
995                .eval_bound("str(CustomError)", None, Some(&ctx))
996                .unwrap()
997                .extract()
998                .unwrap();
999            assert_eq!(type_description, "<class 'mymodule.CustomError'>");
1000            py.run_bound(
1001                "assert CustomError('oops').args == ('oops',)",
1002                None,
1003                Some(&ctx),
1004            )
1005            .unwrap();
1006            py.run_bound(
1007                "assert CustomError.__doc__ == 'Some docs'",
1008                None,
1009                Some(&ctx),
1010            )
1011            .unwrap();
1012        });
1013    }
1014
1015    #[test]
1016    fn custom_exception_doc_expr() {
1017        create_exception!(
1018            mymodule,
1019            CustomError,
1020            PyException,
1021            concat!("Some", " more ", stringify!(docs))
1022        );
1023
1024        Python::with_gil(|py| {
1025            let error_type = py.get_type_bound::<CustomError>();
1026            let ctx = [("CustomError", error_type)].into_py_dict_bound(py);
1027            let type_description: String = py
1028                .eval_bound("str(CustomError)", None, Some(&ctx))
1029                .unwrap()
1030                .extract()
1031                .unwrap();
1032            assert_eq!(type_description, "<class 'mymodule.CustomError'>");
1033            py.run_bound(
1034                "assert CustomError('oops').args == ('oops',)",
1035                None,
1036                Some(&ctx),
1037            )
1038            .unwrap();
1039            py.run_bound(
1040                "assert CustomError.__doc__ == 'Some more docs'",
1041                None,
1042                Some(&ctx),
1043            )
1044            .unwrap();
1045        });
1046    }
1047
1048    #[test]
1049    fn native_exception_debug() {
1050        Python::with_gil(|py| {
1051            let exc = py
1052                .run_bound("raise Exception('banana')", None, None)
1053                .expect_err("raising should have given us an error")
1054                .into_value(py)
1055                .into_bound(py);
1056            assert_eq!(
1057                format!("{:?}", exc),
1058                exc.repr().unwrap().extract::<String>().unwrap()
1059            );
1060        });
1061    }
1062
1063    #[test]
1064    fn native_exception_display() {
1065        Python::with_gil(|py| {
1066            let exc = py
1067                .run_bound("raise Exception('banana')", None, None)
1068                .expect_err("raising should have given us an error")
1069                .into_value(py)
1070                .into_bound(py);
1071            assert_eq!(
1072                exc.to_string(),
1073                exc.str().unwrap().extract::<String>().unwrap()
1074            );
1075        });
1076    }
1077
1078    #[test]
1079    #[cfg(feature = "gil-refs")]
1080    fn native_exception_chain() {
1081        use std::error::Error;
1082
1083        Python::with_gil(|py| {
1084            #[allow(deprecated)]
1085            let exc = py
1086                .run_bound(
1087                    "raise Exception('banana') from TypeError('peach')",
1088                    None,
1089                    None,
1090                )
1091                .expect_err("raising should have given us an error")
1092                .into_value(py)
1093                .into_ref(py);
1094
1095            assert_eq!(format!("{:?}", exc), "Exception('banana')");
1096
1097            let source = exc.source().expect("cause should exist");
1098
1099            assert_eq!(format!("{:?}", source), "TypeError('peach')");
1100
1101            let source_source = source.source();
1102            assert!(source_source.is_none(), "source_source should be None");
1103        });
1104    }
1105
1106    #[test]
1107    fn unicode_decode_error() {
1108        let invalid_utf8 = b"fo\xd8o";
1109        #[cfg_attr(invalid_from_utf8_lint, allow(invalid_from_utf8))]
1110        let err = std::str::from_utf8(invalid_utf8).expect_err("should be invalid utf8");
1111        Python::with_gil(|py| {
1112            let decode_err = PyUnicodeDecodeError::new_utf8_bound(py, invalid_utf8, err).unwrap();
1113            assert_eq!(
1114                format!("{:?}", decode_err),
1115                "UnicodeDecodeError('utf-8', b'fo\\xd8o', 2, 3, 'invalid utf-8')"
1116            );
1117
1118            // Restoring should preserve the same error
1119            let e: PyErr = decode_err.into();
1120            e.restore(py);
1121
1122            assert_eq!(
1123                PyErr::fetch(py).to_string(),
1124                "UnicodeDecodeError: \'utf-8\' codec can\'t decode byte 0xd8 in position 2: invalid utf-8"
1125            );
1126        });
1127    }
1128    #[cfg(Py_3_11)]
1129    test_exception!(PyBaseExceptionGroup, |_| PyBaseExceptionGroup::new_err((
1130        "msg",
1131        vec![PyValueError::new_err("err")]
1132    )));
1133    test_exception!(PyBaseException);
1134    test_exception!(PyException);
1135    test_exception!(PyStopAsyncIteration);
1136    test_exception!(PyStopIteration);
1137    test_exception!(PyGeneratorExit);
1138    test_exception!(PyArithmeticError);
1139    test_exception!(PyLookupError);
1140    test_exception!(PyAssertionError);
1141    test_exception!(PyAttributeError);
1142    test_exception!(PyBufferError);
1143    test_exception!(PyEOFError);
1144    test_exception!(PyFloatingPointError);
1145    test_exception!(PyOSError);
1146    test_exception!(PyImportError);
1147    test_exception!(PyModuleNotFoundError);
1148    test_exception!(PyIndexError);
1149    test_exception!(PyKeyError);
1150    test_exception!(PyKeyboardInterrupt);
1151    test_exception!(PyMemoryError);
1152    test_exception!(PyNameError);
1153    test_exception!(PyOverflowError);
1154    test_exception!(PyRuntimeError);
1155    test_exception!(PyRecursionError);
1156    test_exception!(PyNotImplementedError);
1157    test_exception!(PySyntaxError);
1158    test_exception!(PyReferenceError);
1159    test_exception!(PySystemError);
1160    test_exception!(PySystemExit);
1161    test_exception!(PyTypeError);
1162    test_exception!(PyUnboundLocalError);
1163    test_exception!(PyUnicodeError);
1164    test_exception!(PyUnicodeDecodeError, |py| {
1165        let invalid_utf8 = b"fo\xd8o";
1166        #[cfg_attr(invalid_from_utf8_lint, allow(invalid_from_utf8))]
1167        let err = std::str::from_utf8(invalid_utf8).expect_err("should be invalid utf8");
1168        PyErr::from_value_bound(
1169            PyUnicodeDecodeError::new_utf8_bound(py, invalid_utf8, err)
1170                .unwrap()
1171                .into_any(),
1172        )
1173    });
1174    test_exception!(PyUnicodeEncodeError, |py| py
1175        .eval_bound("chr(40960).encode('ascii')", None, None)
1176        .unwrap_err());
1177    test_exception!(PyUnicodeTranslateError, |_| {
1178        PyUnicodeTranslateError::new_err(("\u{3042}", 0, 1, "ouch"))
1179    });
1180    test_exception!(PyValueError);
1181    test_exception!(PyZeroDivisionError);
1182    test_exception!(PyBlockingIOError);
1183    test_exception!(PyBrokenPipeError);
1184    test_exception!(PyChildProcessError);
1185    test_exception!(PyConnectionError);
1186    test_exception!(PyConnectionAbortedError);
1187    test_exception!(PyConnectionRefusedError);
1188    test_exception!(PyConnectionResetError);
1189    test_exception!(PyFileExistsError);
1190    test_exception!(PyFileNotFoundError);
1191    test_exception!(PyInterruptedError);
1192    test_exception!(PyIsADirectoryError);
1193    test_exception!(PyNotADirectoryError);
1194    test_exception!(PyPermissionError);
1195    test_exception!(PyProcessLookupError);
1196    test_exception!(PyTimeoutError);
1197    test_exception!(PyEnvironmentError);
1198    test_exception!(PyIOError);
1199    #[cfg(windows)]
1200    test_exception!(PyWindowsError);
1201
1202    test_exception!(PyWarning);
1203    test_exception!(PyUserWarning);
1204    test_exception!(PyDeprecationWarning);
1205    test_exception!(PyPendingDeprecationWarning);
1206    test_exception!(PySyntaxWarning);
1207    test_exception!(PyRuntimeWarning);
1208    test_exception!(PyFutureWarning);
1209    test_exception!(PyImportWarning);
1210    test_exception!(PyUnicodeWarning);
1211    test_exception!(PyBytesWarning);
1212    #[cfg(Py_3_10)]
1213    test_exception!(PyEncodingWarning);
1214}