Merge #3120
3120: feat: add #[pyo3(from_item_all)] for FromPyObject r=davidhewitt a=BlueGlassBlock This PR addresses #3112 by implementing ~~`#[pyo3(item_all)]`~~ `#[pyo3(from_item_all)]`. Co-authored-by: BlueGlassBlock <blueglassblock@outlook.com>
This commit is contained in:
commit
6356acb2f9
|
@ -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(from_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(from_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
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Allow using `#[pyo3(from_item_all)]` when deriving `FromPyObject` to specify `get_item` as getter for all fields.
|
|
@ -21,6 +21,7 @@ pub mod kw {
|
|||
syn::custom_keyword!(get);
|
||||
syn::custom_keyword!(get_all);
|
||||
syn::custom_keyword!(item);
|
||||
syn::custom_keyword!(from_item_all);
|
||||
syn::custom_keyword!(mapping);
|
||||
syn::custom_keyword!(module);
|
||||
syn::custom_keyword!(name);
|
||||
|
|
|
@ -171,7 +171,22 @@ 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 from_item_all) = options.from_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!(from_item_all.span() => "Useless `item` - the struct is already annotated with `from_item_all`"),
|
||||
FieldGetter::GetAttr(_) => bail_spanned!(
|
||||
from_item_all.span() => "The struct is already annotated with `from_item_all`, `attribute` is not allowed"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(NamedStructField {
|
||||
ident,
|
||||
|
@ -343,6 +358,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.
|
||||
from_item_all: Option<attributes::kw::from_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 +370,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::from_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 +384,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::from_item_all) {
|
||||
let kw: attributes::kw::from_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 +414,13 @@ impl ContainerOptions {
|
|||
);
|
||||
options.transparent = true;
|
||||
}
|
||||
ContainerPyO3Attribute::ItemAll(kw) => {
|
||||
ensure_spanned!(
|
||||
matches!(options.from_item_all, None),
|
||||
kw.span() => "`from_item_all` may only be provided once"
|
||||
);
|
||||
options.from_item_all = Some(kw);
|
||||
}
|
||||
ContainerPyO3Attribute::ErrorAnnotation(lit_str) => {
|
||||
ensure_spanned!(
|
||||
options.annotation.is_none(),
|
||||
|
@ -494,7 +523,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!(
|
||||
|
|
|
@ -175,4 +175,42 @@ struct InvalidTransparentWithGetter {
|
|||
field: String,
|
||||
}
|
||||
|
||||
#[derive(FromPyObject)]
|
||||
#[pyo3(from_item_all)]
|
||||
struct FromItemAllOnTuple(String);
|
||||
|
||||
#[derive(FromPyObject)]
|
||||
#[pyo3(from_item_all)]
|
||||
#[pyo3(transparent)]
|
||||
struct FromItemAllWithTransparent {
|
||||
field: String,
|
||||
}
|
||||
|
||||
#[derive(FromPyObject)]
|
||||
#[pyo3(from_item_all, from_item_all)]
|
||||
struct MultipleFromItemAll {
|
||||
field: String,
|
||||
}
|
||||
|
||||
#[derive(FromPyObject)]
|
||||
#[pyo3(from_item_all)]
|
||||
struct UselessItemAttr {
|
||||
#[pyo3(item)]
|
||||
field: String,
|
||||
}
|
||||
|
||||
#[derive(FromPyObject)]
|
||||
#[pyo3(from_item_all)]
|
||||
struct FromItemAllConflictAttr {
|
||||
#[pyo3(attribute)]
|
||||
field: String,
|
||||
}
|
||||
|
||||
#[derive(FromPyObject)]
|
||||
#[pyo3(from_item_all)]
|
||||
struct FromItemAllConflictAttrWithArgs {
|
||||
#[pyo3(attribute("f"))]
|
||||
field: String,
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
|
|
|
@ -132,7 +132,7 @@ error: only one of `attribute` or `item` can be provided
|
|||
118 | #[pyo3(item, attribute)]
|
||||
| ^
|
||||
|
||||
error: expected one of: `transparent`, `annotation`, `crate`
|
||||
error: expected one of: `transparent`, `from_item_all`, `annotation`, `crate`
|
||||
--> tests/ui/invalid_frompy_derive.rs:123:8
|
||||
|
|
||||
123 | #[pyo3(unknown = "should not work")]
|
||||
|
@ -193,3 +193,33 @@ error: `transparent` structs may not have a `getter` for the inner field
|
|||
|
|
||||
175 | field: String,
|
||||
| ^^^^^
|
||||
|
||||
error: `transparent` structs may not have a `getter` for the inner field
|
||||
--> tests/ui/invalid_frompy_derive.rs:186:5
|
||||
|
|
||||
186 | field: String,
|
||||
| ^^^^^
|
||||
|
||||
error: `from_item_all` may only be provided once
|
||||
--> tests/ui/invalid_frompy_derive.rs:190:23
|
||||
|
|
||||
190 | #[pyo3(from_item_all, from_item_all)]
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error: Useless `item` - the struct is already annotated with `from_item_all`
|
||||
--> tests/ui/invalid_frompy_derive.rs:196:8
|
||||
|
|
||||
196 | #[pyo3(from_item_all)]
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error: The struct is already annotated with `from_item_all`, `attribute` is not allowed
|
||||
--> tests/ui/invalid_frompy_derive.rs:203:8
|
||||
|
|
||||
203 | #[pyo3(from_item_all)]
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error: The struct is already annotated with `from_item_all`, `attribute` is not allowed
|
||||
--> tests/ui/invalid_frompy_derive.rs:210:8
|
||||
|
|
||||
210 | #[pyo3(from_item_all)]
|
||||
| ^^^^^^^^^^^^^
|
||||
|
|
Loading…
Reference in New Issue