More FromPyObject derive suggestions by @davidhewitt

This commit is contained in:
Sebastian Pütz 2020-08-30 19:04:42 +02:00
parent 7a9f4a1633
commit 0f32f886b8
4 changed files with 61 additions and 30 deletions

View File

@ -183,17 +183,28 @@ use pyo3::prelude::*;
struct RustyTuple(String, String);
```
#### Deriving [`FromPyObject`] for wrapper types
The `pyo3(transparent)` attribute can be used on structs with exactly one field. This results
in extracting directly from the input object, i.e. `obj.extract()`, rather than trying to access
an item or attribute.
Tuple structs with a single field are treated as wrapper types which are described in the
following section. To override this behaviour and ensure that the input is in fact a tuple,
specify the struct as
```
use pyo3::prelude::*;
#[derive(FromPyObject)]
#[pyo3(transparent)]
struct RustyTransparentTuple(String);
struct RustyTuple((String,));
```
#### Deriving [`FromPyObject`] for wrapper types
The `pyo3(transparent)` attribute can be used on structs with exactly one field. This results
in extracting directly from the input object, i.e. `obj.extract()`, rather than trying to access
an item or attribute. This behaviour is enabled per default for newtype structs and tuple-variants
with a single field.
```
use pyo3::prelude::*;
#[derive(FromPyObject)]
struct RustyTransparentTupleStruct(String);
#[derive(FromPyObject)]
#[pyo3(transparent)]
@ -217,12 +228,10 @@ use pyo3::prelude::*;
#[derive(FromPyObject)]
enum RustyEnum<'a> {
#[pyo3(transparent)]
Int(usize), // input is a positive int
#[pyo3(transparent)]
String(String), // input is a string
IntTuple(usize, usize), // input is a 2-tuple with positive ints
StringIntTuple(String, usize), // innput is a 2-tuple with String and int
StringIntTuple(String, usize), // input is a 2-tuple with String and int
Coordinates3d { // needs to be in front of 2d
x: usize,
y: usize,
@ -257,9 +266,28 @@ enum RustyEnum {
```
If the input is neither a string nor an integer, the error message will be:
`"Can't convert <INPUT> to str, int"`, where `<INPUT>` is replaced by the type name and
`"Can't convert <INPUT> to Union[str, int]"`, where `<INPUT>` is replaced by the type name and
`repr()` of the input object.
#### `#[derive(FromPyObject)]` Container Attributes
- `pyo3(transparent)`
- extract the field directly from the object as `obj.extract()` instead of `get_item()` or
`getattr()`
- Newtype structs and tuple-variants are treated as transparent per default.
- only supported for single-field structs and enum variants
- `pyo3(annotation = "name")`
- changes the name of the failed variant in the generated error message in case of failure.
- e.g. `pyo3("int")` reports the variant's type as `int`.
- only supported for enum variants
#### `#[derive(FromPyObject)]` Field Attributes
- `pyo3(attribute)`, `pyo3(attribute("name"))`
- retrieve the field from an attribute, possibly with a custom name specified as an argument
- argument must be a string-literal.
- `pyo3(item)`, `pyo3(item("key"))`
- retrieve the field from a mapping, possibly with the custom key specified as an argument.
- can be any literal that implements `ToBorrowedObject`
### `IntoPy<T>`
This trait defines the to-python conversion for a Rust type. It is usually implemented as

View File

@ -65,6 +65,11 @@ impl<'a> Enum<'a> {
error_names.push_str(", ");
}
}
let error_names = if self.variants.len() > 1 {
format!("Union[{}]", error_names)
} else {
error_names
};
quote!(
#(#var_extracts)*
let type_name = obj.get_type().name();
@ -134,7 +139,13 @@ impl<'a> Container<'a> {
}
let style = match (fields, transparent) {
(Fields::Unnamed(_), true) => ContainerType::TupleNewtype,
(Fields::Unnamed(unnamed), false) => ContainerType::Tuple(unnamed.unnamed.len()),
(Fields::Unnamed(unnamed), false) => {
if unnamed.unnamed.len() == 1 {
ContainerType::TupleNewtype
} else {
ContainerType::Tuple(unnamed.unnamed.len())
}
}
(Fields::Named(named), true) => {
let field = named
.named
@ -321,7 +332,8 @@ impl ContainerAttribute {
if let syn::NestedMeta::Meta(metaitem) = &meta {
match metaitem {
Meta::Path(p) if p.is_ident("transparent") => {
attrs.push(ContainerAttribute::Transparent)
attrs.push(ContainerAttribute::Transparent);
continue;
}
Meta::NameValue(nv) if nv.path.is_ident("annotation") => {
if let syn::Lit::Str(s) = &nv.lit {
@ -329,21 +341,13 @@ impl ContainerAttribute {
} else {
return Err(spanned_err(&nv.lit, "Expected string literal."));
}
continue;
}
other => {
return Err(spanned_err(
other,
"Expected `transparent` or `annotation = \"name\"`",
))
}
_ => {} // return Err below
}
} else {
return Err(spanned_err(
meta,
"Unknown container attribute, expected `transparent` or \
`annotation(\"err_name\")`",
));
}
return Err(spanned_err(meta, "Unrecognized `pyo3` container attribute"));
}
Ok(attrs)
}
@ -514,7 +518,7 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result<TokenStream> {
syn::Data::Union(_) => {
return Err(spanned_err(
tokens,
"FromPyObject can not be derived for unions.",
"#[derive(FromPyObject)] is not supported for unions.",
))
}
};

View File

@ -155,7 +155,6 @@ fn test_tuple_struct() {
}
#[derive(FromPyObject)]
#[pyo3(transparent)]
pub struct TransparentTuple(String);
#[test]
@ -301,7 +300,7 @@ fn test_err_rename() {
PyErrValue::ToObject(to) => {
let o = to.to_object(py);
let s = String::extract(o.as_ref(py)).expect("Err val is not a string");
assert_eq!(s, "Can't convert {} (dict) to str, uint, int")
assert_eq!(s, "Can't convert {} (dict) to Union[str, uint, int]")
}
_ => panic!("Expected PyErrValue::ToObject"),
},

View File

@ -132,7 +132,7 @@ error: Only one of `item`, `attribute` can be provided, possibly with an additio
118 | #[pyo3(item, attribute)]
| ^^^^^^^^^^^^^^^
error: Expected `transparent` or `annotation = "name"`
error: Unrecognized `pyo3` container attribute
--> $DIR/invalid_frompy_derive.rs:123:8
|
123 | #[pyo3(unknown = "should not work")]
@ -156,7 +156,7 @@ error: FromPyObject can be derived with at most one lifetime parameter.
141 | enum TooManyLifetimes<'a, 'b> {
| ^^^^^^^^
error: FromPyObject can not be derived for unions.
error: #[derive(FromPyObject)] is not supported for unions.
--> $DIR/invalid_frompy_derive.rs:147:1
|
147 | / union Union {