Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support serialising to named mappings #62

Merged
merged 14 commits into from
Aug 7, 2024

Conversation

juntyr
Copy link
Contributor

@juntyr juntyr commented May 18, 2024

This PR includes four changes:

  • Implement PythonizeListType for PyTuple, a small convenience that cannot be achieved outside the crate
  • Add support for prebuilding mappings during serialisation, which delays the collection of mapping key-value pairs until all are known
  • Add support for named mappings during serialisation, which adds a tiny default-implemented helper trait method to pass forward the struct / tuple / enum name to the mapping type
  • Allow deserializing an enum from any mapping instead of just dicts

I have found them to be very useful to serialise to and from class-like Python types (e.g. namedtuple which keep more type information around).

@juntyr
Copy link
Contributor Author

juntyr commented May 27, 2024

@davidhewitt what are your thoughts on this?

@juntyr
Copy link
Contributor Author

juntyr commented Jun 13, 2024

@davidhewitt I apologise for pinging again - I'm hoping that this PR could move forward soon-ish as I'm working on publishing a crate of mine (which would need a feature similar to this) for a publication.

@MarshalX
Copy link

@juntyr Perhaps he unfollowed this repository or pull request. Maybe try pinging him on X, for example, as he has a few social links on his GitHub profile

@davidhewitt
Copy link
Owner

Hi @juntyr sorry for the delay! I have seen this PR but haven't had a chance to investigate yet. Please hold on and I'll do my best to get to this soon - I'm trying to wrap up PyO3 0.22 and then let's aim to ship this in pythonize 0.22 👍

@juntyr
Copy link
Contributor Author

juntyr commented Jun 26, 2024

@davidhewitt Congrats on shipping pyo3 v0.22, I'm excited to start using it!

I'm looking forward to collaborating with you to push this PR forward :)

@juntyr
Copy link
Contributor Author

juntyr commented Aug 3, 2024

The merge conflict should now be resolved again :)

@juntyr
Copy link
Contributor Author

juntyr commented Aug 3, 2024

Once #66 is merged, I will very quickly rebase this PR again so that we can hopefully still get it into v0.22

Copy link
Owner

@davidhewitt davidhewitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks very much for this and sorry for the slow reply. Yes let's get this into 0.22. Can you please add CHANGELOG entries too?

I think overall I'm 👍 for this and understand the motivation, however want to just discuss a couple of the design choices especially with the intention to avoid performance overhead.

src/ser.rs Outdated
Comment on lines 13 to 49
pub trait PythonizeDictType {
/// Constructor
fn create_mapping(py: Python) -> PyResult<Bound<PyMapping>>;

/// Constructor
fn create_mapping_with_items<
K: ToPyObject,
V: ToPyObject,
U: ExactSizeIterator<Item = (K, V)>,
>(
py: Python,
items: impl IntoIterator<Item = (K, V), IntoIter = U>,
) -> PyResult<Bound<PyMapping>> {
let mapping = Self::create_mapping(py)?;

for (key, value) in items {
mapping.set_item(key, value)?;
}

Ok(mapping)
}

/// Constructor, allows the mappings to be named
fn create_mapping_with_items_name<
'py,
K: ToPyObject,
V: ToPyObject,
U: ExactSizeIterator<Item = (K, V)>,
>(
py: Python<'py>,
name: &str,
items: impl IntoIterator<Item = (K, V), IntoIter = U>,
) -> PyResult<Bound<'py, PyMapping>> {
let _name = name;
Self::create_mapping_with_items(py, items)
}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a couple of thoughts here:

  • I wonder, is the split between create_mapping_with_items and create_mapping_with_items_name a bit confusing? Should we potentially instead model this by having PythonizeTypes have a NamedMap type to handle the named cases?
  • I slightly worry about the performance regression here by adding an intermediate Vec. I think the way to solve this would be to replace create_mapping with create_builder(len), which returns an associated type implementing trait MappingBuilder { fn push_item(k, v); fn finish() -> Bound<'py, PyMapping> }. For the default dict implementation, the builder is then just a thin wrapper around the dict, in the named tuple case the builder can do the pre-collecting into a Vec.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I implemented both suggestions. I generally like both, even though they are more infective than the previous change. Perhaps you have some suggestions for how to further improve them?

Copy link

codecov bot commented Aug 4, 2024

Codecov Report

Attention: Patch coverage is 75.25773% with 24 lines in your changes missing coverage. Please review.

Project coverage is 83.41%. Comparing base (6c2c3f2) to head (7c18b9f).

Files Patch % Lines
src/ser.rs 77.77% 4 Missing and 16 partials ⚠️
src/de.rs 42.85% 0 Missing and 4 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #62      +/-   ##
==========================================
- Coverage   83.83%   83.41%   -0.42%     
==========================================
  Files           3        3              
  Lines        1169     1224      +55     
  Branches     1169     1224      +55     
==========================================
+ Hits          980     1021      +41     
- Misses        140      144       +4     
- Partials       49       59      +10     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@juntyr
Copy link
Contributor Author

juntyr commented Aug 4, 2024

GATs were only stabilised in 1.65 but pythonize uses 1.63 … so the builder can’t use a GAT. Perhaps I can inline the builder methods into the main trait itself (hopefully while keeping Map = PyDict)

@juntyr
Copy link
Contributor Author

juntyr commented Aug 4, 2024

I also tried to add a small convenience wrapper type PythonizeUnnamedMappingWrapper so that

  1. it is more explicit that type NamedMap = PythonizeUnnamedMappingWrapper<PyDict> discards the name
  2. it is reusable for others so you don't need to copy-past two trait impls

Copy link
Owner

@davidhewitt davidhewitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking great, thanks for the repeated rounds and sorry again for the delays!

Just a few finishing touches...

tests/test_custom_types.rs Outdated Show resolved Hide resolved
src/ser.rs Outdated Show resolved Hide resolved
src/ser.rs Outdated Show resolved Hide resolved
src/ser.rs Outdated Show resolved Hide resolved
src/ser.rs Outdated Show resolved Hide resolved
Copy link
Owner

@davidhewitt davidhewitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks perfect, thanks! One final small thing to check, and once we've agreed on that, let's merge 👍

src/ser.rs Outdated Show resolved Hide resolved
The adapter is anyways constructed through the builder method

Co-authored-by: David Hewitt <mail@davidhewitt.dev>
@juntyr
Copy link
Contributor Author

juntyr commented Aug 7, 2024

Thanks for your review @davidhewitt!

@juntyr
Copy link
Contributor Author

juntyr commented Aug 7, 2024

I’m unsure if the coverage can be easily improved as all new misses seem to be partial and short-circuiting related

@davidhewitt
Copy link
Owner

Agreed, I'm very happy to call this as good as it gets! Thanks again 🚀

@davidhewitt davidhewitt merged commit d7f54f1 into davidhewitt:main Aug 7, 2024
20 of 22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants