Implement get/set all on pyclass
This commit is contained in:
parent
c9b26f57cd
commit
d254134154
|
@ -7,10 +7,12 @@
|
|||
| <span style="white-space: pre">`extends = BaseType`</span> | Use a custom baseclass. Defaults to [`PyAny`][params-1] |
|
||||
| <span style="white-space: pre">`freelist = N`</span> | Implements a [free list][params-2] of size N. This can improve performance for types that are often created and deleted in quick succession. Profile your code to see whether `freelist` is right for you. |
|
||||
| <span style="white-space: pre">`frozen`</span> | Declares that your pyclass is immutable. It removes the borrowchecker overhead when retrieving a shared reference to the Rust struct, but disables the ability to get a mutable reference. |
|
||||
| `get_all` | Generates getters for all fields of the pyclass. |
|
||||
| `mapping` | Inform PyO3 that this class is a [`Mapping`][params-mapping], and so leave its implementation of sequence C-API slots empty. |
|
||||
| <span style="white-space: pre">`module = "module_name"`</span> | Python code will see the class as being defined in this module. Defaults to `builtins`. |
|
||||
| <span style="white-space: pre">`name = "python_name"`</span> | Sets the name that Python sees this class as. Defaults to the name of the Rust struct. |
|
||||
| `sequence` | Inform PyO3 that this class is a [`Sequence`][params-sequence], and so leave its C-API mapping length slot empty. |
|
||||
| `set_all` | Generates setters for all fields of the pyclass. |
|
||||
| `subclass` | Allows other Python classes and `#[pyclass]` to inherit from this class. Enums cannot be subclassed. |
|
||||
| <span style="white-space: pre">`text_signature = "(arg1, arg2, ...)"`</span> | Sets the text signature for the Python class' `__new__` method. |
|
||||
| `unsendable` | Required if your struct is not [`Send`][params-3]. Rather than using `unsendable`, consider implementing your struct in a threadsafe way by e.g. substituting [`Rc`][params-4] with [`Arc`][params-5]. By using `unsendable`, your class will panic when accessed by another thread.|
|
||||
|
|
|
@ -18,6 +18,7 @@ pub mod kw {
|
|||
syn::custom_keyword!(frozen);
|
||||
syn::custom_keyword!(gc);
|
||||
syn::custom_keyword!(get);
|
||||
syn::custom_keyword!(get_all);
|
||||
syn::custom_keyword!(item);
|
||||
syn::custom_keyword!(mapping);
|
||||
syn::custom_keyword!(module);
|
||||
|
@ -25,6 +26,7 @@ pub mod kw {
|
|||
syn::custom_keyword!(pass_module);
|
||||
syn::custom_keyword!(sequence);
|
||||
syn::custom_keyword!(set);
|
||||
syn::custom_keyword!(set_all);
|
||||
syn::custom_keyword!(signature);
|
||||
syn::custom_keyword!(subclass);
|
||||
syn::custom_keyword!(text_signature);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::mem;
|
||||
|
||||
use crate::attributes::{
|
||||
self, kw, take_pyo3_options, CrateAttribute, ExtendsAttribute, FreelistAttribute,
|
||||
|
@ -60,12 +61,14 @@ pub struct PyClassPyO3Options {
|
|||
pub krate: Option<CrateAttribute>,
|
||||
pub dict: Option<kw::dict>,
|
||||
pub extends: Option<ExtendsAttribute>,
|
||||
pub get_all: Option<kw::get_all>,
|
||||
pub freelist: Option<FreelistAttribute>,
|
||||
pub frozen: Option<kw::frozen>,
|
||||
pub mapping: Option<kw::mapping>,
|
||||
pub module: Option<ModuleAttribute>,
|
||||
pub name: Option<NameAttribute>,
|
||||
pub sequence: Option<kw::sequence>,
|
||||
pub set_all: Option<kw::set_all>,
|
||||
pub subclass: Option<kw::subclass>,
|
||||
pub text_signature: Option<TextSignatureAttribute>,
|
||||
pub unsendable: Option<kw::unsendable>,
|
||||
|
@ -80,10 +83,12 @@ enum PyClassPyO3Option {
|
|||
Extends(ExtendsAttribute),
|
||||
Freelist(FreelistAttribute),
|
||||
Frozen(kw::frozen),
|
||||
GetAll(kw::get_all),
|
||||
Mapping(kw::mapping),
|
||||
Module(ModuleAttribute),
|
||||
Name(NameAttribute),
|
||||
Sequence(kw::sequence),
|
||||
SetAll(kw::set_all),
|
||||
Subclass(kw::subclass),
|
||||
TextSignature(TextSignatureAttribute),
|
||||
Unsendable(kw::unsendable),
|
||||
|
@ -105,6 +110,8 @@ impl Parse for PyClassPyO3Option {
|
|||
input.parse().map(PyClassPyO3Option::Freelist)
|
||||
} else if lookahead.peek(attributes::kw::frozen) {
|
||||
input.parse().map(PyClassPyO3Option::Frozen)
|
||||
} else if lookahead.peek(attributes::kw::get_all) {
|
||||
input.parse().map(PyClassPyO3Option::GetAll)
|
||||
} else if lookahead.peek(attributes::kw::mapping) {
|
||||
input.parse().map(PyClassPyO3Option::Mapping)
|
||||
} else if lookahead.peek(attributes::kw::module) {
|
||||
|
@ -113,6 +120,8 @@ impl Parse for PyClassPyO3Option {
|
|||
input.parse().map(PyClassPyO3Option::Name)
|
||||
} else if lookahead.peek(attributes::kw::sequence) {
|
||||
input.parse().map(PyClassPyO3Option::Sequence)
|
||||
} else if lookahead.peek(attributes::kw::set_all) {
|
||||
input.parse().map(PyClassPyO3Option::SetAll)
|
||||
} else if lookahead.peek(attributes::kw::subclass) {
|
||||
input.parse().map(PyClassPyO3Option::Subclass)
|
||||
} else if lookahead.peek(attributes::kw::text_signature) {
|
||||
|
@ -165,10 +174,12 @@ impl PyClassPyO3Options {
|
|||
PyClassPyO3Option::Extends(extends) => set_option!(extends),
|
||||
PyClassPyO3Option::Freelist(freelist) => set_option!(freelist),
|
||||
PyClassPyO3Option::Frozen(frozen) => set_option!(frozen),
|
||||
PyClassPyO3Option::GetAll(get_all) => set_option!(get_all),
|
||||
PyClassPyO3Option::Mapping(mapping) => set_option!(mapping),
|
||||
PyClassPyO3Option::Module(module) => set_option!(module),
|
||||
PyClassPyO3Option::Name(name) => set_option!(name),
|
||||
PyClassPyO3Option::Sequence(sequence) => set_option!(sequence),
|
||||
PyClassPyO3Option::SetAll(set_all) => set_option!(set_all),
|
||||
PyClassPyO3Option::Subclass(subclass) => set_option!(subclass),
|
||||
PyClassPyO3Option::TextSignature(text_signature) => set_option!(text_signature),
|
||||
PyClassPyO3Option::Unsendable(unsendable) => set_option!(unsendable),
|
||||
|
@ -212,7 +223,7 @@ pub fn build_py_class(
|
|||
For an explanation, see https://pyo3.rs/latest/class.html#no-generic-parameters"
|
||||
);
|
||||
|
||||
let field_options = match &mut class.fields {
|
||||
let mut field_options: Vec<(&syn::Field, FieldPyO3Options)> = match &mut class.fields {
|
||||
syn::Fields::Named(fields) => fields
|
||||
.named
|
||||
.iter_mut()
|
||||
|
@ -230,11 +241,33 @@ pub fn build_py_class(
|
|||
})
|
||||
.collect::<Result<_>>()?,
|
||||
syn::Fields::Unit => {
|
||||
if let Some(attr) = args.options.set_all {
|
||||
return Err(syn::Error::new(attr.span(), UNIT_SET));
|
||||
};
|
||||
if let Some(attr) = args.options.get_all {
|
||||
return Err(syn::Error::new(attr.span(), UNIT_GET));
|
||||
};
|
||||
// No fields for unit struct
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(attr) = args.options.get_all {
|
||||
for (_, FieldPyO3Options { get, .. }) in &mut field_options {
|
||||
if mem::replace(get, true) {
|
||||
return Err(syn::Error::new(attr.span(), DUPE_GET));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(attr) = args.options.set_all {
|
||||
for (_, FieldPyO3Options { set, .. }) in &mut field_options {
|
||||
if mem::replace(set, true) {
|
||||
return Err(syn::Error::new(attr.span(), DUPE_SET));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_class(&class.ident, &args, doc, field_options, methods_type, krate)
|
||||
}
|
||||
|
||||
|
@ -1085,3 +1118,10 @@ fn define_inventory_class(inventory_class_name: &syn::Ident) -> TokenStream {
|
|||
_pyo3::inventory::collect!(#inventory_class_name);
|
||||
}
|
||||
}
|
||||
|
||||
const DUPE_SET: &str = "duplicate `set` - the struct is already annotated with `set_all`";
|
||||
const DUPE_GET: &str = "duplicate `get` - the struct is already annotated with `get_all`";
|
||||
const UNIT_GET: &str =
|
||||
"`get_all` on an unit struct does nothing, because unit structs have no fields";
|
||||
const UNIT_SET: &str =
|
||||
"`set_all` on an unit struct does nothing, because unit structs have no fields";
|
||||
|
|
|
@ -117,6 +117,7 @@ fn _test_compile_errors() {
|
|||
t.compile_fail("tests/ui/not_send.rs");
|
||||
t.compile_fail("tests/ui/not_send2.rs");
|
||||
t.compile_fail("tests/ui/not_send3.rs");
|
||||
t.compile_fail("tests/ui/get_set_all.rs");
|
||||
}
|
||||
|
||||
#[rustversion::before(1.63)]
|
||||
|
|
|
@ -158,3 +158,34 @@ fn tuple_struct_getter_setter() {
|
|||
py_assert!(py, inst, "inst.num == 20");
|
||||
});
|
||||
}
|
||||
|
||||
#[pyclass(get_all, set_all)]
|
||||
struct All {
|
||||
num: i32,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_set_all() {
|
||||
Python::with_gil(|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::with_gil(|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");
|
||||
});
|
||||
}
|
||||
|
|
21
tests/ui/get_set_all.rs
Normal file
21
tests/ui/get_set_all.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
use pyo3::prelude::*;
|
||||
|
||||
#[pyclass(set_all)]
|
||||
struct Foo;
|
||||
|
||||
#[pyclass(set_all)]
|
||||
struct Foo2{
|
||||
#[pyo3(set)]
|
||||
field: u8,
|
||||
}
|
||||
|
||||
#[pyclass(get_all)]
|
||||
struct Foo3;
|
||||
|
||||
#[pyclass(get_all)]
|
||||
struct Foo4{
|
||||
#[pyo3(get)]
|
||||
field: u8,
|
||||
}
|
||||
|
||||
fn main() {}
|
23
tests/ui/get_set_all.stderr
Normal file
23
tests/ui/get_set_all.stderr
Normal file
|
@ -0,0 +1,23 @@
|
|||
error: `set_all` on an unit struct does nothing, because unit structs have no fields
|
||||
--> tests/ui/get_set_all.rs:3:11
|
||||
|
|
||||
3 | #[pyclass(set_all)]
|
||||
| ^^^^^^^
|
||||
|
||||
error: duplicate `set` - the struct is already annotated with `set_all`
|
||||
--> tests/ui/get_set_all.rs:6:11
|
||||
|
|
||||
6 | #[pyclass(set_all)]
|
||||
| ^^^^^^^
|
||||
|
||||
error: `get_all` on an unit struct does nothing, because unit structs have no fields
|
||||
--> tests/ui/get_set_all.rs:12:11
|
||||
|
|
||||
12 | #[pyclass(get_all)]
|
||||
| ^^^^^^^
|
||||
|
||||
error: duplicate `get` - the struct is already annotated with `get_all`
|
||||
--> tests/ui/get_set_all.rs:15:11
|
||||
|
|
||||
15 | #[pyclass(get_all)]
|
||||
| ^^^^^^^
|
|
@ -1,4 +1,4 @@
|
|||
error: expected one of: `crate`, `dict`, `extends`, `freelist`, `frozen`, `mapping`, `module`, `name`, `sequence`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc`
|
||||
error: expected one of: `crate`, `dict`, `extends`, `freelist`, `frozen`, `get_all`, `mapping`, `module`, `name`, `sequence`, `set_all`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc`
|
||||
--> tests/ui/invalid_pyclass_args.rs:3:11
|
||||
|
|
||||
3 | #[pyclass(extend=pyo3::types::PyDict)]
|
||||
|
@ -34,7 +34,7 @@ error: expected string literal
|
|||
18 | #[pyclass(module = my_module)]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: expected one of: `crate`, `dict`, `extends`, `freelist`, `frozen`, `mapping`, `module`, `name`, `sequence`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc`
|
||||
error: expected one of: `crate`, `dict`, `extends`, `freelist`, `frozen`, `get_all`, `mapping`, `module`, `name`, `sequence`, `set_all`, `subclass`, `text_signature`, `unsendable`, `weakref`, `gc`
|
||||
--> tests/ui/invalid_pyclass_args.rs:21:11
|
||||
|
|
||||
21 | #[pyclass(weakrev)]
|
||||
|
|
Loading…
Reference in a new issue