feat: add #[pyo3(item_all)] for FromPyObject

This commit is contained in:
BlueGlassBlock 2023-04-25 19:29:44 +08:00
parent f060088792
commit 30b2ed1d85
No known key found for this signature in database
GPG Key ID: E69DA4EC7A98B286
3 changed files with 59 additions and 2 deletions

View File

@ -137,6 +137,35 @@ from a mapping with the key `"key"`. The arguments for `attribute` are restricte
non-empty string literals while `item` can take any valid literal that implements
`ToBorrowedObject`.
You can use `#[pyo3(item_all)]` on a struct to extract every field with `get_item` method.
In this case, you can't use `#[pyo3(attribute)]` or barely use `#[pyo3(item)]` on any field.
However, using `#[pyo3(item("key"))]` to specify the key for a field is still allowed.
```rust
use pyo3::prelude::*;
#[derive(FromPyObject)]
#[pyo3(item_all)]
struct RustyStruct {
foo: String,
bar: String,
#[pyo3(item("foobar"))]
baz: String,
}
#
# fn main() -> PyResult<()> {
# Python::with_gil(|py| -> PyResult<()> {
# let py_dict = py.eval("{'foo': 'foo', 'bar': 'bar', 'foobar': 'foobar'}", None, None)?;
# let rustystruct: RustyStruct = py_dict.extract()?;
# assert_eq!(rustystruct.foo, "foo");
# assert_eq!(rustystruct.bar, "bar");
# assert_eq!(rustystruct.baz, "foobar");
#
# Ok(())
# })
# }
```
#### Deriving [`FromPyObject`] for tuple structs
Tuple structs are also supported but do not allow customizing the extraction. The input is

View File

@ -21,6 +21,7 @@ pub mod kw {
syn::custom_keyword!(get);
syn::custom_keyword!(get_all);
syn::custom_keyword!(item);
syn::custom_keyword!(item_all);
syn::custom_keyword!(mapping);
syn::custom_keyword!(module);
syn::custom_keyword!(name);

View File

@ -171,7 +171,20 @@ impl<'a> Container<'a> {
.ident
.as_ref()
.expect("Named fields should have identifiers");
let attrs = FieldPyO3Attributes::from_attrs(&field.attrs)?;
let mut attrs = FieldPyO3Attributes::from_attrs(&field.attrs)?;
if let Some(ref item_all) = options.item_all {
if let Some(replaced) = attrs.getter.replace(FieldGetter::GetItem(None))
{
match replaced {
FieldGetter::GetItem(Some(item_name)) => {
attrs.getter = Some(FieldGetter::GetItem(Some(item_name)));
}
FieldGetter::GetItem(None) => bail_spanned!(item_all.span() => "Useless `item` - the struct is already annotated with `item_all`"),
FieldGetter::GetAttr(_) => bail_spanned!(item_all.span() => "The struct is already annotated with `item_all`, `attr` is not allowed"),
}
}
}
Ok(NamedStructField {
ident,
@ -343,6 +356,8 @@ impl<'a> Container<'a> {
struct ContainerOptions {
/// Treat the Container as a Wrapper, directly extract its fields from the input object.
transparent: bool,
/// Force every field to be extracted from item of source Python object.
item_all: Option<attributes::kw::item_all>,
/// Change the name of an enum variant in the generated error message.
annotation: Option<syn::LitStr>,
/// Change the path for the pyo3 crate
@ -353,6 +368,8 @@ struct ContainerOptions {
enum ContainerPyO3Attribute {
/// Treat the Container as a Wrapper, directly extract its fields from the input object.
Transparent(attributes::kw::transparent),
/// Force every field to be extracted from item of source Python object.
ItemAll(attributes::kw::item_all),
/// Change the name of an enum variant in the generated error message.
ErrorAnnotation(LitStr),
/// Change the path for the pyo3 crate
@ -365,6 +382,9 @@ impl Parse for ContainerPyO3Attribute {
if lookahead.peek(attributes::kw::transparent) {
let kw: attributes::kw::transparent = input.parse()?;
Ok(ContainerPyO3Attribute::Transparent(kw))
} else if lookahead.peek(attributes::kw::item_all) {
let kw: attributes::kw::item_all = input.parse()?;
Ok(ContainerPyO3Attribute::ItemAll(kw))
} else if lookahead.peek(attributes::kw::annotation) {
let _: attributes::kw::annotation = input.parse()?;
let _: Token![=] = input.parse()?;
@ -392,6 +412,13 @@ impl ContainerOptions {
);
options.transparent = true;
}
ContainerPyO3Attribute::ItemAll(kw) => {
ensure_spanned!(
matches!(options.item_all, None),
kw.span() => "`item_all` may only be provided once"
);
options.item_all = Some(kw);
}
ContainerPyO3Attribute::ErrorAnnotation(lit_str) => {
ensure_spanned!(
options.annotation.is_none(),
@ -494,7 +521,7 @@ impl FieldPyO3Attributes {
getter.is_none(),
attr.span() => "only one of `attribute` or `item` can be provided"
);
getter = Some(field_getter)
getter = Some(field_getter);
}
FieldPyO3Attribute::FromPyWith(from_py_with_attr) => {
ensure_spanned!(