Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "pythonize"
version = "0.25.0"
authors = ["David Hewitt <[email protected]>"]
edition = "2021"
rust-version = "1.63"
rust-version = "1.65"
license = "MIT"
description = "Serde Serializer & Deserializer from Rust <--> Python, backed by PyO3."
homepage = "https://github.com/davidhewitt/pythonize"
Expand Down
114 changes: 56 additions & 58 deletions src/ser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,44 +9,46 @@ 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 PythonizeMappingType<'py> {
pub trait PythonizeMappingType {
/// Builder type for Python mappings
type Builder;
type Builder<'py>: 'py;

/// Create a builder for a Python mapping
fn builder(py: Python<'py>, len: Option<usize>) -> PyResult<Self::Builder>;
fn builder<'py>(py: Python<'py>, len: Option<usize>) -> PyResult<Self::Builder<'py>>;

/// Adds the key-value item to the mapping being built
fn push_item(
builder: &mut Self::Builder,
fn push_item<'py>(
builder: &mut Self::Builder<'py>,
key: Bound<'py, PyAny>,
value: Bound<'py, PyAny>,
) -> PyResult<()>;

/// Build the Python mapping
fn finish(builder: Self::Builder) -> PyResult<Bound<'py, PyMapping>>;
fn finish<'py>(builder: Self::Builder<'py>) -> PyResult<Bound<'py, PyMapping>>;
}

// 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> {
pub trait PythonizeNamedMappingType {
/// Builder type for Python mappings with a name
type Builder;
type Builder<'py>: 'py;

/// Create a builder for a Python mapping with a name
fn builder(py: Python<'py>, len: usize, name: &'static str) -> PyResult<Self::Builder>;
fn builder<'py>(
py: Python<'py>,
len: usize,
name: &'static str,
) -> PyResult<Self::Builder<'py>>;

/// Adds the field to the named mapping being built
fn push_field(
builder: &mut Self::Builder,
fn push_field<'py>(
builder: &mut Self::Builder<'py>,
name: Bound<'py, PyString>,
value: Bound<'py, PyAny>,
) -> PyResult<()>;

/// Build the Python mapping
fn finish(builder: Self::Builder) -> PyResult<Bound<'py, PyMapping>>;
fn finish<'py>(builder: Self::Builder<'py>) -> PyResult<Bound<'py, PyMapping>>;
}

/// Trait for types which can represent a Python sequence
Expand All @@ -61,33 +63,32 @@ pub trait PythonizeListType: Sized {
U: ExactSizeIterator<Item = T>;
}

// TODO: remove 'py lifetime once GATs are available in MSRV
/// Custom types for serialization
pub trait PythonizeTypes<'py> {
pub trait PythonizeTypes {
/// Python map type (should be representable as python mapping)
type Map: PythonizeMappingType<'py>;
type Map: PythonizeMappingType;
/// Python (struct-like) named map type (should be representable as python mapping)
type NamedMap: PythonizeNamedMappingType<'py>;
type NamedMap: PythonizeNamedMappingType;
/// Python sequence type (should be representable as python sequence)
type List: PythonizeListType;
}

