Skip to content

Commit 6f5a8de

Browse files
committed
Documentation, testing and hygiene
1 parent 4dcdede commit 6f5a8de

File tree

6 files changed

+74
-6
lines changed

6 files changed

+74
-6
lines changed

guide/src/conversions/traits.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,38 @@ If the input is neither a string nor an integer, the error message will be:
488488
- apply a custom function to convert the field from Python the desired Rust type.
489489
- the argument must be the name of the function as a string.
490490
- the function signature must be `fn(&Bound<PyAny>) -> PyResult<T>` where `T` is the Rust type of the argument.
491+
- `pyo3(default)`, `pyo3(default = ...)`
492+
- if the argument is set, uses the given default value.
493+
- in this case, the argument must be a Rust expression returning a value of the desired Rust type.
494+
- if the argument is not set, [`Default::default`](https://doc.rust-lang.org/std/default/trait.Default.html#tymethod.default) is used.
495+
- this attribute is only supported on named struct fields.
496+
497+
For example, the code below applies the given conversion function on the `"value"` dict item to compute its length or fall back to the type default value (0):
498+
499+
```rust
500+
use pyo3::prelude::*;
501+
502+
#[derive(FromPyObject)]
503+
struct RustyStruct {
504+
#[pyo3(item("value"), default, from_py_with = "Bound::<'_, PyAny>::len")]
505+
len: usize,
506+
}
507+
#
508+
# fn main() -> PyResult<()> {
509+
# Python::with_gil(|py| -> PyResult<()> {
510+
# // Filled case
511+
# let dict = PyDict::new(py);
512+
# dict.set_item("value", (1,)).unwrap();
513+
# let result = dict.extract::<RustyStruct>()?;
514+
# assert_eq!(result, RustyStruct { len: 1 });
515+
#
516+
# // Empty case
517+
# let dict = PyDict::new(py);
518+
# let result = dict.extract::<RustyStruct>()?;
519+
# assert_eq!(result, RustyStruct { len: 0 });
520+
# })
521+
# }
522+
```
491523

492524
### `IntoPyObject`
493525
The ['IntoPyObject'] trait defines the to-python conversion for a Rust type. All types in PyO3 implement this trait,

newsfragments/4829.added.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
`derive(FromPyObject)` allow a `default` attribute to set a default value for extracted fields. The default value is either provided explicitly or fetched via `Default::default()`.
1+
`derive(FromPyObject)` allow a `default` attribute to set a default value for extracted fields of named structs. The default value is either provided explicitly or fetched via `Default::default()`.

pyo3-macros-backend/src/attributes.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ pub mod kw {
4545
syn::custom_keyword!(unsendable);
4646
syn::custom_keyword!(weakref);
4747
syn::custom_keyword!(gil_used);
48-
syn::custom_keyword!(default);
4948
}
5049

5150
fn take_int(read: &mut &str, tracker: &mut usize) -> String {
@@ -352,7 +351,7 @@ impl<K: ToTokens, V: ToTokens> ToTokens for OptionalKeywordAttribute<K, V> {
352351

353352
pub type FromPyWithAttribute = KeywordAttribute<kw::from_py_with, LitStrValue<ExprPath>>;
354353

355-
pub type DefaultAttribute = OptionalKeywordAttribute<kw::default, Expr>;
354+
pub type DefaultAttribute = OptionalKeywordAttribute<Token![default], Expr>;
356355

357356
/// For specifying the path to the pyo3 crate.
358357
pub type CrateAttribute = KeywordAttribute<Token![crate], LitStrValue<Path>>;

pyo3-macros-backend/src/frompyobject.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -362,9 +362,9 @@ impl<'a> Container<'a> {
362362
let default_expr = if let Some(default_expr) = &default.value {
363363
default_expr.to_token_stream()
364364
} else {
365-
quote!(Default::default())
365+
quote!(::std::default::Default::default())
366366
};
367-
quote!(if let Ok(value) = #getter {
367+
quote!(if let ::std::result::Result::Ok(value) = #getter {
368368
#extractor
369369
} else {
370370
#default_expr
@@ -533,7 +533,7 @@ impl Parse for FieldPyO3Attribute {
533533
}
534534
} else if lookahead.peek(attributes::kw::from_py_with) {
535535
input.parse().map(FieldPyO3Attribute::FromPyWith)
536-
} else if lookahead.peek(attributes::kw::default) {
536+
} else if lookahead.peek(Token![default]) {
537537
input.parse().map(FieldPyO3Attribute::Default)
538538
} else {
539539
Err(lookahead.error())

src/tests/hygiene/misc.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ struct Derive3 {
1212
f: i32,
1313
#[pyo3(item(42))]
1414
g: i32,
15+
#[pyo3(default)]
16+
h: i32,
1517
} // struct case
1618

1719
#[derive(crate::FromPyObject)]

tests/test_frompyobject.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,3 +718,38 @@ fn test_with_explicit_default_item() {
718718
assert_eq!(result, expected);
719719
});
720720
}
721+
722+
#[derive(Debug, FromPyObject, PartialEq, Eq)]
723+
pub struct WithDefaultItemAndConversionFunction {
724+
#[pyo3(item, default, from_py_with = "Bound::<'_, PyAny>::len")]
725+
value: usize,
726+
}
727+
728+
#[test]
729+
fn test_with_default_item_and_conversion_function() {
730+
Python::with_gil(|py| {
731+
// Filled case
732+
let dict = PyDict::new(py);
733+
dict.set_item("value", (1,)).unwrap();
734+
let result = dict
735+
.extract::<WithDefaultItemAndConversionFunction>()
736+
.unwrap();
737+
let expected = WithDefaultItemAndConversionFunction { value: 1 };
738+
assert_eq!(result, expected);
739+
740+
// Empty case
741+
let dict = PyDict::new(py);
742+
let result = dict
743+
.extract::<WithDefaultItemAndConversionFunction>()
744+
.unwrap();
745+
let expected = WithDefaultItemAndConversionFunction { value: 0 };
746+
assert_eq!(result, expected);
747+
748+
// Error case
749+
let dict = PyDict::new(py);
750+
dict.set_item("value", 1).unwrap();
751+
assert!(dict
752+
.extract::<WithDefaultItemAndConversionFunction>()
753+
.is_err());
754+
});
755+
}

0 commit comments

Comments
 (0)