Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add #[pyo3(from_python_with = ...)] attribute #1239

Closed
davidhewitt opened this issue Oct 15, 2020 · 4 comments · Fixed by #1411
Closed

Add #[pyo3(from_python_with = ...)] attribute #1239

davidhewitt opened this issue Oct 15, 2020 · 4 comments · Fixed by #1411

Comments

@davidhewitt
Copy link
Member

davidhewitt commented Oct 15, 2020

I've been thinking for a while it would be interesting to have an attribute which can be used to customize the from-python conversion in our proc macros. This need has come up in Gitter as well as #884.

The attribute will go on #[pyfunction] arguments or #[derive(FromPyObject)] fields. It takes a value which is a path to a function fn (&PyAny) -> Result<T, E>, where T is the annotated type and E: Into<PyErr>. For examples see the detailed design below.

This issue is invite users to comment on the design and for anyone interested to volunteer to implement it (before I eventually get around to it).

In detail, the two applications I see for this attribute are:

In #[pyfunction] arguments

Consider the following pyfunction:

#[pyfunction]
fn foo(datetime: chrono::DateTime) { ... }

Currently chrono::DateTime does not implement FromPyObject, so this function won't compile. An existing workaround is to create a newtype MyDateTime(chrono::DateTime) and implement FromPyObject for that, but it's a lot of boilerplate.

The attribute would allow the user to leverage a single function without adding extra types:

fn extract_datetime(any: &PyAny) -> PyResult<chrono::DateTime> { ... }

#[pyfunction]
fn foo(
    #[pyo3(from_python_with = extract_datetime)]
    datetime: chrono::DateTime
) { ... }

On #[derive(FromPyObject)] fields

Similarly, this applies in a #[derive(FromPyObject)] struct:

#[derive(FromPyObject)]
#[pyo3(transparent)]
struct Foo {
    #[pyo3(from_python_with = extract_datetime)]
    datetime: chrono::DateTime
}

The cool thing here is that this lets us add #[derive(FromPyObject)] to a struct which has a field which itself doesn't implement FromPyObject.

Bikeshedding

  • should it be from_py_with instead of from_python_with?
  • should the function be allowed to take more specific argument types than &PyAny? E.g. in the examples above it could be helpful to take &PyDateTime. But it's also trivial to convert PyAny with .downcast().
@davidhewitt
Copy link
Member Author

cc @gilescope

@daniil-konovalenko
Copy link
Contributor

Hi, I would like to try to implement this, if it's okay.
I haven't touched procedural macros yet though so I think it may take a while

@gilescope
Copy link
Contributor

gilescope commented Jan 12, 2021 via email

@davidhewitt
Copy link
Member Author

Please feel welcome to take it on! I'm happy to answer any questions you have.

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

Successfully merging a pull request may close this issue.

3 participants