port `PyObject::downcast` to `Bound` API (#3856)

* port `PyObject::downcast` to `Bound` API

* relax traits bounds for unchecked variant in `Bound` API

* deprecate `Python::(checked_)cast_as`

* reword deprecation warning
This commit is contained in:
Icxolu 2024-02-18 02:11:43 +01:00 committed by GitHub
parent 0dd568d397
commit 1d295a12a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 97 additions and 37 deletions

View File

@ -121,7 +121,7 @@ mod tests {
map.insert(1, 1);
let m = map.to_object(py);
let py_map: &PyDict = m.downcast(py).unwrap();
let py_map = m.downcast_bound::<PyDict>(py).unwrap();
assert!(py_map.len() == 1);
assert!(
@ -143,7 +143,7 @@ mod tests {
map.insert(1, 1);
let m: PyObject = map.into_py(py);
let py_map: &PyDict = m.downcast(py).unwrap();
let py_map = m.downcast_bound::<PyDict>(py).unwrap();
assert!(py_map.len() == 1);
assert!(

View File

@ -149,7 +149,7 @@ mod test_indexmap {
map.insert(1, 1);
let m = map.to_object(py);
let py_map: &PyDict = m.downcast(py).unwrap();
let py_map = m.downcast_bound::<PyDict>(py).unwrap();
assert!(py_map.len() == 1);
assert!(
@ -175,7 +175,7 @@ mod test_indexmap {
map.insert(1, 1);
let m: PyObject = map.into_py(py);
let py_map: &PyDict = m.downcast(py).unwrap();
let py_map = m.downcast_bound::<PyDict>(py).unwrap();
assert!(py_map.len() == 1);
assert!(

View File

@ -239,7 +239,7 @@ mod tests {
Python::with_gil(|py| {
let array: [Foo; 8] = [Foo, Foo, Foo, Foo, Foo, Foo, Foo, Foo];
let pyobject = array.into_py(py);
let list: &PyList = pyobject.downcast(py).unwrap();
let list = pyobject.downcast_bound::<PyList>(py).unwrap();
let _cell: &crate::PyCell<Foo> = list.get_item(4).unwrap().extract().unwrap();
});
}

View File

@ -121,7 +121,7 @@ mod tests {
map.insert(1, 1);
let m = map.to_object(py);
let py_map: &PyDict = m.downcast(py).unwrap();
let py_map = m.downcast_bound::<PyDict>(py).unwrap();
assert!(py_map.len() == 1);
assert!(
@ -144,7 +144,7 @@ mod tests {
map.insert(1, 1);
let m = map.to_object(py);
let py_map: &PyDict = m.downcast(py).unwrap();
let py_map = m.downcast_bound::<PyDict>(py).unwrap();
assert!(py_map.len() == 1);
assert!(
@ -167,7 +167,7 @@ mod tests {
map.insert(1, 1);
let m: PyObject = map.into_py(py);
let py_map: &PyDict = m.downcast(py).unwrap();
let py_map = m.downcast_bound::<PyDict>(py).unwrap();
assert!(py_map.len() == 1);
assert!(
@ -189,7 +189,7 @@ mod tests {
map.insert(1, 1);
let m: PyObject = map.into_py(py);
let py_map: &PyDict = m.downcast(py).unwrap();
let py_map = m.downcast_bound::<PyDict>(py).unwrap();
assert!(py_map.len() == 1);
assert!(

View File

@ -62,6 +62,15 @@ impl<'a> PyDowncastError<'a> {
to: to.into(),
}
}
/// Compatibility API to convert the Bound variant `DowncastError` into the
/// gil-ref variant
pub(crate) fn from_downcast_err(DowncastError { from, to }: DowncastError<'a, 'a>) -> Self {
Self {
from: from.as_gil_ref(),
to,
}
}
}
/// Error that indicates a failure to convert a PyAny to a more specific Python type.

View File

@ -7,8 +7,8 @@ use crate::types::any::PyAnyMethods;
use crate::types::string::PyStringMethods;
use crate::types::{PyDict, PyString, PyTuple};
use crate::{
ffi, AsPyPointer, FromPyObject, IntoPy, PyAny, PyClass, PyClassInitializer, PyRef, PyRefMut,
PyTypeInfo, Python, ToPyObject,
ffi, AsPyPointer, DowncastError, FromPyObject, IntoPy, PyAny, PyClass, PyClassInitializer,
PyRef, PyRefMut, PyTypeInfo, Python, ToPyObject,
};
use crate::{gil, PyTypeCheck};
use std::marker::PhantomData;
@ -1686,6 +1686,23 @@ impl<T> std::fmt::Debug for Py<T> {
pub type PyObject = Py<PyAny>;
impl PyObject {
/// Deprecated form of [`PyObject::downcast_bound`]
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyObject::downcast` will be replaced by `PyObject::downcast_bound` in a future PyO3 version"
)
)]
#[inline]
pub fn downcast<'py, T>(&'py self, py: Python<'py>) -> Result<&'py T, PyDowncastError<'py>>
where
T: PyTypeCheck<AsRefTarget = T>,
{
self.downcast_bound::<T>(py)
.map(Bound::as_gil_ref)
.map_err(PyDowncastError::from_downcast_err)
}
/// Downcast this `PyObject` to a concrete Python type or pyclass.
///
/// Note that you can often avoid downcasting yourself by just specifying
@ -1703,8 +1720,8 @@ impl PyObject {
/// Python::with_gil(|py| {
/// let any: PyObject = PyDict::new_bound(py).into();
///
/// assert!(any.downcast::<PyDict>(py).is_ok());
/// assert!(any.downcast::<PyList>(py).is_err());
/// assert!(any.downcast_bound::<PyDict>(py).is_ok());
/// assert!(any.downcast_bound::<PyList>(py).is_err());
/// });
/// ```
///
@ -1725,9 +1742,9 @@ impl PyObject {
/// Python::with_gil(|py| {
/// let class: PyObject = Py::new(py, Class { i: 0 }).unwrap().into_py(py);
///
/// let class_cell: &PyCell<Class> = class.downcast(py)?;
/// let class_bound = class.downcast_bound::<Class>(py)?;
///
/// class_cell.borrow_mut().i += 1;
/// class_bound.borrow_mut().i += 1;
///
/// // Alternatively you can get a `PyRefMut` directly
/// let class_ref: PyRefMut<'_, Class> = class.extract(py)?;
@ -1737,11 +1754,34 @@ impl PyObject {
/// # }
/// ```
#[inline]
pub fn downcast<'py, T>(&'py self, py: Python<'py>) -> Result<&'py T, PyDowncastError<'py>>
pub fn downcast_bound<'py, T>(
&self,
py: Python<'py>,
) -> Result<&Bound<'py, T>, DowncastError<'_, 'py>>
where
T: PyTypeCheck<AsRefTarget = T>,
T: PyTypeCheck,
{
self.as_ref(py).downcast()
self.bind(py).downcast()
}
/// Deprecated form of [`PyObject::downcast_bound_unchecked`]
///
/// # Safety
///
/// Callers must ensure that the type is valid or risk type confusion.
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyObject::downcast_unchecked` will be replaced by `PyObject::downcast_bound_unchecked` in a future PyO3 version"
)
)]
#[inline]
pub unsafe fn downcast_unchecked<'py, T>(&'py self, py: Python<'py>) -> &T
where
T: HasPyGilRef<AsRefTarget = T>,
{
self.downcast_bound_unchecked::<T>(py).as_gil_ref()
}
/// Casts the PyObject to a concrete Python object type without checking validity.
@ -1750,11 +1790,8 @@ impl PyObject {
///
/// Callers must ensure that the type is valid or risk type confusion.
#[inline]
pub unsafe fn downcast_unchecked<'p, T>(&'p self, py: Python<'p>) -> &T
where
T: HasPyGilRef<AsRefTarget = T>,
{
self.as_ref(py).downcast_unchecked()
pub unsafe fn downcast_bound_unchecked<'py, T>(&self, py: Python<'py>) -> &Bound<'py, T> {
self.bind(py).downcast_unchecked()
}
}

View File

@ -826,6 +826,13 @@ impl<'py> Python<'py> {
}
/// Registers the object in the release pool, and tries to downcast to specific type.
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "part of the deprecated GIL Ref API; to migrate use `obj.downcast_bound::<T>(py)` instead of `py.checked_cast_as::<T>(obj)`"
)
)]
pub fn checked_cast_as<T>(self, obj: PyObject) -> Result<&'py T, PyDowncastError<'py>>
where
T: PyTypeCheck<AsRefTarget = T>,
@ -839,6 +846,13 @@ impl<'py> Python<'py> {
/// # Safety
///
/// Callers must ensure that ensure that the cast is valid.
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "part of the deprecated GIL Ref API; to migrate use `obj.downcast_bound_unchecked::<T>(py)` instead of `py.cast_as::<T>(obj)`"
)
)]
pub unsafe fn cast_as<T>(self, obj: PyObject) -> &'py T
where
T: HasPyGilRef<AsRefTarget = T>,

View File

@ -299,13 +299,13 @@ mod tests {
Python::with_gil(|py| {
let mut v = HashMap::new();
let ob = v.to_object(py);
let mapping: &PyMapping = ob.downcast(py).unwrap();
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: &PyMapping = ob.downcast(py).unwrap();
let mapping2 = ob.downcast_bound::<PyMapping>(py).unwrap();
assert_eq!(1, mapping2.len().unwrap());
assert!(!mapping2.is_empty().unwrap());
});
@ -317,7 +317,7 @@ mod tests {
let mut v = HashMap::new();
v.insert("key0", 1234);
let ob = v.to_object(py);
let mapping: &PyMapping = ob.downcast(py).unwrap();
let mapping = ob.downcast_bound::<PyMapping>(py).unwrap();
mapping.set_item("key1", "foo").unwrap();
assert!(mapping.contains("key0").unwrap());
@ -332,7 +332,7 @@ mod tests {
let mut v = HashMap::new();
v.insert(7, 32);
let ob = v.to_object(py);
let mapping: &PyMapping = ob.downcast(py).unwrap();
let mapping = ob.downcast_bound::<PyMapping>(py).unwrap();
assert_eq!(
32,
mapping.get_item(7i32).unwrap().extract::<i32>().unwrap()
@ -350,7 +350,7 @@ mod tests {
let mut v = HashMap::new();
v.insert(7, 32);
let ob = v.to_object(py);
let mapping: &PyMapping = ob.downcast(py).unwrap();
let mapping = ob.downcast_bound::<PyMapping>(py).unwrap();
assert!(mapping.set_item(7i32, 42i32).is_ok()); // change
assert!(mapping.set_item(8i32, 123i32).is_ok()); // insert
assert_eq!(
@ -370,7 +370,7 @@ mod tests {
let mut v = HashMap::new();
v.insert(7, 32);
let ob = v.to_object(py);
let mapping: &PyMapping = ob.downcast(py).unwrap();
let mapping = ob.downcast_bound::<PyMapping>(py).unwrap();
assert!(mapping.del_item(7i32).is_ok());
assert_eq!(0, mapping.len().unwrap());
assert!(mapping
@ -388,12 +388,12 @@ mod tests {
v.insert(8, 42);
v.insert(9, 123);
let ob = v.to_object(py);
let mapping: &PyMapping = ob.downcast(py).unwrap();
let mapping = ob.downcast_bound::<PyMapping>(py).unwrap();
// Can't just compare against a vector of tuples since we don't have a guaranteed ordering.
let mut key_sum = 0;
let mut value_sum = 0;
for el in mapping.items().unwrap().iter().unwrap() {
let tuple = el.unwrap().downcast::<PyTuple>().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();
}
@ -410,7 +410,7 @@ mod tests {
v.insert(8, 42);
v.insert(9, 123);
let ob = v.to_object(py);
let mapping: &PyMapping = ob.downcast(py).unwrap();
let mapping = ob.downcast_bound::<PyMapping>(py).unwrap();
// Can't just compare against a vector of tuples since we don't have a guaranteed ordering.
let mut key_sum = 0;
for el in mapping.keys().unwrap().iter().unwrap() {
@ -428,7 +428,7 @@ mod tests {
v.insert(8, 42);
v.insert(9, 123);
let ob = v.to_object(py);
let mapping: &PyMapping = ob.downcast(py).unwrap();
let mapping = ob.downcast_bound::<PyMapping>(py).unwrap();
// Can't just compare against a vector of tuples since we don't have a guaranteed ordering.
let mut values_sum = 0;
for el in mapping.values().unwrap().iter().unwrap() {

View File

@ -103,7 +103,7 @@ mod tests {
#[test]
fn test_unit_to_object_is_none() {
Python::with_gil(|py| {
assert!(().to_object(py).downcast::<PyNone>(py).is_ok());
assert!(().to_object(py).downcast_bound::<PyNone>(py).is_ok());
})
}
@ -111,7 +111,7 @@ mod tests {
fn test_unit_into_py_is_none() {
Python::with_gil(|py| {
let obj: PyObject = ().into_py(py);
assert!(obj.downcast::<PyNone>(py).is_ok());
assert!(obj.downcast_bound::<PyNone>(py).is_ok());
})
}

View File

@ -169,7 +169,7 @@ impl PyTuple {
/// # fn main() -> PyResult<()> {
/// Python::with_gil(|py| -> PyResult<()> {
/// let ob = (1, 2, 3).to_object(py);
/// let tuple: &PyTuple = ob.downcast(py).unwrap();
/// let tuple = ob.downcast_bound::<PyTuple>(py).unwrap();
/// let obj = tuple.get_item(0);
/// assert_eq!(obj.unwrap().extract::<i32>().unwrap(), 1);
/// Ok(())
@ -273,7 +273,7 @@ pub trait PyTupleMethods<'py> {
/// # fn main() -> PyResult<()> {
/// Python::with_gil(|py| -> PyResult<()> {
/// let ob = (1, 2, 3).to_object(py);
/// let tuple: &PyTuple = ob.downcast(py).unwrap();
/// let tuple = ob.downcast_bound::<PyTuple>(py).unwrap();
/// let obj = tuple.get_item(0);
/// assert_eq!(obj.unwrap().extract::<i32>().unwrap(), 1);
/// Ok(())