pyo3/conversions/anyhow.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
#![cfg(feature = "anyhow")]
//! A conversion from [anyhow]’s [`Error`][anyhow-error] type to [`PyErr`].
//!
//! Use of an error handling library like [anyhow] is common in application code and when you just
//! want error handling to be easy. If you are writing a library or you need more control over your
//! errors you might want to design your own error type instead.
//!
//! When the inner error is a [`PyErr`] without source, it will be extracted out.
//! Otherwise a Python [`RuntimeError`] will be created.
//! You might find that you need to map the error from your Rust code into another Python exception.
//! See [`PyErr::new`] for more information about that.
//!
//! For information about error handling in general, see the [Error handling] chapter of the Rust
//! book.
//!
//! # Setup
//!
//! To use this feature, add this to your **`Cargo.toml`**:
//!
//! ```toml
//! [dependencies]
//! ## change * to the version you want to use, ideally the latest.
//! anyhow = "*"
#![doc = concat!("pyo3 = { version = \"", env!("CARGO_PKG_VERSION"), "\", features = [\"anyhow\"] }")]
//! ```
//!
//! Note that you must use compatible versions of anyhow and PyO3.
//! The required anyhow version may vary based on the version of PyO3.
//!
//! # Example: Propagating a `PyErr` into [`anyhow::Error`]
//!
//! ```rust
//! use pyo3::prelude::*;
//! use std::path::PathBuf;
//!
//! // A wrapper around a Rust function.
//! // The pyfunction macro performs the conversion to a PyErr
//! #[pyfunction]
//! fn py_open(filename: PathBuf) -> anyhow::Result<Vec<u8>> {
//! let data = std::fs::read(filename)?;
//! Ok(data)
//! }
//!
//! fn main() {
//! let error = Python::with_gil(|py| -> PyResult<Vec<u8>> {
//! let fun = wrap_pyfunction!(py_open, py)?;
//! let text = fun.call1(("foo.txt",))?.extract::<Vec<u8>>()?;
//! Ok(text)
//! }).unwrap_err();
//!
//! println!("{}", error);
//! }
//! ```
//!
//! # Example: Using `anyhow` in general
//!
//! Note that you don't need this feature to convert a [`PyErr`] into an [`anyhow::Error`], because
//! it can already convert anything that implements [`Error`](std::error::Error):
//!
//! ```rust
//! use pyo3::prelude::*;
//! use pyo3::types::PyBytes;
//!
//! // An example function that must handle multiple error types.
//! //
//! // To do this you usually need to design your own error type or use
//! // `Box<dyn Error>`. `anyhow` is a convenient alternative for this.
//! pub fn decompress(bytes: &[u8]) -> anyhow::Result<String> {
//! // An arbitrary example of a Python api you
//! // could call inside an application...
//! // This might return a `PyErr`.
//! let res = Python::with_gil(|py| {
//! let zlib = PyModule::import(py, "zlib")?;
//! let decompress = zlib.getattr("decompress")?;
//! let bytes = PyBytes::new(py, bytes);
//! let value = decompress.call1((bytes,))?;
//! value.extract::<Vec<u8>>()
//! })?;
//!
//! // This might be a `FromUtf8Error`.
//! let text = String::from_utf8(res)?;
//!
//! Ok(text)
//! }
//!
//! fn main() -> anyhow::Result<()> {
//! let bytes: &[u8] = b"x\x9c\x8b\xcc/U(\xce\xc8/\xcdIQ((\xcaOJL\xca\xa9T\
//! (-NU(\xc9HU\xc8\xc9LJ\xcbI,IUH.\x02\x91\x99y\xc5%\
//! \xa9\x89)z\x00\xf2\x15\x12\xfe";
//! let text = decompress(bytes)?;
//!
//! println!("The text is \"{}\"", text);
//! # assert_eq!(text, "You should probably use the libflate crate instead.");
//! Ok(())
//! }
//! ```
//!
//! [anyhow]: https://docs.rs/anyhow/ "A trait object based error system for easy idiomatic error handling in Rust applications."
//! [anyhow-error]: https://docs.rs/anyhow/latest/anyhow/struct.Error.html "Anyhows `Error` type, a wrapper around a dynamic error type"
//! [`RuntimeError`]: https://docs.python.org/3/library/exceptions.html#RuntimeError "Built-in Exceptions — Python documentation"
//! [Error handling]: https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html "Recoverable Errors with Result - The Rust Programming Language"
use crate::exceptions::PyRuntimeError;
use crate::PyErr;
impl From<anyhow::Error> for PyErr {
fn from(mut error: anyhow::Error) -> Self {
// Errors containing a PyErr without chain or context are returned as the underlying error
if error.source().is_none() {
error = match error.downcast::<Self>() {
Ok(py_err) => return py_err,
Err(error) => error,
};
}
PyRuntimeError::new_err(format!("{:?}", error))
}
}
#[cfg(test)]
mod test_anyhow {
use crate::exceptions::{PyRuntimeError, PyValueError};
use crate::types::IntoPyDict;
use crate::{ffi, prelude::*};
use anyhow::{anyhow, bail, Context, Result};
fn f() -> Result<()> {
use std::io;
bail!(io::Error::new(io::ErrorKind::PermissionDenied, "oh no!"));
}
fn g() -> Result<()> {
f().context("f failed")
}
fn h() -> Result<()> {
g().context("g failed")
}
#[test]
fn test_pyo3_exception_contents() {
let err = h().unwrap_err();
let expected_contents = format!("{:?}", err);
let pyerr = PyErr::from(err);
Python::with_gil(|py| {
let locals = [("err", pyerr)].into_py_dict(py).unwrap();
let pyerr = py
.run(ffi::c_str!("raise err"), None, Some(&locals))
.unwrap_err();
assert_eq!(pyerr.value(py).to_string(), expected_contents);
})
}
fn k() -> Result<()> {
Err(anyhow!("Some sort of error"))
}
#[test]
fn test_pyo3_exception_contents2() {
let err = k().unwrap_err();
let expected_contents = format!("{:?}", err);
let pyerr = PyErr::from(err);
Python::with_gil(|py| {
let locals = [("err", pyerr)].into_py_dict(py).unwrap();
let pyerr = py
.run(ffi::c_str!("raise err"), None, Some(&locals))
.unwrap_err();
assert_eq!(pyerr.value(py).to_string(), expected_contents);
})
}
#[test]
fn test_pyo3_unwrap_simple_err() {
let origin_exc = PyValueError::new_err("Value Error");
let err: anyhow::Error = origin_exc.into();
let converted: PyErr = err.into();
assert!(Python::with_gil(
|py| converted.is_instance_of::<PyValueError>(py)
))
}
#[test]
fn test_pyo3_unwrap_complex_err() {
let origin_exc = PyValueError::new_err("Value Error");
let mut err: anyhow::Error = origin_exc.into();
err = err.context("Context");
let converted: PyErr = err.into();
assert!(Python::with_gil(
|py| converted.is_instance_of::<PyRuntimeError>(py)
))
}
}