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 inext_lib
, I cannot really get information about the fields. At that point, I might as well introduce pyo3 inext_lib
. -
Using serde to serialize structs from
ext_lib
into the structs in the Python interface lib. Still requires thePy*
struct definitions, but would at least make theimpl 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 inext_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 (orHashMap<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 Wrapping external rust crate #287. -
Adding a
cfg
flag that only includes the#[pyclass]
if thepyo3
feature inext_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.