diff --git a/CHANGELOG.md b/CHANGELOG.md index f245889..d6c1c1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ - `Depythonizer` now contains a `&Bound` and so has an extra lifetime `'bound` - `Depythonizer::from_object()` now takes a `&Bound` and is no longer deprecated - Fix overflow error attempting to depythonize `u64` values greater than `i64::MAX` to types like `serde_json::Value` +- Implement `PythonizeListType` for `PyTuple` +- Support deserializing enums from any `PyMapping` instead of just `PyDict` +- Support serializing struct-like types to named mappings using `PythonizeTypes::NamedMap` ## 0.21.1 - 2024-04-02 diff --git a/src/de.rs b/src/de.rs index 11d0fcc..986d0a6 100644 --- a/src/de.rs +++ b/src/de.rs @@ -294,21 +294,21 @@ impl<'a, 'py, 'de, 'bound> de::Deserializer<'de> for &'a mut Depythonizer<'py, ' V: de::Visitor<'de>, { let item = &self.input; - if let Ok(d) = item.downcast::() { - // Get the enum variant from the dict key - if d.len() != 1 { + if let Ok(s) = item.downcast::() { + visitor.visit_enum(s.to_cow()?.into_deserializer()) + } else if let Ok(m) = item.downcast::() { + // Get the enum variant from the mapping key + if m.len()? != 1 { return Err(PythonizeError::invalid_length_enum()); } - let variant = d - .keys() + let variant: Bound = m + .keys()? .get_item(0)? .downcast_into::() .map_err(|_| PythonizeError::dict_key_not_string())?; - let value = d.get_item(&variant)?.unwrap(); + let value = m.get_item(&variant)?; let mut de = Depythonizer::from_object(&value); visitor.visit_enum(PyEnumAccess::new(&mut de, variant)) - } else if let Ok(s) = item.downcast::() { - visitor.visit_enum(s.to_cow()?.into_deserializer()) } else { Err(PythonizeError::invalid_enum_type()) } diff --git a/src/lib.rs b/src/lib.rs index f85a025..4ce1751 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,6 +45,6 @@ pub use crate::de::depythonize_bound; pub use crate::de::{depythonize, Depythonizer}; pub use crate::error::{PythonizeError, Result}; pub use crate::ser::{ - pythonize, pythonize_custom, PythonizeDefault, PythonizeDictType, PythonizeListType, - PythonizeTypes, Pythonizer, + pythonize, pythonize_custom, PythonizeDefault, PythonizeListType, PythonizeMappingType, + PythonizeNamedMappingType, PythonizeTypes, PythonizeUnnamedMappingAdapter, Pythonizer, }; diff --git a/src/ser.rs b/src/ser.rs index 282c011..072212e 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -1,15 +1,52 @@ use std::marker::PhantomData; -use pyo3::types::{PyAnyMethods, PyDict, PyList, PyMapping, PySequence, PyString, PyTuple}; +use pyo3::types::{ + PyAnyMethods, PyDict, PyDictMethods, PyList, PyMapping, PySequence, PyString, PyTuple, + PyTupleMethods, +}; use pyo3::{Bound, IntoPy, PyAny, PyResult, Python, ToPyObject}; use serde::{ser, Serialize}; use crate::error::{PythonizeError, Result}; +// TODO: move 'py lifetime into builder once GATs are available in MSRV /// Trait for types which can represent a Python mapping -pub trait PythonizeDictType { - /// Constructor - fn create_mapping(py: Python) -> PyResult>; +pub trait PythonizeMappingType<'py> { + /// Builder type for Python mappings + type Builder; + + /// Create a builder for a Python mapping + fn builder(py: Python<'py>, len: Option) -> PyResult; + + /// Adds the key-value item to the mapping being built + fn push_item( + builder: &mut Self::Builder, + key: Bound<'py, PyAny>, + value: Bound<'py, PyAny>, + ) -> PyResult<()>; + + /// Build the Python mapping + fn finish(builder: Self::Builder) -> PyResult>; +} + +// TODO: move 'py lifetime into builder once GATs are available in MSRV +/// Trait for types which can represent a Python mapping and have a name +pub trait PythonizeNamedMappingType<'py> { + /// Builder type for Python mappings with a name + type Builder; + + /// Create a builder for a Python mapping with a name + fn builder(py: Python<'py>, len: usize, name: &'static str) -> PyResult; + + /// Adds the field to the named mapping being built + fn push_field( + builder: &mut Self::Builder, + name: Bound<'py, PyString>, + value: Bound<'py, PyAny>, + ) -> PyResult<()>; + + /// Build the Python mapping + fn finish(builder: Self::Builder) -> PyResult>; } /// Trait for types which can represent a Python sequence @@ -24,17 +61,68 @@ pub trait PythonizeListType: Sized { U: ExactSizeIterator; } +// TODO: remove 'py lifetime once GATs are available in MSRV /// Custom types for serialization -pub trait PythonizeTypes { +pub trait PythonizeTypes<'py> { /// Python map type (should be representable as python mapping) - type Map: PythonizeDictType; + type Map: PythonizeMappingType<'py>; + /// Python (struct-like) named map type (should be representable as python mapping) + type NamedMap: PythonizeNamedMappingType<'py>; /// Python sequence type (should be representable as python sequence) type List: PythonizeListType; } -impl PythonizeDictType for PyDict { - fn create_mapping(py: Python) -> PyResult> { - Ok(PyDict::new_bound(py).into_any().downcast_into().unwrap()) +impl<'py> PythonizeMappingType<'py> for PyDict { + type Builder = Bound<'py, Self>; + + fn builder(py: Python<'py>, _len: Option) -> PyResult { + Ok(Self::new_bound(py)) + } + + fn push_item( + builder: &mut Self::Builder, + key: Bound<'py, PyAny>, + value: Bound<'py, PyAny>, + ) -> PyResult<()> { + builder.set_item(key, value) + } + + fn finish(builder: Self::Builder) -> PyResult> { + Ok(builder.into_mapping()) + } +} + +/// Adapter type to use an unnamed mapping type, i.e. one that implements +/// [`PythonizeMappingType`], as a named mapping type, i.e. one that implements +/// [`PythonizeNamedMappingType`]. The adapter simply drops the provided name. +/// +/// This adapter is commonly applied to use the same unnamed mapping type for +/// both [`PythonizeTypes::Map`] and [`PythonizeTypes::NamedMap`] while only +/// implementing [`PythonizeMappingType`]. +pub struct PythonizeUnnamedMappingAdapter<'py, T: PythonizeMappingType<'py>> { + _unnamed: T, + _marker: PhantomData<&'py ()>, +} + +impl<'py, T: PythonizeMappingType<'py>> PythonizeNamedMappingType<'py> + for PythonizeUnnamedMappingAdapter<'py, T> +{ + type Builder = >::Builder; + + fn builder(py: Python<'py>, len: usize, _name: &'static str) -> PyResult { + ::builder(py, Some(len)) + } + + fn push_field( + builder: &mut Self::Builder, + name: Bound<'py, PyString>, + value: Bound<'py, PyAny>, + ) -> PyResult<()> { + ::push_item(builder, name.into_any(), value) + } + + fn finish(builder: Self::Builder) -> PyResult> { + ::finish(builder) } } @@ -54,10 +142,24 @@ impl PythonizeListType for PyList { } } +impl PythonizeListType for PyTuple { + fn create_sequence( + py: Python, + elements: impl IntoIterator, + ) -> PyResult> + where + T: ToPyObject, + U: ExactSizeIterator, + { + Ok(PyTuple::new_bound(py, elements).into_sequence()) + } +} + pub struct PythonizeDefault; -impl PythonizeTypes for PythonizeDefault { +impl<'py> PythonizeTypes<'py> for PythonizeDefault { type Map = PyDict; + type NamedMap = PythonizeUnnamedMappingAdapter<'py, PyDict>; type List = PyList; } @@ -74,7 +176,7 @@ where pub fn pythonize_custom<'py, P, T>(py: Python<'py>, value: &T) -> Result> where T: ?Sized + Serialize, - P: PythonizeTypes, + P: PythonizeTypes<'py>, { value.serialize(Pythonizer::custom::