impl<'py> PythonizeMappingType<'py> for PyDict {
type Builder = Bound<'py, Self>;
impl PythonizeMappingType for PyDict {
type Builder<'py> = Bound<'py, Self>;

fn builder(py: Python<'py>, _len: Option<usize>) -> PyResult<Self::Builder> {
fn builder<'py>(py: Python<'py>, _len: Option<usize>) -> PyResult<Self::Builder<'py>> {
Ok(Self::new(py))
}

fn push_item(
builder: &mut Self::Builder,
fn push_item<'py>(
builder: &mut Self::Builder<'py>,
key: Bound<'py, PyAny>,
value: Bound<'py, PyAny>,
) -> PyResult<()> {
builder.set_item(key, value)
}

fn finish(builder: Self::Builder) -> PyResult<Bound<'py, PyMapping>> {
fn finish<'py>(builder: Self::Builder<'py>) -> PyResult<Bound<'py, PyMapping>> {
Ok(builder.into_mapping())
}
}
Expand All @@ -99,30 +100,31 @@ impl<'py> PythonizeMappingType<'py> for PyDict {
/// 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>> {
pub struct PythonizeUnnamedMappingAdapter<T: PythonizeMappingType> {
_unnamed: T,
_marker: PhantomData<&'py ()>,
}

impl<'py, T: PythonizeMappingType<'py>> PythonizeNamedMappingType<'py>
for PythonizeUnnamedMappingAdapter<'py, T>
{
type Builder = <T as PythonizeMappingType<'py>>::Builder;
impl<T: PythonizeMappingType> PythonizeNamedMappingType for PythonizeUnnamedMappingAdapter<T> {
type Builder<'py> = T::Builder<'py>;

fn builder(py: Python<'py>, len: usize, _name: &'static str) -> PyResult<Self::Builder> {
<T as PythonizeMappingType>::builder(py, Some(len))
fn builder<'py>(
py: Python<'py>,
len: usize,
_name: &'static str,
) -> PyResult<Self::Builder<'py>> {
T::builder(py, Some(len))
}

fn push_field(
builder: &mut Self::Builder,
fn push_field<'py>(
builder: &mut Self::Builder<'py>,
name: Bound<'py, PyString>,
value: Bound<'py, PyAny>,
) -> PyResult<()> {
<T as PythonizeMappingType>::push_item(builder, name.into_any(), value)
T::push_item(builder, name.into_any(), value)
}

fn finish(builder: Self::Builder) -> PyResult<Bound<'py, PyMapping>> {
<T as PythonizeMappingType>::finish(builder)
fn finish<'py>(builder: Self::Builder<'py>) -> PyResult<Bound<'py, PyMapping>> {
T::finish(builder)
}
}

Expand Down Expand Up @@ -154,9 +156,9 @@ impl PythonizeListType for PyTuple {

pub struct PythonizeDefault;

impl<'py> PythonizeTypes<'py> for PythonizeDefault {
impl PythonizeTypes for PythonizeDefault {
type Map = PyDict;
type NamedMap = PythonizeUnnamedMappingAdapter<'py, PyDict>;
type NamedMap = PythonizeUnnamedMappingAdapter<PyDict>;
type List = PyList;
}

Expand All @@ -173,7 +175,7 @@ where
pub fn pythonize_custom<'py, P, T>(py: Python<'py>, value: &T) -> Result<Bound<'py, PyAny>>
where
T: ?Sized + Serialize,
P: PythonizeTypes<'py>,
P: PythonizeTypes,
{
value.serialize(Pythonizer::custom::<P>(py))
}
Expand Down Expand Up @@ -221,28 +223,28 @@ pub struct PythonTupleVariantSerializer<'py, P> {
}

#[doc(hidden)]
pub struct PythonStructVariantSerializer<'py, P: PythonizeTypes<'py>> {
pub struct PythonStructVariantSerializer<'py, P: PythonizeTypes> {
name: &'static str,
variant: &'static str,
inner: PythonStructDictSerializer<'py, P>,
}

#[doc(hidden)]
pub struct PythonStructDictSerializer<'py, P: PythonizeTypes<'py>> {
pub struct PythonStructDictSerializer<'py, P: PythonizeTypes> {
py: Python<'py>,
builder: <P::NamedMap as PythonizeNamedMappingType<'py>>::Builder,
builder: <P::NamedMap as PythonizeNamedMappingType>::Builder<'py>,
_types: PhantomData<P>,
}

#[doc(hidden)]
pub struct PythonMapSerializer<'py, P: PythonizeTypes<'py>> {
pub struct PythonMapSerializer<'py, P: PythonizeTypes> {
py: Python<'py>,
builder: <P::Map as PythonizeMappingType<'py>>::Builder,
builder: <P::Map as PythonizeMappingType>::Builder<'py>,
key: Option<Bound<'py, PyAny>>,
_types: PhantomData<P>,
}

