Skip to main content

pyo3/
exceptions.rs

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