Skip to content

docs: Improves IntoPyObject documentation in the guides#5835

Open
Cheukting wants to merge 12 commits intoPyO3:mainfrom
Cheukting:doc-5709
Open

docs: Improves IntoPyObject documentation in the guides#5835
Cheukting wants to merge 12 commits intoPyO3:mainfrom
Cheukting:doc-5709

Conversation

@Cheukting
Copy link
Contributor

closes #5709

@Cheukting Cheukting changed the title docs: Improves IntoPyObject documentation in the guides [WIP] docs: Improves IntoPyObject documentation in the guides Feb 22, 2026
Copy link
Member

@Icxolu Icxolu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for working on this! I spotted a few things and left comments below.

Comment on lines +697 to +698
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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from_item_all has no functionality for IntoPyObject since it exclusively uses set_item. It is just accepted for compatibility with FromPyObject. (Same is true for for other attributes as well, for example attribute or default)

For `enum`s each variant is converted according to the rules for `struct`s above.

```rust,no_run
```rust
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It only makes sense to run these if there is main which does something, otherwise it is better to leave these as no_run for a faster test suite.

Comment on lines +880 to +887
- `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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one has no effect for IntoPyObject and we should not document it.


#### `#[derive(IntoPyObject)]`/`#[derive(IntoPyObjectRef)]` Field Attributes

- `pyo3(item)`, `pyo3(item("key"))`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pyo3(item) is actually the default and only pyo3(item("key")) has the effect to use a different key

…er field attributes in `IntoPyObject` traits.
@Cheukting
Copy link
Contributor Author

It is updated considering all your feedback and suggestions @Icxolu please feel free to check again

- 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not quite sure what you mean here

struct RustyStruct {
#[pyo3(item("key"))]
string_in_mapping: String,
#[pyo3(attribute("name"))] // no effect on this field
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this has no effect here, I would not put it in an example. I think this is just confusing to people. What I think we can do is to write a sentence somewhere that FromPyObject and IntoPyObject for convenience (technical) reasons accept each other arguments, but that not all of them are supported by both of them equally, then linking to the sections with the list of supported attributes of each.


- `pyo3(transparent)`
- convert the field directly to the object instead of `set_item()`
- Newtype structs and tuple-variants are treated as transparent per default.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Newtype structs and tuple-variants are treated as transparent per default.
- Newtype tuple structs and tuple-variants are treated as transparent per default.

Comment on lines +793 to +795
- `#[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)]`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned here, I think we should not list the ones that don't do anything, instead writing a more generic paragraph and maybe link to it from here. IMO this just clutters up the list without bringing too much valuable info to a user who is looking to customize IntoPyObject, but maybe other have different opinions here.

```

- `pyo3(default)`, `pyo3(default = ...)`
- Added for avoid erroring when `FromPyObject` is dervived together
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what this means. Together with what?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @Icxolu has explained that, it is common practice to derviced IntoPyObject and FromPyObject together and this option is available to avoid errors. I think it is obvious to people who are using this feature but I can add more explanation once we have decided on the format.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I was just having trouble with the grammar. Maybe instead:

"Used to avoid errors when deriving both FromPyObject and IntoPyObject"

- 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what this means either.

Copy link
Contributor Author

@Cheukting Cheukting Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an explanation above, this is just a list of organising them together in one place. Please read the paragraph above

Comment on lines 449 to 452
### `#[derive(FromPyObject)]` Container Attributes

- `pyo3(transparent)`
- extract the field directly from the object as `obj.extract()` instead of `get_item()` or
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Observation: this is currently in a list. Some of these macros have the options documented as a table, e.g. https://pyo3.rs/v0.28.2/class.html#customizing-the-class

I think we're probably quite inconsistent across the documentation, maybe we should create a new issue to decide on preferred style (table or list) and convert them all?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do like the table format better, we can tie the effect of the attribution for FromPyObject and IntoPyObject together. I can work on that this weekend if it looks better

@Cheukting
Copy link
Contributor Author

In my opinion, we need a list or table to list out the behaviour of each option, including the no-op ones because:

  1. The original issue was saying that they are not documented even though it is allowed
  2. If not list out users will assume those not show are not allowed.

If a table is clearer and easier to explain I don't mind moving all items in the lists into tables. But I do believe they need to be explicitly documentated.

@Icxolu
Copy link
Member

Icxolu commented Feb 26, 2026

Hmm, the reason I don't think we should list them is that I would not consider these "public" API. You should not use the options if you only implementing IntoPyObject, these are FromPyObject specific options. We only have to accept them here for technical reasons. I would argue that these are basically #[doc(hidden)] options and thus should only be documented where we "officially" support them, which here is FromPyObject.

Alternatively, I think we could follow serde's example and just create list (or table) of all supported by both. They for example don't specify that deserialize_with is Deserialize only and Serialize does not care. Listing these unsupported options directly under the IntoPyObject heading just feels plain wrong to me.

@Cheukting
Copy link
Contributor Author

Let's follow serde's example then, I also think it looks neater by not having two lists.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Doc Improvements: IntoPyObject actually supports item / attribute attributes

4 participants