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
This commit is contained in:
Lily Foote 2024-03-23 01:13:24 +00:00 committed by GitHub
parent 9808f7111c
commit 009cd32a44
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 80 additions and 1 deletions

View file

@ -0,0 +1 @@
Added `Bound<'py, PyDict>::into_mapping`, `Bound<'py, PyList>::into_sequence` and `Bound<'py, PyTuple>::into_sequence`.

View file

@ -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::<i32, i32>::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::<i32>().unwrap(), 1);
});
}
#[cfg(not(PyPy))]
fn abc_dict(py: Python<'_>) -> Bound<'_, PyDict> {
let mut map = HashMap::<&'static str, i32>::new();

View file

@ -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::<i32>()
.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::<i32>().unwrap(), 2);
});
}
#[test]
fn test_extract() {
Python::with_gil(|py| {

View file

@ -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| {