pyo3/types/
dict.rs

1use super::PyMapping;
2use crate::err::{self, PyErr, PyResult};
3use crate::ffi::Py_ssize_t;
4use crate::ffi_ptr_ext::FfiPtrExt;
5use crate::instance::{Borrowed, Bound};
6use crate::py_result_ext::PyResultExt;
7use crate::types::any::PyAnyMethods;
8use crate::types::{PyAny, PyList};
9#[cfg(feature = "gil-refs")]
10use crate::PyNativeType;
11use crate::{ffi, Python, ToPyObject};
12
13/// Represents a Python `dict`.
14///
15/// Values of this type are accessed via PyO3's smart pointers, e.g. as
16/// [`Py<PyDict>`][crate::Py] or [`Bound<'py, PyDict>`][Bound].
17///
18/// For APIs available on `dict` objects, see the [`PyDictMethods`] trait which is implemented for
19/// [`Bound<'py, PyDict>`][Bound].
20#[repr(transparent)]
21pub struct PyDict(PyAny);
22
23pyobject_native_type!(
24    PyDict,
25    ffi::PyDictObject,
26    pyobject_native_static_type_object!(ffi::PyDict_Type),
27    #checkfunction=ffi::PyDict_Check
28);
29
30/// Represents a Python `dict_keys`.
31#[cfg(not(any(PyPy, GraalPy)))]
32#[repr(transparent)]
33pub struct PyDictKeys(PyAny);
34
35#[cfg(not(any(PyPy, GraalPy)))]
36pyobject_native_type_core!(
37    PyDictKeys,
38    pyobject_native_static_type_object!(ffi::PyDictKeys_Type),
39    #checkfunction=ffi::PyDictKeys_Check
40);
41
42/// Represents a Python `dict_values`.
43#[cfg(not(any(PyPy, GraalPy)))]
44#[repr(transparent)]
45pub struct PyDictValues(PyAny);
46
47#[cfg(not(any(PyPy, GraalPy)))]
48pyobject_native_type_core!(
49    PyDictValues,
50    pyobject_native_static_type_object!(ffi::PyDictValues_Type),
51    #checkfunction=ffi::PyDictValues_Check
52);
53
54/// Represents a Python `dict_items`.
55#[cfg(not(any(PyPy, GraalPy)))]
56#[repr(transparent)]
57pub struct PyDictItems(PyAny);
58
59#[cfg(not(any(PyPy, GraalPy)))]
60pyobject_native_type_core!(
61    PyDictItems,
62    pyobject_native_static_type_object!(ffi::PyDictItems_Type),
63    #checkfunction=ffi::PyDictItems_Check
64);
65
66impl PyDict {
67    /// Creates a new empty dictionary.
68    pub fn new_bound(py: Python<'_>) -> Bound<'_, PyDict> {
69        unsafe { ffi::PyDict_New().assume_owned(py).downcast_into_unchecked() }
70    }
71
72    /// Creates a new dictionary from the sequence given.
73    ///
74    /// The sequence must consist of `(PyObject, PyObject)`. This is
75    /// equivalent to `dict([("a", 1), ("b", 2)])`.
76    ///
77    /// Returns an error on invalid input. In the case of key collisions,
78    /// this keeps the last entry seen.
79    #[cfg(not(any(PyPy, GraalPy)))]
80    pub fn from_sequence_bound<'py>(seq: &Bound<'py, PyAny>) -> PyResult<Bound<'py, PyDict>> {
81        let py = seq.py();
82        let dict = Self::new_bound(py);
83        err::error_on_minusone(py, unsafe {
84            ffi::PyDict_MergeFromSeq2(dict.as_ptr(), seq.as_ptr(), 1)
85        })?;
86        Ok(dict)
87    }
88}
89
90#[cfg(feature = "gil-refs")]
91impl PyDict {
92    /// Deprecated form of [`new_bound`][PyDict::new_bound].
93    #[deprecated(
94        since = "0.21.0",
95        note = "`PyDict::new` will be replaced by `PyDict::new_bound` in a future PyO3 version"
96    )]
97    #[inline]
98    pub fn new(py: Python<'_>) -> &PyDict {
99        Self::new_bound(py).into_gil_ref()
100    }
101
102    /// Deprecated form of [`from_sequence_bound`][PyDict::from_sequence_bound].
103    #[deprecated(
104        since = "0.21.0",
105        note = "`PyDict::from_sequence` will be replaced by `PyDict::from_sequence_bound` in a future PyO3 version"
106    )]
107    #[inline]
108    #[cfg(not(any(PyPy, GraalPy)))]
109    pub fn from_sequence(seq: &PyAny) -> PyResult<&PyDict> {
110        Self::from_sequence_bound(&seq.as_borrowed()).map(Bound::into_gil_ref)
111    }
112
113    /// Returns a new dictionary that contains the same key-value pairs as self.
114    ///
115    /// This is equivalent to the Python expression `self.copy()`.
116    pub fn copy(&self) -> PyResult<&PyDict> {
117        self.as_borrowed().copy().map(Bound::into_gil_ref)
118    }
119
120    /// Empties an existing dictionary of all key-value pairs.
121    pub fn clear(&self) {
122        self.as_borrowed().clear()
123    }
124
125    /// Return the number of items in the dictionary.
126    ///
127    /// This is equivalent to the Python expression `len(self)`.
128    pub fn len(&self) -> usize {
129        self.as_borrowed().len()
130    }
131
132    /// Checks if the dict is empty, i.e. `len(self) == 0`.
133    pub fn is_empty(&self) -> bool {
134        self.as_borrowed().is_empty()
135    }
136
137    /// Determines if the dictionary contains the specified key.
138    ///
139    /// This is equivalent to the Python expression `key in self`.
140    pub fn contains<K>(&self, key: K) -> PyResult<bool>
141    where
142        K: ToPyObject,
143    {
144        self.as_borrowed().contains(key)
145    }
146
147    /// Gets an item from the dictionary.
148    ///
149    /// Returns `Ok(None)` if the item is not present. To get a `KeyError` for
150    /// non-existing keys, use [`PyAny::get_item`].
151    ///
152    /// Returns `Err(PyErr)` if Python magic methods `__hash__` or `__eq__` used in dictionary
153    /// lookup raise an exception, for example if the key `K` is not hashable. Usually it is
154    /// best to bubble this error up to the caller using the `?` operator.
155    ///
156    /// # Examples
157    ///
158    /// The following example calls `get_item` for the dictionary `{"a": 1}` with various
159    /// keys.
160    /// - `get_item("a")` returns `Ok(Some(...))`, with the `PyAny` being a reference to the Python
161    ///   int `1`.
162    /// - `get_item("b")` returns `Ok(None)`, because "b" is not in the dictionary.
163    /// - `get_item(dict)` returns an `Err(PyErr)`. The error will be a `TypeError` because a dict is not
164    ///   hashable.
165    ///
166    /// ```rust
167    /// use pyo3::prelude::*;
168    /// use pyo3::types::{IntoPyDict};
169    /// use pyo3::exceptions::{PyTypeError, PyKeyError};
170    ///
171    /// # fn main() {
172    /// # let _ =
173    /// Python::with_gil(|py| -> PyResult<()> {
174    ///     let dict = &[("a", 1)].into_py_dict_bound(py);
175    ///     // `a` is in the dictionary, with value 1
176    ///     assert!(dict.get_item("a")?.map_or(Ok(false), |x| x.eq(1))?);
177    ///     // `b` is not in the dictionary
178    ///     assert!(dict.get_item("b")?.is_none());
179    ///     // `dict` is not hashable, so this returns an error
180    ///     assert!(dict.get_item(dict).unwrap_err().is_instance_of::<PyTypeError>(py));
181    ///
182    ///     // `PyAny::get_item("b")` will raise a `KeyError` instead of returning `None`
183    ///     let any = dict.as_any();
184    ///     assert!(any.get_item("b").unwrap_err().is_instance_of::<PyKeyError>(py));
185    ///     Ok(())
186    /// });
187    /// # }
188    /// ```
189    pub fn get_item<K>(&self, key: K) -> PyResult<Option<&PyAny>>
190    where
191        K: ToPyObject,
192    {
193        match self.as_borrowed().get_item(key) {
194            Ok(Some(item)) => Ok(Some(item.into_gil_ref())),
195            Ok(None) => Ok(None),
196            Err(e) => Err(e),
197        }
198    }
199
200    /// Deprecated version of `get_item`.
201    #[deprecated(
202        since = "0.20.0",
203        note = "this is now equivalent to `PyDict::get_item`"
204    )]
205    #[inline]
206    pub fn get_item_with_error<K>(&self, key: K) -> PyResult<Option<&PyAny>>
207    where
208        K: ToPyObject,
209    {
210        self.get_item(key)
211    }
212
213    /// Sets an item value.
214    ///
215    /// This is equivalent to the Python statement `self[key] = value`.
216    pub fn set_item<K, V>(&self, key: K, value: V) -> PyResult<()>
217    where
218        K: ToPyObject,
219        V: ToPyObject,
220    {
221        self.as_borrowed().set_item(key, value)
222    }
223
224    /// Deletes an item.
225    ///
226    /// This is equivalent to the Python statement `del self[key]`.
227    pub fn del_item<K>(&self, key: K) -> PyResult<()>
228    where
229        K: ToPyObject,
230    {
231        self.as_borrowed().del_item(key)
232    }
233
234    /// Returns a list of dict keys.
235    ///
236    /// This is equivalent to the Python expression `list(dict.keys())`.
237    pub fn keys(&self) -> &PyList {
238        self.as_borrowed().keys().into_gil_ref()
239    }
240
241    /// Returns a list of dict values.
242    ///
243    /// This is equivalent to the Python expression `list(dict.values())`.
244    pub fn values(&self) -> &PyList {
245        self.as_borrowed().values().into_gil_ref()
246    }
247
248    /// Returns a list of dict items.
249    ///
250    /// This is equivalent to the Python expression `list(dict.items())`.
251    pub fn items(&self) -> &PyList {
252        self.as_borrowed().items().into_gil_ref()
253    }
254
255    /// Returns an iterator of `(key, value)` pairs in this dictionary.
256    ///
257    /// # Panics
258    ///
259    /// If PyO3 detects that the dictionary is mutated during iteration, it will panic.
260    /// It is allowed to modify values as you iterate over the dictionary, but only
261    /// so long as the set of keys does not change.
262    pub fn iter(&self) -> PyDictIterator<'_> {
263        PyDictIterator(self.as_borrowed().iter())
264    }
265
266    /// Returns `self` cast as a `PyMapping`.
267    pub fn as_mapping(&self) -> &PyMapping {
268        unsafe { self.downcast_unchecked() }
269    }
270
271    /// Update this dictionary with the key/value pairs from another.
272    ///
273    /// This is equivalent to the Python expression `self.update(other)`. If `other` is a `PyDict`, you may want
274    /// to use `self.update(other.as_mapping())`, note: `PyDict::as_mapping` is a zero-cost conversion.
275    pub fn update(&self, other: &PyMapping) -> PyResult<()> {
276        self.as_borrowed().update(&other.as_borrowed())
277    }
278
279    /// Add key/value pairs from another dictionary to this one only when they do not exist in this.
280    ///
281    /// This is equivalent to the Python expression `self.update({k: v for k, v in other.items() if k not in self})`.
282    /// If `other` is a `PyDict`, you may want to use `self.update_if_missing(other.as_mapping())`,
283    /// note: `PyDict::as_mapping` is a zero-cost conversion.
284    ///
285    /// This method uses [`PyDict_Merge`](https://docs.python.org/3/c-api/dict.html#c.PyDict_Merge) internally,
286    /// so should have the same performance as `update`.
287    pub fn update_if_missing(&self, other: &PyMapping) -> PyResult<()> {
288        self.as_borrowed().update_if_missing(&other.as_borrowed())
289    }
290}
291
292/// Implementation of functionality for [`PyDict`].
293///
294/// These methods are defined for the `Bound<'py, PyDict>` smart pointer, so to use method call
295/// syntax these methods are separated into a trait, because stable Rust does not yet support
296/// `arbitrary_self_types`.
297#[doc(alias = "PyDict")]
298pub trait PyDictMethods<'py>: crate::sealed::Sealed {
299    /// Returns a new dictionary that contains the same key-value pairs as self.
300    ///
301    /// This is equivalent to the Python expression `self.copy()`.
302    fn copy(&self) -> PyResult<Bound<'py, PyDict>>;
303
304    /// Empties an existing dictionary of all key-value pairs.
305    fn clear(&self);
306
307    /// Return the number of items in the dictionary.
308    ///
309    /// This is equivalent to the Python expression `len(self)`.
310    fn len(&self) -> usize;
311
312    /// Checks if the dict is empty, i.e. `len(self) == 0`.
313    fn is_empty(&self) -> bool;
314
315    /// Determines if the dictionary contains the specified key.
316    ///
317    /// This is equivalent to the Python expression `key in self`.
318    fn contains<K>(&self, key: K) -> PyResult<bool>
319    where
320        K: ToPyObject;
321
322    /// Gets an item from the dictionary.
323    ///
324    /// Returns `None` if the item is not present, or if an error occurs.
325    ///
326    /// To get a `KeyError` for non-existing keys, use `PyAny::get_item`.
327    fn get_item<K>(&self, key: K) -> PyResult<Option<Bound<'py, PyAny>>>
328    where
329        K: ToPyObject;
330
331    /// Sets an item value.
332    ///
333    /// This is equivalent to the Python statement `self[key] = value`.
334    fn set_item<K, V>(&self, key: K, value: V) -> PyResult<()>
335    where
336        K: ToPyObject,
337        V: ToPyObject;
338
339    /// Deletes an item.
340    ///
341    /// This is equivalent to the Python statement `del self[key]`.
342    fn del_item<K>(&self, key: K) -> PyResult<()>
343    where
344        K: ToPyObject;
345
346    /// Returns a list of dict keys.
347    ///
348    /// This is equivalent to the Python expression `list(dict.keys())`.
349    fn keys(&self) -> Bound<'py, PyList>;
350
351    /// Returns a list of dict values.
352    ///
353    /// This is equivalent to the Python expression `list(dict.values())`.
354    fn values(&self) -> Bound<'py, PyList>;
355
356    /// Returns a list of dict items.
357    ///
358    /// This is equivalent to the Python expression `list(dict.items())`.
359    fn items(&self) -> Bound<'py, PyList>;
360
361    /// Returns an iterator of `(key, value)` pairs in this dictionary.
362    ///
363    /// # Panics
364    ///
365    /// If PyO3 detects that the dictionary is mutated during iteration, it will panic.
366    /// It is allowed to modify values as you iterate over the dictionary, but only
367    /// so long as the set of keys does not change.
368    fn iter(&self) -> BoundDictIterator<'py>;
369
370    /// Returns `self` cast as a `PyMapping`.
371    fn as_mapping(&self) -> &Bound<'py, PyMapping>;
372
373    /// Returns `self` cast as a `PyMapping`.
374    fn into_mapping(self) -> Bound<'py, PyMapping>;
375
376    /// Update this dictionary with the key/value pairs from another.
377    ///
378    /// This is equivalent to the Python expression `self.update(other)`. If `other` is a `PyDict`, you may want
379    /// to use `self.update(other.as_mapping())`, note: `PyDict::as_mapping` is a zero-cost conversion.
380    fn update(&self, other: &Bound<'_, PyMapping>) -> PyResult<()>;
381
382    /// Add key/value pairs from another dictionary to this one only when they do not exist in this.
383    ///
384    /// This is equivalent to the Python expression `self.update({k: v for k, v in other.items() if k not in self})`.
385    /// If `other` is a `PyDict`, you may want to use `self.update_if_missing(other.as_mapping())`,
386    /// note: `PyDict::as_mapping` is a zero-cost conversion.
387    ///
388    /// This method uses [`PyDict_Merge`](https://docs.python.org/3/c-api/dict.html#c.PyDict_Merge) internally,
389    /// so should have the same performance as `update`.
390    fn update_if_missing(&self, other: &Bound<'_, PyMapping>) -> PyResult<()>;
391}
392
393impl<'py> PyDictMethods<'py> for Bound<'py, PyDict> {
394    fn copy(&self) -> PyResult<Bound<'py, PyDict>> {
395        unsafe {
396            ffi::PyDict_Copy(self.as_ptr())
397                .assume_owned_or_err(self.py())
398                .downcast_into_unchecked()
399        }
400    }
401
402    fn clear(&self) {
403        unsafe { ffi::PyDict_Clear(self.as_ptr()) }
404    }
405
406    fn len(&self) -> usize {
407        dict_len(self) as usize
408    }
409
410    fn is_empty(&self) -> bool {
411        self.len() == 0
412    }
413
414    fn contains<K>(&self, key: K) -> PyResult<bool>
415    where
416        K: ToPyObject,
417    {
418        fn inner(dict: &Bound<'_, PyDict>, key: Bound<'_, PyAny>) -> PyResult<bool> {
419            match unsafe { ffi::PyDict_Contains(dict.as_ptr(), key.as_ptr()) } {
420                1 => Ok(true),
421                0 => Ok(false),
422                _ => Err(PyErr::fetch(dict.py())),
423            }
424        }
425
426        let py = self.py();
427        inner(self, key.to_object(py).into_bound(py))
428    }
429
430    fn get_item<K>(&self, key: K) -> PyResult<Option<Bound<'py, PyAny>>>
431    where
432        K: ToPyObject,
433    {
434        fn inner<'py>(
435            dict: &Bound<'py, PyDict>,
436            key: Bound<'_, PyAny>,
437        ) -> PyResult<Option<Bound<'py, PyAny>>> {
438            let py = dict.py();
439            let mut result: *mut ffi::PyObject = std::ptr::null_mut();
440            match unsafe {
441                ffi::compat::PyDict_GetItemRef(dict.as_ptr(), key.as_ptr(), &mut result)
442            } {
443                std::os::raw::c_int::MIN..=-1 => Err(PyErr::fetch(py)),
444                0 => Ok(None),
445                1..=std::os::raw::c_int::MAX => Ok(Some(unsafe { result.assume_owned(py) })),
446            }
447        }
448
449        let py = self.py();
450        inner(self, key.to_object(py).into_bound(py))
451    }
452
453    fn set_item<K, V>(&self, key: K, value: V) -> PyResult<()>
454    where
455        K: ToPyObject,
456        V: ToPyObject,
457    {
458        fn inner(
459            dict: &Bound<'_, PyDict>,
460            key: Bound<'_, PyAny>,
461            value: Bound<'_, PyAny>,
462        ) -> PyResult<()> {
463            err::error_on_minusone(dict.py(), unsafe {
464                ffi::PyDict_SetItem(dict.as_ptr(), key.as_ptr(), value.as_ptr())
465            })
466        }
467
468        let py = self.py();
469        inner(
470            self,
471            key.to_object(py).into_bound(py),
472            value.to_object(py).into_bound(py),
473        )
474    }
475
476    fn del_item<K>(&self, key: K) -> PyResult<()>
477    where
478        K: ToPyObject,
479    {
480        fn inner(dict: &Bound<'_, PyDict>, key: Bound<'_, PyAny>) -> PyResult<()> {
481            err::error_on_minusone(dict.py(), unsafe {
482                ffi::PyDict_DelItem(dict.as_ptr(), key.as_ptr())
483            })
484        }
485
486        let py = self.py();
487        inner(self, key.to_object(py).into_bound(py))
488    }
489
490    fn keys(&self) -> Bound<'py, PyList> {
491        unsafe {
492            ffi::PyDict_Keys(self.as_ptr())
493                .assume_owned(self.py())
494                .downcast_into_unchecked()
495        }
496    }
497
498    fn values(&self) -> Bound<'py, PyList> {
499        unsafe {
500            ffi::PyDict_Values(self.as_ptr())
501                .assume_owned(self.py())
502                .downcast_into_unchecked()
503        }
504    }
505
506    fn items(&self) -> Bound<'py, PyList> {
507        unsafe {
508            ffi::PyDict_Items(self.as_ptr())
509                .assume_owned(self.py())
510                .downcast_into_unchecked()
511        }
512    }
513
514    fn iter(&self) -> BoundDictIterator<'py> {
515        BoundDictIterator::new(self.clone())
516    }
517
518    fn as_mapping(&self) -> &Bound<'py, PyMapping> {
519        unsafe { self.downcast_unchecked() }
520    }
521
522    fn into_mapping(self) -> Bound<'py, PyMapping> {
523        unsafe { self.into_any().downcast_into_unchecked() }
524    }
525
526    fn update(&self, other: &Bound<'_, PyMapping>) -> PyResult<()> {
527        err::error_on_minusone(self.py(), unsafe {
528            ffi::PyDict_Update(self.as_ptr(), other.as_ptr())
529        })
530    }
531
532    fn update_if_missing(&self, other: &Bound<'_, PyMapping>) -> PyResult<()> {
533        err::error_on_minusone(self.py(), unsafe {
534            ffi::PyDict_Merge(self.as_ptr(), other.as_ptr(), 0)
535        })
536    }
537}
538
539impl<'a, 'py> Borrowed<'a, 'py, PyDict> {
540    /// Iterates over the contents of this dictionary without incrementing reference counts.
541    ///
542    /// # Safety
543    /// It must be known that this dictionary will not be modified during iteration.
544    pub(crate) unsafe fn iter_borrowed(self) -> BorrowedDictIter<'a, 'py> {
545        BorrowedDictIter::new(self)
546    }
547}
548
549fn dict_len(dict: &Bound<'_, PyDict>) -> Py_ssize_t {
550    #[cfg(any(not(Py_3_8), PyPy, GraalPy, Py_LIMITED_API))]
551    unsafe {
552        ffi::PyDict_Size(dict.as_ptr())
553    }
554
555    #[cfg(all(Py_3_8, not(PyPy), not(GraalPy), not(Py_LIMITED_API)))]
556    unsafe {
557        (*dict.as_ptr().cast::<ffi::PyDictObject>()).ma_used
558    }
559}
560
561/// PyO3 implementation of an iterator for a Python `dict` object.
562#[cfg(feature = "gil-refs")]
563pub struct PyDictIterator<'py>(BoundDictIterator<'py>);
564
565#[cfg(feature = "gil-refs")]
566impl<'py> Iterator for PyDictIterator<'py> {
567    type Item = (&'py PyAny, &'py PyAny);
568
569    #[inline]
570    fn next(&mut self) -> Option<Self::Item> {
571        let (key, value) = self.0.next()?;
572        Some((key.into_gil_ref(), value.into_gil_ref()))
573    }
574
575    #[inline]
576    fn size_hint(&self) -> (usize, Option<usize>) {
577        self.0.size_hint()
578    }
579}
580
581#[cfg(feature = "gil-refs")]
582impl<'py> ExactSizeIterator for PyDictIterator<'py> {
583    fn len(&self) -> usize {
584        self.0.len()
585    }
586}
587
588#[cfg(feature = "gil-refs")]
589impl<'a> IntoIterator for &'a PyDict {
590    type Item = (&'a PyAny, &'a PyAny);
591    type IntoIter = PyDictIterator<'a>;
592
593    fn into_iter(self) -> Self::IntoIter {
594        self.iter()
595    }
596}
597
598/// PyO3 implementation of an iterator for a Python `dict` object.
599pub struct BoundDictIterator<'py> {
600    dict: Bound<'py, PyDict>,
601    ppos: ffi::Py_ssize_t,
602    di_used: ffi::Py_ssize_t,
603    len: ffi::Py_ssize_t,
604}
605
606impl<'py> Iterator for BoundDictIterator<'py> {
607    type Item = (Bound<'py, PyAny>, Bound<'py, PyAny>);
608
609    #[inline]
610    fn next(&mut self) -> Option<Self::Item> {
611        let ma_used = dict_len(&self.dict);
612
613        // These checks are similar to what CPython does.
614        //
615        // If the dimension of the dict changes e.g. key-value pairs are removed
616        // or added during iteration, this will panic next time when `next` is called
617        if self.di_used != ma_used {
618            self.di_used = -1;
619            panic!("dictionary changed size during iteration");
620        };
621
622        // If the dict is changed in such a way that the length remains constant
623        // then this will panic at the end of iteration - similar to this:
624        //
625        // d = {"a":1, "b":2, "c": 3}
626        //
627        // for k, v in d.items():
628        //     d[f"{k}_"] = 4
629        //     del d[k]
630        //     print(k)
631        //
632        if self.len == -1 {
633            self.di_used = -1;
634            panic!("dictionary keys changed during iteration");
635        };
636
637        let mut key: *mut ffi::PyObject = std::ptr::null_mut();
638        let mut value: *mut ffi::PyObject = std::ptr::null_mut();
639
640        if unsafe { ffi::PyDict_Next(self.dict.as_ptr(), &mut self.ppos, &mut key, &mut value) }
641            != 0
642        {
643            self.len -= 1;
644            let py = self.dict.py();
645            // Safety:
646            // - PyDict_Next returns borrowed values
647            // - we have already checked that `PyDict_Next` succeeded, so we can assume these to be non-null
648            Some((
649                unsafe { key.assume_borrowed_unchecked(py) }.to_owned(),
650                unsafe { value.assume_borrowed_unchecked(py) }.to_owned(),
651            ))
652        } else {
653            None
654        }
655    }
656
657    #[inline]
658    fn size_hint(&self) -> (usize, Option<usize>) {
659        let len = self.len();
660        (len, Some(len))
661    }
662}
663
664impl<'py> ExactSizeIterator for BoundDictIterator<'py> {
665    fn len(&self) -> usize {
666        self.len as usize
667    }
668}
669
670impl<'py> BoundDictIterator<'py> {
671    fn new(dict: Bound<'py, PyDict>) -> Self {
672        let len = dict_len(&dict);
673        BoundDictIterator {
674            dict,
675            ppos: 0,
676            di_used: len,
677            len,
678        }
679    }
680}
681
682impl<'py> IntoIterator for Bound<'py, PyDict> {
683    type Item = (Bound<'py, PyAny>, Bound<'py, PyAny>);
684    type IntoIter = BoundDictIterator<'py>;
685
686    fn into_iter(self) -> Self::IntoIter {
687        BoundDictIterator::new(self)
688    }
689}
690
691impl<'py> IntoIterator for &Bound<'py, PyDict> {
692    type Item = (Bound<'py, PyAny>, Bound<'py, PyAny>);
693    type IntoIter = BoundDictIterator<'py>;
694
695    fn into_iter(self) -> Self::IntoIter {
696        self.iter()
697    }
698}
699
700mod borrowed_iter {
701    use super::*;
702
703    /// Variant of the above which is used to iterate the items of the dictionary
704    /// without incrementing reference counts. This is only safe if it's known
705    /// that the dictionary will not be modified during iteration.
706    pub struct BorrowedDictIter<'a, 'py> {
707        dict: Borrowed<'a, 'py, PyDict>,
708        ppos: ffi::Py_ssize_t,
709        len: ffi::Py_ssize_t,
710    }
711
712    impl<'a, 'py> Iterator for BorrowedDictIter<'a, 'py> {
713        type Item = (Borrowed<'a, 'py, PyAny>, Borrowed<'a, 'py, PyAny>);
714
715        #[inline]
716        fn next(&mut self) -> Option<Self::Item> {
717            let mut key: *mut ffi::PyObject = std::ptr::null_mut();
718            let mut value: *mut ffi::PyObject = std::ptr::null_mut();
719
720            // Safety: self.dict lives sufficiently long that the pointer is not dangling
721            if unsafe { ffi::PyDict_Next(self.dict.as_ptr(), &mut self.ppos, &mut key, &mut value) }
722                != 0
723            {
724                let py = self.dict.py();
725                self.len -= 1;
726                // Safety:
727                // - PyDict_Next returns borrowed values
728                // - we have already checked that `PyDict_Next` succeeded, so we can assume these to be non-null
729                Some(unsafe { (key.assume_borrowed(py), value.assume_borrowed(py)) })
730            } else {
731                None
732            }
733        }
734
735        #[inline]
736        fn size_hint(&self) -> (usize, Option<usize>) {
737            let len = self.len();
738            (len, Some(len))
739        }
740    }
741
742    impl ExactSizeIterator for BorrowedDictIter<'_, '_> {
743        fn len(&self) -> usize {
744            self.len as usize
745        }
746    }
747
748    impl<'a, 'py> BorrowedDictIter<'a, 'py> {
749        pub(super) fn new(dict: Borrowed<'a, 'py, PyDict>) -> Self {
750            let len = dict_len(&dict);
751            BorrowedDictIter { dict, ppos: 0, len }
752        }
753    }
754}
755
756pub(crate) use borrowed_iter::BorrowedDictIter;
757
758/// Conversion trait that allows a sequence of tuples to be converted into `PyDict`
759/// Primary use case for this trait is `call` and `call_method` methods as keywords argument.
760pub trait IntoPyDict: Sized {
761    /// Converts self into a `PyDict` object pointer. Whether pointer owned or borrowed
762    /// depends on implementation.
763    #[cfg(feature = "gil-refs")]
764    #[deprecated(
765        since = "0.21.0",
766        note = "`IntoPyDict::into_py_dict` will be replaced by `IntoPyDict::into_py_dict_bound` in a future PyO3 version"
767    )]
768    fn into_py_dict(self, py: Python<'_>) -> &PyDict {
769        Self::into_py_dict_bound(self, py).into_gil_ref()
770    }
771
772    /// Converts self into a `PyDict` object pointer. Whether pointer owned or borrowed
773    /// depends on implementation.
774    fn into_py_dict_bound(self, py: Python<'_>) -> Bound<'_, PyDict>;
775}
776
777impl<T, I> IntoPyDict for I
778where
779    T: PyDictItem,
780    I: IntoIterator<Item = T>,
781{
782    fn into_py_dict_bound(self, py: Python<'_>) -> Bound<'_, PyDict> {
783        let dict = PyDict::new_bound(py);
784        for item in self {
785            dict.set_item(item.key(), item.value())
786                .expect("Failed to set_item on dict");
787        }
788        dict
789    }
790}
791
792/// Represents a tuple which can be used as a PyDict item.
793pub trait PyDictItem {
794    type K: ToPyObject;
795    type V: ToPyObject;
796    fn key(&self) -> &Self::K;
797    fn value(&self) -> &Self::V;
798}
799
800impl<K, V> PyDictItem for (K, V)
801where
802    K: ToPyObject,
803    V: ToPyObject,
804{
805    type K = K;
806    type V = V;
807    fn key(&self) -> &Self::K {
808        &self.0
809    }
810    fn value(&self) -> &Self::V {
811        &self.1
812    }
813}
814
815impl<K, V> PyDictItem for &(K, V)
816where
817    K: ToPyObject,
818    V: ToPyObject,
819{
820    type K = K;
821    type V = V;
822    fn key(&self) -> &Self::K {
823        &self.0
824    }
825    fn value(&self) -> &Self::V {
826        &self.1
827    }
828}
829
830#[cfg(test)]
831mod tests {
832    use super::*;
833    use crate::types::PyTuple;
834    use std::collections::{BTreeMap, HashMap};
835
836    #[test]
837    fn test_new() {
838        Python::with_gil(|py| {
839            let dict = [(7, 32)].into_py_dict_bound(py);
840            assert_eq!(
841                32,
842                dict.get_item(7i32)
843                    .unwrap()
844                    .unwrap()
845                    .extract::<i32>()
846                    .unwrap()
847            );
848            assert!(dict.get_item(8i32).unwrap().is_none());
849            let map: HashMap<i32, i32> = [(7, 32)].iter().cloned().collect();
850            assert_eq!(map, dict.extract().unwrap());
851            let map: BTreeMap<i32, i32> = [(7, 32)].iter().cloned().collect();
852            assert_eq!(map, dict.extract().unwrap());
853        });
854    }
855
856    #[test]
857    #[cfg(not(any(PyPy, GraalPy)))]
858    fn test_from_sequence() {
859        Python::with_gil(|py| {
860            let items = PyList::new_bound(py, vec![("a", 1), ("b", 2)]);
861            let dict = PyDict::from_sequence_bound(&items).unwrap();
862            assert_eq!(
863                1,
864                dict.get_item("a")
865                    .unwrap()
866                    .unwrap()
867                    .extract::<i32>()
868                    .unwrap()
869            );
870            assert_eq!(
871                2,
872                dict.get_item("b")
873                    .unwrap()
874                    .unwrap()
875                    .extract::<i32>()
876                    .unwrap()
877            );
878            let map: HashMap<String, i32> =
879                [("a".into(), 1), ("b".into(), 2)].into_iter().collect();
880            assert_eq!(map, dict.extract().unwrap());
881            let map: BTreeMap<String, i32> =
882                [("a".into(), 1), ("b".into(), 2)].into_iter().collect();
883            assert_eq!(map, dict.extract().unwrap());
884        });
885    }
886
887    #[test]
888    #[cfg(not(any(PyPy, GraalPy)))]
889    fn test_from_sequence_err() {
890        Python::with_gil(|py| {
891            let items = PyList::new_bound(py, vec!["a", "b"]);
892            assert!(PyDict::from_sequence_bound(&items).is_err());
893        });
894    }
895
896    #[test]
897    fn test_copy() {
898        Python::with_gil(|py| {
899            let dict = [(7, 32)].into_py_dict_bound(py);
900
901            let ndict = dict.copy().unwrap();
902            assert_eq!(
903                32,
904                ndict
905                    .get_item(7i32)
906                    .unwrap()
907                    .unwrap()
908                    .extract::<i32>()
909                    .unwrap()
910            );
911            assert!(ndict.get_item(8i32).unwrap().is_none());
912        });
913    }
914
915    #[test]
916    fn test_len() {
917        Python::with_gil(|py| {
918            let mut v = HashMap::new();
919            let ob = v.to_object(py);
920            let dict = ob.downcast_bound::<PyDict>(py).unwrap();
921            assert_eq!(0, dict.len());
922            v.insert(7, 32);
923            let ob = v.to_object(py);
924            let dict2 = ob.downcast_bound::<PyDict>(py).unwrap();
925            assert_eq!(1, dict2.len());
926        });
927    }
928
929    #[test]
930    fn test_contains() {
931        Python::with_gil(|py| {
932            let mut v = HashMap::new();
933            v.insert(7, 32);
934            let ob = v.to_object(py);
935            let dict = ob.downcast_bound::<PyDict>(py).unwrap();
936            assert!(dict.contains(7i32).unwrap());
937            assert!(!dict.contains(8i32).unwrap());
938        });
939    }
940
941    #[test]
942    fn test_get_item() {
943        Python::with_gil(|py| {
944            let mut v = HashMap::new();
945            v.insert(7, 32);
946            let ob = v.to_object(py);
947            let dict = ob.downcast_bound::<PyDict>(py).unwrap();
948            assert_eq!(
949                32,
950                dict.get_item(7i32)
951                    .unwrap()
952                    .unwrap()
953                    .extract::<i32>()
954                    .unwrap()
955            );
956            assert!(dict.get_item(8i32).unwrap().is_none());
957        });
958    }
959
960    #[cfg(feature = "macros")]
961    #[test]
962    fn test_get_item_error_path() {
963        use crate::exceptions::PyTypeError;
964
965        #[crate::pyclass(crate = "crate")]
966        struct HashErrors;
967
968        #[crate::pymethods(crate = "crate")]
969        impl HashErrors {
970            #[new]
971            fn new() -> Self {
972                HashErrors {}
973            }
974
975            fn __hash__(&self) -> PyResult<isize> {
976                Err(PyTypeError::new_err("Error from __hash__"))
977            }
978        }
979
980        Python::with_gil(|py| {
981            let class = py.get_type_bound::<HashErrors>();
982            let instance = class.call0().unwrap();
983            let d = PyDict::new_bound(py);
984            match d.get_item(instance) {
985                Ok(_) => {
986                    panic!("this get_item call should always error")
987                }
988                Err(err) => {
989                    assert!(err.is_instance_of::<PyTypeError>(py));
990                    assert_eq!(err.value_bound(py).to_string(), "Error from __hash__")
991                }
992            }
993        })
994    }
995
996    #[test]
997    #[allow(deprecated)]
998    #[cfg(all(not(any(PyPy, GraalPy)), feature = "gil-refs"))]
999    fn test_get_item_with_error() {
1000        Python::with_gil(|py| {
1001            let mut v = HashMap::new();
1002            v.insert(7, 32);
1003            let ob = v.to_object(py);
1004            let dict = ob.downcast::<PyDict>(py).unwrap();
1005            assert_eq!(
1006                32,
1007                dict.get_item_with_error(7i32)
1008                    .unwrap()
1009                    .unwrap()
1010                    .extract::<i32>()
1011                    .unwrap()
1012            );
1013            assert!(dict.get_item_with_error(8i32).unwrap().is_none());
1014            assert!(dict
1015                .get_item_with_error(dict)
1016                .unwrap_err()
1017                .is_instance_of::<crate::exceptions::PyTypeError>(py));
1018        });
1019    }
1020
1021    #[test]
1022    fn test_set_item() {
1023        Python::with_gil(|py| {
1024            let mut v = HashMap::new();
1025            v.insert(7, 32);
1026            let ob = v.to_object(py);
1027            let dict = ob.downcast_bound::<PyDict>(py).unwrap();
1028            assert!(dict.set_item(7i32, 42i32).is_ok()); // change
1029            assert!(dict.set_item(8i32, 123i32).is_ok()); // insert
1030            assert_eq!(
1031                42i32,
1032                dict.get_item(7i32)
1033                    .unwrap()
1034                    .unwrap()
1035                    .extract::<i32>()
1036                    .unwrap()
1037            );
1038            assert_eq!(
1039                123i32,
1040                dict.get_item(8i32)
1041                    .unwrap()
1042                    .unwrap()
1043                    .extract::<i32>()
1044                    .unwrap()
1045            );
1046        });
1047    }
1048
1049    #[test]
1050    fn test_set_item_refcnt() {
1051        Python::with_gil(|py| {
1052            let cnt;
1053            let obj = py.eval_bound("object()", None, None).unwrap();
1054            {
1055                cnt = obj.get_refcnt();
1056                let _dict = [(10, &obj)].into_py_dict_bound(py);
1057            }
1058            {
1059                assert_eq!(cnt, obj.get_refcnt());
1060            }
1061        });
1062    }
1063
1064    #[test]
1065    fn test_set_item_does_not_update_original_object() {
1066        Python::with_gil(|py| {
1067            let mut v = HashMap::new();
1068            v.insert(7, 32);
1069            let ob = v.to_object(py);
1070            let dict = ob.downcast_bound::<PyDict>(py).unwrap();
1071            assert!(dict.set_item(7i32, 42i32).is_ok()); // change
1072            assert!(dict.set_item(8i32, 123i32).is_ok()); // insert
1073            assert_eq!(32i32, v[&7i32]); // not updated!
1074            assert_eq!(None, v.get(&8i32));
1075        });
1076    }
1077
1078    #[test]
1079    fn test_del_item() {
1080        Python::with_gil(|py| {
1081            let mut v = HashMap::new();
1082            v.insert(7, 32);
1083            let ob = v.to_object(py);
1084            let dict = ob.downcast_bound::<PyDict>(py).unwrap();
1085            assert!(dict.del_item(7i32).is_ok());
1086            assert_eq!(0, dict.len());
1087            assert!(dict.get_item(7i32).unwrap().is_none());
1088        });
1089    }
1090
1091    #[test]
1092    fn test_del_item_does_not_update_original_object() {
1093        Python::with_gil(|py| {
1094            let mut v = HashMap::new();
1095            v.insert(7, 32);
1096            let ob = v.to_object(py);
1097            let dict = ob.downcast_bound::<PyDict>(py).unwrap();
1098            assert!(dict.del_item(7i32).is_ok()); // change
1099            assert_eq!(32i32, *v.get(&7i32).unwrap()); // not updated!
1100        });
1101    }
1102
1103    #[test]
1104    fn test_items() {
1105        Python::with_gil(|py| {
1106            let mut v = HashMap::new();
1107            v.insert(7, 32);
1108            v.insert(8, 42);
1109            v.insert(9, 123);
1110            let ob = v.to_object(py);
1111            let dict = ob.downcast_bound::<PyDict>(py).unwrap();
1112            // Can't just compare against a vector of tuples since we don't have a guaranteed ordering.
1113            let mut key_sum = 0;
1114            let mut value_sum = 0;
1115            for el in dict.items() {
1116                let tuple = el.downcast::<PyTuple>().unwrap();
1117                key_sum += tuple.get_item(0).unwrap().extract::<i32>().unwrap();
1118                value_sum += tuple.get_item(1).unwrap().extract::<i32>().unwrap();
1119            }
1120            assert_eq!(7 + 8 + 9, key_sum);
1121            assert_eq!(32 + 42 + 123, value_sum);
1122        });
1123    }
1124
1125    #[test]
1126    fn test_keys() {
1127        Python::with_gil(|py| {
1128            let mut v = HashMap::new();
1129            v.insert(7, 32);
1130            v.insert(8, 42);
1131            v.insert(9, 123);
1132            let ob = v.to_object(py);
1133            let dict = ob.downcast_bound::<PyDict>(py).unwrap();
1134            // Can't just compare against a vector of tuples since we don't have a guaranteed ordering.
1135            let mut key_sum = 0;
1136            for el in dict.keys() {
1137                key_sum += el.extract::<i32>().unwrap();
1138            }
1139            assert_eq!(7 + 8 + 9, key_sum);
1140        });
1141    }
1142
1143    #[test]
1144    fn test_values() {
1145        Python::with_gil(|py| {
1146            let mut v = HashMap::new();
1147            v.insert(7, 32);
1148            v.insert(8, 42);
1149            v.insert(9, 123);
1150            let ob = v.to_object(py);
1151            let dict = ob.downcast_bound::<PyDict>(py).unwrap();
1152            // Can't just compare against a vector of tuples since we don't have a guaranteed ordering.
1153            let mut values_sum = 0;
1154            for el in dict.values() {
1155                values_sum += el.extract::<i32>().unwrap();
1156            }
1157            assert_eq!(32 + 42 + 123, values_sum);
1158        });
1159    }
1160
1161    #[test]
1162    fn test_iter() {
1163        Python::with_gil(|py| {
1164            let mut v = HashMap::new();
1165            v.insert(7, 32);
1166            v.insert(8, 42);
1167            v.insert(9, 123);
1168            let ob = v.to_object(py);
1169            let dict = ob.downcast_bound::<PyDict>(py).unwrap();
1170            let mut key_sum = 0;
1171            let mut value_sum = 0;
1172            for (key, value) in dict {
1173                key_sum += key.extract::<i32>().unwrap();
1174                value_sum += value.extract::<i32>().unwrap();
1175            }
1176            assert_eq!(7 + 8 + 9, key_sum);
1177            assert_eq!(32 + 42 + 123, value_sum);
1178        });
1179    }
1180
1181    #[test]
1182    fn test_iter_bound() {
1183        Python::with_gil(|py| {
1184            let mut v = HashMap::new();
1185            v.insert(7, 32);
1186            v.insert(8, 42);
1187            v.insert(9, 123);
1188            let ob = v.to_object(py);
1189            let dict: &Bound<'_, PyDict> = ob.downcast_bound(py).unwrap();
1190            let mut key_sum = 0;
1191            let mut value_sum = 0;
1192            for (key, value) in dict {
1193                key_sum += key.extract::<i32>().unwrap();
1194                value_sum += value.extract::<i32>().unwrap();
1195            }
1196            assert_eq!(7 + 8 + 9, key_sum);
1197            assert_eq!(32 + 42 + 123, value_sum);
1198        });
1199    }
1200
1201    #[test]
1202    fn test_iter_value_mutated() {
1203        Python::with_gil(|py| {
1204            let mut v = HashMap::new();
1205            v.insert(7, 32);
1206            v.insert(8, 42);
1207            v.insert(9, 123);
1208
1209            let ob = v.to_object(py);
1210            let dict = ob.downcast_bound::<PyDict>(py).unwrap();
1211
1212            for (key, value) in dict {
1213                dict.set_item(key, value.extract::<i32>().unwrap() + 7)
1214                    .unwrap();
1215            }
1216        });
1217    }
1218
1219    #[test]
1220    #[should_panic]
1221    fn test_iter_key_mutated() {
1222        Python::with_gil(|py| {
1223            let mut v = HashMap::new();
1224            for i in 0..10 {
1225                v.insert(i * 2, i * 2);
1226            }
1227            let ob = v.to_object(py);
1228            let dict = ob.downcast_bound::<PyDict>(py).unwrap();
1229
1230            for (i, (key, value)) in dict.iter().enumerate() {
1231                let key = key.extract::<i32>().unwrap();
1232                let value = value.extract::<i32>().unwrap();
1233
1234                dict.set_item(key + 1, value + 1).unwrap();
1235
1236                if i > 1000 {
1237                    // avoid this test just running out of memory if it fails
1238                    break;
1239                };
1240            }
1241        });
1242    }
1243
1244    #[test]
1245    #[should_panic]
1246    fn test_iter_key_mutated_constant_len() {
1247        Python::with_gil(|py| {
1248            let mut v = HashMap::new();
1249            for i in 0..10 {
1250                v.insert(i * 2, i * 2);
1251            }
1252            let ob = v.to_object(py);
1253            let dict = ob.downcast_bound::<PyDict>(py).unwrap();
1254
1255            for (i, (key, value)) in dict.iter().enumerate() {
1256                let key = key.extract::<i32>().unwrap();
1257                let value = value.extract::<i32>().unwrap();
1258                dict.del_item(key).unwrap();
1259                dict.set_item(key + 1, value + 1).unwrap();
1260
1261                if i > 1000 {
1262                    // avoid this test just running out of memory if it fails
1263                    break;
1264                };
1265            }
1266        });
1267    }
1268
1269    #[test]
1270    fn test_iter_size_hint() {
1271        Python::with_gil(|py| {
1272            let mut v = HashMap::new();
1273            v.insert(7, 32);
1274            v.insert(8, 42);
1275            v.insert(9, 123);
1276            let ob = v.to_object(py);
1277            let dict = ob.downcast_bound::<PyDict>(py).unwrap();
1278
1279            let mut iter = dict.iter();
1280            assert_eq!(iter.size_hint(), (v.len(), Some(v.len())));
1281            iter.next();
1282            assert_eq!(iter.size_hint(), (v.len() - 1, Some(v.len() - 1)));
1283
1284            // Exhaust iterator.
1285            for _ in &mut iter {}
1286
1287            assert_eq!(iter.size_hint(), (0, Some(0)));
1288
1289            assert!(iter.next().is_none());
1290
1291            assert_eq!(iter.size_hint(), (0, Some(0)));
1292        });
1293    }
1294
1295    #[test]
1296    fn test_into_iter() {
1297        Python::with_gil(|py| {
1298            let mut v = HashMap::new();
1299            v.insert(7, 32);
1300            v.insert(8, 42);
1301            v.insert(9, 123);
1302            let ob = v.to_object(py);
1303            let dict = ob.downcast_bound::<PyDict>(py).unwrap();
1304            let mut key_sum = 0;
1305            let mut value_sum = 0;
1306            for (key, value) in dict {
1307                key_sum += key.extract::<i32>().unwrap();
1308                value_sum += value.extract::<i32>().unwrap();
1309            }
1310            assert_eq!(7 + 8 + 9, key_sum);
1311            assert_eq!(32 + 42 + 123, value_sum);
1312        });
1313    }
1314
1315    #[test]
1316    fn test_hashmap_into_dict() {
1317        Python::with_gil(|py| {
1318            let mut map = HashMap::<i32, i32>::new();
1319            map.insert(1, 1);
1320
1321            let py_map = map.into_py_dict_bound(py);
1322
1323            assert_eq!(py_map.len(), 1);
1324            assert_eq!(
1325                py_map
1326                    .get_item(1)
1327                    .unwrap()
1328                    .unwrap()
1329                    .extract::<i32>()
1330                    .unwrap(),
1331                1
1332            );
1333        });
1334    }
1335
1336    #[test]
1337    fn test_btreemap_into_dict() {
1338        Python::with_gil(|py| {
1339            let mut map = BTreeMap::<i32, i32>::new();
1340            map.insert(1, 1);
1341
1342            let py_map = map.into_py_dict_bound(py);
1343
1344            assert_eq!(py_map.len(), 1);
1345            assert_eq!(
1346                py_map
1347                    .get_item(1)
1348                    .unwrap()
1349                    .unwrap()
1350                    .extract::<i32>()
1351                    .unwrap(),
1352                1
1353            );
1354        });
1355    }
1356
1357    #[test]
1358    fn test_vec_into_dict() {
1359        Python::with_gil(|py| {
1360            let vec = vec![("a", 1), ("b", 2), ("c", 3)];
1361            let py_map = vec.into_py_dict_bound(py);
1362
1363            assert_eq!(py_map.len(), 3);
1364            assert_eq!(
1365                py_map
1366                    .get_item("b")
1367                    .unwrap()
1368                    .unwrap()
1369                    .extract::<i32>()
1370                    .unwrap(),
1371                2
1372            );
1373        });
1374    }
1375
1376    #[test]
1377    fn test_slice_into_dict() {
1378        Python::with_gil(|py| {
1379            let arr = [("a", 1), ("b", 2), ("c", 3)];
1380            let py_map = arr.into_py_dict_bound(py);
1381
1382            assert_eq!(py_map.len(), 3);
1383            assert_eq!(
1384                py_map
1385                    .get_item("b")
1386                    .unwrap()
1387                    .unwrap()
1388                    .extract::<i32>()
1389                    .unwrap(),
1390                2
1391            );
1392        });
1393    }
1394
1395    #[test]
1396    fn dict_as_mapping() {
1397        Python::with_gil(|py| {
1398            let mut map = HashMap::<i32, i32>::new();
1399            map.insert(1, 1);
1400
1401            let py_map = map.into_py_dict_bound(py);
1402
1403            assert_eq!(py_map.as_mapping().len().unwrap(), 1);
1404            assert_eq!(
1405                py_map
1406                    .as_mapping()
1407                    .get_item(1)
1408                    .unwrap()
1409                    .extract::<i32>()
1410                    .unwrap(),
1411                1
1412            );
1413        });
1414    }
1415
1416    #[test]
1417    fn dict_into_mapping() {
1418        Python::with_gil(|py| {
1419            let mut map = HashMap::<i32, i32>::new();
1420            map.insert(1, 1);
1421
1422            let py_map = map.into_py_dict_bound(py);
1423
1424            let py_mapping = py_map.into_mapping();
1425            assert_eq!(py_mapping.len().unwrap(), 1);
1426            assert_eq!(py_mapping.get_item(1).unwrap().extract::<i32>().unwrap(), 1);
1427        });
1428    }
1429
1430    #[cfg(not(any(PyPy, GraalPy)))]
1431    fn abc_dict(py: Python<'_>) -> Bound<'_, PyDict> {
1432        let mut map = HashMap::<&'static str, i32>::new();
1433        map.insert("a", 1);
1434        map.insert("b", 2);
1435        map.insert("c", 3);
1436        map.into_py_dict_bound(py)
1437    }
1438
1439    #[test]
1440    #[cfg(not(any(PyPy, GraalPy)))]
1441    fn dict_keys_view() {
1442        Python::with_gil(|py| {
1443            let dict = abc_dict(py);
1444            let keys = dict.call_method0("keys").unwrap();
1445            assert!(keys
1446                .is_instance(&py.get_type_bound::<PyDictKeys>().as_borrowed())
1447                .unwrap());
1448        })
1449    }
1450
1451    #[test]
1452    #[cfg(not(any(PyPy, GraalPy)))]
1453    fn dict_values_view() {
1454        Python::with_gil(|py| {
1455            let dict = abc_dict(py);
1456            let values = dict.call_method0("values").unwrap();
1457            assert!(values
1458                .is_instance(&py.get_type_bound::<PyDictValues>().as_borrowed())
1459                .unwrap());
1460        })
1461    }
1462
1463    #[test]
1464    #[cfg(not(any(PyPy, GraalPy)))]
1465    fn dict_items_view() {
1466        Python::with_gil(|py| {
1467            let dict = abc_dict(py);
1468            let items = dict.call_method0("items").unwrap();
1469            assert!(items
1470                .is_instance(&py.get_type_bound::<PyDictItems>().as_borrowed())
1471                .unwrap());
1472        })
1473    }
1474
1475    #[test]
1476    fn dict_update() {
1477        Python::with_gil(|py| {
1478            let dict = [("a", 1), ("b", 2), ("c", 3)].into_py_dict_bound(py);
1479            let other = [("b", 4), ("c", 5), ("d", 6)].into_py_dict_bound(py);
1480            dict.update(other.as_mapping()).unwrap();
1481            assert_eq!(dict.len(), 4);
1482            assert_eq!(
1483                dict.get_item("a")
1484                    .unwrap()
1485                    .unwrap()
1486                    .extract::<i32>()
1487                    .unwrap(),
1488                1
1489            );
1490            assert_eq!(
1491                dict.get_item("b")
1492                    .unwrap()
1493                    .unwrap()
1494                    .extract::<i32>()
1495                    .unwrap(),
1496                4
1497            );
1498            assert_eq!(
1499                dict.get_item("c")
1500                    .unwrap()
1501                    .unwrap()
1502                    .extract::<i32>()
1503                    .unwrap(),
1504                5
1505            );
1506            assert_eq!(
1507                dict.get_item("d")
1508                    .unwrap()
1509                    .unwrap()
1510                    .extract::<i32>()
1511                    .unwrap(),
1512                6
1513            );
1514
1515            assert_eq!(other.len(), 3);
1516            assert_eq!(
1517                other
1518                    .get_item("b")
1519                    .unwrap()
1520                    .unwrap()
1521                    .extract::<i32>()
1522                    .unwrap(),
1523                4
1524            );
1525            assert_eq!(
1526                other
1527                    .get_item("c")
1528                    .unwrap()
1529                    .unwrap()
1530                    .extract::<i32>()
1531                    .unwrap(),
1532                5
1533            );
1534            assert_eq!(
1535                other
1536                    .get_item("d")
1537                    .unwrap()
1538                    .unwrap()
1539                    .extract::<i32>()
1540                    .unwrap(),
1541                6
1542            );
1543        })
1544    }
1545
1546    #[test]
1547    fn dict_update_if_missing() {
1548        Python::with_gil(|py| {
1549            let dict = [("a", 1), ("b", 2), ("c", 3)].into_py_dict_bound(py);
1550            let other = [("b", 4), ("c", 5), ("d", 6)].into_py_dict_bound(py);
1551            dict.update_if_missing(other.as_mapping()).unwrap();
1552            assert_eq!(dict.len(), 4);
1553            assert_eq!(
1554                dict.get_item("a")
1555                    .unwrap()
1556                    .unwrap()
1557                    .extract::<i32>()
1558                    .unwrap(),
1559                1
1560            );
1561            assert_eq!(
1562                dict.get_item("b")
1563                    .unwrap()
1564                    .unwrap()
1565                    .extract::<i32>()
1566                    .unwrap(),
1567                2
1568            );
1569            assert_eq!(
1570                dict.get_item("c")
1571                    .unwrap()
1572                    .unwrap()
1573                    .extract::<i32>()
1574                    .unwrap(),
1575                3
1576            );
1577            assert_eq!(
1578                dict.get_item("d")
1579                    .unwrap()
1580                    .unwrap()
1581                    .extract::<i32>()
1582                    .unwrap(),
1583                6
1584            );
1585
1586            assert_eq!(other.len(), 3);
1587            assert_eq!(
1588                other
1589                    .get_item("b")
1590                    .unwrap()
1591                    .unwrap()
1592                    .extract::<i32>()
1593                    .unwrap(),
1594                4
1595            );
1596            assert_eq!(
1597                other
1598                    .get_item("c")
1599                    .unwrap()
1600                    .unwrap()
1601                    .extract::<i32>()
1602                    .unwrap(),
1603                5
1604            );
1605            assert_eq!(
1606                other
1607                    .get_item("d")
1608                    .unwrap()
1609                    .unwrap()
1610                    .extract::<i32>()
1611                    .unwrap(),
1612                6
1613            );
1614        })
1615    }
1616}