(py)) } @@ -116,32 +218,34 @@ pub struct PythonCollectionSerializer<'py, P> { #[doc(hidden)] pub struct PythonTupleVariantSerializer<'py, P> { + name: &'static str, variant: &'static str, inner: PythonCollectionSerializer<'py, P>, } #[doc(hidden)] -pub struct PythonStructVariantSerializer<'py, P: PythonizeTypes> { +pub struct PythonStructVariantSerializer<'py, P: PythonizeTypes<'py>> { + name: &'static str, variant: &'static str, - inner: PythonDictSerializer<'py, P>, + inner: PythonStructDictSerializer<'py, P>, } #[doc(hidden)] -pub struct PythonDictSerializer<'py, P: PythonizeTypes> { +pub struct PythonStructDictSerializer<'py, P: PythonizeTypes<'py>> { py: Python<'py>, - dict: Bound<'py, PyMapping>, + builder: >::Builder, _types: PhantomData

, } #[doc(hidden)] -pub struct PythonMapSerializer<'py, P: PythonizeTypes> { +pub struct PythonMapSerializer<'py, P: PythonizeTypes<'py>> { py: Python<'py>, - map: Bound<'py, PyMapping>, + builder: >::Builder, key: Option>, _types: PhantomData

, } -impl<'py, P: PythonizeTypes> ser::Serializer for Pythonizer<'py, P> { +impl<'py, P: PythonizeTypes<'py>> ser::Serializer for Pythonizer<'py, P> { type Ok = Bound<'py, PyAny>; type Error = PythonizeError; type SerializeSeq = PythonCollectionSerializer<'py, P>; @@ -149,7 +253,7 @@ impl<'py, P: PythonizeTypes> ser::Serializer for Pythonizer<'py, P> { type SerializeTupleStruct = PythonCollectionSerializer<'py, P>; type SerializeTupleVariant = PythonTupleVariantSerializer<'py, P>; type SerializeMap = PythonMapSerializer<'py, P>; - type SerializeStruct = PythonDictSerializer<'py, P>; + type SerializeStruct = PythonStructDictSerializer<'py, P>; type SerializeStructVariant = PythonStructVariantSerializer<'py, P>; fn serialize_bool(self, v: bool) -> Result> { @@ -249,7 +353,7 @@ impl<'py, P: PythonizeTypes> ser::Serializer for Pythonizer<'py, P> { fn serialize_newtype_variant( self, - _name: &'static str, + name: &'static str, _variant_index: u32, variant: &'static str, value: &T, @@ -257,9 +361,13 @@ impl<'py, P: PythonizeTypes> ser::Serializer for Pythonizer<'py, P> { where T: ?Sized + Serialize, { - let d = PyDict::new_bound(self.py); - d.set_item(variant, value.serialize(self)?)?; - Ok(d.into_any()) + let mut m = P::NamedMap::builder(self.py, 1, name)?; + P::NamedMap::push_field( + &mut m, + PyString::new_bound(self.py, variant), + value.serialize(self)?, + )?; + Ok(P::NamedMap::finish(m)?.into_any()) } fn serialize_seq(self, len: Option) -> Result> { @@ -292,18 +400,22 @@ impl<'py, P: PythonizeTypes> ser::Serializer for Pythonizer<'py, P> { fn serialize_tuple_variant( self, - _name: &'static str, + name: &'static str, _variant_index: u32, variant: &'static str, len: usize, ) -> Result> { let inner = self.serialize_tuple(len)?; - Ok(PythonTupleVariantSerializer { variant, inner }) + Ok(PythonTupleVariantSerializer { + name, + variant, + inner, + }) } - fn serialize_map(self, _len: Option) -> Result> { + fn serialize_map(self, len: Option) -> Result> { Ok(PythonMapSerializer { - map: P::Map::create_mapping(self.py)?, + builder: P::Map::builder(self.py, len)?, key: None, py: self.py, _types: PhantomData, @@ -312,35 +424,36 @@ impl<'py, P: PythonizeTypes> ser::Serializer for Pythonizer<'py, P> { fn serialize_struct( self, - _name: &'static str, - _len: usize, - ) -> Result> { - Ok(PythonDictSerializer { - dict: P::Map::create_mapping(self.py)?, + name: &'static str, + len: usize, + ) -> Result> { + Ok(PythonStructDictSerializer { py: self.py, + builder: P::NamedMap::builder(self.py, len, name)?, _types: PhantomData, }) } fn serialize_struct_variant( self, - _name: &'static str, + name: &'static str, _variant_index: u32, variant: &'static str, - _len: usize, + len: usize, ) -> Result> { Ok(PythonStructVariantSerializer { + name, variant, - inner: PythonDictSerializer { - dict: P::Map::create_mapping(self.py)?, + inner: PythonStructDictSerializer { py: self.py, + builder: P::NamedMap::builder(self.py, len, variant)?, _types: PhantomData, }, }) } } -impl<'py, P: PythonizeTypes> ser::SerializeSeq for PythonCollectionSerializer<'py, P> { +impl<'py, P: PythonizeTypes<'py>> ser::SerializeSeq for PythonCollectionSerializer<'py, P> { type Ok = Bound<'py, PyAny>; type Error = PythonizeError; @@ -358,7 +471,7 @@ impl<'py, P: PythonizeTypes> ser::SerializeSeq for PythonCollectionSerializer<'p } } -impl<'py, P: PythonizeTypes> ser::SerializeTuple for PythonCollectionSerializer<'py, P> { +impl<'py, P: PythonizeTypes<'py>> ser::SerializeTuple for PythonCollectionSerializer<'py, P> { type Ok = Bound<'py, PyAny>; type Error = PythonizeError; @@ -374,7 +487,7 @@ impl<'py, P: PythonizeTypes> ser::SerializeTuple for PythonCollectionSerializer< } } -impl<'py, P: PythonizeTypes> ser::SerializeTupleStruct for PythonCollectionSerializer<'py, P> { +impl<'py, P: PythonizeTypes<'py>> ser::SerializeTupleStruct for PythonCollectionSerializer<'py, P> { type Ok = Bound<'py, PyAny>; type Error = PythonizeError; @@ -390,7 +503,9 @@ impl<'py, P: PythonizeTypes> ser::SerializeTupleStruct for PythonCollectionSeria } } -impl<'py, P: PythonizeTypes> ser::SerializeTupleVariant for PythonTupleVariantSerializer<'py, P> { +impl<'py, P: PythonizeTypes<'py>> ser::SerializeTupleVariant + for PythonTupleVariantSerializer<'py, P> +{ type Ok = Bound<'py, PyAny>; type Error = PythonizeError; @@ -402,13 +517,17 @@ impl<'py, P: PythonizeTypes> ser::SerializeTupleVariant for PythonTupleVariantSe } fn end(self) -> Result> { - let d = PyDict::new_bound(self.inner.py); - d.set_item(self.variant, ser::SerializeTuple::end(self.inner)?)?; - Ok(d.into_any()) + let mut m = P::NamedMap::builder(self.inner.py, 1, self.name)?; + P::NamedMap::push_field( + &mut m, + PyString::new_bound(self.inner.py, self.variant), + ser::SerializeTuple::end(self.inner)?, + )?; + Ok(P::NamedMap::finish(m)?.into_any()) } } -impl<'py, P: PythonizeTypes> ser::SerializeMap for PythonMapSerializer<'py, P> { +impl<'py, P: PythonizeTypes<'py>> ser::SerializeMap for PythonMapSerializer<'py, P> { type Ok = Bound<'py, PyAny>; type Error = PythonizeError; @@ -424,7 +543,8 @@ impl<'py, P: PythonizeTypes> ser::SerializeMap for PythonMapSerializer<'py, P> { where T: ?Sized + Serialize, { - self.map.set_item( + P::Map::push_item( + &mut self.builder, self.key .take() .expect("serialize_value should always be called after serialize_key"), @@ -434,11 +554,11 @@ impl<'py, P: PythonizeTypes> ser::SerializeMap for PythonMapSerializer<'py, P> { } fn end(self) -> Result> { - Ok(self.map.into_any()) + Ok(P::Map::finish(self.builder)?.into_any()) } } -impl<'py, P: PythonizeTypes> ser::SerializeStruct for PythonDictSerializer<'py, P> { +impl<'py, P: PythonizeTypes<'py>> ser::SerializeStruct for PythonStructDictSerializer<'py, P> { type Ok = Bound<'py, PyAny>; type Error = PythonizeError; @@ -446,17 +566,22 @@ impl<'py, P: PythonizeTypes> ser::SerializeStruct for PythonDictSerializer<'py, where T: ?Sized + Serialize, { - Ok(self - .dict - .set_item(key, pythonize_custom::(self.py, value)?)?) + P::NamedMap::push_field( + &mut self.builder, + PyString::new_bound(self.py, key), + pythonize_custom::(self.py, value)?, + )?; + Ok(()) } fn end(self) -> Result> { - Ok(self.dict.into_any()) + Ok(P::NamedMap::finish(self.builder)?.into_any()) } } -impl<'py, P: PythonizeTypes> ser::SerializeStructVariant for PythonStructVariantSerializer<'py, P> { +impl<'py, P: PythonizeTypes<'py>> ser::SerializeStructVariant + for PythonStructVariantSerializer<'py, P> +{ type Ok = Bound<'py, PyAny>; type Error = PythonizeError; @@ -464,16 +589,23 @@ impl<'py, P: PythonizeTypes> ser::SerializeStructVariant for PythonStructVariant where T: ?Sized + Serialize, { - self.inner - .dict - .set_item(key, pythonize_custom::(self.inner.py, value)?)?; + P::NamedMap::push_field( + &mut self.inner.builder, + PyString::new_bound(self.inner.py, key), + pythonize_custom::(self.inner.py, value)?, + )?; Ok(()) } fn end(self) -> Result> { - let d = PyDict::new_bound(self.inner.py); - d.set_item(self.variant, self.inner.dict)?; - Ok(d.into_any()) + let v = P::NamedMap::finish(self.inner.builder)?; + let mut m = P::NamedMap::builder(self.inner.py, 1, self.name)?; + P::NamedMap::push_field( + &mut m, + PyString::new_bound(self.inner.py, self.variant), + v.into_any(), + )?; + Ok(P::NamedMap::finish(m)?.into_any()) } } diff --git a/tests/test_custom_types.rs b/tests/test_custom_types.rs index 9027c74..5f0084b 100644 --- a/tests/test_custom_types.rs +++ b/tests/test_custom_types.rs @@ -3,10 +3,11 @@ use std::collections::HashMap; use pyo3::{ exceptions::{PyIndexError, PyKeyError}, prelude::*, - types::{PyDict, PyList, PyMapping, PySequence}, + types::{PyDict, PyMapping, PySequence, PyTuple}, }; use pythonize::{ - depythonize, pythonize_custom, PythonizeDictType, PythonizeListType, PythonizeTypes, Pythonizer, + depythonize, pythonize_custom, PythonizeListType, PythonizeMappingType, + PythonizeNamedMappingType, PythonizeTypes, PythonizeUnnamedMappingAdapter, Pythonizer, }; use serde::Serialize; use serde_json::{json, Value}; @@ -55,8 +56,9 @@ impl PythonizeListType for CustomList { } struct PythonizeCustomList; -impl PythonizeTypes for PythonizeCustomList { +impl<'py> PythonizeTypes<'py> for PythonizeCustomList { type Map = PyDict; + type NamedMap = PythonizeUnnamedMappingAdapter<'py, PyDict>; type List = CustomList; } @@ -103,23 +105,36 @@ impl CustomDict { } } -impl PythonizeDictType for CustomDict { - fn create_mapping(py: Python) -> PyResult> { - let mapping = Bound::new( +impl<'py> PythonizeMappingType<'py> for CustomDict { + type Builder = Bound<'py, CustomDict>; + + fn builder(py: Python<'py>, len: Option) -> PyResult { + Bound::new( py, CustomDict { - items: HashMap::new(), + items: HashMap::with_capacity(len.unwrap_or(0)), }, - )? - .into_any(); - Ok(unsafe { mapping.downcast_into_unchecked() }) + ) + } + + fn push_item( + builder: &mut Self::Builder, + key: Bound<'py, PyAny>, + value: Bound<'py, PyAny>, + ) -> PyResult<()> { + unsafe { builder.downcast_unchecked::() }.set_item(key, value) + } + + fn finish(builder: Self::Builder) -> PyResult> { + Ok(unsafe { builder.into_any().downcast_into_unchecked() }) } } struct PythonizeCustomDict; -impl PythonizeTypes for PythonizeCustomDict { +impl<'py> PythonizeTypes<'py> for PythonizeCustomDict { type Map = CustomDict; - type List = PyList; + type NamedMap = PythonizeUnnamedMappingAdapter<'py, CustomDict>; + type List = PyTuple; } #[test] @@ -136,6 +151,19 @@ fn test_custom_dict() { }) } +#[test] +fn test_tuple() { + Python::with_gil(|py| { + PyMapping::register::(py).unwrap(); + let serialized = + pythonize_custom::(py, &json!([1, 2, 3, 4])).unwrap(); + assert!(serialized.is_instance_of::()); + + let deserialized: Value = depythonize(&serialized).unwrap(); + assert_eq!(deserialized, json!([1, 2, 3, 4])); + }) +} + #[test] fn test_pythonizer_can_be_created() { // https://github.com/davidhewitt/pythonize/pull/56 @@ -152,3 +180,102 @@ fn test_pythonizer_can_be_created() { .is_instance_of::()); }) } + +#[pyclass(mapping)] +struct NamedCustomDict { + name: String, + items: HashMap, +} + +#[pymethods] +impl NamedCustomDict { + fn __len__(&self) -> usize { + self.items.len() + } + + fn __getitem__(&self, key: String) -> PyResult { + self.items + .get(&key) + .cloned() + .ok_or_else(|| PyKeyError::new_err(key)) + } + + fn __setitem__(&mut self, key: String, value: PyObject) { + self.items.insert(key, value); + } + + fn keys(&self) -> Vec<&String> { + self.items.keys().collect() + } + + fn values(&self) -> Vec { + self.items.values().cloned().collect() + } +} + +impl<'py> PythonizeNamedMappingType<'py> for NamedCustomDict { + type Builder = Bound<'py, NamedCustomDict>; + + fn builder(py: Python<'py>, len: usize, name: &'static str) -> PyResult { + Bound::new( + py, + NamedCustomDict { + name: String::from(name), + items: HashMap::with_capacity(len), + }, + ) + } + + fn push_field( + builder: &mut Self::Builder, + name: Bound<'py, pyo3::types::PyString>, + value: Bound<'py, PyAny>, + ) -> PyResult<()> { + unsafe { builder.downcast_unchecked::() }.set_item(name, value) + } + + fn finish(builder: Self::Builder) -> PyResult> { + Ok(unsafe { builder.into_any().downcast_into_unchecked() }) + } +} + +struct PythonizeNamedCustomDict; +impl<'py> PythonizeTypes<'py> for PythonizeNamedCustomDict { + type Map = CustomDict; + type NamedMap = NamedCustomDict; + type List = PyTuple; +} + +#[derive(Serialize)] +struct Struct { + hello: u8, + world: i8, +} + +#[test] +fn test_custom_unnamed_dict() { + Python::with_gil(|py| { + PyMapping::register::(py).unwrap(); + let serialized = + pythonize_custom::(py, &Struct { hello: 1, world: 2 }).unwrap(); + assert!(serialized.is_instance_of::()); + + let deserialized: Value = depythonize(&serialized).unwrap(); + assert_eq!(deserialized, json!({ "hello": 1, "world": 2 })); + }) +} + +#[test] +fn test_custom_named_dict() { + Python::with_gil(|py| { + PyMapping::register::(py).unwrap(); + let serialized = + pythonize_custom::(py, &Struct { hello: 1, world: 2 }) + .unwrap(); + let named: Bound = serialized.extract().unwrap(); + assert_eq!(named.borrow().name, "Struct"); + + let deserialized: Value = depythonize(&serialized).unwrap(); + assert_eq!(deserialized, json!({ "hello": 1, "world": 2 })); + }) +} diff --git a/tests/test_with_serde_path_to_err.rs b/tests/test_with_serde_path_to_err.rs index acb34d7..1321c2b 100644 --- a/tests/test_with_serde_path_to_err.rs +++ b/tests/test_with_serde_path_to_err.rs @@ -4,7 +4,7 @@ use pyo3::{ prelude::*, types::{PyDict, PyList}, }; -use pythonize::PythonizeTypes; +use pythonize::{PythonizeTypes, PythonizeUnnamedMappingAdapter}; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] @@ -13,8 +13,9 @@ struct Root { root_map: BTreeMap>, } -impl PythonizeTypes for Root { +impl<'py, T> PythonizeTypes<'py> for Root { type Map = PyDict; + type NamedMap = PythonizeUnnamedMappingAdapter<'py, PyDict>; type List = PyList; }