Skip to content

Commit

Permalink
Add into_mapping and into_sequence methods to Bound types (#3982)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
LilyFoote authored Mar 23, 2024
1 parent 9808f71 commit 009cd32
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 1 deletion.
1 change: 1 addition & 0 deletions newsfragments/3982.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added `Bound<'py, PyDict>::into_mapping`, `Bound<'py, PyList>::into_sequence` and `Bound<'py, PyTuple>::into_sequence`.
21 changes: 21 additions & 0 deletions src/types/dict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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();
Expand Down
40 changes: 40 additions & 0 deletions src/types/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
/// ```
Expand Down Expand Up @@ -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
/// ```
Expand Down Expand Up @@ -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};
Expand Down Expand Up @@ -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| {
Expand Down
19 changes: 18 additions & 1 deletion src/types/tuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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());
Expand All @@ -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| {
Expand Down

0 comments on commit 009cd32

Please sign in to comment.