impl<'py, P: PythonizeTypes<'py>> Pythonizer<'py, P> {
impl<'py, P: PythonizeTypes> Pythonizer<'py, P> {
/// The default implementation for serialisation functions.
#[inline]
fn serialise_default<T>(self, v: T) -> Result<Bound<'py, PyAny>>
Expand All @@ -256,7 +258,7 @@ impl<'py, P: PythonizeTypes<'py>> Pythonizer<'py, P> {
}
}

impl<'py, P: PythonizeTypes<'py>> ser::Serializer for Pythonizer<'py, P> {
impl<'py, P: PythonizeTypes> ser::Serializer for Pythonizer<'py, P> {
type Ok = Bound<'py, PyAny>;
type Error = PythonizeError;
type SerializeSeq = PythonCollectionSerializer<'py, P>;
Expand Down Expand Up @@ -464,7 +466,7 @@ impl<'py, P: PythonizeTypes<'py>> ser::Serializer for Pythonizer<'py, P> {
}
}

impl<'py, P: PythonizeTypes<'py>> ser::SerializeSeq for PythonCollectionSerializer<'py, P> {
impl<'py, P: PythonizeTypes> ser::SerializeSeq for PythonCollectionSerializer<'py, P> {
type Ok = Bound<'py, PyAny>;
type Error = PythonizeError;

Expand All @@ -482,7 +484,7 @@ impl<'py, P: PythonizeTypes<'py>> ser::SerializeSeq for PythonCollectionSerializ
}
}

impl<'py, P: PythonizeTypes<'py>> ser::SerializeTuple for PythonCollectionSerializer<'py, P> {
impl<'py, P: PythonizeTypes> ser::SerializeTuple for PythonCollectionSerializer<'py, P> {
type Ok = Bound<'py, PyAny>;
type Error = PythonizeError;

Expand All @@ -498,7 +500,7 @@ impl<'py, P: PythonizeTypes<'py>> ser::SerializeTuple for PythonCollectionSerial
}
}

impl<'py, P: PythonizeTypes<'py>> ser::SerializeTupleStruct for PythonCollectionSerializer<'py, P> {
impl<'py, P: PythonizeTypes> ser::SerializeTupleStruct for PythonCollectionSerializer<'py, P> {
type Ok = Bound<'py, PyAny>;
type Error = PythonizeError;

Expand All @@ -514,9 +516,7 @@ impl<'py, P: PythonizeTypes<'py>> ser::SerializeTupleStruct for PythonCollection
}
}

impl<'py, P: PythonizeTypes<'py>> ser::SerializeTupleVariant
for PythonTupleVariantSerializer<'py, P>
{
impl<'py, P: PythonizeTypes> ser::SerializeTupleVariant for PythonTupleVariantSerializer<'py, P> {
type Ok = Bound<'py, PyAny>;
type Error = PythonizeError;

Expand All @@ -538,7 +538,7 @@ impl<'py, P: PythonizeTypes<'py>> ser::SerializeTupleVariant
}
}

impl<'py, P: PythonizeTypes<'py>> ser::SerializeMap for PythonMapSerializer<'py, P> {
impl<'py, P: PythonizeTypes> ser::SerializeMap for PythonMapSerializer<'py, P> {
type Ok = Bound<'py, PyAny>;
type Error = PythonizeError;

Expand Down Expand Up @@ -569,7 +569,7 @@ impl<'py, P: PythonizeTypes<'py>> ser::SerializeMap for PythonMapSerializer<'py,
}
}

impl<'py, P: PythonizeTypes<'py>> ser::SerializeStruct for PythonStructDictSerializer<'py, P> {
impl<'py, P: PythonizeTypes> ser::SerializeStruct for PythonStructDictSerializer<'py, P> {
type Ok = Bound<'py, PyAny>;
type Error = PythonizeError;

Expand All @@ -590,9 +590,7 @@ impl<'py, P: PythonizeTypes<'py>> ser::SerializeStruct for PythonStructDictSeria
}
}

