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