Skip to content

Commit 2ad3989

Browse files
committed
feat: expose a visitor to deserialize a pyobject
1 parent c29579f commit 2ad3989

File tree

4 files changed

+233
-0
lines changed

4 files changed

+233
-0
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## Unreleased
2+
3+
- Add `pyobject` module for serialize and deserialize `PyObject`
4+
- Add `PyObjectVisitor` that can be used to implement deserialize
5+
- Add feature `serde_with`
6+
17
## 0.21.1 - 2024-04-02
28

39
- Fix compile error when using PyO3 `abi3` feature targeting a minimum version below 3.10

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,15 @@ homepage = "https://github.com/davidhewitt/pythonize"
1010
repository = "https://github.com/davidhewitt/pythonize"
1111
documentation = "https://docs.rs/crate/pythonize/"
1212

13+
[features]
14+
default = ["serde_with"]
15+
serde_with = ["serde-transcode"]
1316

1417
[dependencies]
1518
serde = { version = "1.0", default-features = false, features = ["std"] }
1619
pyo3 = { version = "0.21.0", default-features = false }
20+
serde-transcode = { version = "1.0", default-features = false, optional = true }
21+
1722

1823
[dev-dependencies]
1924
serde = { version = "1.0", default-features = false, features = ["derive"] }

src/lib.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
mod de;
4040
mod error;
4141
mod ser;
42+
mod visitor;
4243

4344
#[allow(deprecated)]
4445
pub use crate::de::depythonize;
@@ -48,3 +49,41 @@ pub use crate::ser::{
4849
pythonize, pythonize_custom, PythonizeDefault, PythonizeDictType, PythonizeListType,
4950
PythonizeTypes, Pythonizer,
5051
};
52+
pub use crate::visitor::PyObjectVisitor;
53+
54+
#[cfg(feature = "serde_with")]
55+
/// This module provides a Serde `Serialize` and `Deserialize` implementation for `PyObject`.
56+
///
57+
/// ```rust
58+
/// use pyo3::PyObject;
59+
/// use serde::{Serialize, Deserialize};
60+
///
61+
/// #[derive(Serialize, Deserialize)]
62+
/// struct Foo {
63+
/// #[serde(with = "pythonize::pyobject")]
64+
/// #[serde(flatten)]
65+
/// inner: PyObject,
66+
/// }
67+
/// ```
68+
pub mod pyobject {
69+
use pyo3::{PyObject, Python};
70+
use serde::Serializer;
71+
72+
pub fn serialize<S>(obj: &PyObject, serializer: S) -> Result<S::Ok, S::Error>
73+
where
74+
S: Serializer,
75+
{
76+
Python::with_gil(|py| {
77+
let mut deserializer =
78+
crate::Depythonizer::from_object_bound(obj.clone().into_bound(py));
79+
serde_transcode::transcode(&mut deserializer, serializer)
80+
})
81+
}
82+
83+
pub fn deserialize<'de, D>(deserializer: D) -> Result<PyObject, D::Error>
84+
where
85+
D: serde::Deserializer<'de>,
86+
{
87+
Python::with_gil(|py| deserializer.deserialize_any(crate::PyObjectVisitor::new(py)))
88+
}
89+
}

