Skip to content

Commit

Permalink
Merge pull request #52 from pexip/sam/expose-deserialiser
Browse files Browse the repository at this point in the history
Expose the Pythonizer and Depythonizer to integrate with the serde_path_to_err crate
  • Loading branch information
davidhewitt authored Feb 2, 2024
2 parents 21a911c + f840ea7 commit c237ce1
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 7 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ serde = { version = "1.0", default-features = false, features = ["derive"] }
pyo3 = { version = "0.20.0", default-features = false, features = ["auto-initialize", "macros"] }
serde_json = "1.0"
maplit = "1.0.2"
serde_path_to_error = "0.1.15"
2 changes: 2 additions & 0 deletions src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ where
T::deserialize(&mut depythonizer)
}

/// A structure that deserializes Python objects into Rust values
pub struct Depythonizer<'de> {
input: &'de PyAny,
}

impl<'de> Depythonizer<'de> {
/// Create a deserializer from a Python object
pub fn from_object(input: &'de PyAny) -> Self {
Depythonizer { input }
}
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ mod de;
mod error;
mod ser;

pub use crate::de::depythonize;
pub use crate::de::{depythonize, Depythonizer};
pub use crate::error::{PythonizeError, Result};
pub use crate::ser::{
pythonize, pythonize_custom, PythonizeDictType, PythonizeListType, PythonizeTypes,
pythonize, pythonize_custom, PythonizeDictType, PythonizeListType, PythonizeTypes, Pythonizer,
};
29 changes: 24 additions & 5 deletions src/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ pub fn pythonize<T>(py: Python, value: &T) -> Result<PyObject>
where
T: ?Sized + Serialize,
{
pythonize_custom::<PythonizeDefault, _>(py, value)
value.serialize(Pythonizer::new(py))
}

/// Attempt to convert the given data into a Python object.
Expand All @@ -73,18 +73,37 @@ where
T: ?Sized + Serialize,
P: PythonizeTypes,
{
value.serialize(Pythonizer::<P> {
py,
_types: PhantomData,
})
value.serialize(Pythonizer::custom::<P>(py))
}

/// A structure that serializes Rust values into Python objects
#[derive(Clone, Copy)]
pub struct Pythonizer<'py, P> {
py: Python<'py>,
_types: PhantomData<P>,
}

impl<'py, P> From<Python<'py>> for Pythonizer<'py, P> {
fn from(py: Python<'py>) -> Self {
Self {
py,
_types: PhantomData,
}
}
}

