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