From a629e8267adcd6e924822deef7278aa7e3ead14f Mon Sep 17 00:00:00 2001 From: Antoine Romero-Romero Date: Sun, 5 Mar 2023 18:44:45 +0000 Subject: [PATCH] feat: add #[pyo3(get, set)] for Cell --- guide/src/class.md | 2 ++ newsfragments/3014.added.md | 1 + src/conversion.rs | 19 +++++++++++++++++++ tests/test_getter_setter.rs | 27 +++++++++++++++++++++++++++ 4 files changed, 49 insertions(+) create mode 100644 newsfragments/3014.added.md diff --git a/guide/src/class.md b/guide/src/class.md index 5220ea94..2f02c25f 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -437,6 +437,8 @@ To use these annotations, your field type must implement some conversion traits: - For `get` the field type must implement both `IntoPy` and `Clone`. - For `set` the field type must implement `FromPyObject`. +For example, implementations of those traits are provided for the `Cell` type, if the inner type also implements the trait. This means you can use `#[pyo3(get, set)]` on fields wrapped in a `Cell`. + ### Object properties using `#[getter]` and `#[setter]` For cases which don't satisfy the `#[pyo3(get, set)]` trait requirements, or need side effects, descriptor methods can be defined in a `#[pymethods]` `impl` block. diff --git a/newsfragments/3014.added.md b/newsfragments/3014.added.md new file mode 100644 index 00000000..ff11a135 --- /dev/null +++ b/newsfragments/3014.added.md @@ -0,0 +1 @@ +Add an `IntoPy`, `ToPyObject` and `FromPyObject` impl for `Cell`. \ No newline at end of file diff --git a/src/conversion.rs b/src/conversion.rs index 5ecf884e..4eb3dadd 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -10,6 +10,7 @@ use crate::types::PyTuple; use crate::{ ffi, gil, Py, PyAny, PyCell, PyClass, PyNativeType, PyObject, PyRef, PyRefMut, Python, }; +use std::cell::Cell; use std::ptr::NonNull; /// Returns a borrowed pointer to a Python object. @@ -370,6 +371,24 @@ where } } +impl ToPyObject for Cell { + fn to_object(&self, py: Python<'_>) -> PyObject { + self.get().to_object(py) + } +} + +impl> IntoPy for Cell { + fn into_py(self, py: Python<'_>) -> PyObject { + self.get().into_py(py) + } +} + +impl<'a, T: FromPyObject<'a>> FromPyObject<'a> for Cell { + fn extract(ob: &'a PyAny) -> PyResult { + T::extract(ob).map(Cell::new) + } +} + impl<'a, T> FromPyObject<'a> for &'a PyCell where T: PyClass, diff --git a/tests/test_getter_setter.rs b/tests/test_getter_setter.rs index 0928a818..222421ce 100644 --- a/tests/test_getter_setter.rs +++ b/tests/test_getter_setter.rs @@ -1,5 +1,7 @@ #![cfg(feature = "macros")] +use std::cell::Cell; + use pyo3::prelude::*; use pyo3::py_run; use pyo3::types::{IntoPyDict, PyList}; @@ -189,3 +191,28 @@ fn get_all_and_set() { py_run!(py, inst, "inst.num = 20; assert inst.num == 20"); }); } + +#[pyclass] +struct CellGetterSetter { + #[pyo3(get, set)] + cell_inner: Cell, +} + +#[test] +fn cell_getter_setter() { + let c = CellGetterSetter { + cell_inner: Cell::new(10), + }; + Python::with_gil(|py| { + let inst = Py::new(py, c).unwrap().to_object(py); + let cell = Cell::new(20).to_object(py); + + py_run!(py, cell, "assert cell == 20"); + py_run!(py, inst, "assert inst.cell_inner == 10"); + py_run!( + py, + inst, + "inst.cell_inner = 20; assert inst.cell_inner == 20" + ); + }); +}