More FromPyObject derive suggestions by @davidhewitt
This commit is contained in:
parent
7a9f4a1633
commit
0f32f886b8
|
@ -183,17 +183,28 @@ use pyo3::prelude::*;
|
||||||
struct RustyTuple(String, String);
|
struct RustyTuple(String, String);
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Deriving [`FromPyObject`] for wrapper types
|
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,
|
||||||
The `pyo3(transparent)` attribute can be used on structs with exactly one field. This results
|
specify the struct as
|
||||||
in extracting directly from the input object, i.e. `obj.extract()`, rather than trying to access
|
|
||||||
an item or attribute.
|
|
||||||
```
|
```
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
#[derive(FromPyObject)]
|
#[derive(FromPyObject)]
|
||||||
#[pyo3(transparent)]
|
struct RustyTuple((String,));
|
||||||
struct RustyTransparentTuple(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)]
|
#[derive(FromPyObject)]
|
||||||
#[pyo3(transparent)]
|
#[pyo3(transparent)]
|
||||||
|
@ -217,12 +228,10 @@ use pyo3::prelude::*;
|
||||||
|
|
||||||
#[derive(FromPyObject)]
|
#[derive(FromPyObject)]
|
||||||
enum RustyEnum<'a> {
|
enum RustyEnum<'a> {
|
||||||
#[pyo3(transparent)]
|
|
||||||
Int(usize), // input is a positive int
|
Int(usize), // input is a positive int
|
||||||
#[pyo3(transparent)]
|
|
||||||
String(String), // input is a string
|
String(String), // input is a string
|
||||||
IntTuple(usize, usize), // input is a 2-tuple with positive ints
|
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
|
Coordinates3d { // needs to be in front of 2d
|
||||||
x: usize,
|
x: usize,
|
||||||
y: usize,
|
y: usize,
|
||||||
|
@ -257,9 +266,28 @@ enum RustyEnum {
|
||||||
```
|
```
|
||||||
|
|
||||||
If the input is neither a string nor an integer, the error message will be:
|
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.
|
`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>`
|
### `IntoPy<T>`
|
||||||
|
|
||||||
This trait defines the to-python conversion for a Rust type. It is usually implemented as
|
This trait defines the to-python conversion for a Rust type. It is usually implemented as
|
||||||
|
|
|
@ -65,6 +65,11 @@ impl<'a> Enum<'a> {
|
||||||
error_names.push_str(", ");
|
error_names.push_str(", ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let error_names = if self.variants.len() > 1 {
|
||||||
|
format!("Union[{}]", error_names)
|
||||||
|
} else {
|
||||||
|
error_names
|
||||||
|
};
|
||||||
quote!(
|
quote!(
|
||||||
#(#var_extracts)*
|
#(#var_extracts)*
|
||||||
let type_name = obj.get_type().name();
|
let type_name = obj.get_type().name();
|
||||||
|
@ -134,7 +139,13 @@ impl<'a> Container<'a> {
|
||||||
}
|
}
|
||||||
let style = match (fields, transparent) {
|
let style = match (fields, transparent) {
|
||||||
(Fields::Unnamed(_), true) => ContainerType::TupleNewtype,
|
(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) => {
|
(Fields::Named(named), true) => {
|
||||||
let field = named
|
let field = named
|
||||||
.named
|
.named
|
||||||
|
@ -321,7 +332,8 @@ impl ContainerAttribute {
|
||||||
if let syn::NestedMeta::Meta(metaitem) = &meta {
|
if let syn::NestedMeta::Meta(metaitem) = &meta {
|
||||||
match metaitem {
|
match metaitem {
|
||||||
Meta::Path(p) if p.is_ident("transparent") => {
|
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") => {
|
Meta::NameValue(nv) if nv.path.is_ident("annotation") => {
|
||||||
if let syn::Lit::Str(s) = &nv.lit {
|
if let syn::Lit::Str(s) = &nv.lit {
|
||||||
|
@ -329,21 +341,13 @@ impl ContainerAttribute {
|
||||||
} else {
|
} else {
|
||||||
return Err(spanned_err(&nv.lit, "Expected string literal."));
|
return Err(spanned_err(&nv.lit, "Expected string literal."));
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
other => {
|
_ => {} // return Err below
|
||||||
return Err(spanned_err(
|
|
||||||
other,
|
|
||||||
"Expected `transparent` or `annotation = \"name\"`",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} 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)
|
Ok(attrs)
|
||||||
}
|
}
|
||||||
|
@ -514,7 +518,7 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result<TokenStream> {
|
||||||
syn::Data::Union(_) => {
|
syn::Data::Union(_) => {
|
||||||
return Err(spanned_err(
|
return Err(spanned_err(
|
||||||
tokens,
|
tokens,
|
||||||
"FromPyObject can not be derived for unions.",
|
"#[derive(FromPyObject)] is not supported for unions.",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -155,7 +155,6 @@ fn test_tuple_struct() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromPyObject)]
|
#[derive(FromPyObject)]
|
||||||
#[pyo3(transparent)]
|
|
||||||
pub struct TransparentTuple(String);
|
pub struct TransparentTuple(String);
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -301,7 +300,7 @@ fn test_err_rename() {
|
||||||
PyErrValue::ToObject(to) => {
|
PyErrValue::ToObject(to) => {
|
||||||
let o = to.to_object(py);
|
let o = to.to_object(py);
|
||||||
let s = String::extract(o.as_ref(py)).expect("Err val is not a string");
|
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"),
|
_ => panic!("Expected PyErrValue::ToObject"),
|
||||||
},
|
},
|
||||||
|
|
|
@ -132,7 +132,7 @@ error: Only one of `item`, `attribute` can be provided, possibly with an additio
|
||||||
118 | #[pyo3(item, attribute)]
|
118 | #[pyo3(item, attribute)]
|
||||||
| ^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
error: Expected `transparent` or `annotation = "name"`
|
error: Unrecognized `pyo3` container attribute
|
||||||
--> $DIR/invalid_frompy_derive.rs:123:8
|
--> $DIR/invalid_frompy_derive.rs:123:8
|
||||||
|
|
|
|
||||||
123 | #[pyo3(unknown = "should not work")]
|
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> {
|
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
|
--> $DIR/invalid_frompy_derive.rs:147:1
|
||||||
|
|
|
|
||||||
147 | / union Union {
|
147 | / union Union {
|
||||||
|
|
Loading…
Reference in New Issue