From 1ea7c204e6b568e2c64b6c13b7795d10114de7e9 Mon Sep 17 00:00:00 2001 From: Ryan Butler Date: Tue, 5 Jan 2021 12:49:15 -0500 Subject: [PATCH] Add support for conversion to pyo3::PyErr (#47) * Added convertion to pyo3::PyErr gated behind feature flag * Added test_pyo3 * fix ci to not test unsupportable feature msrv * fix rustfmt Co-authored-by: Jane Lusby --- .github/workflows/ci.yml | 28 +++++++++++++++++++++++++--- Cargo.toml | 23 ++++++++++++----------- src/error.rs | 3 +++ src/error/pyo3_compat.rs | 7 +++++++ tests/test_pyo3.rs | 33 +++++++++++++++++++++++++++++++++ 5 files changed, 80 insertions(+), 14 deletions(-) create mode 100644 src/error/pyo3_compat.rs create mode 100644 tests/test_pyo3.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4ae5a3..85bacd1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: with: command: check - test-versions: + test-matrix: name: Test Suite runs-on: ubuntu-latest strategy: @@ -33,10 +33,12 @@ jobs: - stable - beta - nightly - - 1.39.0 features: - - --all-features + - # default - --no-default-features + - --features track-caller + - --features pyo3 + - --all-features steps: - uses: actions/checkout@v1 - uses: actions-rs/toolchain@v1 @@ -48,6 +50,26 @@ jobs: command: test args: ${{ matrix.features }} + test-msrv: + name: Test Suite + runs-on: ubuntu-latest + strategy: + matrix: + features: + - # default + - --no-default-features + - --features track-caller + # skip `--features pyo3` and `--all-features` because pyo3 doesn't support this msrv + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: 1.39 + override: true + - uses: actions-rs/cargo@v1 + with: + command: test + test-os: name: Test Suite runs-on: ${{ matrix.os }} diff --git a/Cargo.toml b/Cargo.toml index 6776810..196bf40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ anyhow = "1.0.28" [dependencies] indenter = "0.3.0" once_cell = "1.4.0" +pyo3 = { version = "0.13", optional = true, default-features = false } [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -36,7 +37,7 @@ no-dev-version = true [[package.metadata.release.pre-release-replacements]] file = "CHANGELOG.md" search = "Unreleased" -replace="{{version}}" +replace = "{{version}}" [[package.metadata.release.pre-release-replacements]] file = "src/lib.rs" @@ -47,22 +48,22 @@ exactly = 1 [[package.metadata.release.pre-release-replacements]] file = "CHANGELOG.md" search = "\\.\\.\\.HEAD" -replace="...{{tag_name}}" +replace = "...{{tag_name}}" exactly = 1 [[package.metadata.release.pre-release-replacements]] file = "CHANGELOG.md" search = "ReleaseDate" -replace="{{date}}" +replace = "{{date}}" [[package.metadata.release.pre-release-replacements]] -file="CHANGELOG.md" -search="" -replace="\n\n## [Unreleased] - ReleaseDate" -exactly=1 +file = "CHANGELOG.md" +search = "" +replace = "\n\n## [Unreleased] - ReleaseDate" +exactly = 1 [[package.metadata.release.pre-release-replacements]] -file="CHANGELOG.md" -search="" -replace="\n[Unreleased]: https://github.com/yaahc/{{crate_name}}/compare/{{tag_name}}...HEAD" -exactly=1 +file = "CHANGELOG.md" +search = "" +replace = "\n[Unreleased]: https://github.com/yaahc/{{crate_name}}/compare/{{tag_name}}...HEAD" +exactly = 1 diff --git a/src/error.rs b/src/error.rs index d3edaf2..20f34ac 100644 --- a/src/error.rs +++ b/src/error.rs @@ -8,6 +8,9 @@ use core::ptr::{self, NonNull}; use core::ops::{Deref, DerefMut}; +#[cfg(feature = "pyo3")] +mod pyo3_compat; + impl Report { /// Create a new error object from any error type. /// diff --git a/src/error/pyo3_compat.rs b/src/error/pyo3_compat.rs new file mode 100644 index 0000000..b70b6a5 --- /dev/null +++ b/src/error/pyo3_compat.rs @@ -0,0 +1,7 @@ +use crate::Report; + +impl From for pyo3::PyErr { + fn from(error: Report) -> Self { + pyo3::exceptions::PyRuntimeError::new_err(format!("{:?}", error)) + } +} diff --git a/tests/test_pyo3.rs b/tests/test_pyo3.rs new file mode 100644 index 0000000..0ad71af --- /dev/null +++ b/tests/test_pyo3.rs @@ -0,0 +1,33 @@ +#![cfg(feature = "pyo3")] + +use pyo3::prelude::*; + +use eyre::{bail, Result, WrapErr}; + +fn f() -> Result<()> { + use std::io; + bail!(io::Error::new(io::ErrorKind::PermissionDenied, "oh no!")); +} + +fn g() -> Result<()> { + f().wrap_err("f failed") +} + +fn h() -> Result<()> { + g().wrap_err("g failed") +} + +#[test] +fn test_pyo3_exception_contents() { + use pyo3::types::IntoPyDict; + + 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); + let pyerr = py.run("raise err", None, Some(locals)).unwrap_err(); + assert_eq!(pyerr.pvalue(py).to_string(), expected_contents); + }) +}