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