Skip to content

Commit

Permalink
implement Serialize, Deserialize for Py<T>
Browse files Browse the repository at this point in the history
  • Loading branch information
daniil-konovalenko committed Jan 6, 2021
1 parent c8172e9 commit 1719ba3
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 1 deletion.
4 changes: 3 additions & 1 deletion Cargo.toml
Expand Up @@ -27,14 +27,16 @@ paste = { version = "1.0.3", optional = true }
pyo3-macros = { path = "pyo3-macros", version = "=0.13.0", optional = true }
unindent = { version = "0.1.4", optional = true }
hashbrown = { version = "0.9", optional = true }
serde = {version = "1.0", optional = true}

[dev-dependencies]
assert_approx_eq = "1.1.0"
trybuild = "1.0.23"
rustversion = "1.0"
proptest = { version = "0.10.1", default-features = false, features = ["std"] }
# features needed to run the PyO3 test suite
pyo3 = { path = ".", default-features = false, features = ["macros", "auto-initialize"] }
pyo3 = { path = ".", default-features = false, features = ["macros", "auto-initialize", "serde"] }
serde_json = "1.0.61"

[features]
default = ["macros", "auto-initialize"]
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Expand Up @@ -211,6 +211,9 @@ mod python;
pub mod type_object;
pub mod types;

#[cfg(feature = "serde")]
pub mod serde;

/// The proc macros, which are also part of the prelude.
#[cfg(feature = "macros")]
pub mod proc_macro {
Expand Down
36 changes: 36 additions & 0 deletions src/serde.rs
@@ -0,0 +1,36 @@
use crate::type_object::PyBorrowFlagLayout;
use crate::{Py, PyClass, PyClassInitializer, PyTypeInfo, Python};
use serde::{de, ser, Deserialize, Deserializer, Serialize, Serializer};

impl<T> Serialize for Py<T>
where
T: Serialize + PyClass,
{
fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
where
S: Serializer,
{
Python::with_gil(|py| {
self.try_borrow(py)
.map_err(|e| ser::Error::custom(e.to_string()))?
.serialize(serializer)
})
}
}

impl<'de, T> Deserialize<'de> for Py<T>
where
T: Into<PyClassInitializer<T>> + PyClass + Deserialize<'de>,
<T as PyTypeInfo>::BaseLayout: PyBorrowFlagLayout<<T as PyTypeInfo>::BaseType>,
{
fn deserialize<D>(deserializer: D) -> Result<Py<T>, D::Error>
where
D: Deserializer<'de>,
{
let deserialized = T::deserialize(deserializer)?;

Python::with_gil(|py| {
Py::new(py, deserialized).map_err(|e| de::Error::custom(e.to_string()))
})
}
}
80 changes: 80 additions & 0 deletions tests/test_serde.rs
@@ -0,0 +1,80 @@
#[cfg(feature = "serde")]
mod test_serde {
use pyo3::prelude::*;

use serde::{Deserialize, Serialize};
use serde_json;

#[pyclass]
#[derive(Debug, Serialize, Deserialize)]
struct Group {
name: String,
}

#[pyclass]
#[derive(Debug, Clone, Serialize, Deserialize)]
struct User {
username: String,
group: Option<Py<Group>>,
friends: Vec<Py<User>>,
}

#[test]
fn test_serialize() {
let friend1 = User {
username: "friend 1".into(),
group: None,
friends: vec![],
};
let friend2 = User {
username: "friend 2".into(),
..friend1.clone()
};

let user = Python::with_gil(|py| {
let py_friend1 = Py::new(py, friend1).expect("failed to create friend 1");
let py_friend2 = Py::new(py, friend2).expect("failed to create friend 2");

let friends = vec![py_friend1, py_friend2];
let py_group = Py::new(
py,
Group {
name: "group name".into(),
},
)
.unwrap();

User {
username: "danya".into(),
group: Some(py_group),
friends,
}
});

let serialized = serde_json::to_string(&user).expect("failed to serialize");
assert_eq!(
serialized,
r#"{"username":"danya","group":{"name":"group name"},"friends":[{"username":"friend 1","group":null,"friends":[]},{"username":"friend 2","group":null,"friends":[]}]}"#
);
}

#[test]
fn test_deserialize() {
let serialized = r#"{"username": "danya", "friends":
[{"username": "friend", "group": {"name": "danya's friends"}, "friends": []}]}"#;
let user: User = serde_json::from_str(serialized).expect("failed to deserialize");

assert_eq!(user.username, "danya");
assert_eq!(user.group, None);
assert_eq!(user.friends.len(), 1usize);
let friend = user.friends.get(0).unwrap();

Python::with_gil(|py| {
assert_eq!(friend.borrow(py).username, "friend");
assert_eq!(
friend.borrow(py).group.as_ref().unwrap().borrow(py).name,
"danya's friends"
)
});
}
}

0 comments on commit 1719ba3

Please sign in to comment.