impl<'py, P: PythonizeTypes<'py>> ser::SerializeStructVariant
for PythonStructVariantSerializer<'py, P>
{
impl<'py, P: PythonizeTypes> ser::SerializeStructVariant for PythonStructVariantSerializer<'py, P> {
type Ok = Bound<'py, PyAny>;
type Error = PythonizeError;

Expand Down
38 changes: 21 additions & 17 deletions tests/test_custom_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ impl PythonizeListType for CustomList {
}

struct PythonizeCustomList;
impl<'py> PythonizeTypes<'py> for PythonizeCustomList {
impl<'py> PythonizeTypes for PythonizeCustomList {
type Map = PyDict;
type NamedMap = PythonizeUnnamedMappingAdapter<'py, PyDict>;
type NamedMap = PythonizeUnnamedMappingAdapter<PyDict>;
type List = CustomList;
}

Expand Down Expand Up @@ -107,10 +107,10 @@ impl CustomDict {
}
}

impl<'py> PythonizeMappingType<'py> for CustomDict {
type Builder = Bound<'py, CustomDict>;
impl PythonizeMappingType for CustomDict {
type Builder<'py> = Bound<'py, CustomDict>;

fn builder(py: Python<'py>, len: Option<usize>) -> PyResult<Self::Builder> {
fn builder<'py>(py: Python<'py>, len: Option<usize>) -> PyResult<Self::Builder<'py>> {
Bound::new(
py,
CustomDict {
Expand All @@ -119,23 +119,23 @@ impl<'py> PythonizeMappingType<'py> for CustomDict {
)
}

fn push_item(
builder: &mut Self::Builder,
fn push_item<'py>(
builder: &mut Self::Builder<'py>,
key: Bound<'py, PyAny>,
value: Bound<'py, PyAny>,
) -> PyResult<()> {
unsafe { builder.downcast_unchecked::<PyMapping>() }.set_item(key, value)
}

fn finish(builder: Self::Builder) -> PyResult<Bound<'py, PyMapping>> {
fn finish<'py>(builder: Self::Builder<'py>) -> PyResult<Bound<'py, PyMapping>> {
Ok(unsafe { builder.into_any().downcast_into_unchecked() })
}
}

struct PythonizeCustomDict;
impl<'py> PythonizeTypes<'py> for PythonizeCustomDict {
impl<'py> PythonizeTypes for PythonizeCustomDict {
type Map = CustomDict;
type NamedMap = PythonizeUnnamedMappingAdapter<'py, CustomDict>;
type NamedMap = PythonizeUnnamedMappingAdapter<CustomDict>;
type List = PyTuple;
}

Expand Down Expand Up @@ -215,10 +215,14 @@ impl NamedCustomDict {
}
}

impl<'py> PythonizeNamedMappingType<'py> for NamedCustomDict {
type Builder = Bound<'py, NamedCustomDict>;
impl PythonizeNamedMappingType for NamedCustomDict {
type Builder<'py> = Bound<'py, NamedCustomDict>;

fn builder(py: Python<'py>, len: usize, name: &'static str) -> PyResult<Self::Builder> {
fn builder<'py>(
py: Python<'py>,
len: usize,
name: &'static str,
) -> PyResult<Self::Builder<'py>> {
Bound::new(
py,
NamedCustomDict {
Expand All @@ -228,21 +232,21 @@ impl<'py> PythonizeNamedMappingType<'py> for NamedCustomDict {
)
}

fn push_field(
builder: &mut Self::Builder,
fn push_field<'py>(
builder: &mut Self::Builder<'py>,
name: Bound<'py, pyo3::types::PyString>,
value: Bound<'py, PyAny>,
) -> PyResult<()> {
unsafe { builder.downcast_unchecked::<PyMapping>() }.set_item(name, value)
}

fn finish(builder: Self::Builder) -> PyResult<Bound<'py, PyMapping>> {
fn finish<'py>(builder: Self::Builder<'py>) -> PyResult<Bound<'py, PyMapping>> {
Ok(unsafe { builder.into_any().downcast_into_unchecked() })
}
}

struct PythonizeNamedCustomDict;
impl<'py> PythonizeTypes<'py> for PythonizeNamedCustomDict {
impl<'py> PythonizeTypes for PythonizeNamedCustomDict {
type Map = CustomDict;
type NamedMap = NamedCustomDict;
type List = PyTuple;
Expand Down
Loading
Loading