feature gate deprecated APIs for `Py` (#4142)

This commit is contained in:
Icxolu 2024-05-01 12:57:03 +02:00 committed by GitHub
parent 261d27d197
commit dc9a41521a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 88 additions and 91 deletions

View File

@ -177,9 +177,11 @@ What happens to the memory when the last `Py<PyAny>` is dropped and its
reference count reaches zero? It depends whether or not we are holding the GIL.
```rust
# #![allow(unused_imports)]
# use pyo3::prelude::*;
# use pyo3::types::PyString;
# fn main() -> PyResult<()> {
# #[cfg(feature = "gil-refs")]
Python::with_gil(|py| -> PyResult<()> {
#[allow(deprecated)] // py.eval() is part of the GIL Refs API
let hello: Py<PyString> = py.eval("\"Hello World!\"", None, None)?.extract()?;
@ -203,9 +205,12 @@ This example wasn't very interesting. We could have just used a GIL-bound
we are *not* holding the GIL?
```rust
# #![allow(unused_imports)]
# use pyo3::prelude::*;
# use pyo3::types::PyString;
# fn main() -> PyResult<()> {
# #[cfg(feature = "gil-refs")]
# {
let hello: Py<PyString> = Python::with_gil(|py| {
#[allow(deprecated)] // py.eval() is part of the GIL Refs API
py.eval("\"Hello World!\"", None, None)?.extract()
@ -224,6 +229,7 @@ Python::with_gil(|py|
// Memory for `hello` is released here.
# ()
);
# }
# Ok(())
# }
```
@ -237,9 +243,12 @@ We can avoid the delay in releasing memory if we are careful to drop the
`Py<Any>` while the GIL is held.
```rust
# #![allow(unused_imports)]
# use pyo3::prelude::*;
# use pyo3::types::PyString;
# fn main() -> PyResult<()> {
# #[cfg(feature = "gil-refs")]
# {
#[allow(deprecated)] // py.eval() is part of the GIL Refs API
let hello: Py<PyString> =
Python::with_gil(|py| py.eval("\"Hello World!\"", None, None)?.extract())?;
@ -252,6 +261,7 @@ Python::with_gil(|py| {
}
drop(hello); // Memory released here.
});
# }
# Ok(())
# }
```
@ -263,9 +273,12 @@ that rather than being released immediately, the memory will not be released
until the GIL is dropped.
```rust
# #![allow(unused_imports)]
# use pyo3::prelude::*;
# use pyo3::types::PyString;
# fn main() -> PyResult<()> {
# #[cfg(feature = "gil-refs")]
# {
#[allow(deprecated)] // py.eval() is part of the GIL Refs API
let hello: Py<PyString> =
Python::with_gil(|py| py.eval("\"Hello World!\"", None, None)?.extract())?;
@ -280,6 +293,7 @@ Python::with_gil(|py| {
// Do more stuff...
// Memory released here at end of `with_gil()` closure.
});
# }
# Ok(())
# }
```

View File

@ -107,7 +107,7 @@ fn main() -> PyResult<()> {
<div class="warning">
During PyO3's [migration from "GIL Refs" to the `Bound<T>` smart pointer](../migration.md#migrating-from-the-gil-refs-api-to-boundt), [`Py<T>::call`]({{#PYO3_DOCS_URL}}/pyo3/struct.Py.html#method.call) is temporarily named `call_bound` (and `call_method` is temporarily `call_method_bound`).
During PyO3's [migration from "GIL Refs" to the `Bound<T>` smart pointer](../migration.md#migrating-from-the-gil-refs-api-to-boundt), `Py<T>::call` is temporarily named [`Py<T>::call_bound`]({{#PYO3_DOCS_URL}}/pyo3/struct.Py.html#method.call_bound) (and `call_method` is temporarily `call_method_bound`).
(This temporary naming is only the case for the `Py<T>` smart pointer. The methods on the `&PyAny` GIL Ref such as `call` have not been given replacements, and the methods on the `Bound<PyAny>` smart pointer such as [`Bound<PyAny>::call`]({{#PYO3_DOCS_URL}}/pyo3/types/trait.PyAnyMethods.html#tymethod.call) already use follow the newest API conventions.)

View File

@ -353,8 +353,10 @@ let _: Py<PyList> = obj.extract()?;
For a `&PyAny` object reference `any` where the underlying object is a `#[pyclass]`:
```rust
# #![allow(unused_imports)]
# use pyo3::prelude::*;
# #[pyclass] #[derive(Clone)] struct MyClass { }
# #[cfg(feature = "gil-refs")]
# Python::with_gil(|py| -> PyResult<()> {
#[allow(deprecated)] // into_ref is part of the deprecated GIL Refs API
let obj: &PyAny = Py::new(py, MyClass {})?.into_ref(py);

View File

@ -64,6 +64,7 @@ impl<'a> PyDowncastError<'a> {
/// Compatibility API to convert the Bound variant `DowncastError` into the
/// gil-ref variant
#[cfg(feature = "gil-refs")]
pub(crate) fn from_downcast_err(DowncastError { from, to }: DowncastError<'a, 'a>) -> Self {
#[allow(deprecated)]
let from = unsafe { from.py().from_borrowed_ptr(from.as_ptr()) };

View File

@ -1,4 +1,4 @@
use crate::err::{self, PyDowncastError, PyErr, PyResult};
use crate::err::{self, PyErr, PyResult};
use crate::impl_::pycell::PyClassObject;
use crate::pycell::{PyBorrowError, PyBorrowMutError};
use crate::pyclass::boolean_struct::{False, True};
@ -721,12 +721,12 @@ impl<T> IntoPy<PyObject> for Borrowed<'_, '_, T> {
///
/// This type does not auto-dereference to the inner object because you must prove you hold the GIL to access it.
/// Instead, call one of its methods to access the inner object:
/// - [`Py::as_ref`], to borrow a GIL-bound reference to the contained object.
/// - [`Py::bind`] or [`Py::into_bound`], to borrow a GIL-bound reference to the contained object.
/// - [`Py::borrow`], [`Py::try_borrow`], [`Py::borrow_mut`], or [`Py::try_borrow_mut`],
/// to get a (mutable) reference to a contained pyclass, using a scheme similar to std's [`RefCell`].
/// See the [`PyCell` guide entry](https://pyo3.rs/latest/class.html#pycell-and-interior-mutability)
/// See the [guide entry](https://pyo3.rs/latest/class.html#bound-and-interior-mutability)
/// for more information.
/// - You can call methods directly on `Py` with [`Py::call`], [`Py::call_method`] and friends.
/// - You can call methods directly on `Py` with [`Py::call_bound`], [`Py::call_method_bound`] and friends.
/// These require passing in the [`Python<'py>`](crate::Python) token but are otherwise similar to the corresponding
/// methods on [`PyAny`].
///
@ -991,12 +991,10 @@ where
/// assert!(my_class_cell.try_borrow().is_ok());
/// });
/// ```
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "use `obj.bind(py)` instead of `obj.as_ref(py)`"
)
)]
pub fn as_ref<'py>(&'py self, _py: Python<'py>) -> &'py T::AsRefTarget {
let any = self.as_ptr() as *const PyAny;
@ -1046,12 +1044,10 @@ where
/// obj.into_ref(py)
/// }
/// ```
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "use `obj.into_bound(py)` instead of `obj.into_ref(py)`"
)
)]
pub fn into_ref(self, py: Python<'_>) -> &T::AsRefTarget {
#[allow(deprecated)]
@ -1464,12 +1460,10 @@ impl<T> Py<T> {
}
/// Deprecated form of [`call_bound`][Py::call_bound].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`call` will be replaced by `call_bound` in a future PyO3 version"
)
)]
#[inline]
pub fn call<A>(&self, py: Python<'_>, args: A, kwargs: Option<&PyDict>) -> PyResult<PyObject>
@ -1506,12 +1500,10 @@ impl<T> Py<T> {
}
/// Deprecated form of [`call_method_bound`][Py::call_method_bound].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`call_method` will be replaced by `call_method_bound` in a future PyO3 version"
)
)]
#[inline]
pub fn call_method<N, A>(
@ -1779,6 +1771,7 @@ impl<T> std::convert::From<Bound<'_, T>> for Py<T> {
}
// `&PyCell<T>` can be converted to `Py<T>`
#[cfg(feature = "gil-refs")]
#[allow(deprecated)]
impl<T> std::convert::From<&crate::PyCell<T>> for Py<T>
where
@ -1844,10 +1837,7 @@ where
{
/// Extracts `Self` from the source `PyObject`.
fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
// TODO update MSRV past 1.59 and use .cloned() to make
// clippy happy
#[allow(clippy::map_clone)]
ob.downcast().map(Clone::clone).map_err(Into::into)
ob.downcast().cloned().map_err(Into::into)
}
}
@ -1888,21 +1878,22 @@ pub type PyObject = Py<PyAny>;
impl PyObject {
/// Deprecated form of [`PyObject::downcast_bound`]
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
#[cfg(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>>
pub fn downcast<'py, T>(
&'py self,
py: Python<'py>,
) -> Result<&'py T, crate::err::PyDowncastError<'py>>
where
T: PyTypeCheck<AsRefTarget = T>,
{
self.downcast_bound::<T>(py)
.map(Bound::as_gil_ref)
.map_err(PyDowncastError::from_downcast_err)
.map_err(crate::err::PyDowncastError::from_downcast_err)
}
/// Downcast this `PyObject` to a concrete Python type or pyclass.
///
@ -1970,12 +1961,10 @@ impl PyObject {
/// # Safety
///
/// Callers must ensure that the type is valid or risk type confusion.
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
#[cfg(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
@ -1997,35 +1986,31 @@ impl PyObject {
}
#[cfg(test)]
#[cfg_attr(not(feature = "gil-refs"), allow(deprecated))]
mod tests {
use super::{Bound, Py, PyObject};
use crate::types::any::PyAnyMethods;
use crate::types::{dict::IntoPyDict, PyDict, PyString};
use crate::types::{PyCapsule, PyStringMethods};
use crate::{ffi, Borrowed, PyAny, PyNativeType, PyResult, Python, ToPyObject};
use crate::{ffi, Borrowed, PyAny, PyResult, Python, ToPyObject};
#[test]
fn test_call() {
Python::with_gil(|py| {
let obj = py.get_type::<PyDict>().to_object(py);
let obj = py.get_type_bound::<PyDict>().to_object(py);
let assert_repr = |obj: &PyAny, expected: &str| {
assert_eq!(obj.repr().unwrap().to_str().unwrap(), expected);
let assert_repr = |obj: &Bound<'_, PyAny>, expected: &str| {
assert_eq!(obj.repr().unwrap().to_cow().unwrap(), expected);
};
assert_repr(obj.call0(py).unwrap().as_ref(py), "{}");
assert_repr(obj.call1(py, ()).unwrap().as_ref(py), "{}");
assert_repr(obj.call(py, (), None).unwrap().as_ref(py), "{}");
assert_repr(obj.call0(py).unwrap().bind(py), "{}");
assert_repr(obj.call1(py, ()).unwrap().bind(py), "{}");
assert_repr(obj.call_bound(py, (), None).unwrap().bind(py), "{}");
assert_repr(
obj.call1(py, ((('x', 1),),)).unwrap().as_ref(py),
"{'x': 1}",
);
assert_repr(obj.call1(py, ((('x', 1),),)).unwrap().bind(py), "{'x': 1}");
assert_repr(
obj.call_bound(py, (), Some(&[('x', 1)].into_py_dict_bound(py)))
.unwrap()
.as_ref(py),
.bind(py),
"{'x': 1}",
);
})
@ -2037,7 +2022,7 @@ mod tests {
let obj: PyObject = PyDict::new_bound(py).into();
assert!(obj.call_method0(py, "asdf").is_err());
assert!(obj
.call_method(py, "nonexistent_method", (1,), None)
.call_method_bound(py, "nonexistent_method", (1,), None)
.is_err());
assert!(obj.call_method0(py, "nonexistent_method").is_err());
assert!(obj.call_method1(py, "nonexistent_method", (1,)).is_err());
@ -2083,7 +2068,7 @@ a = A()
assert!(instance
.getattr(py, "foo")?
.as_ref(py)
.bind(py)
.eq(PyString::new_bound(py, "bar"))?);
instance.getattr(py, "foo")?;
@ -2109,7 +2094,7 @@ a = A()
instance.getattr(py, foo).unwrap_err();
instance.setattr(py, foo, bar)?;
assert!(instance.getattr(py, foo)?.as_ref(py).eq(bar)?);
assert!(instance.getattr(py, foo)?.bind(py).eq(bar)?);
Ok(())
})
}
@ -2117,7 +2102,7 @@ a = A()
#[test]
fn invalid_attr() -> PyResult<()> {
Python::with_gil(|py| {
let instance: Py<PyAny> = py.eval("object()", None, None)?.into();
let instance: Py<PyAny> = py.eval_bound("object()", None, None)?.into();
instance.getattr(py, "foo").unwrap_err();
@ -2130,7 +2115,7 @@ a = A()
#[test]
fn test_py2_from_py_object() {
Python::with_gil(|py| {
let instance: &PyAny = py.eval("object()", None, None).unwrap();
let instance = py.eval_bound("object()", None, None).unwrap();
let ptr = instance.as_ptr();
let instance: Bound<'_, PyAny> = instance.extract().unwrap();
assert_eq!(instance.as_ptr(), ptr);
@ -2141,7 +2126,7 @@ a = A()
fn test_py2_into_py_object() {
Python::with_gil(|py| {
let instance = py
.eval("object()", None, None)
.eval_bound("object()", None, None)
.unwrap()
.as_borrowed()
.to_owned();

View File

@ -116,18 +116,17 @@
//! [`SendWrapper`]: https://docs.rs/send_wrapper/latest/send_wrapper/struct.SendWrapper.html
//! [`Rc`]: std::rc::Rc
//! [`Py`]: crate::Py
use crate::err::{self, PyDowncastError, PyErr, PyResult};
use crate::err::{self, PyErr, PyResult};
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::gil::{GILGuard, SuspendGIL};
use crate::impl_::not_send::NotSend;
use crate::py_result_ext::PyResultExt;
use crate::type_object::HasPyGilRef;
use crate::types::any::PyAnyMethods;
use crate::types::{
PyAny, PyDict, PyEllipsis, PyModule, PyNone, PyNotImplemented, PyString, PyType,
};
use crate::version::PythonVersionInfo;
use crate::{ffi, Bound, IntoPy, Py, PyNativeType, PyObject, PyTypeCheck, PyTypeInfo};
use crate::{ffi, Bound, IntoPy, Py, PyNativeType, PyObject, PyTypeInfo};
#[allow(deprecated)]
use crate::{gil::GILPool, FromPyPointer};
use std::ffi::{CStr, CString};
@ -839,16 +838,17 @@ 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(
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "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>>
pub fn checked_cast_as<T>(
self,
obj: PyObject,
) -> Result<&'py T, crate::err::PyDowncastError<'py>>
where
T: PyTypeCheck<AsRefTarget = T>,
T: crate::PyTypeCheck<AsRefTarget = T>,
{
#[allow(deprecated)]
obj.into_ref(self).downcast()
@ -860,16 +860,14 @@ impl<'py> Python<'py> {
/// # Safety
///
/// Callers must ensure that ensure that the cast is valid.
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "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>,
T: crate::type_object::HasPyGilRef<AsRefTarget = T>,
{
#[allow(deprecated)]
obj.into_ref(self).downcast_unchecked()

View File

@ -2705,17 +2705,16 @@ class SimpleClass:
}
#[test]
#[allow(deprecated)]
fn test_is_ellipsis() {
Python::with_gil(|py| {
let v = py
.eval("...", None, None)
.eval_bound("...", None, None)
.map_err(|e| e.display(py))
.unwrap();
assert!(v.is_ellipsis());
let not_ellipsis = 5.to_object(py).into_ref(py);
let not_ellipsis = 5.to_object(py).into_bound(py);
assert!(!not_ellipsis.is_ellipsis());
});
}

View File

@ -817,8 +817,6 @@ where
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(any(PyPy, GraalPy)))]
use crate::exceptions;
use crate::types::PyTuple;
use std::collections::{BTreeMap, HashMap};
@ -948,7 +946,7 @@ mod tests {
#[test]
#[allow(deprecated)]
#[cfg(not(any(PyPy, GraalPy)))]
#[cfg(all(not(any(PyPy, GraalPy)), feature = "gil-refs"))]
fn test_get_item_with_error() {
Python::with_gil(|py| {
let mut v = HashMap::new();
@ -967,7 +965,7 @@ mod tests {
assert!(dict
.get_item_with_error(dict)
.unwrap_err()
.is_instance_of::<exceptions::PyTypeError>(py));
.is_instance_of::<crate::exceptions::PyTypeError>(py));
});
}