always normalize exceptions before raising

This commit is contained in:
David Hewitt 2023-09-23 14:51:24 +01:00
parent 42f9cd4476
commit 1e8833e15e
6 changed files with 106 additions and 12 deletions

View File

@ -0,0 +1 @@
Fix `IterNextOutput::Return` not returning a value on PyPy.

View File

@ -1,5 +1,6 @@
hypothesis>=3.55 hypothesis>=3.55
pytest>=6.0 pytest>=6.0
pytest-asyncio>=0.21
pytest-benchmark>=3.4 pytest-benchmark>=3.4
psutil>=5.6 psutil>=5.6
typing_extensions>=4.0.0 typing_extensions>=4.0.0

87
pytests/src/awaitable.rs Normal file
View File

@ -0,0 +1,87 @@
//! The following classes are examples of objects which implement Python's
//! awaitable protocol.
//!
//! Both IterAwaitable and FutureAwaitable will return a value immediately
//! when awaited, see guide examples related to pyo3-asyncio for ways
//! to suspend tasks and await results.
use pyo3::{prelude::*, pyclass::IterNextOutput};
#[pyclass]
#[derive(Debug)]
pub(crate) struct IterAwaitable {
result: Option<PyResult<PyObject>>,
}
#[pymethods]
impl IterAwaitable {
#[new]
fn new(result: PyObject) -> Self {
IterAwaitable {
result: Some(Ok(result)),
}
}
fn __await__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> {
pyself
}
fn __iter__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> {
pyself
}
fn __next__(&mut self, py: Python<'_>) -> PyResult<IterNextOutput<PyObject, PyObject>> {
match self.result.take() {
Some(res) => match res {
Ok(v) => Ok(IterNextOutput::Return(v)),
Err(err) => Err(err),
},
_ => Ok(IterNextOutput::Yield(py.None())),
}
}
}
#[pyclass]
pub(crate) struct FutureAwaitable {
#[pyo3(get, set, name = "_asyncio_future_blocking")]
py_block: bool,
result: Option<PyResult<PyObject>>,
}
#[pymethods]
impl FutureAwaitable {
#[new]
fn new(result: PyObject) -> Self {
FutureAwaitable {
py_block: false,
result: Some(Ok(result)),
}
}
fn __await__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> {
pyself
}
fn __iter__(pyself: PyRef<'_, Self>) -> PyRef<'_, Self> {
pyself
}
fn __next__(
mut pyself: PyRefMut<'_, Self>,
) -> PyResult<IterNextOutput<PyRefMut<'_, Self>, PyObject>> {
match pyself.result {
Some(_) => match pyself.result.take().unwrap() {
Ok(v) => Ok(IterNextOutput::Return(v)),
Err(err) => Err(err),
},
_ => Ok(IterNextOutput::Yield(pyself)),
}
}
}
#[pymodule]
pub fn awaitable(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_class::<IterAwaitable>()?;
m.add_class::<FutureAwaitable>()?;
Ok(())
}

View File

@ -2,6 +2,7 @@ use pyo3::prelude::*;
use pyo3::types::PyDict; use pyo3::types::PyDict;
use pyo3::wrap_pymodule; use pyo3::wrap_pymodule;
pub mod awaitable;
pub mod buf_and_str; pub mod buf_and_str;
pub mod comparisons; pub mod comparisons;
pub mod datetime; pub mod datetime;
@ -17,6 +18,7 @@ pub mod subclassing;
#[pymodule] #[pymodule]
fn pyo3_pytests(py: Python<'_>, m: &PyModule) -> PyResult<()> { fn pyo3_pytests(py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_wrapped(wrap_pymodule!(awaitable::awaitable))?;
#[cfg(not(Py_LIMITED_API))] #[cfg(not(Py_LIMITED_API))]
m.add_wrapped(wrap_pymodule!(buf_and_str::buf_and_str))?; m.add_wrapped(wrap_pymodule!(buf_and_str::buf_and_str))?;
m.add_wrapped(wrap_pymodule!(comparisons::comparisons))?; m.add_wrapped(wrap_pymodule!(comparisons::comparisons))?;
@ -37,6 +39,7 @@ fn pyo3_pytests(py: Python<'_>, m: &PyModule) -> PyResult<()> {
let sys = PyModule::import(py, "sys")?; let sys = PyModule::import(py, "sys")?;
let sys_modules: &PyDict = sys.getattr("modules")?.downcast()?; let sys_modules: &PyDict = sys.getattr("modules")?.downcast()?;
sys_modules.set_item("pyo3_pytests.awaitable", m.getattr("awaitable")?)?;
sys_modules.set_item("pyo3_pytests.buf_and_str", m.getattr("buf_and_str")?)?; sys_modules.set_item("pyo3_pytests.buf_and_str", m.getattr("buf_and_str")?)?;
sys_modules.set_item("pyo3_pytests.comparisons", m.getattr("comparisons")?)?; sys_modules.set_item("pyo3_pytests.comparisons", m.getattr("comparisons")?)?;
sys_modules.set_item("pyo3_pytests.datetime", m.getattr("datetime")?)?; sys_modules.set_item("pyo3_pytests.datetime", m.getattr("datetime")?)?;

View File

@ -0,0 +1,13 @@
import pytest
from pyo3_pytests.awaitable import IterAwaitable, FutureAwaitable
@pytest.mark.asyncio
async def test_iter_awaitable():
assert await IterAwaitable(5) == 5
@pytest.mark.asyncio
async def test_future_awaitable():
assert await FutureAwaitable(5) == 5

View File

@ -156,18 +156,7 @@ impl PyErrState {
#[cfg(not(Py_3_12))] #[cfg(not(Py_3_12))]
pub(crate) fn restore(self, py: Python<'_>) { pub(crate) fn restore(self, py: Python<'_>) {
let (ptype, pvalue, ptraceback) = match self { let (ptype, pvalue, ptraceback) = match self {
PyErrState::Lazy(lazy) => { PyErrState::Lazy(lazy) => lazy_into_normalized_ffi_tuple(py, lazy),
let PyErrStateLazyFnOutput { ptype, pvalue } = lazy(py);
if unsafe { ffi::PyExceptionClass_Check(ptype.as_ptr()) } == 0 {
(
PyTypeError::type_object_raw(py).cast(),
PyString::new(py, "exceptions must derive from BaseException").into_ptr(),
std::ptr::null_mut(),
)
} else {
(ptype.into_ptr(), pvalue.into_ptr(), std::ptr::null_mut())
}
}
PyErrState::FfiTuple { PyErrState::FfiTuple {
ptype, ptype,
pvalue, pvalue,