From 05aedc9032e74cde1dd24958c0836426b42e535a Mon Sep 17 00:00:00 2001 From: Icxolu <10486322+Icxolu@users.noreply.github.com> Date: Fri, 16 Feb 2024 01:12:43 +0100 Subject: [PATCH] port `PyErr::warn` to `Bound` API (#3842) * port `PyErr::new_type` * port `PyErr::warn` and `PyErr::warn_explicit` --- src/conversions/chrono.rs | 8 ++- src/err/mod.rs | 120 +++++++++++++++++++++++++++++++------- src/exceptions.rs | 5 +- 3 files changed, 106 insertions(+), 27 deletions(-) diff --git a/src/conversions/chrono.rs b/src/conversions/chrono.rs index 421a7aec..d67818e6 100644 --- a/src/conversions/chrono.rs +++ b/src/conversions/chrono.rs @@ -52,7 +52,9 @@ use crate::types::{ }; #[cfg(Py_LIMITED_API)] use crate::{intern, DowncastError}; -use crate::{Bound, FromPyObject, IntoPy, PyAny, PyErr, PyObject, PyResult, Python, ToPyObject}; +use crate::{ + Bound, FromPyObject, IntoPy, PyAny, PyErr, PyNativeType, PyObject, PyResult, Python, ToPyObject, +}; use chrono::offset::{FixedOffset, Utc}; use chrono::{ DateTime, Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime, Offset, TimeZone, Timelike, @@ -457,9 +459,9 @@ fn naive_datetime_to_py_datetime( fn warn_truncated_leap_second(obj: &Bound<'_, PyAny>) { let py = obj.py(); - if let Err(e) = PyErr::warn( + if let Err(e) = PyErr::warn_bound( py, - py.get_type::(), + &py.get_type::().as_borrowed(), "ignored leap-second, `datetime` does not support leap-seconds", 0, ) { diff --git a/src/err/mod.rs b/src/err/mod.rs index 5c2631b9..b5e72a71 100644 --- a/src/err/mod.rs +++ b/src/err/mod.rs @@ -445,6 +445,30 @@ 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" + ) + )] + pub fn new_type( + py: Python<'_>, + name: &str, + doc: Option<&str>, + base: Option<&PyType>, + dict: Option, + ) -> PyResult> { + Self::new_type_bound( + py, + name, + doc, + base.map(PyNativeType::as_borrowed).as_deref(), + dict, + ) + } + /// Creates a new exception type with the given name and docstring. /// /// - `base` can be an existing exception type to subclass, or a tuple of classes. @@ -459,11 +483,11 @@ impl PyErr { /// # Panics /// /// This function will panic if `name` or `doc` cannot be converted to [`CString`]s. - pub fn new_type( - py: Python<'_>, + pub fn new_type_bound<'py>( + py: Python<'py>, name: &str, doc: Option<&str>, - base: Option<&PyType>, + base: Option<&Bound<'py, PyType>>, dict: Option, ) -> PyResult> { let base: *mut ffi::PyObject = match base { @@ -635,6 +659,18 @@ impl PyErr { unsafe { ffi::PyErr_WriteUnraisable(obj.map_or(std::ptr::null_mut(), Bound::as_ptr)) } } + /// 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" + ) + )] + pub fn warn(py: Python<'_>, category: &PyAny, message: &str, stacklevel: i32) -> PyResult<()> { + Self::warn_bound(py, &category.as_borrowed(), message, stacklevel) + } + /// Issues a warning message. /// /// May return an `Err(PyErr)` if warnings-as-errors is enabled. @@ -650,13 +686,18 @@ impl PyErr { /// # use pyo3::prelude::*; /// # fn main() -> PyResult<()> { /// Python::with_gil(|py| { - /// let user_warning = py.get_type::(); - /// PyErr::warn(py, user_warning, "I am warning you", 0)?; + /// let user_warning = py.get_type::().as_borrowed(); + /// PyErr::warn_bound(py, &user_warning, "I am warning you", 0)?; /// Ok(()) /// }) /// # } /// ``` - pub fn warn(py: Python<'_>, category: &PyAny, message: &str, stacklevel: i32) -> PyResult<()> { + pub fn warn_bound<'py>( + py: Python<'py>, + category: &Bound<'py, PyAny>, + message: &str, + stacklevel: i32, + ) -> PyResult<()> { let message = CString::new(message)?; error_on_minusone(py, unsafe { ffi::PyErr_WarnEx( @@ -667,14 +708,14 @@ impl PyErr { }) } - /// Issues a warning message, with more control over the warning attributes. - /// - /// May return a `PyErr` if warnings-as-errors is enabled. - /// - /// Equivalent to `warnings.warn_explicit()` in Python. - /// - /// The `category` should be one of the `Warning` classes available in - /// [`pyo3::exceptions`](crate::exceptions), or a subclass. + /// 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" + ) + )] pub fn warn_explicit( py: Python<'_>, category: &PyAny, @@ -683,6 +724,34 @@ impl PyErr { lineno: i32, module: Option<&str>, registry: Option<&PyAny>, + ) -> PyResult<()> { + Self::warn_explicit_bound( + py, + &category.as_borrowed(), + message, + filename, + lineno, + module, + registry.map(PyNativeType::as_borrowed).as_deref(), + ) + } + + /// Issues a warning message, with more control over the warning attributes. + /// + /// May return a `PyErr` if warnings-as-errors is enabled. + /// + /// Equivalent to `warnings.warn_explicit()` in Python. + /// + /// The `category` should be one of the `Warning` classes available in + /// [`pyo3::exceptions`](crate::exceptions), or a subclass. + pub fn warn_explicit_bound<'py>( + py: Python<'py>, + category: &Bound<'py, PyAny>, + message: &str, + filename: &str, + lineno: i32, + module: Option<&str>, + registry: Option<&Bound<'py, PyAny>>, ) -> PyResult<()> { let message = CString::new(message)?; let filename = CString::new(filename)?; @@ -975,7 +1044,7 @@ mod tests { use super::PyErrState; use crate::exceptions::{self, PyTypeError, PyValueError}; use crate::types::any::PyAnyMethods; - use crate::{PyErr, PyTypeInfo, Python}; + use crate::{PyErr, PyNativeType, PyTypeInfo, Python}; #[test] fn no_error() { @@ -1172,7 +1241,7 @@ mod tests { // GIL locked should prevent effects to be visible to other testing // threads. Python::with_gil(|py| { - let cls = py.get_type::(); + let cls = py.get_type::().as_borrowed(); // Reset warning filter to default state let warnings = py.import_bound("warnings").unwrap(); @@ -1181,7 +1250,7 @@ mod tests { // First, test the warning is emitted assert_warnings!( py, - { PyErr::warn(py, cls, "I am warning you", 0).unwrap() }, + { PyErr::warn_bound(py, &cls, "I am warning you", 0).unwrap() }, [(exceptions::PyUserWarning, "I am warning you")] ); @@ -1189,7 +1258,7 @@ mod tests { warnings .call_method1("simplefilter", ("error", cls)) .unwrap(); - PyErr::warn(py, cls, "I am warning you", 0).unwrap_err(); + PyErr::warn_bound(py, &cls, "I am warning you", 0).unwrap_err(); // Test with error for an explicit module warnings.call_method0("resetwarnings").unwrap(); @@ -1200,13 +1269,20 @@ mod tests { // This has the wrong module and will not raise, just be emitted assert_warnings!( py, - { PyErr::warn(py, cls, "I am warning you", 0).unwrap() }, + { PyErr::warn_bound(py, &cls, "I am warning you", 0).unwrap() }, [(exceptions::PyUserWarning, "I am warning you")] ); - let err = - PyErr::warn_explicit(py, cls, "I am warning you", "pyo3test.py", 427, None, None) - .unwrap_err(); + let err = PyErr::warn_explicit_bound( + py, + &cls, + "I am warning you", + "pyo3test.py", + 427, + None, + None, + ) + .unwrap_err(); assert!(err .value(py) .getattr("args") diff --git a/src/exceptions.rs b/src/exceptions.rs index 245c3bbd..bcbe91b9 100644 --- a/src/exceptions.rs +++ b/src/exceptions.rs @@ -240,16 +240,17 @@ macro_rules! create_exception_type_object { impl $name { fn type_object_raw(py: $crate::Python<'_>) -> *mut $crate::ffi::PyTypeObject { use $crate::sync::GILOnceCell; + use $crate::PyNativeType; static TYPE_OBJECT: GILOnceCell<$crate::Py<$crate::types::PyType>> = GILOnceCell::new(); TYPE_OBJECT .get_or_init(py, || - $crate::PyErr::new_type( + $crate::PyErr::new_type_bound( py, concat!(stringify!($module), ".", stringify!($name)), $doc, - ::std::option::Option::Some(py.get_type::<$base>()), + ::std::option::Option::Some(&py.get_type::<$base>().as_borrowed()), ::std::option::Option::None, ).expect("Failed to initialize new exception type.") ).as_ptr() as *mut $crate::ffi::PyTypeObject