From 14d1d19371cdede1d46ed232d8b4bee527aa2aac Mon Sep 17 00:00:00 2001 From: Cheukting Date: Sat, 21 Feb 2026 16:38:26 +0000 Subject: [PATCH 01/12] Document advanced `IntoPyObject` field customization with examples. --- guide/src/conversions/traits.md | 131 ++++++++++++++++++++++++++++++-- 1 file changed, 123 insertions(+), 8 deletions(-) diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md index 83e958a7c9a..5177c81d727 100644 --- a/guide/src/conversions/traits.md +++ b/guide/src/conversions/traits.md @@ -565,7 +565,7 @@ Both `struct`s and `enum`s are supported. `struct`s will turn into a `PyDict` using the field names as keys, tuple `struct`s will turn convert into `PyTuple` with the fields in declaration order. -```rust,no_run +```rust # #![allow(dead_code)] # use pyo3::prelude::*; # use std::collections::HashMap; @@ -576,7 +576,7 @@ Both `struct`s and `enum`s are supported. struct Struct { count: usize, obj: Py, -} + // tuple structs convert into `PyTuple` // lifetimes and generics are supported, the impl will be bounded by @@ -585,9 +585,124 @@ struct Struct { struct Tuple<'a, K: Hash + Eq, V>(&'a str, HashMap); ``` +Same as `FromPyObject`, the argument passed to `getattr` and `get_item` can also be configured: + +```rust +# #![allow(dead_code)] +use pyo3::prelude::*; +# use pyo3::types::PyString; + +#[derive(IntoPyObject)] +struct RustyStruct { + #[pyo3(item("key"))] + string_in_mapping: String, + #[pyo3(attribute("name"))] + string_attr: String, +} + +# impl RustyStruct { +# fn new() -> Self { +# Self { +# string_in_mapping: String::from("test2"), +# string_attr: String::from("test"), +# } +# } +# } +# +# fn main() -> PyResult<()> { +# Python::attach(|py| -> PyResult<()> { +# let rustystruct = RustyStruct::new(); +# let python_dict = rustystruct.into_pyobject(py)?; +# assert_eq!( +# python_dict +# .call_method1("get_attribute", ("name",)) +# .unwrap() +# .cast::() +# .unwrap(), +# "test" +# ); +# assert_eq!( +# python_dict +# .call_method1("get_item", ("key",)) +# .unwrap() +# .cast::() +# .unwrap(), +# "test2" +# ); +# +# Ok(()) +# }) +# } +``` + +This tries to convert `string_attr` to the attribute `name` and `string_in_mapping` to a mapping with the key `"key"`. +The arguments for `attribute` are restricted to non-empty string literals while `item` can take any valid literal that implements `ToBorrowedObject`. + +You can also use `#[pyo3(from_item_all)]` on a struct to convert every field to be used 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 +# #![allow(dead_code)] +use pyo3::prelude::*; +# use pyo3::types::PyString; + +#[derive(IntoPyObject)] +#[pyo3(from_item_all)] +struct RustyStruct { + foo: String, + bar: String, + #[pyo3(item("foobar"))] + baz: String, +} + +impl RustyStruct { + fn new() -> Self { + Self { + foo: String::from("foo"), + bar: String::from("bar"), + baz: String::from("foobar") } + } +} + +# +# fn main() -> PyResult<()> { +# Python::attach(|py| -> PyResult<()> { +# let rustystruct = RustyStruct::new(); +# let python_dict = rustystruct.into_pyobject(py)?; +# assert_eq!( +# python_dict +# .call_method1("get_item", ("foo",)) +# .unwrap() +# .cast::() +# .unwrap(), +# "foo" +# ); +# assert_eq!( +# python_dict +# .call_method1("get_item", ("bar",)) +# .unwrap() +# .cast::() +# .unwrap(), +# "bar" +# ); +# assert_eq!( +# python_dict +# .call_method1("get_item", ("foobar",)) +# .unwrap() +# .cast::() +# .unwrap(), +# "foobar" +# ); +# +# Ok(()) +# }) +# } +``` + For structs with a single field (newtype pattern) the `#[pyo3(transparent)]` option can be used to forward the implementation to the inner type. -```rust,no_run +```rust # #![allow(dead_code)] # use pyo3::prelude::*; @@ -604,7 +719,7 @@ struct TransparentStruct<'py> { For `enum`s each variant is converted according to the rules for `struct`s above. -```rust,no_run +```rust # #![allow(dead_code)] # use pyo3::prelude::*; # use std::collections::HashMap; @@ -632,7 +747,7 @@ All the same rules from above apply as well. - `#[derive(IntoPyObject)]` will invoke the function with `Cow::Owned` - `#[derive(IntoPyObjectRef)]` will invoke the function with `Cow::Borrowed` - ```rust,no_run + ```rust # use pyo3::prelude::*; # use pyo3::IntoPyObjectExt; # use std::borrow::Cow; @@ -655,7 +770,7 @@ All the same rules from above apply as well. If the derive macro is not suitable for your use case, `IntoPyObject` can be implemented manually as demonstrated below. -```rust,no_run +```rust # use pyo3::prelude::*; # #[allow(dead_code)] struct MyPyObjectWrapper(Py); @@ -687,7 +802,7 @@ impl<'a, 'py> IntoPyObject<'py> for &'a MyPyObjectWrapper { `IntoPyObject::into_py_object` returns either `Bound` or `Borrowed` depending on the implementation for a concrete type. For example, the `IntoPyObject` implementation for `u32` produces a `Bound<'py, PyInt>` and the `bool` implementation produces a `Borrowed<'py, 'py, PyBool>`: -```rust,no_run +```rust use pyo3::prelude::*; use pyo3::IntoPyObject; use pyo3::types::{PyBool, PyInt}; @@ -714,7 +829,7 @@ In this example if we wanted to combine `ints_as_pyints` and `bools_as_pybool` i Instead, we can write a function that generically converts vectors of either integers or bools into a vector of `Py` using the [`BoundObject`] trait: -```rust,no_run +```rust # use pyo3::prelude::*; # use pyo3::BoundObject; # use pyo3::IntoPyObject; From 5a0cde64c2bc6ad747676ebf5da571f70815b18f Mon Sep 17 00:00:00 2001 From: Cheukting Date: Sun, 22 Feb 2026 00:43:47 +0000 Subject: [PATCH 02/12] Clarify `get_item` usage and update examples for field conversion customization. --- guide/src/conversions/traits.md | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md index 5177c81d727..acfa97070b9 100644 --- a/guide/src/conversions/traits.md +++ b/guide/src/conversions/traits.md @@ -576,7 +576,7 @@ Both `struct`s and `enum`s are supported. struct Struct { count: usize, obj: Py, - +} // tuple structs convert into `PyTuple` // lifetimes and generics are supported, the impl will be bounded by @@ -585,7 +585,7 @@ struct Struct { struct Tuple<'a, K: Hash + Eq, V>(&'a str, HashMap); ``` -Same as `FromPyObject`, the argument passed to `getattr` and `get_item` can also be configured: +Similar to `FromPyObject`, the argument passed to `get_item` can also be configured: ```rust # #![allow(dead_code)] @@ -596,15 +596,12 @@ use pyo3::prelude::*; struct RustyStruct { #[pyo3(item("key"))] string_in_mapping: String, - #[pyo3(attribute("name"))] - string_attr: String, } # impl RustyStruct { # fn new() -> Self { # Self { -# string_in_mapping: String::from("test2"), -# string_attr: String::from("test"), +# string_in_mapping: String::from("test"), # } # } # } @@ -615,27 +612,19 @@ struct RustyStruct { # let python_dict = rustystruct.into_pyobject(py)?; # assert_eq!( # python_dict -# .call_method1("get_attribute", ("name",)) +# .call_method1("__getitem__", ("key",)) # .unwrap() # .cast::() # .unwrap(), # "test" # ); -# assert_eq!( -# python_dict -# .call_method1("get_item", ("key",)) -# .unwrap() -# .cast::() -# .unwrap(), -# "test2" -# ); # # Ok(()) # }) # } ``` -This tries to convert `string_attr` to the attribute `name` and `string_in_mapping` to a mapping with the key `"key"`. +This tries to convert `doc_string` to the attribute `__doc__` and `string_in_mapping` to a mapping with the key `"key"`. The arguments for `attribute` are restricted to non-empty string literals while `item` can take any valid literal that implements `ToBorrowedObject`. You can also use `#[pyo3(from_item_all)]` on a struct to convert every field to be used with `get_item` method. @@ -672,7 +661,7 @@ impl RustyStruct { # let python_dict = rustystruct.into_pyobject(py)?; # assert_eq!( # python_dict -# .call_method1("get_item", ("foo",)) +# .call_method1("__getitem__", ("foo",)) # .unwrap() # .cast::() # .unwrap(), @@ -680,7 +669,7 @@ impl RustyStruct { # ); # assert_eq!( # python_dict -# .call_method1("get_item", ("bar",)) +# .call_method1("__getitem__", ("bar",)) # .unwrap() # .cast::() # .unwrap(), @@ -688,7 +677,7 @@ impl RustyStruct { # ); # assert_eq!( # python_dict -# .call_method1("get_item", ("foobar",)) +# .call_method1("__getitem__", ("foobar",)) # .unwrap() # .cast::() # .unwrap(), From 0891f56e31909bb30bf4135ce2c5bd647f5d9473 Mon Sep 17 00:00:00 2001 From: Cheukting Date: Sun, 22 Feb 2026 12:33:26 +0000 Subject: [PATCH 03/12] Add doc tests --- guide/src/conversions/traits.md | 72 +++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 3 deletions(-) diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md index acfa97070b9..a9b01b1f4a3 100644 --- a/guide/src/conversions/traits.md +++ b/guide/src/conversions/traits.md @@ -566,8 +566,8 @@ Both `struct`s and `enum`s are supported. `struct`s will turn into a `PyDict` using the field names as keys, tuple `struct`s will turn convert into `PyTuple` with the fields in declaration order. ```rust -# #![allow(dead_code)] # use pyo3::prelude::*; +# use pyo3::types::{PyInt, PyString, PyDict}; # use std::collections::HashMap; # use std::hash::Hash; @@ -583,12 +583,79 @@ struct Struct { // `K: IntoPyObject, V: IntoPyObject` #[derive(IntoPyObject)] struct Tuple<'a, K: Hash + Eq, V>(&'a str, HashMap); + +# impl Struct { +# fn new() -> Self { +# Python::attach(|py| Self { +# count: 0, +# obj: Py::from(PyString::new(py, "test")), +# }) +# } +# } + +# impl Tuple<'_, K, V> { +# fn new() -> Self { +# Self("test2", HashMap::new()) +# } +# } +# +# fn main() -> PyResult<()> { +# Python::attach(|py| -> PyResult<()> { +# let rustystruct = Struct::new(); +# let python_dict = rustystruct.into_pyobject(py)?; +# assert_eq!( +# python_dict +# .call_method1("__getitem__", ("count",)) +# .unwrap() +# .cast::() +# .unwrap() +# .extract::() +# .unwrap(), +# 0 +# ); +# assert_eq!( +# python_dict +# .call_method1("__getitem__", ("obj",)) +# .unwrap() +# .cast::() +# .unwrap(), +# "test" +# ); +# +# let mut rustytuple: Tuple<'_, String, i32> = Tuple::new(); +# rustytuple.1.insert("foo".to_string(), 42); +# let python_tuple = rustytuple.into_pyobject(py)?; +# +# assert_eq!( +# python_tuple +# .call_method1("__getitem__", (0,)) +# .unwrap() +# .cast::() +# .unwrap(), +# "test2" +# ); +# +# assert_eq!( +# python_tuple +# .call_method1("__getitem__", (1,)) +# .unwrap() +# .cast::() +# .unwrap() +# .call_method0("__str__") +# .unwrap() +# .cast::() +# .unwrap(), +# "{'foo': 42}" +# ); +# +# Ok(()) +# }) +# } ``` Similar to `FromPyObject`, the argument passed to `get_item` can also be configured: ```rust -# #![allow(dead_code)] use pyo3::prelude::*; # use pyo3::types::PyString; @@ -632,7 +699,6 @@ In this case, you can't use `#[pyo3(attribute)]` or barely use `#[pyo3(item)]` o However, using `#[pyo3(item("key"))]` to specify the key for a field is still allowed. ```rust -# #![allow(dead_code)] use pyo3::prelude::*; # use pyo3::types::PyString; From b06044dfdc2eb76889363082a47b6736763c0c54 Mon Sep 17 00:00:00 2001 From: Cheukting Date: Sun, 22 Feb 2026 14:24:04 +0000 Subject: [PATCH 04/12] Simplify and clarify field conversion trait documentation. --- guide/src/conversions/traits.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md index a9b01b1f4a3..37aab6f750d 100644 --- a/guide/src/conversions/traits.md +++ b/guide/src/conversions/traits.md @@ -691,12 +691,10 @@ struct RustyStruct { # } ``` -This tries to convert `doc_string` to the attribute `__doc__` and `string_in_mapping` to a mapping with the key `"key"`. -The arguments for `attribute` are restricted to non-empty string literals while `item` can take any valid literal that implements `ToBorrowedObject`. +This tries to convert a mapping with the key `"key"`. The `item` can take any valid literal that implements `ToBorrowedObject`. You can also use `#[pyo3(from_item_all)]` on a struct to convert every field to be used 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. +In this case, you don't need to use `#[pyo3(item)]` on each field. However, using `#[pyo3(item("key"))]` to specify the key for a field is still allowed. ```rust use pyo3::prelude::*; From ef76c2527c0bbe119ef3d7d121c3dcfd94c77de9 Mon Sep 17 00:00:00 2001 From: Cheukting Date: Sun, 22 Feb 2026 16:08:06 +0000 Subject: [PATCH 05/12] Add doc test for `#[pyo3(transparent)]` usage in `IntoPyObject` for structs and tuples. --- guide/src/conversions/traits.md | 39 ++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md index 37aab6f750d..2d6f59a1a57 100644 --- a/guide/src/conversions/traits.md +++ b/guide/src/conversions/traits.md @@ -756,8 +756,8 @@ impl RustyStruct { For structs with a single field (newtype pattern) the `#[pyo3(transparent)]` option can be used to forward the implementation to the inner type. ```rust -# #![allow(dead_code)] # use pyo3::prelude::*; +# use pyo3::types::PyString; // newtype tuple structs are implicitly `transparent` #[derive(IntoPyObject)] @@ -768,6 +768,43 @@ struct TransparentTuple(Py); struct TransparentStruct<'py> { inner: Bound<'py, PyAny>, // `'py` lifetime will be used as the Python lifetime } + +# impl<'py> TransparentStruct<'py> { +# fn new(py: Python<'py>) -> Self { +# Self { +# inner: PyString::new(py, "test").into_any(), +# } +# } +# } +# +# impl TransparentTuple { +# fn new() -> Self { +# Python::attach(|py| Self(PyString::new(py, "test2").into())) +# } +# } +# +# fn main() -> PyResult<()> { +# Python::attach(|py| -> PyResult<()> { +# let rustystruct = TransparentStruct::new(py); +# let python_obj1 = rustystruct.into_pyobject(py)?; +# let rustytuple = TransparentTuple::new(); +# let python_obj2 = rustytuple.into_pyobject(py)?; +# assert_eq!( +# python_obj1 +# .cast::() +# .unwrap(), +# "test" +# ); +# assert_eq!( +# python_obj2 +# .cast::() +# .unwrap(), +# "test2" +# ); + + Ok(()) + }) +} ``` For `enum`s each variant is converted according to the rules for `struct`s above. From 88fe9f41692512f414a139b804ee32c9e30101a8 Mon Sep 17 00:00:00 2001 From: Cheukting Date: Sun, 22 Feb 2026 18:51:58 +0000 Subject: [PATCH 06/12] Document container and field attributes for `#[derive(IntoPyObject)]` and `#[derive(IntoPyObjectRef)]`. --- guide/src/conversions/traits.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md index 2d6f59a1a57..39dc9ebab24 100644 --- a/guide/src/conversions/traits.md +++ b/guide/src/conversions/traits.md @@ -828,8 +828,27 @@ enum Enum<'a, 'py, K: Hash + Eq, V> { // enums are supported and convert using t Additionally `IntoPyObject` can be derived for a reference to a struct or enum using the `IntoPyObjectRef` derive macro. All the same rules from above apply as well. +#### `#[derive(IntoPyObject)]`/`#[derive(IntoPyObjectRef)]` Container Attributes + +- `pyo3(transparent)` + - convert the field directly to 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 +- `pyo3(rename_all = "...")` + - renames all item keys according to the specified renaming rule + - Possible values are: "camelCase", "kebab-case", "lowercase", "PascalCase", "SCREAMING-KEBAB-CASE", "SCREAMING_SNAKE_CASE", "snake_case", "UPPERCASE". + - fields with an explicit renaming via `item(...)` are not affected + #### `#[derive(IntoPyObject)]`/`#[derive(IntoPyObjectRef)]` Field Attributes +- `pyo3(item)`, `pyo3(item("key"))` + - convert the field to a mapping, possibly with the custom key specified as an argument. + - can be any literal that implements `ToBorrowedObject` - `pyo3(into_py_with = ...)` - apply a custom function to convert the field from Rust into Python. - the argument must be the function identifier @@ -855,6 +874,13 @@ All the same rules from above apply as well. not_into_py.0.into_bound_py_any(py) } ``` + - `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. + - note that the default value is only used if the field is not set. + If the field is set and the conversion function from Rust to Python fails, an exception is raised and the default value is not used. + - this attribute is only supported on named fields. ### manual implementation From 329cbe43e0bc1b1925320a3bfe64b11c003cb8a4 Mon Sep 17 00:00:00 2001 From: Cheukting Date: Sun, 22 Feb 2026 18:53:34 +0000 Subject: [PATCH 07/12] Clarify and format documentation for `#[pyo3]` attributes, improving readability and consistency. --- guide/src/conversions/traits.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md index 39dc9ebab24..126d8ccdfb8 100644 --- a/guide/src/conversions/traits.md +++ b/guide/src/conversions/traits.md @@ -691,10 +691,12 @@ struct RustyStruct { # } ``` -This tries to convert a mapping with the key `"key"`. The `item` can take any valid literal that implements `ToBorrowedObject`. +This tries to convert a mapping with the key `"key"`. +The `item` can take any valid literal that implements `ToBorrowedObject`. You can also use `#[pyo3(from_item_all)]` on a struct to convert every field to be used with `get_item` method. -In this case, you don't need to use `#[pyo3(item)]` on each field. However, using `#[pyo3(item("key"))]` to specify the key for a field is still allowed. +In this case, you don't need to use `#[pyo3(item)]` on each field. +However, using `#[pyo3(item("key"))]` to specify the key for a field is still allowed. ```rust use pyo3::prelude::*; @@ -843,7 +845,7 @@ All the same rules from above apply as well. - renames all item keys according to the specified renaming rule - Possible values are: "camelCase", "kebab-case", "lowercase", "PascalCase", "SCREAMING-KEBAB-CASE", "SCREAMING_SNAKE_CASE", "snake_case", "UPPERCASE". - fields with an explicit renaming via `item(...)` are not affected - + #### `#[derive(IntoPyObject)]`/`#[derive(IntoPyObjectRef)]` Field Attributes - `pyo3(item)`, `pyo3(item("key"))` @@ -874,6 +876,7 @@ All the same rules from above apply as well. not_into_py.0.into_bound_py_any(py) } ``` + - `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. From e6030c135260f16f83a67e856e399b28323a62c1 Mon Sep 17 00:00:00 2001 From: Cheukting Date: Sun, 22 Feb 2026 18:55:27 +0000 Subject: [PATCH 08/12] Fix formatting for `pyo3(default)` in field conversion trait documentation. --- guide/src/conversions/traits.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md index 126d8ccdfb8..7fe960c2e91 100644 --- a/guide/src/conversions/traits.md +++ b/guide/src/conversions/traits.md @@ -877,7 +877,7 @@ All the same rules from above apply as well. } ``` - - `pyo3(default)`, `pyo3(default = ...)` +- `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. From eb525122ed68743a99b8fbc6a39baa72d5d60a55 Mon Sep 17 00:00:00 2001 From: Cheukting Date: Mon, 23 Feb 2026 18:24:29 +0000 Subject: [PATCH 09/12] Update and clarify documentation for `#[pyo3(from_item_all)]` and other field attributes in `IntoPyObject` traits. --- guide/src/conversions/traits.md | 102 ++++++++------------------------ 1 file changed, 24 insertions(+), 78 deletions(-) diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md index 7fe960c2e91..45854e25b9d 100644 --- a/guide/src/conversions/traits.md +++ b/guide/src/conversions/traits.md @@ -125,7 +125,7 @@ struct RustyStruct { ``` This tries to extract `string_attr` from the attribute `name` and `string_in_mapping` from a mapping with the key `"key"`. -The arguments for `attribute` are restricted to non-empty string literals while `item` can take any valid literal that implements `ToBorrowedObject`. +The arguments for `attribute` are restricted to non-empty string literals while `item` can take any valid literal. 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. @@ -461,6 +461,10 @@ If the input is neither a string nor an integer, the error message will be: `"'< - renames all attributes/item keys according to the specified renaming rule - Possible values are: "camelCase", "kebab-case", "lowercase", "PascalCase", "SCREAMING-KEBAB-CASE", "SCREAMING_SNAKE_CASE", "snake_case", "UPPERCASE". - fields with an explicit renaming via `attribute(...)`/`item(...)` are not affected +- `#[pyo3(from_item_all)]` + - extract every field with `get_item` method. + - can't use `#[pyo3(attribute)]` or barely use `#[pyo3(item)]` on any field after. + - using `#[pyo3(item("key"))]` to specify the key for a field is still allowed. ### `#[derive(FromPyObject)]` Field Attributes @@ -653,7 +657,7 @@ struct Tuple<'a, K: Hash + Eq, V>(&'a str, HashMap); # } ``` -Similar to `FromPyObject`, the argument passed to `get_item` can also be configured: +Similar to `FromPyObject`, the argument passed to `set_item` can also be configured: ```rust use pyo3::prelude::*; @@ -663,6 +667,8 @@ use pyo3::prelude::*; struct RustyStruct { #[pyo3(item("key"))] string_in_mapping: String, + #[pyo3(attribute("name"))] // no effect on this field + string_attr: String, } # impl RustyStruct { @@ -691,69 +697,7 @@ struct RustyStruct { # } ``` -This tries to convert a mapping with the key `"key"`. -The `item` can take any valid literal that implements `ToBorrowedObject`. - -You can also use `#[pyo3(from_item_all)]` on a struct to convert every field to be used with `get_item` method. -In this case, you don't need to use `#[pyo3(item)]` on each field. -However, using `#[pyo3(item("key"))]` to specify the key for a field is still allowed. - -```rust -use pyo3::prelude::*; -# use pyo3::types::PyString; - -#[derive(IntoPyObject)] -#[pyo3(from_item_all)] -struct RustyStruct { - foo: String, - bar: String, - #[pyo3(item("foobar"))] - baz: String, -} - -impl RustyStruct { - fn new() -> Self { - Self { - foo: String::from("foo"), - bar: String::from("bar"), - baz: String::from("foobar") } - } -} - -# -# fn main() -> PyResult<()> { -# Python::attach(|py| -> PyResult<()> { -# let rustystruct = RustyStruct::new(); -# let python_dict = rustystruct.into_pyobject(py)?; -# assert_eq!( -# python_dict -# .call_method1("__getitem__", ("foo",)) -# .unwrap() -# .cast::() -# .unwrap(), -# "foo" -# ); -# assert_eq!( -# python_dict -# .call_method1("__getitem__", ("bar",)) -# .unwrap() -# .cast::() -# .unwrap(), -# "bar" -# ); -# assert_eq!( -# python_dict -# .call_method1("__getitem__", ("foobar",)) -# .unwrap() -# .cast::() -# .unwrap(), -# "foobar" -# ); -# -# Ok(()) -# }) -# } -``` +This tries to convert a mapping with the key `"key"`. The `item` can take any valid literal. For structs with a single field (newtype pattern) the `#[pyo3(transparent)]` option can be used to forward the implementation to the inner type. @@ -833,8 +777,7 @@ All the same rules from above apply as well. #### `#[derive(IntoPyObject)]`/`#[derive(IntoPyObjectRef)]` Container Attributes - `pyo3(transparent)` - - convert the field directly to the object as `obj.extract()` instead of `get_item()` or - `getattr()` + - convert the field directly to the object instead of `set_item()` - Newtype structs and tuple-variants are treated as transparent per default. - only supported for single-field structs and enum variants - `pyo3(annotation = "name")` @@ -845,12 +788,19 @@ All the same rules from above apply as well. - renames all item keys according to the specified renaming rule - Possible values are: "camelCase", "kebab-case", "lowercase", "PascalCase", "SCREAMING-KEBAB-CASE", "SCREAMING_SNAKE_CASE", "snake_case", "UPPERCASE". - fields with an explicit renaming via `item(...)` are not affected +- `#[pyo3(from_item_all)]` + - Added for avoid erroring when `FromPyObject` is dervived together + - It will be a no-op attribute for `#[derive(IntoPyObject)]`/`#[derive(IntoPyObjectRef)]` #### `#[derive(IntoPyObject)]`/`#[derive(IntoPyObjectRef)]` Field Attributes +- `pyo3(attribute)`, `pyo3(attribute("name"))` + - Added for avoid erroring when `FromPyObject` is dervived together + - It will be a no-op attribute for `#[derive(IntoPyObject)]`/`#[derive(IntoPyObjectRef)]` - `pyo3(item)`, `pyo3(item("key"))` - convert the field to a mapping, possibly with the custom key specified as an argument. - - can be any literal that implements `ToBorrowedObject` + - `pyo3(item)` is used as default for `#[derive(IntoPyObject)]`/`#[derive(IntoPyObjectRef)]` fields + - can be any literal - `pyo3(into_py_with = ...)` - apply a custom function to convert the field from Rust into Python. - the argument must be the function identifier @@ -858,7 +808,7 @@ All the same rules from above apply as well. - `#[derive(IntoPyObject)]` will invoke the function with `Cow::Owned` - `#[derive(IntoPyObjectRef)]` will invoke the function with `Cow::Borrowed` - ```rust + ```rust, no_run # use pyo3::prelude::*; # use pyo3::IntoPyObjectExt; # use std::borrow::Cow; @@ -878,18 +828,14 @@ All the same rules from above apply as well. ``` - `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. - - note that the default value is only used if the field is not set. - If the field is set and the conversion function from Rust to Python fails, an exception is raised and the default value is not used. - - this attribute is only supported on named fields. + - Added for avoid erroring when `FromPyObject` is dervived together + - It will be a no-op attribute for `#[derive(IntoPyObject)]`/`#[derive(IntoPyObjectRef)]` ### manual implementation If the derive macro is not suitable for your use case, `IntoPyObject` can be implemented manually as demonstrated below. -```rust +```rust, no_run # use pyo3::prelude::*; # #[allow(dead_code)] struct MyPyObjectWrapper(Py); @@ -921,7 +867,7 @@ impl<'a, 'py> IntoPyObject<'py> for &'a MyPyObjectWrapper { `IntoPyObject::into_py_object` returns either `Bound` or `Borrowed` depending on the implementation for a concrete type. For example, the `IntoPyObject` implementation for `u32` produces a `Bound<'py, PyInt>` and the `bool` implementation produces a `Borrowed<'py, 'py, PyBool>`: -```rust +```rust, no_run use pyo3::prelude::*; use pyo3::IntoPyObject; use pyo3::types::{PyBool, PyInt}; @@ -948,7 +894,7 @@ In this example if we wanted to combine `ints_as_pyints` and `bools_as_pybool` i Instead, we can write a function that generically converts vectors of either integers or bools into a vector of `Py` using the [`BoundObject`] trait: -```rust +```rust, no_run # use pyo3::prelude::*; # use pyo3::BoundObject; # use pyo3::IntoPyObject; From 6f57ad6e99c9d1ee5c37f3817d04b1d148a56b84 Mon Sep 17 00:00:00 2001 From: Cheukting Date: Mon, 23 Feb 2026 18:27:45 +0000 Subject: [PATCH 10/12] Improve documentation formatting and add `no_run` to code examples for clarity --- guide/src/conversions/traits.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md index 45854e25b9d..0e547bc3ab5 100644 --- a/guide/src/conversions/traits.md +++ b/guide/src/conversions/traits.md @@ -697,7 +697,8 @@ struct RustyStruct { # } ``` -This tries to convert a mapping with the key `"key"`. The `item` can take any valid literal. +This tries to convert a mapping with the key `"key"`. +The `item` can take any valid literal. For structs with a single field (newtype pattern) the `#[pyo3(transparent)]` option can be used to forward the implementation to the inner type. @@ -755,7 +756,7 @@ struct TransparentStruct<'py> { For `enum`s each variant is converted according to the rules for `struct`s above. -```rust +```rust, no_run # #![allow(dead_code)] # use pyo3::prelude::*; # use std::collections::HashMap; From 2af35cc730937a910935e871aee0b1e1c1917348 Mon Sep 17 00:00:00 2001 From: Cheukting Date: Mon, 23 Feb 2026 18:45:56 +0000 Subject: [PATCH 11/12] one missing no_run --- guide/src/conversions/traits.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md index 0e547bc3ab5..1e5995484ea 100644 --- a/guide/src/conversions/traits.md +++ b/guide/src/conversions/traits.md @@ -528,7 +528,7 @@ Over time this has turned out problematic for a few reasons, the major one being Over the next few releases the blanket implementation is gradually phased out, and eventually replaced by an opt-in option. As a first step of this migration a new `skip_from_py_object` option for `#[pyclass]` was introduced, to opt-out of the blanket implementation and allow downstream users to provide their own implementation: -```rust +```rust, no_run # #![allow(dead_code)] # use pyo3::prelude::*; From fac1cc1f95e6269b1665479f53b4cf6e77a4e23a Mon Sep 17 00:00:00 2001 From: Cheukting Date: Mon, 23 Feb 2026 18:57:37 +0000 Subject: [PATCH 12/12] Fix doc test --- guide/src/conversions/traits.md | 1 + 1 file changed, 1 insertion(+) diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md index 1e5995484ea..4077935e149 100644 --- a/guide/src/conversions/traits.md +++ b/guide/src/conversions/traits.md @@ -675,6 +675,7 @@ struct RustyStruct { # fn new() -> Self { # Self { # string_in_mapping: String::from("test"), +# string_attr: String::from(""), # } # } # }