impl<'py> Pythonizer<'py, PythonizeDefault> {
/// Creates a serializer to convert data into a Python object using the default mapping class
pub fn new(py: Python<'py>) -> Self {
Self::from(py)
}

/// Creates a serializer to convert data into a Python object using a custom mapping class
pub fn custom<P>(py: Python<'py>) -> Pythonizer<'py, P> {
Pythonizer::from(py)
}
}

#[doc(hidden)]
pub struct PythonCollectionSerializer<'py, P> {
items: Vec<PyObject>,
Expand Down
207 changes: 207 additions & 0 deletions tests/test_with_serde_path_to_err.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
use std::collections::BTreeMap;

use pyo3::{
types::{PyDict, PyList},
Py, PyAny, Python,
};
use pythonize::PythonizeTypes;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
struct Root<T> {
root_key: String,
root_map: BTreeMap<String, Nested<T>>,
}

impl<T> PythonizeTypes for Root<T> {
type Map = PyDict;
type List = PyList;
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
struct Nested<T> {
nested_key: T,
}

#[derive(Deserialize, Debug, PartialEq, Eq)]
struct CannotSerialize {}

impl Serialize for CannotSerialize {
fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
Err(serde::ser::Error::custom(
"something went intentionally wrong",
))
}
}

#[test]
fn test_de_valid() {
Python::with_gil(|py| {
let pyroot = PyDict::new(py);
pyroot.set_item("root_key", "root_value").unwrap();

let nested = PyDict::new(py);
let nested_0 = PyDict::new(py);
nested_0.set_item("nested_key", "nested_value_0").unwrap();
nested.set_item("nested_0", nested_0).unwrap();
let nested_1 = PyDict::new(py);
nested_1.set_item("nested_key", "nested_value_1").unwrap();
nested.set_item("nested_1", nested_1).unwrap();

pyroot.set_item("root_map", nested).unwrap();

let de = &mut pythonize::Depythonizer::from_object(pyroot);
let root: Root<String> = serde_path_to_error::deserialize(de).unwrap();

assert_eq!(
root,
Root {
root_key: String::from("root_value"),
root_map: BTreeMap::from([
(
String::from("nested_0"),
Nested {
nested_key: String::from("nested_value_0")
}
),
(
String::from("nested_1"),
Nested {
nested_key: String::from("nested_value_1")
}
)
])
}
);
})
}

#[test]
fn test_de_invalid() {
Python::with_gil(|py| {
let pyroot = PyDict::new(py);
pyroot.set_item("root_key", "root_value").unwrap();

let nested = PyDict::new(py);
let nested_0 = PyDict::new(py);
nested_0.set_item("nested_key", "nested_value_0").unwrap();
nested.set_item("nested_0", nested_0).unwrap();
let nested_1 = PyDict::new(py);
nested_1.set_item("nested_key", 1).unwrap();
nested.set_item("nested_1", nested_1).unwrap();

pyroot.set_item("root_map", nested).unwrap();

let de = &mut pythonize::Depythonizer::from_object(pyroot);
let err = serde_path_to_error::deserialize::<_, Root<String>>(de).unwrap_err();

assert_eq!(err.path().to_string(), "root_map.nested_1.nested_key");
assert_eq!(err.to_string(), "root_map.nested_1.nested_key: unexpected type: 'int' object cannot be converted to 'PyString'");
})
}

#[test]
fn test_ser_valid() {
Python::with_gil(|py| {
let root = Root {
root_key: String::from("root_value"),
root_map: BTreeMap::from([
(
String::from("nested_0"),
Nested {
nested_key: String::from("nested_value_0"),
},
),
(
String::from("nested_1"),
Nested {
nested_key: String::from("nested_value_1"),
},
),
]),
};

let ser = pythonize::Pythonizer::<Root<String>>::from(py);
let pyroot: Py<PyAny> = serde_path_to_error::serialize(&root, ser).unwrap();

let pyroot: &PyDict = pyroot.downcast(py).unwrap();
assert_eq!(pyroot.len(), 2);

let root_value: &str = pyroot
.get_item("root_key")
.unwrap()
.unwrap()
.extract()
.unwrap();
assert_eq!(root_value, "root_value");

let root_map: &PyDict = pyroot
.get_item("root_map")
.unwrap()
.unwrap()
.extract()
.unwrap();
assert_eq!(root_map.len(), 2);

let nested_0: &PyDict = root_map
.get_item("nested_0")
.unwrap()
.unwrap()
.extract()
.unwrap();
assert_eq!(nested_0.len(), 1);
let nested_key_0: &str = nested_0
.get_item("nested_key")
.unwrap()
.unwrap()
.extract()
.unwrap();
assert_eq!(nested_key_0, "nested_value_0");

let nested_1: &PyDict = root_map
.get_item("nested_1")
.unwrap()
.unwrap()
.extract()
.unwrap();
assert_eq!(nested_1.len(), 1);
let nested_key_1: &str = nested_1
.get_item("nested_key")
.unwrap()
.unwrap()
.extract()
.unwrap();
assert_eq!(nested_key_1, "nested_value_1");
});
}

#[test]
fn test_ser_invalid() {
Python::with_gil(|py| {
let root = Root {
root_key: String::from("root_value"),
root_map: BTreeMap::from([
(
String::from("nested_0"),
Nested {
nested_key: CannotSerialize {},
},
),
(
String::from("nested_1"),
Nested {
nested_key: CannotSerialize {},
},
),
]),
};

let ser = pythonize::Pythonizer::<Root<String>>::from(py);
let err = serde_path_to_error::serialize(&root, ser).unwrap_err();

assert_eq!(err.path().to_string(), "root_map.nested_0.nested_key");
});
}

0 comments on commit c237ce1

Please sign in to comment.