357 lines
7.7 KiB
Rust
357 lines
7.7 KiB
Rust
#![cfg(feature = "macros")]
|
|
|
|
use pyo3::prelude::*;
|
|
use pyo3::py_run;
|
|
use pyo3::types::PyString;
|
|
use pyo3::types::{IntoPyDict, PyList};
|
|
use std::cell::Cell;
|
|
use std::convert::Infallible;
|
|
|
|
mod test_utils;
|
|
|
|
#[pyclass]
|
|
struct ClassWithProperties {
|
|
num: i32,
|
|
}
|
|
|
|
#[pymethods]
|
|
impl ClassWithProperties {
|
|
fn get_num(&self) -> i32 {
|
|
self.num
|
|
}
|
|
|
|
#[getter(DATA)]
|
|
/// a getter for data
|
|
fn get_data(&self) -> i32 {
|
|
self.num
|
|
}
|
|
#[setter(DATA)]
|
|
fn set_data(&mut self, value: i32) {
|
|
self.num = value;
|
|
}
|
|
|
|
#[getter]
|
|
/// a getter with a type un-wrapped by PyResult
|
|
fn get_unwrapped(&self) -> i32 {
|
|
self.num
|
|
}
|
|
|
|
#[setter]
|
|
fn set_unwrapped(&mut self, value: i32) {
|
|
self.num = value;
|
|
}
|
|
|
|
#[setter]
|
|
fn set_from_len(&mut self, #[pyo3(from_py_with = extract_len)] value: i32) {
|
|
self.num = value;
|
|
}
|
|
|
|
#[setter]
|
|
fn set_from_any(&mut self, value: &Bound<'_, PyAny>) -> PyResult<()> {
|
|
self.num = value.extract()?;
|
|
Ok(())
|
|
}
|
|
|
|
#[getter]
|
|
fn get_data_list<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyList>> {
|
|
PyList::new(py, [self.num])
|
|
}
|
|
}
|
|
|
|
fn extract_len(any: &Bound<'_, PyAny>) -> PyResult<i32> {
|
|
any.len().map(|len| len as i32)
|
|
}
|
|
|
|
#[test]
|
|
fn class_with_properties() {
|
|
Python::attach(|py| {
|
|
let inst = Py::new(py, ClassWithProperties { num: 10 }).unwrap();
|
|
|
|
py_run!(py, inst, "assert inst.get_num() == 10");
|
|
py_run!(py, inst, "assert inst.get_num() == inst.DATA");
|
|
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");
|
|
py_run!(py, inst, "assert inst.data_list == [42]");
|
|
|
|
py_run!(py, inst, "inst.from_len = [0, 0, 0]");
|
|
py_run!(py, inst, "assert inst.get_num() == 3");
|
|
|
|
py_run!(py, inst, "inst.from_any = 15");
|
|
py_run!(py, inst, "assert inst.get_num() == 15");
|
|
|
|
let d = [("C", py.get_type::<ClassWithProperties>())]
|
|
.into_py_dict(py)
|
|
.unwrap();
|
|
py_assert!(py, *d, "C.DATA.__doc__ == 'a getter for data'");
|
|
});
|
|
}
|
|
|
|
#[pyclass]
|
|
struct GetterSetter {
|
|
#[pyo3(get, set)]
|
|
num: i32,
|
|
#[pyo3(get, set)]
|
|
text: String,
|
|
}
|
|
|
|
#[pymethods]
|
|
impl GetterSetter {
|
|
fn get_num2(&self) -> i32 {
|
|
self.num
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn getter_setter_autogen() {
|
|
Python::attach(|py| {
|
|
let inst = Py::new(
|
|
py,
|
|
GetterSetter {
|
|
num: 10,
|
|
text: "Hello".to_string(),
|
|
},
|
|
)
|
|
.unwrap();
|
|
|
|
py_run!(py, inst, "assert inst.num == 10");
|
|
py_run!(py, inst, "inst.num = 20; assert inst.num == 20");
|
|
py_run!(
|
|
py,
|
|
inst,
|
|
"assert inst.text == 'Hello'; inst.text = 'There'; assert inst.text == 'There'"
|
|
);
|
|
});
|
|
}
|
|
|
|
#[pyclass]
|
|
struct RefGetterSetter {
|
|
num: i32,
|
|
}
|
|
|
|
#[pymethods]
|
|
impl RefGetterSetter {
|
|
#[getter]
|
|
fn get_num(slf: PyRef<'_, Self>) -> i32 {
|
|
slf.num
|
|
}
|
|
|
|
#[setter]
|
|
fn set_num(mut slf: PyRefMut<'_, Self>, value: i32) {
|
|
slf.num = value;
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn ref_getter_setter() {
|
|
// Regression test for #837
|
|
Python::attach(|py| {
|
|
let inst = Py::new(py, RefGetterSetter { num: 10 }).unwrap();
|
|
|
|
py_run!(py, inst, "assert inst.num == 10");
|
|
py_run!(py, inst, "inst.num = 20; assert inst.num == 20");
|
|
});
|
|
}
|
|
|
|
#[pyclass]
|
|
struct TupleClassGetterSetter(i32);
|
|
|
|
#[pymethods]
|
|
impl TupleClassGetterSetter {
|
|
#[getter(num)]
|
|
fn get_num(&self) -> i32 {
|
|
self.0
|
|
}
|
|
|
|
#[setter(num)]
|
|
fn set_num(&mut self, value: i32) {
|
|
self.0 = value;
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn tuple_struct_getter_setter() {
|
|
Python::attach(|py| {
|
|
let inst = Py::new(py, TupleClassGetterSetter(10)).unwrap();
|
|
|
|
py_assert!(py, inst, "inst.num == 10");
|
|
py_run!(py, inst, "inst.num = 20");
|
|
py_assert!(py, inst, "inst.num == 20");
|
|
});
|
|
}
|
|
|
|
#[pyclass(get_all, set_all)]
|
|
struct All {
|
|
num: i32,
|
|
}
|
|
|
|
#[test]
|
|
fn get_set_all() {
|
|
Python::attach(|py| {
|
|
let inst = Py::new(py, All { num: 10 }).unwrap();
|
|
|
|
py_run!(py, inst, "assert inst.num == 10");
|
|
py_run!(py, inst, "inst.num = 20; assert inst.num == 20");
|
|
});
|
|
}
|
|
|
|
#[pyclass(get_all)]
|
|
struct All2 {
|
|
#[pyo3(set)]
|
|
num: i32,
|
|
}
|
|
|
|
#[test]
|
|
fn get_all_and_set() {
|
|
Python::attach(|py| {
|
|
let inst = Py::new(py, All2 { num: 10 }).unwrap();
|
|
|
|
py_run!(py, inst, "assert inst.num == 10");
|
|
py_run!(py, inst, "inst.num = 20; assert inst.num == 20");
|
|
});
|
|
}
|
|
|
|
#[pyclass(unsendable)]
|
|
struct CellGetterSetter {
|
|
#[pyo3(get, set)]
|
|
cell_inner: Cell<i32>,
|
|
}
|
|
|
|
#[test]
|
|
fn cell_getter_setter() {
|
|
let c = CellGetterSetter {
|
|
cell_inner: Cell::new(10),
|
|
};
|
|
Python::attach(|py| {
|
|
let inst = Py::new(py, c).unwrap();
|
|
let cell = Cell::new(20i32).into_pyobject(py).unwrap();
|
|
|
|
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"
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn borrowed_value_with_lifetime_of_self() {
|
|
#[pyclass]
|
|
struct BorrowedValue {}
|
|
|
|
#[pymethods]
|
|
impl BorrowedValue {
|
|
#[getter]
|
|
fn value(&self) -> &str {
|
|
"value"
|
|
}
|
|
}
|
|
|
|
Python::attach(|py| {
|
|
let inst = Py::new(py, BorrowedValue {}).unwrap();
|
|
|
|
py_run!(py, inst, "assert inst.value == 'value'");
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn frozen_py_field_get() {
|
|
#[pyclass(frozen)]
|
|
struct FrozenPyField {
|
|
#[pyo3(get)]
|
|
value: Py<PyString>,
|
|
}
|
|
|
|
Python::attach(|py| {
|
|
let inst = Py::new(
|
|
py,
|
|
FrozenPyField {
|
|
value: "value".into_pyobject(py).unwrap().unbind(),
|
|
},
|
|
)
|
|
.unwrap();
|
|
|
|
py_run!(py, inst, "assert inst.value == 'value'");
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_optional_setter() {
|
|
#[pyclass]
|
|
struct SimpleClass {
|
|
field: Option<u32>,
|
|
}
|
|
|
|
#[pymethods]
|
|
impl SimpleClass {
|
|
#[getter]
|
|
fn get_field(&self) -> Option<u32> {
|
|
self.field
|
|
}
|
|
|
|
#[setter]
|
|
fn set_field(&mut self, field: Option<u32>) {
|
|
self.field = field;
|
|
}
|
|
}
|
|
|
|
Python::attach(|py| {
|
|
let instance = Py::new(py, SimpleClass { field: None }).unwrap();
|
|
py_run!(py, instance, "assert instance.field is None");
|
|
py_run!(
|
|
py,
|
|
instance,
|
|
"instance.field = 42; assert instance.field == 42"
|
|
);
|
|
py_run!(
|
|
py,
|
|
instance,
|
|
"instance.field = None; assert instance.field is None"
|
|
);
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn test_ref_only_getter() {
|
|
#[derive(Clone)]
|
|
struct RefOnly;
|
|
|
|
impl<'py> IntoPyObject<'py> for &RefOnly {
|
|
type Target = PyString;
|
|
type Output = Bound<'py, PyString>;
|
|
type Error = Infallible;
|
|
|
|
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
|
|
Ok(PyString::new(py, "value"))
|
|
}
|
|
}
|
|
|
|
#[pyclass]
|
|
struct Container {
|
|
#[pyo3(get)]
|
|
value: RefOnly,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_unit_getter() {
|
|
#[derive(Clone)]
|
|
#[pyclass(skip_from_py_object)]
|
|
struct Container {
|
|
#[pyo3(get)]
|
|
value: (),
|
|
}
|
|
|
|
Python::attach(|py| {
|
|
let instance = Py::new(py, Container { value: () }).unwrap();
|
|
py_run!(py, instance, "assert instance.value is ()");
|
|
})
|
|
}
|