Add support for mappingproxy objects#2435
Conversation
davidhewitt
left a comment
There was a problem hiding this comment.
Thanks very much for implementing this!
It's overall very good. To make sure we get this API right when we merge, I've placed quite a few comments around the place with some things that need changing and then also some places where we can discuss the design a little to ensure we make the right choices.
src/types/dict.rs
Outdated
| let mappingproxy = <PyMappingProxy as PyTryFrom>::try_from(ob)?; | ||
| mappingproxy.copy()?.extract() |
There was a problem hiding this comment.
This .copy() call is sub-optimal as it will create a whole new copy as a Python dict first. Instead, we should just create the HashMap from iterating the mapping proxy.
Maybe to make the code reusable we could refactor into a helper something like this:
fn mappingproxy_to_items_sequence<'py, K: FromPyObject<'py>, V: FromPyObject<'py>>(mappingproxy: &'py PyMappingProxy) -> impl IntoIterator<Item = PyResult<(K, V)> {
mappingproxy.iter()
.map(|(key, value)| Ok((key.extract()?, value.extract()?)))
}and then creating the HashMap should just become
mappingproxy_to_items_sequence(mappingproxy).collect()?
src/types/dict.rs
Outdated
| } | ||
| Ok(ret) | ||
| } | ||
| Err(_) => { |
There was a problem hiding this comment.
If the object is not a mappingproxy it would be nice to return this Err(_) from the dict conversion, to preserve existing UX.
| /// Creates a new mappingproxy from the sequence given. | ||
| /// | ||
| /// The sequence must consist of `(PyObject, PyObject)`. | ||
| /// | ||
| /// Returns an error on invalid input. In the case of key collisions, | ||
| /// this keeps the last entry seen. | ||
| #[cfg(not(PyPy))] | ||
| pub fn from_sequence(py: Python<'_>, seq: PyObject) -> PyResult<&PyMappingProxy> { | ||
| unsafe { | ||
| let dict = py.from_owned_ptr::<PyDict>(PyDict::from_sequence(py, seq)?.as_ptr()); | ||
| py.from_owned_ptr_or_err(ffi::PyDictProxy_New(dict.as_ptr())) | ||
| } | ||
| } |
There was a problem hiding this comment.
I guess this is just a direct copy of the PyDict equivalent? Depending on what we decide for PyMappingProxy::new, this may or may not be necessary.
There was a problem hiding this comment.
I'm thinking that this isn't necessary, users will just be able to call PyMappingProxy::new(PyDict::from_sequence(...).as_mapping())
| /// Returns a new mappingproxy that contains the same key-value pairs as self. | ||
| /// | ||
| /// This is equivalent to the Python expression `self.copy()`. | ||
| pub fn copy(&self) -> PyResult<&PyDict> { |
There was a problem hiding this comment.
I don't think &PyDict is correct, looks like it calls copy on the inner mapping:
So the return type should either be &PyAny or (maybe) &PyMapping.
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { |
| @@ -0,0 +1,86 @@ | |||
| use std::collections::HashMap; | |||
There was a problem hiding this comment.
I'm curious why these tests weren't just written as unit tests too?
Co-authored-by: David Hewitt <1939362+davidhewitt@users.noreply.github.com>
Co-authored-by: David Hewitt <1939362+davidhewitt@users.noreply.github.com>
Co-authored-by: David Hewitt <1939362+davidhewitt@users.noreply.github.com>
Co-authored-by: David Hewitt <1939362+davidhewitt@users.noreply.github.com>
Signed-off-by: Sam Ezeh <sam.z.ezeh@gmail.com>
|
Hi @dignissimus, just wondering what the status of this is and whether you're waiting for any input from me? No rush at all, just browsing our backlog of PRs. |
Hey! For the elements parameter, I'm looking for input on the type? When writing it, I saw the benefit of |
|
Also, some of the builds fail (emscripten) for reasons outside of testing, if I remember correctly it might be to do with imports? I'm not able to fix it on my own without help. I'd need guidance to amend it or have somebody else add a commit to the PR. |
davidhewitt
left a comment
There was a problem hiding this comment.
Cool - added a bunch of suggestions which should help you continue moving!
| @@ -0,0 +1,86 @@ | |||
| use std::collections::HashMap; | |||
| use pyo3::prelude::Python; | ||
| use pyo3::types::{IntoPyMappingProxy, PyInt, PyString}; | ||
|
|
||
| const LEN: usize = 10_000_000; |
There was a problem hiding this comment.
I think reducing this size may fix emscripten; I think it's simply asking for an allocation larger than emscripten can support.
| /// Returns a new mappingproxy that contains the same key-value pairs as self. | ||
| /// | ||
| /// This is equivalent to the Python expression `self.copy()`. | ||
| pub fn copy(&self) -> PyResult<&PyDict> { |
|
|
||
| impl PyMappingProxy { | ||
| /// Creates a mappingproxy from an object. | ||
| pub fn new(py: Python<'_>, elements: impl IntoPyDict) -> PyResult<&'_ PyMappingProxy> { |
There was a problem hiding this comment.
I think we should go with &PyMapping, we can backwards-compatibly widen to &PyAny if that turns out to make sense.
| } | ||
| Err(msg) => { | ||
| if let Ok(mappingproxy) = <PyMappingProxy as PyTryFrom>::try_from(ob) { | ||
| mappingproxy.copy()?.extract() |
There was a problem hiding this comment.
I would still like to avoid this copy by iterating the mappingproxy.
| Ok(ret) | ||
| } | ||
| Err(_) => { | ||
| let mappingproxy = <PyMappingProxy as PyTryFrom>::try_from(ob)?; |
There was a problem hiding this comment.
Same comment r.e. copy here, and that hashbrown / indexmap implementations still needed
| /// Creates a new mappingproxy from the sequence given. | ||
| /// | ||
| /// The sequence must consist of `(PyObject, PyObject)`. | ||
| /// | ||
| /// Returns an error on invalid input. In the case of key collisions, | ||
| /// this keeps the last entry seen. | ||
| #[cfg(not(PyPy))] | ||
| pub fn from_sequence(py: Python<'_>, seq: PyObject) -> PyResult<&PyMappingProxy> { | ||
| unsafe { | ||
| let dict = py.from_owned_ptr::<PyDict>(PyDict::from_sequence(py, seq)?.as_ptr()); | ||
| py.from_owned_ptr_or_err(ffi::PyDictProxy_New(dict.as_ptr())) | ||
| } | ||
| } |
There was a problem hiding this comment.
I'm thinking that this isn't necessary, users will just be able to call PyMappingProxy::new(PyDict::from_sequence(...).as_mapping())
Thanks! I'll make the changes this Saturday |
|
Hello @dignissimus Thank you for contributing to PyO3! The project is undergoing changes on its licensing model to better align with the rest of the Rust crates ecosystem. Before your work gets merged, please express your consent by leaving a comment on this pull request. Thank you! |
|
Has there been any update on this? |
|
No progress since what is seen here, it's also not a priority feature for me so unlikely to move any time soon without further external contributions. |
|
Superseded by #3521 |
This pull request adds support for mappingproxy objects.
Resolves #2108