Skip to main content

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