src/visitor.rs

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
use pyo3::{
2+
types::{PyDict, PyDictMethods, PyList, PyListMethods},
3+
PyObject, Python, ToPyObject,
4+
};
5+
use serde::{
6+
de::{MapAccess, SeqAccess, VariantAccess, Visitor},
7+
Deserialize,
8+
};
9+
10+
pub struct PyObjectVisitor<'py> {
11+
py: Python<'py>,
12+
}
13+
14+
impl<'py> PyObjectVisitor<'py> {
15+
pub fn new(py: Python) -> PyObjectVisitor<'_> {
16+
PyObjectVisitor { py }
17+
}
18+
}
19+
20+
struct Wrapper {
21+
inner: PyObject,
22+
}
23+
24+
impl<'de> Deserialize<'de> for Wrapper {
25+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
26+
where
27+
D: serde::Deserializer<'de>,
28+
{
29+
Python::with_gil(|py| {
30+
deserializer
31+
.deserialize_any(PyObjectVisitor { py })
32+
.map(|inner| Wrapper { inner })
33+
})
34+
}
35+
}
36+
37+
impl<'de, 'py> Visitor<'de> for PyObjectVisitor<'py> {
38+
type Value = PyObject;
39+
40+
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
41+
formatter.write_str("any PyObject")
42+
}
43+
44+
fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E> {
45+
Ok(value.to_object(self.py))
46+
}
47+
48+
fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E> {
49+
Ok(value.to_object(self.py))
50+
}
51+
52+
fn visit_i128<E>(self, value: i128) -> Result<Self::Value, E> {
53+
Ok(value.to_object(self.py))
54+
}
55+
56+
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E> {
57+
Ok(value.to_object(self.py))
58+
}
59+
60+
fn visit_u128<E>(self, value: u128) -> Result<Self::Value, E> {
61+
Ok(value.to_object(self.py))
62+
}
63+
64+
fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E> {
65+
Ok(value.to_object(self.py))
66+
}
67+
68+
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> {
69+
Ok(value.to_object(self.py))
70+
}
71+
72+
fn visit_string<E>(self, value: String) -> Result<Self::Value, E> {
73+
Ok(value.to_object(self.py))
74+
}
75+
76+
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E> {
77+
Ok(v.to_object(self.py))
78+
}
79+
80+
fn visit_none<E>(self) -> Result<Self::Value, E> {
81+
Ok(self.py.None())
82+
}
83+
84+
fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
85+
where
86+
D: serde::Deserializer<'de>,
87+
{
88+
let wrapper: Wrapper = Deserialize::deserialize(deserializer)?;
89+
Ok(wrapper.inner)
90+
}
91+
92+
fn visit_unit<E: serde::de::Error>(self) -> Result<Self::Value, E> {
93+
self.visit_none()
94+
}
95+
96+
fn visit_seq<V>(self, mut visitor: V) -> Result<Self::Value, V::Error>
97+
where
98+
V: SeqAccess<'de>,
99+
{
100+
let list = PyList::empty_bound(self.py);
101+
102+
while let Some(wrapper) = visitor.next_element::<Wrapper>()? {
103+
list.append(wrapper.inner).unwrap();
104+
}
105+
106+
Ok(list.to_object(self.py))
107+
}
108+
109+
fn visit_map<V>(self, mut visitor: V) -> Result<Self::Value, V::Error>
110+
where
111+
V: MapAccess<'de>,
112+
{
113+
let dict = PyDict::new_bound(self.py);
114+
115+
while let Some((key, value)) = visitor.next_entry::<Wrapper, Wrapper>()? {
116+
dict.set_item(key.inner, value.inner).unwrap();
117+
}
118+
119+
Ok(dict.to_object(self.py))
120+
}
121+
122+
fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
123+
where
124+
A: serde::de::EnumAccess<'de>,
125+
{
126+
let dict = PyDict::new_bound(self.py);
127+
let (value, variant): (Wrapper, _) = data.variant()?;
128+
let variant: Wrapper = variant.newtype_variant()?;
129+
dict.set_item(variant.inner, value.inner).unwrap();
130+
131+
Ok(dict.to_object(self.py))
132+
}
133+
}
134+
135+
#[cfg(test)]
136+
mod tests {
137+
use pyo3::{
138+
types::{PyAnyMethods, PyStringMethods},
139+
PyObject, Python,
140+
};
141+
use serde::{Deserialize, Serialize};
142+
use serde_json::json;
143+
144+
#[derive(Serialize, Deserialize)]
145+
struct Foo {
146+
#[serde(with = "crate::pyobject")]
147+
#[serde(flatten)]
148+
inner: PyObject,
149+
}
150+
151+
#[test]
152+
fn simple_test() {
153+
let value = json!({
154+
"code": 200,
155+
"success": true,
156+
"payload": {
157+
"features": [
158+
"serde",
159+
"json"
160+
],
161+
"homepage": null
162+
}
163+
});
164+
165+
Python::with_gil(|py| {
166+
let foo = Foo {
167+
inner: crate::pythonize(py, &value).unwrap(),
168+
};
169+
let serialized = serde_json::to_string(&foo).unwrap();
170+
let deserialized: Foo = serde_json::from_str(&serialized).unwrap();
171+
assert_eq!(
172+
deserialized
173+
.inner
174+
.bind(py)
175+
.repr()
176+
.unwrap()
177+
.to_str()
178+
.unwrap(),
179+
foo.inner.bind(py).repr().unwrap().to_str().unwrap()
180+
);
181+
});
182+
}
183+
}

0 commit comments

Comments
 (0)