Skip to content

Commit c0b5004

Browse files
authored
Merge pull request #3455 from davidhewitt/normalized-exceptions
also use `PyErr::SetObject` on Python versions before 3.12
2 parents 80bbb30 + 2daddb4 commit c0b5004

File tree

4 files changed

+36
-13
lines changed

4 files changed

+36
-13
lines changed

newsfragments/3455.changed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
`Err` returned from `#[pyfunction]` will now have a non-None `__context__` if called from inside a `catch` block.

pytests/src/pyclasses.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,14 @@ impl AssertingBaseClass {
5858
}
5959
}
6060

61+
#[pyclass]
62+
struct ClassWithoutConstructor;
63+
6164
#[pymodule]
6265
pub fn pyclasses(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
6366
m.add_class::<EmptyClass>()?;
6467
m.add_class::<PyClassIter>()?;
6568
m.add_class::<AssertingBaseClass>()?;
69+
m.add_class::<ClassWithoutConstructor>()?;
6670
Ok(())
6771
}

pytests/tests/test_pyclasses.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Type
2+
13
import pytest
24
from pyo3_pytests import pyclasses
35

@@ -32,7 +34,29 @@ class AssertingSubClass(pyclasses.AssertingBaseClass):
3234

3335

3436
def test_new_classmethod():
35-
# The `AssertingBaseClass` constructor errors if it is not passed the relevant subclass.
37+
# The `AssertingBaseClass` constructor errors if it is not passed the
38+
# relevant subclass.
3639
_ = AssertingSubClass(expected_type=AssertingSubClass)
3740
with pytest.raises(ValueError):
3841
_ = AssertingSubClass(expected_type=str)
42+
43+
44+
class ClassWithoutConstructorPy:
45+
def __new__(cls):
46+
raise TypeError("No constructor defined")
47+
48+
49+
@pytest.mark.parametrize(
50+
"cls", [pyclasses.ClassWithoutConstructor, ClassWithoutConstructorPy]
51+
)
52+
def test_no_constructor_defined_propagates_cause(cls: Type):
53+
original_error = ValueError("Original message")
54+
with pytest.raises(Exception) as exc_info:
55+
try:
56+
raise original_error
57+
except Exception:
58+
cls() # should raise TypeError("No constructor defined")
59+
60+
assert exc_info.type is TypeError
61+
assert exc_info.value.args == ("No constructor defined",)
62+
assert exc_info.value.__context__ is original_error

src/err/err_state.rs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
#[cfg(not(Py_3_12))]
2-
use crate::types::PyString;
31
use crate::{
42
exceptions::{PyBaseException, PyTypeError},
53
ffi,
@@ -195,17 +193,14 @@ fn lazy_into_normalized_ffi_tuple(
195193
py: Python<'_>,
196194
lazy: Box<PyErrStateLazyFn>,
197195
) -> (*mut ffi::PyObject, *mut ffi::PyObject, *mut ffi::PyObject) {
198-
let PyErrStateLazyFnOutput { ptype, pvalue } = lazy(py);
199-
let (mut ptype, mut pvalue) = if unsafe { ffi::PyExceptionClass_Check(ptype.as_ptr()) } == 0 {
200-
(
201-
PyTypeError::type_object_raw(py).cast(),
202-
PyString::new(py, "exceptions must derive from BaseException").into_ptr(),
203-
)
204-
} else {
205-
(ptype.into_ptr(), pvalue.into_ptr())
206-
};
196+
// To be consistent with 3.12 logic, go via raise_lazy, but also then normalize
197+
// the resulting exception
198+
raise_lazy(py, lazy);
199+
let mut ptype = std::ptr::null_mut();
200+
let mut pvalue = std::ptr::null_mut();
207201
let mut ptraceback = std::ptr::null_mut();
208202
unsafe {
203+
ffi::PyErr_Fetch(&mut ptype, &mut pvalue, &mut ptraceback);
209204
ffi::PyErr_NormalizeException(&mut ptype, &mut pvalue, &mut ptraceback);
210205
}
211206
(ptype, pvalue, ptraceback)
@@ -218,7 +213,6 @@ fn lazy_into_normalized_ffi_tuple(
218213
///
219214
/// This would require either moving some logic from C to Rust, or requesting a new
220215
/// API in CPython.
221-
#[cfg(Py_3_12)]
222216
fn raise_lazy(py: Python<'_>, lazy: Box<PyErrStateLazyFn>) {
223217
let PyErrStateLazyFnOutput { ptype, pvalue } = lazy(py);
224218
unsafe {

0 commit comments

Comments
 (0)