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

feat: Support for mappingproxy objects #3521

Closed
wants to merge 36 commits into from

Conversation

michaelhly
Copy link

Adds support for mappingproxy objects. Continuation of #2435. Resolves #2108.

dignissimus and others added 30 commits June 2, 2022 12:09
@michaelhly michaelhly changed the title feat: Support mappingproxy feat: Support for mappingproxy objects Oct 16, 2023
@michaelhly michaelhly force-pushed the support-mappingproxy branch 2 times, most recently from 85b554f to 709ee4a Compare October 16, 2023 05:26
Copy link
Member

@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.

Thank you for this implementation, and sorry it's taken me a few days to reply!

Overall looks very good, but I would like to see some changes to propagate errors rather than introduce panics. The only case where I'm not 100% sure is in the iterator, but there I still think it's better to err on the side of fallibility. Would be interesting to see what other maintainers think.

Also, would you be willing to help us prepare this new API in a forwards-compatible way for #3382? I have started some other PRs like #3535 and #3531 which hopefully show you what I'm doing at the moment, we could add PyMappingProxyMethods here in the same way. It would be really interesting to get your opinion as a user how this new API feels, though I confess that it's still early days and unrefined so you might be in for a bumpy ride 🙏

Comment on lines +37 to +39
pub fn is_empty(&self) -> bool {
self.len().unwrap_or_default() == 0
}
Copy link
Member

Choose a reason for hiding this comment

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

I think we can't justify this default for users and should propagate the error:

Suggested change
pub fn is_empty(&self) -> bool {
self.len().unwrap_or_default() == 0
}
pub fn is_empty(&self) -> PyResult<bool> {
self.len().map(|len| len == 0)
}

Comment on lines +46 to +51
pub fn get_item<K>(&self, key: K) -> Option<&PyAny>
where
K: ToPyObject,
{
PyAny::get_item(self, key).ok()
}
Copy link
Member

Choose a reason for hiding this comment

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

Given the recent changes around PyDict::get_item, similarly we should not suppress the error here. As there's no clear advantage of this over PyAny::get_item I guess we just remove this?

Comment on lines +56 to +62
pub fn keys(&self) -> &PyList {
unsafe {
PySequence::try_from_unchecked(self.call_method0("keys").unwrap())
.to_list()
.unwrap()
}
}
Copy link
Member

Choose a reason for hiding this comment

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

The unwraps are not ok here, let's have PyResult<&PyList> as the return type please!

/// Returns a list of mappingproxy values.
///
/// This is equivalent to the Python expression `list(mappingproxy.values())`.
pub fn values(&self) -> &PyList {
Copy link
Member

Choose a reason for hiding this comment

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

Same here PyResult<&PyList>

/// Returns a list of mappingproxy items.
///
/// This is equivalent to the Python expression `list(mappingproxy.items())`.
pub fn items(&self) -> &PyList {
Copy link
Member

Choose a reason for hiding this comment

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

Same here PyResult<&PyList>

Comment on lines +127 to +144
/// Conversion trait that allows a sequence of tuples to be converted into `PyMappingProxy`
/// Primary use case for this trait is `call` and `call_method` methods as keywords argument.
pub trait IntoPyMappingProxy<'py> {
/// Converts self into a `PyMappingProxy` object pointer. Whether pointer owned or borrowed
/// depends on implementation.
fn into_py_mappingproxy(self, py: Python<'py>) -> PyResult<&'py PyMappingProxy>;
}

impl<'py, T, I> IntoPyMappingProxy<'py> for I
where
T: PyDictItem,
I: IntoIterator<Item = T>,
{
fn into_py_mappingproxy(self, py: Python<'py>) -> PyResult<&'py PyMappingProxy> {
let dict = self.into_py_dict(py);
PyMappingProxy::new(py, dict.as_mapping())
}
}
Copy link
Member

Choose a reason for hiding this comment

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

This is a cute trait, but I'm not really convinced this is necessary given the user can just do PyMappingProxy::new(py, v.into_py_dict(py).as_mapping()). Have you got a use case in mind?

}

impl<'py> Iterator for PyMappingProxyIterator<'py> {
type Item = (&'py PyAny, &'py PyAny);
Copy link
Member

Choose a reason for hiding this comment

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

I guess iteration here should probably be fallible? Overall I see why you added this and I think it makes sense to iterate pairs given the PyDictIterator works the same way.

Suggested change
type Item = (&'py PyAny, &'py PyAny);
type Item = PyResult<(&'py PyAny, &'py PyAny)>;

@davidhewitt
Copy link
Member

Continued in #4644

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.

Add support for mappingproxy? (standard library read-only dict)
3 participants