From 009cd32a444ceacd6142f8a5195d9b38ccfa5883 Mon Sep 17 00:00:00 2001 From: Lily Foote Date: Sat, 23 Mar 2024 01:13:24 +0000 Subject: [PATCH] Add into_mapping and into_sequence methods to Bound types (#3982) * Add Bound<'py, PyDict>.into_mapping method * Add Bound<'py, PyTuple>.into_sequence method * Add Bound<'py, PyList>.into_sequence method * Add newsfragment * Add tests for into_mapping and into_sequence --- newsfragments/3982.added.md | 1 + src/types/dict.rs | 21 +++++++++++++++++++ src/types/list.rs | 40 +++++++++++++++++++++++++++++++++++++ src/types/tuple.rs | 19 +++++++++++++++++- 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 newsfragments/3982.added.md diff --git a/newsfragments/3982.added.md b/newsfragments/3982.added.md new file mode 100644 index 00000000..abc89574 --- /dev/null +++ b/newsfragments/3982.added.md @@ -0,0 +1 @@ +Added `Bound<'py, PyDict>::into_mapping`, `Bound<'py, PyList>::into_sequence` and `Bound<'py, PyTuple>::into_sequence`. diff --git a/src/types/dict.rs b/src/types/dict.rs index 2302004d..43bade5a 100644 --- a/src/types/dict.rs +++ b/src/types/dict.rs @@ -365,6 +365,9 @@ pub trait PyDictMethods<'py>: crate::sealed::Sealed { /// Returns `self` cast as a `PyMapping`. fn as_mapping(&self) -> &Bound<'py, PyMapping>; + /// Returns `self` cast as a `PyMapping`. + fn into_mapping(self) -> Bound<'py, PyMapping>; + /// Update this dictionary with the key/value pairs from another. /// /// This is equivalent to the Python expression `self.update(other)`. If `other` is a `PyDict`, you may want @@ -511,6 +514,10 @@ impl<'py> PyDictMethods<'py> for Bound<'py, PyDict> { unsafe { self.downcast_unchecked() } } + fn into_mapping(self) -> Bound<'py, PyMapping> { + unsafe { self.into_any().downcast_into_unchecked() } + } + fn update(&self, other: &Bound<'_, PyMapping>) -> PyResult<()> { err::error_on_minusone(self.py(), unsafe { ffi::PyDict_Update(self.as_ptr(), other.as_ptr()) @@ -1367,6 +1374,20 @@ mod tests { }); } + #[test] + fn dict_into_mapping() { + Python::with_gil(|py| { + let mut map = HashMap::::new(); + map.insert(1, 1); + + let py_map = map.into_py_dict_bound(py); + + let py_mapping = py_map.into_mapping(); + assert_eq!(py_mapping.len().unwrap(), 1); + assert_eq!(py_mapping.get_item(1).unwrap().extract::().unwrap(), 1); + }); + } + #[cfg(not(PyPy))] fn abc_dict(py: Python<'_>) -> Bound<'_, PyDict> { let mut map = HashMap::<&'static str, i32>::new(); diff --git a/src/types/list.rs b/src/types/list.rs index 3aa7ddca..19dbe595 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -295,6 +295,9 @@ pub trait PyListMethods<'py>: crate::sealed::Sealed { /// Returns `self` cast as a `PySequence`. fn as_sequence(&self) -> &Bound<'py, PySequence>; + /// Returns `self` cast as a `PySequence`. + fn into_sequence(self) -> Bound<'py, PySequence>; + /// Gets the list item at the specified index. /// # Example /// ``` @@ -408,6 +411,11 @@ impl<'py> PyListMethods<'py> for Bound<'py, PyList> { unsafe { self.downcast_unchecked() } } + /// Returns `self` cast as a `PySequence`. + fn into_sequence(self) -> Bound<'py, PySequence> { + unsafe { self.into_any().downcast_into_unchecked() } + } + /// Gets the list item at the specified index. /// # Example /// ``` @@ -715,6 +723,9 @@ impl<'py> IntoIterator for &Bound<'py, PyList> { #[cfg(test)] #[cfg_attr(not(feature = "gil-refs"), allow(deprecated))] mod tests { + use crate::types::any::PyAnyMethods; + use crate::types::list::PyListMethods; + use crate::types::sequence::PySequenceMethods; use crate::types::{PyList, PyTuple}; use crate::Python; use crate::{IntoPy, PyObject, ToPyObject}; @@ -934,6 +945,35 @@ mod tests { }); } + #[test] + fn test_as_sequence() { + Python::with_gil(|py| { + let list = PyList::new_bound(py, [1, 2, 3, 4]); + + assert_eq!(list.as_sequence().len().unwrap(), 4); + assert_eq!( + list.as_sequence() + .get_item(1) + .unwrap() + .extract::() + .unwrap(), + 2 + ); + }); + } + + #[test] + fn test_into_sequence() { + Python::with_gil(|py| { + let list = PyList::new_bound(py, [1, 2, 3, 4]); + + let sequence = list.into_sequence(); + + assert_eq!(sequence.len().unwrap(), 4); + assert_eq!(sequence.get_item(1).unwrap().extract::().unwrap(), 2); + }); + } + #[test] fn test_extract() { Python::with_gil(|py| { diff --git a/src/types/tuple.rs b/src/types/tuple.rs index d89830da..4dfe4436 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -258,6 +258,9 @@ pub trait PyTupleMethods<'py>: crate::sealed::Sealed { /// Returns `self` cast as a `PySequence`. fn as_sequence(&self) -> &Bound<'py, PySequence>; + /// Returns `self` cast as a `PySequence`. + fn into_sequence(self) -> Bound<'py, PySequence>; + /// Takes the slice `self[low:high]` and returns it as a new tuple. /// /// Indices must be nonnegative, and out-of-range indices are clipped to @@ -353,6 +356,10 @@ impl<'py> PyTupleMethods<'py> for Bound<'py, PyTuple> { unsafe { self.downcast_unchecked() } } + fn into_sequence(self) -> Bound<'py, PySequence> { + unsafe { self.into_any().downcast_into_unchecked() } + } + fn get_slice(&self, low: usize, high: usize) -> Bound<'py, PyTuple> { unsafe { ffi::PyTuple_GetSlice(self.as_ptr(), get_ssize_index(low), get_ssize_index(high)) @@ -1415,7 +1422,7 @@ mod tests { #[test] fn test_tuple_as_sequence() { Python::with_gil(|py| { - let tuple = PyTuple::new(py, vec![1, 2, 3]); + let tuple = PyTuple::new_bound(py, vec![1, 2, 3]); let sequence = tuple.as_sequence(); assert!(tuple.get_item(0).unwrap().eq(1).unwrap()); assert!(sequence.get_item(0).unwrap().eq(1).unwrap()); @@ -1425,6 +1432,16 @@ mod tests { }) } + #[test] + fn test_tuple_into_sequence() { + Python::with_gil(|py| { + let tuple = PyTuple::new_bound(py, vec![1, 2, 3]); + let sequence = tuple.into_sequence(); + assert!(sequence.get_item(0).unwrap().eq(1).unwrap()); + assert_eq!(sequence.len().unwrap(), 3); + }) + } + #[test] fn test_bound_tuple_get_item() { Python::with_gil(|py| {