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