macros: raise AttributeError on property deletion requests

The setter function will receive a NULL value on deletion requests.
This wasn't properly handled before, leading to a panic.

The new code raises AttributeError in this scenario instead.

A test for the behavior has been added. Documentation has also
been updated to reflect the behavior.
This commit is contained in:
Gregory Szorc 2021-08-10 17:48:50 -07:00
parent 254ea53f3f
commit 410c9f13c9
4 changed files with 12 additions and 1 deletions

View File

@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Fixed
- Restrict FFI definitions `PyGILState_Check` and `Py_tracefunc` to the unlimited API. [#1787](https://github.com/PyO3/pyo3/pull/1787)
- Raise `AttributeError` to avoid panic when calling `del` on a `[setter]` defined class property. [#1775](https://github.com/PyO3/pyo3/issues/1775)
## [0.14.2] - 2021-08-09

View File

@ -454,6 +454,10 @@ impl MyClass {
In this case, the property `number` is defined and available from Python code as `self.number`.
Attributes defined by `#[setter]` or `#[pyo3(set)]` will always raise `AttributeError` on `del`
operations. Support for defining custom `del` behavior is tracked in
[#1778](https://github.com/PyO3/pyo3/issues/1778).
## Instance methods
To define a Python compatible method, an `impl` block for your struct has to be annotated with the

View File

@ -218,7 +218,11 @@ pub fn impl_py_setter_def(cls: &syn::Type, property_type: PropertyType) -> Resul
) -> std::os::raw::c_int {
pyo3::callback::handle_panic(|_py| {
#slf
let _value = _py.from_borrowed_ptr::<pyo3::types::PyAny>(_value);
let _value = _py
.from_borrowed_ptr_or_opt(_value)
.ok_or_else(|| {
pyo3::exceptions::PyAttributeError::new_err("can't delete attribute")
})?;
let _val = pyo3::FromPyObject::extract(_value)?;
pyo3::callback::convert(_py, #setter_impl)

View File

@ -54,6 +54,8 @@ fn class_with_properties() {
py_run!(py, inst, "inst.DATA = 20");
py_run!(py, inst, "assert inst.get_num() == 20 == inst.DATA");
py_expect_exception!(py, inst, "del inst.DATA", PyAttributeError);
py_run!(py, inst, "assert inst.get_num() == inst.unwrapped == 20");
py_run!(py, inst, "inst.unwrapped = 42");
py_run!(py, inst, "assert inst.get_num() == inst.unwrapped == 42");