Skip to content

Commit

Permalink
Documentation, testing and hygiene
Browse files Browse the repository at this point in the history
  • Loading branch information
Tpt committed Jan 6, 2025
1 parent 4dcdede commit 9cbf084
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 6 deletions.
5 changes: 5 additions & 0 deletions guide/src/conversions/traits.md
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,11 @@ If the input is neither a string nor an integer, the error message will be:
- apply a custom function to convert the field from Python the desired Rust type.
- the argument must be the name of the function as a string.
- the function signature must be `fn(&Bound<PyAny>) -> PyResult<T>` where `T` is the Rust type of the argument.
- `pyo3(default)`, `pyo3(default = ...)`
- if the argument is set, uses the given default value.
- in this case, the argument must be a Rust expression returning a value of the desired Rust type.
- if the argument is not set, [`Default::default`](https://doc.rust-lang.org/std/default/trait.Default.html#tymethod.default) is used.
- this attribute is only supported on named struct fields.

### `IntoPyObject`
The ['IntoPyObject'] trait defines the to-python conversion for a Rust type. All types in PyO3 implement this trait,
Expand Down
2 changes: 1 addition & 1 deletion newsfragments/4829.added.md
Original file line number Diff line number Diff line change
@@ -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()`.
`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()`.
3 changes: 1 addition & 2 deletions pyo3-macros-backend/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ pub mod kw {
syn::custom_keyword!(unsendable);
syn::custom_keyword!(weakref);
syn::custom_keyword!(gil_used);
syn::custom_keyword!(default);
}

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

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

pub type DefaultAttribute = OptionalKeywordAttribute<kw::default, Expr>;
pub type DefaultAttribute = OptionalKeywordAttribute<Token![default], Expr>;

/// For specifying the path to the pyo3 crate.
pub type CrateAttribute = KeywordAttribute<Token![crate], LitStrValue<Path>>;
Expand Down
6 changes: 3 additions & 3 deletions pyo3-macros-backend/src/frompyobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,9 +362,9 @@ impl<'a> Container<'a> {
let default_expr = if let Some(default_expr) = &default.value {
default_expr.to_token_stream()
} else {
quote!(Default::default())
quote!(::std::default::Default::default())
};
quote!(if let Ok(value) = #getter {
quote!(if let ::std::result::Result::Ok(value) = #getter {
#extractor
} else {
#default_expr
Expand Down Expand Up @@ -533,7 +533,7 @@ impl Parse for FieldPyO3Attribute {
}
} else if lookahead.peek(attributes::kw::from_py_with) {
input.parse().map(FieldPyO3Attribute::FromPyWith)
} else if lookahead.peek(attributes::kw::default) {
} else if lookahead.peek(Token![default]) {
input.parse().map(FieldPyO3Attribute::Default)
} else {
Err(lookahead.error())
Expand Down
2 changes: 2 additions & 0 deletions src/tests/hygiene/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ struct Derive3 {
f: i32,
#[pyo3(item(42))]
g: i32,
#[pyo3(default)]
h: i32,
} // struct case

#[derive(crate::FromPyObject)]
Expand Down
35 changes: 35 additions & 0 deletions tests/test_frompyobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -718,3 +718,38 @@ fn test_with_explicit_default_item() {
assert_eq!(result, expected);
});
}

#[derive(Debug, FromPyObject, PartialEq, Eq)]
pub struct WithDefaultItemAndConversionFunction {
#[pyo3(item, default, from_py_with = "Bound::<'_, PyAny>::len")]
value: usize,
}

#[test]
fn test_with_default_item_and_conversion_function() {
Python::with_gil(|py| {
// Filled case
let dict = PyDict::new(py);
dict.set_item("value", (1,)).unwrap();
let result = dict
.extract::<WithDefaultItemAndConversionFunction>()
.unwrap();
let expected = WithDefaultItemAndConversionFunction { value: 1 };
assert_eq!(result, expected);

// Empty case
let dict = PyDict::new(py);
let result = dict
.extract::<WithDefaultItemAndConversionFunction>()
.unwrap();
let expected = WithDefaultItemAndConversionFunction { value: 0 };
assert_eq!(result, expected);

// Error case
let dict = PyDict::new(py);
dict.set_item("value", 1).unwrap();
assert!(dict
.extract::<WithDefaultItemAndConversionFunction>()
.is_err());
});
}

0 comments on commit 9cbf084

Please sign in to comment.