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
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f8fcfac
Write mappingproxy tests
dignissimus Jun 2, 2022
e8e3913
Amend tests to reflect proposed API
dignissimus Jun 2, 2022
bd5b89d
Update ffi
dignissimus Jun 2, 2022
4757e6d
Prevent integer underflow in test
dignissimus Jun 5, 2022
d992200
Add support for mappingproxy objects
dignissimus Jun 5, 2022
171fe85
Merge branch 'main' into support-mappingproxy
dignissimus Jun 5, 2022
2d4b883
Formatting
dignissimus Jun 5, 2022
9783ae5
Remove unused imports present during testing
dignissimus Jun 5, 2022
11dfceb
Replace calls to ffi::PyObject_CallMethodNoArgs with calls to PyAny::…
dignissimus Jun 5, 2022
7bb1f09
Add imports to PyPy build
dignissimus Jun 5, 2022
f9b8d9e
Remove unused import on PyPy builds
dignissimus Jun 6, 2022
4b9f02a
Remove unused imports from PyPy build
dignissimus Jun 6, 2022
83fe47a
Add required imports to PyPy build
dignissimus Jun 6, 2022
136d8fa
Move misplaced macro
dignissimus Jun 6, 2022
69baaf8
Remove outdated comments from merge
dignissimus Jun 8, 2022
00bac8e
Remove #[inline] from private function
dignissimus Jun 8, 2022
3bfb811
Don't export PyDictProxyObject
dignissimus Jun 8, 2022
f4b5a05
Don't rely on PyDictProxyObject
dignissimus Jun 8, 2022
7f98dd9
Don't export add_of_mut_shim macro
dignissimus Jun 9, 2022
9bd2f44
Replace instance of &mut with macro call
dignissimus Jun 12, 2022
c5e434d
Preserve error message when extracting dicts
dignissimus Jun 12, 2022
651a007
Add tests for PyDict::is_empty and PyMappingProxy::is_empty
dignissimus Jun 12, 2022
9e0cf63
Merge branch 'main' into support-mappingproxy
michaelhly Oct 16, 2023
1bcfe71
Fix build
michaelhly Oct 16, 2023
322b677
elements: &'py PyMapping
michaelhly Oct 16, 2023
f7e4436
Conversions
michaelhly Oct 16, 2023
10c007c
Move tests
michaelhly Oct 16, 2023
8336961
Return PyMapping on copy
michaelhly Oct 16, 2023
b47bea9
newfragments
michaelhly Oct 16, 2023
54fdc00
unwrap_or_default
michaelhly Oct 16, 2023
5ae7d64
Fix
michaelhly Oct 16, 2023
38671ad
Test hashbrown + indexmap conversions
michaelhly Oct 16, 2023
e949a7e
Clippy and cargo check
michaelhly Oct 16, 2023
16df9bf
Clean up test imports
michaelhly Oct 16, 2023
f66c610
Test hashbrown/indexmap extract conversions
michaelhly Oct 16, 2023
5005fc3
#[cfg(not(PyPy))]
michaelhly Oct 16, 2023
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
1 change: 1 addition & 0 deletions newsfragments/3521.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add `PyMappingProxy` type.
7 changes: 6 additions & 1 deletion pyo3-ffi/src/descrobject.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::methodobject::PyMethodDef;
use crate::object::{PyObject, PyTypeObject};
use crate::Py_ssize_t;
use crate::{PyObject_TypeCheck, Py_ssize_t};
use std::os::raw::{c_char, c_int, c_void};
use std::ptr;

Expand Down Expand Up @@ -68,6 +68,11 @@ extern "C" {
pub fn PyWrapper_New(arg1: *mut PyObject, arg2: *mut PyObject) -> *mut PyObject;
}

#[inline]
pub unsafe fn PyDictProxy_Check(op: *mut PyObject) -> c_int {
PyObject_TypeCheck(op, std::ptr::addr_of_mut!(PyDictProxy_Type))
}

