PyO3 / pyo3

Rust bindings for the Python interpreter
https://pyo3.rs
Apache License 2.0
12.32k stars 760 forks source link

Crazy idea: serde support #566

Open birkenfeld opened 5 years ago

birkenfeld commented 5 years ago

A few times now, I had to take in complex-ish Python data and convert it into an equivalent in Rust. FromPyObject already does the basic types, but I'd also like to convert e.g. nested dicts with certain keys into structs.

Do you think it would make sense to let serde handle this? It already has all the options you'd like, e.g. flattening of sub-structure and renaming of things. For structs, it should support both dicts (taking keys) and other objects (taking attributes).

The "ser" direction could also be supported, although I don't need it, and it would probably only construct dicts for structs.

ohadravid commented 5 years ago

The "ser" direction could also be supported, although I don't need it, and it would probably only construct dicts for structs.

We actually wanted something like this, mostly when you want to return something which is 'data', but don't want (or can't) create a dedicated python wrapper for each struct.

pganssle commented 5 years ago

@birkenfeld I think that this sort of thing is a good idea, but is it really something that belongs in PyO3?

I think it makes sense to leave the core C API wrappers in PyO3 and break out useful additional functionality into their own crates.

jmrgibson commented 5 years ago

I took a stab at something like this, by implementing the custom serde traits, but then ran into lifetime issues with all the python data since everything relies on the interpreter lifetime. Not that it probably isn't possible, but It'll take someone with more experience than me.

I ended up just dumping everything to a raw json string and passing it over, which is a trivial amount of code on both python and rust sides, but not an elegant solution.

gperinazzo commented 5 years ago

I've made a crate to help deal with this. It let's you derive the necessary traits for PyO3 to convert a dict into your struct, as long as it knows how to convert every field.

I plan on having serde-like field attributes in the future, but if you're not using them the base functionality should beat passing things around as json.

m3047 commented 4 years ago

I would encourage you all not to overthink this. So allow me to step back and do exactly that at a more abstract level I guess... :-/

Who is your audience? Is it people who are primarily writing in Rust or Python?

What are they struggling with? Are they struggling with learning one language or the other, or learning how to call something from Python using e.g. ctypes or have they stumbled on pyo3 and are they wondering how that works?

What are they trying to accomplish? Are they trying to translate from one language to the other? Do they feel the "need for speed" and contemplating trying something other than C? Are they primarily Rust programmers looking for a more transparent "glue" for some larger problem (see the popularity of embedded Lua for various tasks)?

I have a brick in the wall to offer as someone primarily writing in Python who had an optimization problem and stumbled across pyo3 and thought "well maybe I'll trying doing it in Rust rather than C": m3047/pyo3-deserializer

(A friend of mine who write primarily in Rust brought this issue to my attention.)

davidhewitt commented 4 years ago

One for the folks watching this: I've made a WIP crate which targets a full serde implementation for PyO3. See https://github.com/davidhewitt/pythonize

The idea is that it should be pretty much equivalent to serde_json but generates Python objects instead of string data.

Note that unlike dict-derive this means that it doesn't integrate very nicely with types that already implement IntoPy / FromPyObject - it's got to be serde all the way down. Maybe there's a way to solve that.

At the moment only the to-Python direction is implemented, but I'd be interested in taking feedback / help finishing this off.

leplatrem commented 4 years ago

I had a similar use-case, convert a PyObject into a serde_json::json::Value. It might be slightly off-topic here, but I thought it could be useful for someone looking for something similar and landing here :) https://github.com/mozilla-services/python-canonicaljson-rs/blob/62599b246055a1c8a78e5777acdfe0fd594be3d8/src/lib.rs#L87-L167 (it was mostly inspired by Matthias Endler's hyperjson)

kivo360 commented 3 years ago

I had a similar use-case, convert a PyObject into a serde_json::json::Value. It might be slightly off-topic here, but I thought it could be useful for someone looking for something similar and landing here :) https://github.com/mozilla-services/python-canonicaljson-rs/blob/62599b246055a1c8a78e5777acdfe0fd594be3d8/src/lib.rs#L87-L167 (it was mostly inspired by Matthias Endler's hyperjson)

This is exactly what I needed.

davidhewitt commented 3 years ago

@kivo360 @leplatrem pythonize can also do similar (and more) using the depythonize API. It can convert PyObject into any type which implements Deserialize.

An interesting question I was wondering this week is whether we should provide serde wrappers for types like Py<T>.

McSpidey commented 2 years ago

Is there any chance pythonize would become a native part of PyO3?

davidhewitt commented 2 years ago

I haven't seen a need for it to become part of the main crate; what's the advantage that you see from doing so?

McSpidey commented 2 years ago

I'm new to PyO3 but even from just porting one simple app it seemed logical a native serialiser/deserialiser would be a fundamental core feature for efficient workflows, and serde is seemingly the defacto rust standard for this.

davidhewitt commented 2 years ago

That's a reasonable opinion. There's no performance or API disadvantages by having pythonize separate, and it reduces compile time for those who don't need it. For now I'm inclined to keep them separate, if there's a strong motivation to merge it in we can revisit.

McSpidey commented 2 years ago

Is there still a compile time disadvantage if it's implemented as a 'feature'?

davidhewitt commented 2 years ago

Probably not a huge difference for users, however the PyO3 CI already takes a long time, and I count that as a compile time disadvantage too!

Also worth bearing in mind that I hoped that anything which merged into PyO3 would have a solution for https://github.com/davidhewitt/pythonize/issues/1 so that there would only be one integrated way to convert Rust -> Python objects, rather than two different mechanisms in the same crate.

I really do hold the opinion that pythonize is solving a different enough problem from PyO3 core that there's no need to make it part of the main API right now.

fzyzcjy commented 6 months ago

Hi, is there any chance to make pythonize work automatically? Or, what is the best practice when returning a complex nested struct? Thanks!

For example, I hope to use sth like:

#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Sample {
    foo: String,
    bar: Option<usize>
}

#[pymethods]
impl MyClass {
  fn f() -> Sample { ... }
}

... and it auto works (instead of manually calling pythonize).

adamreichold commented 6 months ago

I am not sure if we can make this work transparently as the trait bound T: Serialize would be quite broad. I guess, we could use a wrapper type like Pythonize(T) which would automatically call into pythonize behind the scenes (similar to e.g. the extractor and response wrappers provided by Axum), gated behind an optional integration feature as we use for other third-party crates.