use crate::err::PyResult;
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::instance::Bound;
use crate::py_result_ext::PyResultExt;
use crate::sync::GILOnceCell;
use crate::type_object::PyTypeInfo;
use crate::types::any::PyAnyMethods;
use crate::types::{PyAny, PyDict, PySequence, PyType};
#[cfg(feature = "gil-refs")]
use crate::{err::PyDowncastError, PyNativeType};
use crate::{ffi, Py, PyTypeCheck, Python, ToPyObject};
#[repr(transparent)]
pub struct PyMapping(PyAny);
pyobject_native_type_named!(PyMapping);
pyobject_native_type_extract!(PyMapping);
impl PyMapping {
pub fn register<T: PyTypeInfo>(py: Python<'_>) -> PyResult<()> {
let ty = T::type_object_bound(py);
get_mapping_abc(py)?.call_method1("register", (ty,))?;
Ok(())
}
}
#[cfg(feature = "gil-refs")]
impl PyMapping {
#[inline]
pub fn len(&self) -> PyResult<usize> {
self.as_borrowed().len()
}
#[inline]
pub fn is_empty(&self) -> PyResult<bool> {
self.as_borrowed().is_empty()
}
pub fn contains<K>(&self, key: K) -> PyResult<bool>
where
K: ToPyObject,
{
self.as_borrowed().contains(key)
}
#[inline]
pub fn get_item<K>(&self, key: K) -> PyResult<&PyAny>
where
K: ToPyObject,
{
self.as_borrowed().get_item(key).map(Bound::into_gil_ref)
}
#[inline]
pub fn set_item<K, V>(&self, key: K, value: V) -> PyResult<()>
where
K: ToPyObject,
V: ToPyObject,
{
self.as_borrowed().set_item(key, value)
}
#[inline]
pub fn del_item<K>(&self, key: K) -> PyResult<()>
where
K: ToPyObject,
{
self.as_borrowed().del_item(key)
}
#[inline]
pub fn keys(&self) -> PyResult<&PySequence> {
self.as_borrowed().keys().map(Bound::into_gil_ref)
}
#[inline]
pub fn values(&self) -> PyResult<&PySequence> {
self.as_borrowed().values().map(Bound::into_gil_ref)
}
#[inline]
pub fn items(&self) -> PyResult<&PySequence> {
self.as_borrowed().items().map(Bound::into_gil_ref)
}
}
#[doc(alias = "PyMapping")]
pub trait PyMappingMethods<'py>: crate::sealed::Sealed {
fn len(&self) -> PyResult<usize>;
fn is_empty(&self) -> PyResult<bool>;
fn contains<K>(&self, key: K) -> PyResult<bool>
where
K: ToPyObject;
fn get_item<K>(&self, key: K) -> PyResult<Bound<'py, PyAny>>
where
K: ToPyObject;
fn set_item<K, V>(&self, key: K, value: V) -> PyResult<()>
where
K: ToPyObject,
V: ToPyObject;
fn del_item<K>(&self, key: K) -> PyResult<()>
where
K: ToPyObject;
fn keys(&self) -> PyResult<Bound<'py, PySequence>>;
fn values(&self) -> PyResult<Bound<'py, PySequence>>;
fn items(&self) -> PyResult<Bound<'py, PySequence>>;
}
impl<'py> PyMappingMethods<'py> for Bound<'py, PyMapping> {
#[inline]
fn len(&self) -> PyResult<usize> {
let v = unsafe { ffi::PyMapping_Size(self.as_ptr()) };
crate::err::error_on_minusone(self.py(), v)?;
Ok(v as usize)
}
#[inline]
fn is_empty(&self) -> PyResult<bool> {
self.len().map(|l| l == 0)
}
fn contains<K>(&self, key: K) -> PyResult<bool>
where
K: ToPyObject,
{
PyAnyMethods::contains(&**self, key)
}
#[inline]
fn get_item<K>(&self, key: K) -> PyResult<Bound<'py, PyAny>>
where
K: ToPyObject,
{
PyAnyMethods::get_item(&**self, key)
}
#[inline]
fn set_item<K, V>(&self, key: K, value: V) -> PyResult<()>
where
K: ToPyObject,
V: ToPyObject,
{
PyAnyMethods::set_item(&**self, key, value)
}
#[inline]
fn del_item<K>(&self, key: K) -> PyResult<()>
where
K: ToPyObject,
{
PyAnyMethods::del_item(&**self, key)
}
#[inline]
fn keys(&self) -> PyResult<Bound<'py, PySequence>> {
unsafe {
ffi::PyMapping_Keys(self.as_ptr())
.assume_owned_or_err(self.py())
.downcast_into_unchecked()
}
}
#[inline]
fn values(&self) -> PyResult<Bound<'py, PySequence>> {
unsafe {
ffi::PyMapping_Values(self.as_ptr())
.assume_owned_or_err(self.py())
.downcast_into_unchecked()
}
}
#[inline]
fn items(&self) -> PyResult<Bound<'py, PySequence>> {
unsafe {
ffi::PyMapping_Items(self.as_ptr())
.assume_owned_or_err(self.py())
.downcast_into_unchecked()
}
}
}
fn get_mapping_abc(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> {
static MAPPING_ABC: GILOnceCell<Py<PyType>> = GILOnceCell::new();
MAPPING_ABC.get_or_try_init_type_ref(py, "collections.abc", "Mapping")
}
impl PyTypeCheck for PyMapping {
const NAME: &'static str = "Mapping";
#[inline]
fn type_check(object: &Bound<'_, PyAny>) -> bool {
PyDict::is_type_of_bound(object)
|| get_mapping_abc(object.py())
.and_then(|abc| object.is_instance(abc))
.unwrap_or_else(|err| {
err.write_unraisable_bound(object.py(), Some(&object.as_borrowed()));
false
})
}
}
#[cfg(feature = "gil-refs")]
#[allow(deprecated)]
impl<'v> crate::PyTryFrom<'v> for PyMapping {
fn try_from<V: Into<&'v PyAny>>(value: V) -> Result<&'v PyMapping, PyDowncastError<'v>> {
let value = value.into();
if PyMapping::type_check(&value.as_borrowed()) {
unsafe { return Ok(value.downcast_unchecked()) }
}
Err(PyDowncastError::new(value, "Mapping"))
}
#[inline]
fn try_from_exact<V: Into<&'v PyAny>>(value: V) -> Result<&'v PyMapping, PyDowncastError<'v>> {
value.into().downcast()
}
#[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};
use super::*;
#[test]
fn test_len() {
Python::with_gil(|py| {
let mut v = HashMap::new();
let ob = v.to_object(py);
let mapping = ob.downcast_bound::<PyMapping>(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 = ob.downcast_bound::<PyMapping>(py).unwrap();
assert_eq!(1, mapping2.len().unwrap());
assert!(!mapping2.is_empty().unwrap());
});
}
#[test]
fn test_contains() {
Python::with_gil(|py| {
let mut v = HashMap::new();
v.insert("key0", 1234);
let ob = v.to_object(py);
let mapping = ob.downcast_bound::<PyMapping>(py).unwrap();
mapping.set_item("key1", "foo").unwrap();
assert!(mapping.contains("key0").unwrap());
assert!(mapping.contains("key1").unwrap());
assert!(!mapping.contains("key2").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 = ob.downcast_bound::<PyMapping>(py).unwrap();
assert_eq!(
32,
mapping.get_item(7i32).unwrap().extract::<i32>().unwrap()
);
assert!(mapping
.get_item(8i32)
.unwrap_err()
.is_instance_of::<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 = ob.downcast_bound::<PyMapping>(py).unwrap();
assert!(mapping.set_item(7i32, 42i32).is_ok()); assert!(mapping.set_item(8i32, 123i32).is_ok()); 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 = ob.downcast_bound::<PyMapping>(py).unwrap();
assert!(mapping.del_item(7i32).is_ok());
assert_eq!(0, mapping.len().unwrap());
assert!(mapping
.get_item(7i32)
.unwrap_err()
.is_instance_of::<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 = ob.downcast_bound::<PyMapping>(py).unwrap();
let mut key_sum = 0;
let mut value_sum = 0;
for el in mapping.items().unwrap().iter().unwrap() {
let tuple = el.unwrap().downcast_into::<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 = ob.downcast_bound::<PyMapping>(py).unwrap();
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 = ob.downcast_bound::<PyMapping>(py).unwrap();
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);
});
}
#[test]
#[cfg(feature = "gil-refs")]
#[allow(deprecated)]
fn test_mapping_try_from() {
use crate::PyTryFrom;
Python::with_gil(|py| {
let dict = PyDict::new(py);
let _ = <PyMapping as PyTryFrom>::try_from(dict).unwrap();
let _ = PyMapping::try_from_exact(dict).unwrap();
});
}
}