/// Represents the [PyMemberDef](https://docs.python.org/3/c-api/structures.html#c.PyMemberDef)
/// structure.
///
Expand Down
53 changes: 47 additions & 6 deletions src/conversions/hashbrown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
//! The required hashbrown version may vary based on the version of PyO3.
use crate::{
types::set::new_from_iter,
types::{IntoPyDict, PyDict, PySet},
types::{IntoPyDict, PyDict, PyMappingProxy, PySet},
FromPyObject, IntoPy, PyAny, PyErr, PyObject, PyResult, Python, ToPyObject,
};
use std::{cmp, hash};
Expand Down Expand Up @@ -55,12 +55,30 @@ where
S: hash::BuildHasher + Default,
{
fn extract(ob: &'source PyAny) -> Result<Self, PyErr> {
let dict: &PyDict = ob.downcast()?;
let mut ret = hashbrown::HashMap::with_capacity_and_hasher(dict.len(), S::default());
for (k, v) in dict {
ret.insert(K::extract(k)?, V::extract(v)?);
match ob.downcast::<PyDict>() {
Ok(dict) => {
let mut ret =
hashbrown::HashMap::with_capacity_and_hasher(dict.len(), S::default());
for (k, v) in dict {
ret.insert(K::extract(k)?, V::extract(v)?);
}
Ok(ret)
}
Err(msg) => {
if let Ok(mappingproxy) = ob.downcast::<PyMappingProxy>() {
let mut ret = hashbrown::HashMap::with_capacity_and_hasher(
mappingproxy.len().unwrap_or_default(),
S::default(),
);
for (k, v) in mappingproxy {
ret.insert(K::extract(k)?, V::extract(v)?);
}
Ok(ret)
} else {
Err(PyErr::from(msg))
}
}
}
Ok(ret)
}
}

Expand Down Expand Up @@ -101,6 +119,7 @@ where
#[cfg(test)]
mod tests {
use super::*;
use crate::types::IntoPyMappingProxy;

#[test]
fn test_hashbrown_hashmap_to_python() {
Expand Down Expand Up @@ -186,4 +205,26 @@ mod tests {
assert_eq!(hs, hso.extract(py).unwrap());
});
}

#[test]
fn test_hashbrown_hashmap_into_mapping_proxy() {
Python::with_gil(|py| {
let mut map = hashbrown::HashMap::<i32, i32>::new();
map.insert(1, 1);

let mappingproxy = map.clone().into_py_mappingproxy(py).unwrap();

assert_eq!(mappingproxy.len().unwrap(), 1);
assert_eq!(
mappingproxy.get_item(1).unwrap().extract::<i32>().unwrap(),
1
);
assert_eq!(
map,
mappingproxy
.extract::<hashbrown::HashMap::<i32, i32>>()
.unwrap()
);
});
}
}
50 changes: 45 additions & 5 deletions src/conversions/indexmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,30 @@ where
S: hash::BuildHasher + Default,
{
fn extract(ob: &'source PyAny) -> Result<Self, PyErr> {
let dict: &PyDict = ob.downcast()?;
let mut ret = indexmap::IndexMap::with_capacity_and_hasher(dict.len(), S::default());
for (k, v) in dict {
ret.insert(K::extract(k)?, V::extract(v)?);
match ob.downcast::<PyDict>() {
Ok(dict) => {
let mut ret =
indexmap::IndexMap::with_capacity_and_hasher(dict.len(), S::default());
for (k, v) in dict {
ret.insert(K::extract(k)?, V::extract(v)?);
}
Ok(ret)
}
Err(msg) => {
if let Ok(mappingproxy) = ob.downcast::<PyMappingProxy>() {
let mut ret = indexmap::IndexMap::with_capacity_and_hasher(
mappingproxy.len().unwrap_or_default(),
S::default(),
);
for (k, v) in mappingproxy {
ret.insert(K::extract(k)?, V::extract(v)?);
}
Ok(ret)
} else {
Err(PyErr::from(msg))
}
}
}
Ok(ret)
}
}

Expand Down Expand Up @@ -236,4 +254,26 @@ mod test_indexmap {
}
});
}

#[test]
fn test_indexmap_indexmap_into_mappingproxy() {
Python::with_gil(|py| {
let mut map = indexmap::IndexMap::<i32, i32>::new();
map.insert(1, 1);

let mappingproxy = map.clone().into_py_mappingproxy(py).unwrap();

assert_eq!(mappingproxy.len().unwrap(), 1);
assert_eq!(
mappingproxy.get_item(1).unwrap().extract::<i32>().unwrap(),
1
);
assert_eq!(
map,
mappingproxy
.extract::<indexmap::IndexMap<i32, i32>>()
.unwrap()
);
});
}
}
54 changes: 43 additions & 11 deletions src/conversions/std/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{cmp, collections, hash};
#[cfg(feature = "experimental-inspect")]
use crate::inspect::types::TypeInfo;
use crate::{
types::{IntoPyDict, PyDict},
types::{IntoPyDict, PyDict, PyMappingProxy},
FromPyObject, IntoPy, PyAny, PyErr, PyObject, Python, ToPyObject,
};

Expand Down Expand Up @@ -72,12 +72,30 @@ where
S: hash::BuildHasher + Default,
{
fn extract(ob: &'source PyAny) -> Result<Self, PyErr> {
let dict: &PyDict = ob.downcast()?;
let mut ret = collections::HashMap::with_capacity_and_hasher(dict.len(), S::default());
for (k, v) in dict {
ret.insert(K::extract(k)?, V::extract(v)?);
match ob.downcast::<PyDict>() {
Ok(dict) => {
let mut ret =
collections::HashMap::with_capacity_and_hasher(dict.len(), S::default());
for (k, v) in dict {
ret.insert(K::extract(k)?, V::extract(v)?);
}
Ok(ret)
}
Err(msg) => {
if let Ok(mappingproxy) = ob.downcast::<PyMappingProxy>() {
let mut ret = collections::HashMap::with_capacity_and_hasher(
mappingproxy.len().unwrap_or_default(),
S::default(),
);
for (k, v) in mappingproxy {
ret.insert(K::extract(k)?, V::extract(v)?);
}
Ok(ret)
} else {
Err(PyErr::from(msg))
}
}
}
Ok(ret)
}

#[cfg(feature = "experimental-inspect")]
Expand All @@ -92,12 +110,26 @@ where
V: FromPyObject<'source>,
{
fn extract(ob: &'source PyAny) -> Result<Self, PyErr> {
let dict: &PyDict = ob.downcast()?;
let mut ret = collections::BTreeMap::new();
for (k, v) in dict {
ret.insert(K::extract(k)?, V::extract(v)?);
match ob.downcast::<PyDict>() {
Ok(dict) => {
let mut ret = collections::BTreeMap::new();
for (k, v) in dict {
ret.insert(K::extract(k)?, V::extract(v)?);
}
Ok(ret)
}
Err(msg) => {
if let Ok(mappingproxy) = ob.downcast::<PyMappingProxy>() {
let mut ret = collections::BTreeMap::new();
for (k, v) in mappingproxy {
ret.insert(K::extract(k)?, V::extract(v)?);
}
Ok(ret)
} else {
Err(PyErr::from(msg))
}
}
}
Ok(ret)
}

#[cfg(feature = "experimental-inspect")]
Expand Down
Loading
Loading