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