Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature gate deprecated APIs for PyErr and exceptions #4136

Merged
merged 1 commit into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 44 additions & 64 deletions src/err/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ use err_state::{PyErrState, PyErrStateLazyFnOutput, PyErrStateNormalized};
/// compatibility with `?` and other Rust errors) this type supports creating exceptions instances
/// in a lazy fashion, where the full Python object for the exception is created only when needed.
///
/// Accessing the contained exception in any way, such as with [`value`](PyErr::value),
/// [`get_type`](PyErr::get_type), or [`is_instance`](PyErr::is_instance) will create the full
/// exception object if it was not already created.
/// Accessing the contained exception in any way, such as with [`value_bound`](PyErr::value_bound),
/// [`get_type_bound`](PyErr::get_type_bound), or [`is_instance_bound`](PyErr::is_instance_bound)
/// will create the full exception object if it was not already created.
pub struct PyErr {
// Safety: can only hand out references when in the "normalized" state. Will never change
// after normalization.
Expand Down Expand Up @@ -136,7 +136,7 @@ impl PyErr {
///
/// This exception instance will be initialized lazily. This avoids the need for the Python GIL
/// to be held, but requires `args` to be `Send` and `Sync`. If `args` is not `Send` or `Sync`,
/// consider using [`PyErr::from_value`] instead.
/// consider using [`PyErr::from_value_bound`] instead.
///
/// If `T` does not inherit from `BaseException`, then a `TypeError` will be returned.
///
Expand Down Expand Up @@ -192,12 +192,10 @@ impl PyErr {
}

/// Deprecated form of [`PyErr::from_type_bound`]
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyErr::from_type` will be replaced by `PyErr::from_type_bound` in a future PyO3 version"
)
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`PyErr::from_type` will be replaced by `PyErr::from_type_bound` in a future PyO3 version"
)]
pub fn from_type<A>(ty: &PyType, args: A) -> PyErr
where
Expand All @@ -224,12 +222,10 @@ impl PyErr {
}

/// Deprecated form of [`PyErr::from_value_bound`].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyErr::from_value` will be replaced by `PyErr::from_value_bound` in a future PyO3 version"
)
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`PyErr::from_value` will be replaced by `PyErr::from_value_bound` in a future PyO3 version"
)]
pub fn from_value(obj: &PyAny) -> PyErr {
PyErr::from_value_bound(obj.as_borrowed().to_owned())
Expand Down Expand Up @@ -284,12 +280,10 @@ impl PyErr {
}

/// Deprecated form of [`PyErr::get_type_bound`].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyErr::get_type` will be replaced by `PyErr::get_type_bound` in a future PyO3 version"
)
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`PyErr::get_type` will be replaced by `PyErr::get_type_bound` in a future PyO3 version"
)]
pub fn get_type<'py>(&'py self, py: Python<'py>) -> &'py PyType {
self.get_type_bound(py).into_gil_ref()
Expand All @@ -311,12 +305,10 @@ impl PyErr {
}

/// Deprecated form of [`PyErr::value_bound`].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyErr::value` will be replaced by `PyErr::value_bound` in a future PyO3 version"
)
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`PyErr::value` will be replaced by `PyErr::value_bound` in a future PyO3 version"
)]
pub fn value<'py>(&'py self, py: Python<'py>) -> &'py PyBaseException {
self.value_bound(py).as_gil_ref()
Expand Down Expand Up @@ -355,12 +347,10 @@ impl PyErr {
}

/// Deprecated form of [`PyErr::traceback_bound`].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyErr::traceback` will be replaced by `PyErr::traceback_bound` in a future PyO3 version"
)
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`PyErr::traceback` will be replaced by `PyErr::traceback_bound` in a future PyO3 version"
)]
pub fn traceback<'py>(&'py self, py: Python<'py>) -> Option<&'py PyTraceback> {
self.normalized(py).ptraceback(py).map(|b| b.into_gil_ref())
Expand Down Expand Up @@ -508,12 +498,10 @@ impl PyErr {
}

/// Deprecated form of [`PyErr::new_type_bound`]
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyErr::new_type` will be replaced by `PyErr::new_type_bound` in a future PyO3 version"
)
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`PyErr::new_type` will be replaced by `PyErr::new_type_bound` in a future PyO3 version"
)]
pub fn new_type(
py: Python<'_>,
Expand Down Expand Up @@ -636,12 +624,10 @@ impl PyErr {
}

/// Deprecated form of `PyErr::is_instance_bound`.
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyErr::is_instance` will be replaced by `PyErr::is_instance_bound` in a future PyO3 version"
)
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`PyErr::is_instance` will be replaced by `PyErr::is_instance_bound` in a future PyO3 version"
)]
#[inline]
pub fn is_instance(&self, py: Python<'_>, ty: &PyAny) -> bool {
Expand Down Expand Up @@ -675,12 +661,10 @@ impl PyErr {
}

/// Deprecated form of `PyErr::write_unraisable_bound`.
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyErr::write_unraisable` will be replaced by `PyErr::write_unraisable_bound` in a future PyO3 version"
)
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`PyErr::write_unraisable` will be replaced by `PyErr::write_unraisable_bound` in a future PyO3 version"
)]
#[inline]
pub fn write_unraisable(self, py: Python<'_>, obj: Option<&PyAny>) {
Expand Down Expand Up @@ -722,12 +706,10 @@ impl PyErr {
}

/// Deprecated form of [`PyErr::warn_bound`].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyErr::warn` will be replaced by `PyErr::warn_bound` in a future PyO3 version"
)
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`PyErr::warn` will be replaced by `PyErr::warn_bound` in a future PyO3 version"
)]
pub fn warn(py: Python<'_>, category: &PyAny, message: &str, stacklevel: i32) -> PyResult<()> {
Self::warn_bound(py, &category.as_borrowed(), message, stacklevel)
Expand Down Expand Up @@ -771,12 +753,10 @@ impl PyErr {
}

/// Deprecated form of [`PyErr::warn_explicit_bound`].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyErr::warn_explicit` will be replaced by `PyErr::warn_explicit_bound` in a future PyO3 version"
)
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`PyErr::warn_explicit` will be replaced by `PyErr::warn_explicit_bound` in a future PyO3 version"
)]
pub fn warn_explicit(
py: Python<'_>,
Expand Down
40 changes: 22 additions & 18 deletions src/exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ macro_rules! impl_exception_boilerplate {
($name: ident) => {
// FIXME https://github.com/PyO3/pyo3/issues/3903
#[allow(unknown_lints, non_local_definitions)]
#[cfg(feature = "gil-refs")]
impl ::std::convert::From<&$name> for $crate::PyErr {
#[inline]
fn from(err: &$name) -> $crate::PyErr {
Expand Down Expand Up @@ -650,12 +651,10 @@ impl_windows_native_exception!(

impl PyUnicodeDecodeError {
/// Deprecated form of [`PyUnicodeDecodeError::new_bound`].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyUnicodeDecodeError::new` will be replaced by `PyUnicodeDecodeError::new_bound` in a future PyO3 version"
)
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`PyUnicodeDecodeError::new` will be replaced by `PyUnicodeDecodeError::new_bound` in a future PyO3 version"
)]
pub fn new<'p>(
py: Python<'p>,
Expand Down Expand Up @@ -692,12 +691,10 @@ impl PyUnicodeDecodeError {
}

/// Deprecated form of [`PyUnicodeDecodeError::new_utf8_bound`].
#[cfg_attr(
not(feature = "gil-refs"),
deprecated(
since = "0.21.0",
note = "`PyUnicodeDecodeError::new_utf8` will be replaced by `PyUnicodeDecodeError::new_utf8_bound` in a future PyO3 version"
)
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`PyUnicodeDecodeError::new_utf8` will be replaced by `PyUnicodeDecodeError::new_utf8_bound` in a future PyO3 version"
)]
pub fn new_utf8<'p>(
py: Python<'p>,
Expand Down Expand Up @@ -808,7 +805,7 @@ macro_rules! test_exception {
use super::$exc_ty;

$crate::Python::with_gil(|py| {
use std::error::Error;
use $crate::types::PyAnyMethods;
let err: $crate::PyErr = {
None
$(
Expand All @@ -819,13 +816,19 @@ macro_rules! test_exception {

assert!(err.is_instance_of::<$exc_ty>(py));

let value: &$exc_ty = err.value_bound(py).clone().into_gil_ref().downcast().unwrap();
assert!(value.source().is_none());
let value = err.value_bound(py).as_any().downcast::<$exc_ty>().unwrap();

err.set_cause(py, Some($crate::exceptions::PyValueError::new_err("a cause")));
assert!(value.source().is_some());
#[cfg(feature = "gil-refs")]
{
use std::error::Error;
let value = value.as_gil_ref();
assert!(value.source().is_none());

err.set_cause(py, Some($crate::exceptions::PyValueError::new_err("a cause")));
assert!(value.source().is_some());
}

assert!($crate::PyErr::from(value).is_instance_of::<$exc_ty>(py));
assert!($crate::PyErr::from(value.clone()).is_instance_of::<$exc_ty>(py));
})
}
};
Expand Down Expand Up @@ -1068,6 +1071,7 @@ mod tests {
}

#[test]
#[cfg(feature = "gil-refs")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason not to convert this test to use bound? It looks like into_ref() is the only deprecated piece here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This explicitly tests the std::error::Error impl, which the Bound api can't implement anymore.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, do we need to be providing an impl of Error for Bound<'_, T>?

I think I missed this because this PR doesn't (yet?) but that impl behind a cfg, unless I really missed it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Error impl does not exist anymore, because we can't implement source() anymore using the Bound API, see #3820 (comment). (It would also be problematic for user defined exceptions, because of orphan rules, but I think we could work around that using a marker trait and blanket impl if we wanted to)

fn native_exception_chain() {
use std::error::Error;

Expand Down
7 changes: 3 additions & 4 deletions tests/test_exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,25 +100,24 @@ fn test_exception_nosegfault() {

#[test]
#[cfg(Py_3_8)]
#[cfg_attr(not(feature = "gil-refs"), allow(deprecated))]
fn test_write_unraisable() {
use common::UnraisableCapture;
use pyo3::{exceptions::PyRuntimeError, ffi};
use pyo3::{exceptions::PyRuntimeError, ffi, types::PyNotImplemented};

Python::with_gil(|py| {
let capture = UnraisableCapture::install(py);

assert!(capture.borrow(py).capture.is_none());

let err = PyRuntimeError::new_err("foo");
err.write_unraisable(py, None);
err.write_unraisable_bound(py, None);

let (err, object) = capture.borrow_mut(py).capture.take().unwrap();
assert_eq!(err.to_string(), "RuntimeError: foo");
assert!(object.is_none(py));

let err = PyRuntimeError::new_err("bar");
err.write_unraisable(py, Some(py.NotImplemented().as_ref(py)));
err.write_unraisable_bound(py, Some(&PyNotImplemented::get_bound(py)));

let (err, object) = capture.borrow_mut(py).capture.take().unwrap();
assert_eq!(err.to_string(), "RuntimeError: bar");
Expand Down
Loading