gperinazzo / dict-derive

Derive PyO3's FromPyObject to automatically transform Python dicts into Rust structs
Apache License 2.0
105 stars 10 forks source link

Enum Support #5

Open jmrgibson opened 5 years ago

jmrgibson commented 5 years ago

Firstly, thanks for this!

I wanted to use rust enums within my structs, so I prototyped what it would look like using the strum crate to implement the ToString/FromString bits for me.

I feel like I could easily add this into the macro for this crate, but do we want the reliance on strum, or should we re-implement the ToString/FromString derives? We could maybe make it an extra feature?

Also, how much do we care about supporting real python enum.Enum/IntEnum types?

Example generated code:

use dict_derive::{FromPyObject, IntoPyObject};

use strum;
use strum_macros::{EnumString, ToString};

#[derive(EnumString, ToString)]
pub enum ActuatorPosition {
    Release,
    Reset,
}

impl<'source> FromPyObject<'source> for ActuatorPosition {
    fn extract(obj: &'source PyAny) -> PyResult<Self> {
        let s = <PyString as PyTryFrom>::try_from(obj)?
            .to_string()
            .map(Cow::into_owned)?;

        Self::from_str(&s)
            .map_err(|_| exceptions::ValueError::py_err(String::from(format!("Bad enum '{}'", s))))
    }
}

impl ToPyObject for ActuatorPosition {
    #[inline]
    fn to_object(&self, py: Python) -> PyObject {
        PyString::new(py, &self.to_string()).into()
    }
}

impl FromPy<ActuatorPosition> for PyObject {
    fn from_py(other: ActuatorPosition, py: Python) -> Self {
        PyString::new(py, &other.to_string()).into()
    }
}
gperinazzo commented 5 years ago

Firstly, thanks for this!

Glad you found it useful!

I wanted to use rust enums within my structs, so I prototyped what it would look like using the strum crate to implement the ToString/FromString bits for me.

I feel like I could easily add this into the macro for this crate, but do we want the reliance on strum, or should we re-implement the ToString/FromString derives? We could maybe make it an extra feature?

Given that we can specialize the code specifically for enums, I don't think generating the string version of an enum variant would be difficult.

But I would like to support enums eventually as serde does: allow you to set the key in which it'll search for the enum tag, and allow you to have sub-fields, for example:

#[derive(FromPyObject)]
struct SomeStruct {
    field: String,
}

#[derive(FromPyObject)]
#[dict(tag = "type")]
enum Example {
    SomeVariant{
        with_field: bool,
    },
    AnotherVariant(SomeStruct),
}

Should be able to be loaded from

some_variant = {
    "type": "SomeVariant",
    "with_field": true,
}

another_variant = {
    "type": "AnotherVariant",
    "field": "some string",
}

We could add the behavior you want as a separate derive.

Also, how much do we care about supporting real python enum.Enum/IntEnum types?

Properly supporting this would require the class to be generate through PyO3 and exposed through our client's library for the python code to consume. I think it falls out of the scope of the crate.

Deriving a trait works with custom implementations of the trait on fields, so if you want a type to serialize/deserialize differently it's usually easier to implement the traits for your type. You'll still be able to use your new type inside a dict derived with this crate's macro.