PyO3 / PyO3/pyo3

Expose structs from external library in Python

Open
#4,831 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
16.2k
Forks
1k
Avg merge
2d 6h
Merged PRs (30d)
66

Description

Some context

I have written a Rust library (lets call it "ext_lib") with a lot of structs with a lot of fields. I want to create a Python interface to ext_lib while (ideally) not touching ext_lib itself.

Because you cannot implement traits from external libraries (pyo3) for types in other external libraries (ext_lib), this has been a bit of a tedious job.

What I've resorted to doing is essentially copying structs from ext_lib to my Python interface, decorating them with #[pyclass(get_all)] and then adding an impl From<ext_lib::SomeType> for PySomeType for each one.

Small example just to make things as clear as possible:

// ext_lib
pub struct SomeType {
    foo: u32
}


// python interface lib
#[pyclass(name="SomeType", get_all)]
pub struct PySomeType {
    foo: u32
}

impl From<ext_lib::SomeType> for PySomeType {
    fn from(other: ext_lib::SomeType) -> Self {
        Self {
            foo: other.foo
        }
    }
}

This works great, but I have around 50-80 structs, some with up to 30 fields, so doing all this manually takes a lot of time.

Approaches I've considered
  • Creating procedural macros - This requires whatever macro I create to know about the structs in ext_lib. Without applying these macros directly in ext_lib, I cannot really get information about the fields. At that point, I might as well introduce pyo3 in ext_lib.

  • Using serde to serialize structs from ext_lib into the structs in the Python interface lib. Still requires the Py* struct definitions, but would at least make the impl From blocks a bit smaller.

  • Writing a code generation tool - Similar to a macro, except it's just a script that parses the .rs files in ext_lib and spits out #[pyclass] equivalents. Seems like a lot of work to implement.

  • Doing something equivalent to

    MyType = type('MyType', (object,), fields)
    

    with pyo3, where fields is a dict (or HashMap<String, T> for Rust) that contains the fields and their types. I've scoured the docs and issues, but couldn't find anything except for #287.

  • Adding a cfg flag that only includes the #[pyclass] if the pyo3 feature in ext_lib is enabled. Definitely seems like the easiest solution, but I'm afraid that my (so far) standalone Rust library becomes coupled with the Python interface.

So, is there an ergonomic solution for such a use case?

I'm by no means an expert in Rust ( yet :-) ), so I could be overlooking some trivial solution.

I hope an expert can help me find a good solution. Please don't hesitate to question/comment on my use case. This could very well just be an XY problem.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the use case and constraints described in this issue, then review PyO3's pyclass support and the related issue #287. A viable contribution would need a clearly scoped ergonomic approach for exposing structs from an external Rust library, along with an agreed implementation and validation plan.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, rust
Domain
api, backend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.