Merge pull request #1844 from davidhewitt/mapping-type
types: add PyMapping
This commit is contained in:
commit
16ac7d481d
|
@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Add `PyAny::py` as a convenience for `PyNativeType::py`. [#1751](https://github.com/PyO3/pyo3/pull/1751)
|
||||
- Add implementation of `std::ops::Index<usize>` for `PyList`, `PyTuple` and `PySequence`. [#1825](https://github.com/PyO3/pyo3/pull/1825)
|
||||
- Add range indexing implementations of `std::ops::Index` for `PyList`, `PyTuple` and `PySequence`. [#1829](https://github.com/PyO3/pyo3/pull/1829)
|
||||
- Add `PyMapping` type to represent the Python mapping protocol. [#1844](https://github.com/PyO3/pyo3/pull/1844)
|
||||
- Add commonly-used sequence methods to `PyList` and `PyTuple`. [#1849](https://github.com/PyO3/pyo3/pull/1849)
|
||||
- Add `as_sequence` methods to `PyList` and `PyTuple`. [#1860](https://github.com/PyO3/pyo3/pull/1860)
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ The table below contains the Python type and the corresponding function argument
|
|||
| `datetime.timedelta` | - | `&PyDelta` |
|
||||
| `typing.Optional[T]` | `Option<T>` | - |
|
||||
| `typing.Sequence[T]` | `Vec<T>` | `&PySequence` |
|
||||
| `typing.Mapping[K, V]` | `HashMap<K, V>`, `BTreeMap<K, V>`, `hashbrown::HashMap<K, V>`[^2], `indexmap::IndexMap<K, V>`[^3] | `&PyMapping` |
|
||||
| `typing.Iterator[Any]` | - | `&PyIterator` |
|
||||
| `typing.Union[...]` | See [`#[derive(FromPyObject)]`](traits.html#deriving-a-hrefhttpsdocsrspyo3latestpyo3conversiontraitfrompyobjecthtmlfrompyobjecta-for-enums) | - |
|
||||
|
||||
|
|
|
@ -78,7 +78,9 @@ extern "C" {
|
|||
pub fn PyObject_GetItem(o: *mut PyObject, key: *mut PyObject) -> *mut PyObject;
|
||||
#[cfg_attr(PyPy, link_name = "PyPyObject_SetItem")]
|
||||
pub fn PyObject_SetItem(o: *mut PyObject, key: *mut PyObject, v: *mut PyObject) -> c_int;
|
||||
#[cfg_attr(PyPy, link_name = "PyPyObject_DelItemString")]
|
||||
pub fn PyObject_DelItemString(o: *mut PyObject, key: *const c_char) -> c_int;
|
||||
#[cfg_attr(PyPy, link_name = "PyPyObject_DelItem")]
|
||||
pub fn PyObject_DelItem(o: *mut PyObject, key: *mut PyObject) -> c_int;
|
||||
}
|
||||
|
||||
|
@ -300,6 +302,7 @@ pub unsafe fn PyMapping_DelItem(o: *mut PyObject, key: *mut PyObject) -> c_int {
|
|||
extern "C" {
|
||||
#[cfg_attr(PyPy, link_name = "PyPyMapping_HasKeyString")]
|
||||
pub fn PyMapping_HasKeyString(o: *mut PyObject, key: *const c_char) -> c_int;
|
||||
#[cfg_attr(PyPy, link_name = "PyPyMapping_HasKey")]
|
||||
pub fn PyMapping_HasKey(o: *mut PyObject, key: *mut PyObject) -> c_int;
|
||||
#[cfg_attr(PyPy, link_name = "PyPyMapping_Keys")]
|
||||
pub fn PyMapping_Keys(o: *mut PyObject) -> *mut PyObject;
|
||||
|
|
|
@ -12,6 +12,8 @@ use std::collections::{BTreeMap, HashMap};
|
|||
use std::ptr::NonNull;
|
||||
use std::{cmp, collections, hash};
|
||||
|
||||
use super::PyMapping;
|
||||
|
||||
/// Represents a Python `dict`.
|
||||
#[repr(transparent)]
|
||||
pub struct PyDict(PyAny);
|
||||
|
@ -178,6 +180,11 @@ impl PyDict {
|
|||
pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `self` cast as a `PyMapping`.
|
||||
pub fn as_mapping(&self) -> &PyMapping {
|
||||
unsafe { PyMapping::try_from_unchecked(self) }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PyDictIterator<'py> {
|
||||
|
@ -762,4 +769,25 @@ mod tests {
|
|||
assert_eq!(py_map.get_item("b").unwrap().extract::<i32>().unwrap(), 2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dict_as_mapping() {
|
||||
Python::with_gil(|py| {
|
||||
let mut map = HashMap::<i32, i32>::new();
|
||||
map.insert(1, 1);
|
||||
|
||||
let py_map = map.into_py_dict(py);
|
||||
|
||||
assert_eq!(py_map.as_mapping().len().unwrap(), 1);
|
||||
assert_eq!(
|
||||
py_map
|
||||
.as_mapping()
|
||||
.get_item(1)
|
||||
.unwrap()
|
||||
.extract::<i32>()
|
||||
.unwrap(),
|
||||
1
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,259 @@
|
|||
// Copyright (c) 2017-present PyO3 Project and Contributors
|
||||
|
||||
use crate::err::{PyDowncastError, PyErr, PyResult};
|
||||
use crate::types::{PyAny, PySequence};
|
||||
use crate::AsPyPointer;
|
||||
use crate::{ffi, ToPyObject};
|
||||
use crate::{PyTryFrom, ToBorrowedObject};
|
||||
|
||||
/// Represents a reference to a Python object supporting the mapping protocol.
|
||||
#[repr(transparent)]
|
||||
pub struct PyMapping(PyAny);
|
||||
pyobject_native_type_named!(PyMapping);
|
||||
pyobject_native_type_extract!(PyMapping);
|
||||
|
||||
impl PyMapping {
|
||||
/// Returns the number of objects in the mapping.
|
||||
///
|
||||
/// This is equivalent to the Python expression `len(self)`.
|
||||
#[inline]
|
||||
pub fn len(&self) -> PyResult<usize> {
|
||||
let v = unsafe { ffi::PyMapping_Size(self.as_ptr()) };
|
||||
if v == -1 {
|
||||
Err(PyErr::api_call_failed(self.py()))
|
||||
} else {
|
||||
Ok(v as usize)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the mapping is empty.
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> PyResult<bool> {
|
||||
self.len().map(|l| l == 0)
|
||||
}
|
||||
|
||||
/// Gets the item in self with key `key`.
|
||||
///
|
||||
/// Returns an `Err` if the item with specified key is not found, usually `KeyError`.
|
||||
///
|
||||
/// This is equivalent to the Python expression `self[key]`.
|
||||
#[inline]
|
||||
pub fn get_item<K>(&self, key: K) -> PyResult<&PyAny>
|
||||
where
|
||||
K: ToBorrowedObject,
|
||||
{
|
||||
PyAny::get_item(self, key)
|
||||
}
|
||||
|
||||
/// Sets the item in self with key `key`.
|
||||
///
|
||||
/// This is equivalent to the Python expression `self[key] = value`.
|
||||
#[inline]
|
||||
pub fn set_item<K, V>(&self, key: K, value: V) -> PyResult<()>
|
||||
where
|
||||
K: ToPyObject,
|
||||
V: ToPyObject,
|
||||
{
|
||||
PyAny::set_item(self, key, value)
|
||||
}
|
||||
|
||||
/// Deletes the item with key `key`.
|
||||
///
|
||||
/// This is equivalent to the Python statement `del self[key]`.
|
||||
#[inline]
|
||||
pub fn del_item<K>(&self, key: K) -> PyResult<()>
|
||||
where
|
||||
K: ToBorrowedObject,
|
||||
{
|
||||
PyAny::del_item(self, key)
|
||||
}
|
||||
|
||||
/// Returns a sequence containing all keys in the mapping.
|
||||
#[inline]
|
||||
pub fn keys(&self) -> PyResult<&PySequence> {
|
||||
unsafe {
|
||||
self.py()
|
||||
.from_owned_ptr_or_err(ffi::PyMapping_Keys(self.as_ptr()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a sequence containing all values in the mapping.
|
||||
#[inline]
|
||||
pub fn values(&self) -> PyResult<&PySequence> {
|
||||
unsafe {
|
||||
self.py()
|
||||
.from_owned_ptr_or_err(ffi::PyMapping_Values(self.as_ptr()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a sequence of tuples of all (key, value) pairs in the mapping.
|
||||
#[inline]
|
||||
pub fn items(&self) -> PyResult<&PySequence> {
|
||||
unsafe {
|
||||
self.py()
|
||||
.from_owned_ptr_or_err(ffi::PyMapping_Items(self.as_ptr()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'v> PyTryFrom<'v> for PyMapping {
|
||||
fn try_from<V: Into<&'v PyAny>>(value: V) -> Result<&'v PyMapping, PyDowncastError<'v>> {
|
||||
let value = value.into();
|
||||
unsafe {
|
||||
if ffi::PyMapping_Check(value.as_ptr()) != 0 {
|
||||
Ok(<PyMapping as PyTryFrom>::try_from_unchecked(value))
|
||||
} else {
|
||||
Err(PyDowncastError::new(value, "Mapping"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_from_exact<V: Into<&'v PyAny>>(value: V) -> Result<&'v PyMapping, PyDowncastError<'v>> {
|
||||
<PyMapping as PyTryFrom>::try_from(value)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn try_from_unchecked<V: Into<&'v PyAny>>(value: V) -> &'v PyMapping {
|
||||
let ptr = value.into() as *const _ as *const PyMapping;
|
||||
&*ptr
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{exceptions::PyKeyError, types::PyTuple, Python};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_len() {
|
||||
Python::with_gil(|py| {
|
||||
let mut v = HashMap::new();
|
||||
let ob = v.to_object(py);
|
||||
let mapping = <PyMapping as PyTryFrom>::try_from(ob.as_ref(py)).unwrap();
|
||||
assert_eq!(0, mapping.len().unwrap());
|
||||
assert!(mapping.is_empty().unwrap());
|
||||
|
||||
v.insert(7, 32);
|
||||
let ob = v.to_object(py);
|
||||
let mapping2 = <PyMapping as PyTryFrom>::try_from(ob.as_ref(py)).unwrap();
|
||||
assert_eq!(1, mapping2.len().unwrap());
|
||||
assert!(!mapping2.is_empty().unwrap());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_item() {
|
||||
Python::with_gil(|py| {
|
||||
let mut v = HashMap::new();
|
||||
v.insert(7, 32);
|
||||
let ob = v.to_object(py);
|
||||
let mapping = <PyMapping as PyTryFrom>::try_from(ob.as_ref(py)).unwrap();
|
||||
assert_eq!(
|
||||
32,
|
||||
mapping.get_item(7i32).unwrap().extract::<i32>().unwrap()
|
||||
);
|
||||
assert!(mapping
|
||||
.get_item(8i32)
|
||||
.unwrap_err()
|
||||
.is_instance::<PyKeyError>(py));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_item() {
|
||||
Python::with_gil(|py| {
|
||||
let mut v = HashMap::new();
|
||||
v.insert(7, 32);
|
||||
let ob = v.to_object(py);
|
||||
let mapping = <PyMapping as PyTryFrom>::try_from(ob.as_ref(py)).unwrap();
|
||||
assert!(mapping.set_item(7i32, 42i32).is_ok()); // change
|
||||
assert!(mapping.set_item(8i32, 123i32).is_ok()); // insert
|
||||
assert_eq!(
|
||||
42i32,
|
||||
mapping.get_item(7i32).unwrap().extract::<i32>().unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
123i32,
|
||||
mapping.get_item(8i32).unwrap().extract::<i32>().unwrap()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_del_item() {
|
||||
Python::with_gil(|py| {
|
||||
let mut v = HashMap::new();
|
||||
v.insert(7, 32);
|
||||
let ob = v.to_object(py);
|
||||
let mapping = <PyMapping as PyTryFrom>::try_from(ob.as_ref(py)).unwrap();
|
||||
assert!(mapping.del_item(7i32).is_ok());
|
||||
assert_eq!(0, mapping.len().unwrap());
|
||||
assert!(mapping
|
||||
.get_item(7i32)
|
||||
.unwrap_err()
|
||||
.is_instance::<PyKeyError>(py));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_items() {
|
||||
Python::with_gil(|py| {
|
||||
let mut v = HashMap::new();
|
||||
v.insert(7, 32);
|
||||
v.insert(8, 42);
|
||||
v.insert(9, 123);
|
||||
let ob = v.to_object(py);
|
||||
let mapping = <PyMapping as PyTryFrom>::try_from(ob.as_ref(py)).unwrap();
|
||||
// Can't just compare against a vector of tuples since we don't have a guaranteed ordering.
|
||||
let mut key_sum = 0;
|
||||
let mut value_sum = 0;
|
||||
for el in mapping.items().unwrap().iter().unwrap() {
|
||||
let tuple = el.unwrap().cast_as::<PyTuple>().unwrap();
|
||||
key_sum += tuple.get_item(0).unwrap().extract::<i32>().unwrap();
|
||||
value_sum += tuple.get_item(1).unwrap().extract::<i32>().unwrap();
|
||||
}
|
||||
assert_eq!(7 + 8 + 9, key_sum);
|
||||
assert_eq!(32 + 42 + 123, value_sum);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keys() {
|
||||
Python::with_gil(|py| {
|
||||
let mut v = HashMap::new();
|
||||
v.insert(7, 32);
|
||||
v.insert(8, 42);
|
||||
v.insert(9, 123);
|
||||
let ob = v.to_object(py);
|
||||
let mapping = <PyMapping as PyTryFrom>::try_from(ob.as_ref(py)).unwrap();
|
||||
// Can't just compare against a vector of tuples since we don't have a guaranteed ordering.
|
||||
let mut key_sum = 0;
|
||||
for el in mapping.keys().unwrap().iter().unwrap() {
|
||||
key_sum += el.unwrap().extract::<i32>().unwrap();
|
||||
}
|
||||
assert_eq!(7 + 8 + 9, key_sum);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_values() {
|
||||
Python::with_gil(|py| {
|
||||
let mut v = HashMap::new();
|
||||
v.insert(7, 32);
|
||||
v.insert(8, 42);
|
||||
v.insert(9, 123);
|
||||
let ob = v.to_object(py);
|
||||
let mapping = <PyMapping as PyTryFrom>::try_from(ob.as_ref(py)).unwrap();
|
||||
// Can't just compare against a vector of tuples since we don't have a guaranteed ordering.
|
||||
let mut values_sum = 0;
|
||||
for el in mapping.values().unwrap().iter().unwrap() {
|
||||
values_sum += el.unwrap().extract::<i32>().unwrap();
|
||||
}
|
||||
assert_eq!(32 + 42 + 123, values_sum);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ pub use self::floatob::PyFloat;
|
|||
pub use self::function::{PyCFunction, PyFunction};
|
||||
pub use self::iterator::PyIterator;
|
||||
pub use self::list::PyList;
|
||||
pub use self::mapping::PyMapping;
|
||||
pub use self::module::PyModule;
|
||||
pub use self::num::PyLong;
|
||||
pub use self::num::PyLong as PyInt;
|
||||
|
@ -231,6 +232,7 @@ mod floatob;
|
|||
mod function;
|
||||
mod iterator;
|
||||
mod list;
|
||||
mod mapping;
|
||||
mod module;
|
||||
mod num;
|
||||
mod sequence;
|
||||
|
|
Loading…
Reference in New Issue