port `PyErr::warn` to `Bound` API (#3842)

* port `PyErr::new_type`

* port `PyErr::warn` and `PyErr::warn_explicit`
This commit is contained in:
Icxolu 2024-02-16 01:12:43 +01:00 committed by GitHub
parent dc8b948201
commit 05aedc9032
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 106 additions and 27 deletions

View File

@ -52,7 +52,9 @@ use crate::types::{
}; };
#[cfg(Py_LIMITED_API)] #[cfg(Py_LIMITED_API)]
use crate::{intern, DowncastError}; 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::offset::{FixedOffset, Utc};
use chrono::{ use chrono::{
DateTime, Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime, Offset, TimeZone, Timelike, 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>) { fn warn_truncated_leap_second(obj: &Bound<'_, PyAny>) {
let py = obj.py(); let py = obj.py();
if let Err(e) = PyErr::warn( if let Err(e) = PyErr::warn_bound(
py, py,
py.get_type::<PyUserWarning>(), &py.get_type::<PyUserWarning>().as_borrowed(),
"ignored leap-second, `datetime` does not support leap-seconds", "ignored leap-second, `datetime` does not support leap-seconds",
0, 0,
) { ) {

View File

@ -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<PyObject>,
) -> PyResult<Py<PyType>> {
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. /// 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. /// - `base` can be an existing exception type to subclass, or a tuple of classes.
@ -459,11 +483,11 @@ impl PyErr {
/// # Panics /// # Panics
/// ///
/// This function will panic if `name` or `doc` cannot be converted to [`CString`]s. /// This function will panic if `name` or `doc` cannot be converted to [`CString`]s.
pub fn new_type( pub fn new_type_bound<'py>(
py: Python<'_>, py: Python<'py>,
name: &str, name: &str,
doc: Option<&str>, doc: Option<&str>,
base: Option<&PyType>, base: Option<&Bound<'py, PyType>>,
dict: Option<PyObject>, dict: Option<PyObject>,
) -> PyResult<Py<PyType>> { ) -> PyResult<Py<PyType>> {
let base: *mut ffi::PyObject = match base { 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)) } 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. /// Issues a warning message.
/// ///
/// May return an `Err(PyErr)` if warnings-as-errors is enabled. /// May return an `Err(PyErr)` if warnings-as-errors is enabled.
@ -650,13 +686,18 @@ impl PyErr {
/// # use pyo3::prelude::*; /// # use pyo3::prelude::*;
/// # fn main() -> PyResult<()> { /// # fn main() -> PyResult<()> {
/// Python::with_gil(|py| { /// Python::with_gil(|py| {
/// let user_warning = py.get_type::<pyo3::exceptions::PyUserWarning>(); /// let user_warning = py.get_type::<pyo3::exceptions::PyUserWarning>().as_borrowed();
/// PyErr::warn(py, user_warning, "I am warning you", 0)?; /// PyErr::warn_bound(py, &user_warning, "I am warning you", 0)?;
/// Ok(()) /// 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)?; let message = CString::new(message)?;
error_on_minusone(py, unsafe { error_on_minusone(py, unsafe {
ffi::PyErr_WarnEx( ffi::PyErr_WarnEx(
@ -667,14 +708,14 @@ impl PyErr {
}) })
} }
/// Issues a warning message, with more control over the warning attributes. /// Deprecated form of [`PyErr::warn_explicit_bound`].
/// #[cfg_attr(
/// May return a `PyErr` if warnings-as-errors is enabled. not(feature = "gil-refs"),
/// deprecated(
/// Equivalent to `warnings.warn_explicit()` in Python. since = "0.21.0",
/// note = "`PyErr::warn_explicit` will be replaced by `PyErr::warn_explicit_bound` in a future PyO3 version"
/// The `category` should be one of the `Warning` classes available in )
/// [`pyo3::exceptions`](crate::exceptions), or a subclass. )]
pub fn warn_explicit( pub fn warn_explicit(
py: Python<'_>, py: Python<'_>,
category: &PyAny, category: &PyAny,
@ -683,6 +724,34 @@ impl PyErr {
lineno: i32, lineno: i32,
module: Option<&str>, module: Option<&str>,
registry: Option<&PyAny>, 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<()> { ) -> PyResult<()> {
let message = CString::new(message)?; let message = CString::new(message)?;
let filename = CString::new(filename)?; let filename = CString::new(filename)?;
@ -975,7 +1044,7 @@ mod tests {
use super::PyErrState; use super::PyErrState;
use crate::exceptions::{self, PyTypeError, PyValueError}; use crate::exceptions::{self, PyTypeError, PyValueError};
use crate::types::any::PyAnyMethods; use crate::types::any::PyAnyMethods;
use crate::{PyErr, PyTypeInfo, Python}; use crate::{PyErr, PyNativeType, PyTypeInfo, Python};
#[test] #[test]
fn no_error() { fn no_error() {
@ -1172,7 +1241,7 @@ mod tests {
// GIL locked should prevent effects to be visible to other testing // GIL locked should prevent effects to be visible to other testing
// threads. // threads.
Python::with_gil(|py| { Python::with_gil(|py| {
let cls = py.get_type::<exceptions::PyUserWarning>(); let cls = py.get_type::<exceptions::PyUserWarning>().as_borrowed();
// Reset warning filter to default state // Reset warning filter to default state
let warnings = py.import_bound("warnings").unwrap(); let warnings = py.import_bound("warnings").unwrap();
@ -1181,7 +1250,7 @@ mod tests {
// First, test the warning is emitted // First, test the warning is emitted
assert_warnings!( assert_warnings!(
py, 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")] [(exceptions::PyUserWarning, "I am warning you")]
); );
@ -1189,7 +1258,7 @@ mod tests {
warnings warnings
.call_method1("simplefilter", ("error", cls)) .call_method1("simplefilter", ("error", cls))
.unwrap(); .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 // Test with error for an explicit module
warnings.call_method0("resetwarnings").unwrap(); warnings.call_method0("resetwarnings").unwrap();
@ -1200,13 +1269,20 @@ mod tests {
// This has the wrong module and will not raise, just be emitted // This has the wrong module and will not raise, just be emitted
assert_warnings!( assert_warnings!(
py, 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")] [(exceptions::PyUserWarning, "I am warning you")]
); );
let err = let err = PyErr::warn_explicit_bound(
PyErr::warn_explicit(py, cls, "I am warning you", "pyo3test.py", 427, None, None) py,
.unwrap_err(); &cls,
"I am warning you",
"pyo3test.py",
427,
None,
None,
)
.unwrap_err();
assert!(err assert!(err
.value(py) .value(py)
.getattr("args") .getattr("args")

View File

@ -240,16 +240,17 @@ macro_rules! create_exception_type_object {
impl $name { impl $name {
fn type_object_raw(py: $crate::Python<'_>) -> *mut $crate::ffi::PyTypeObject { fn type_object_raw(py: $crate::Python<'_>) -> *mut $crate::ffi::PyTypeObject {
use $crate::sync::GILOnceCell; use $crate::sync::GILOnceCell;
use $crate::PyNativeType;
static TYPE_OBJECT: GILOnceCell<$crate::Py<$crate::types::PyType>> = static TYPE_OBJECT: GILOnceCell<$crate::Py<$crate::types::PyType>> =
GILOnceCell::new(); GILOnceCell::new();
TYPE_OBJECT TYPE_OBJECT
.get_or_init(py, || .get_or_init(py, ||
$crate::PyErr::new_type( $crate::PyErr::new_type_bound(
py, py,
concat!(stringify!($module), ".", stringify!($name)), concat!(stringify!($module), ".", stringify!($name)),
$doc, $doc,
::std::option::Option::Some(py.get_type::<$base>()), ::std::option::Option::Some(&py.get_type::<$base>().as_borrowed()),
::std::option::Option::None, ::std::option::Option::None,
).expect("Failed to initialize new exception type.") ).expect("Failed to initialize new exception type.")
).as_ptr() as *mut $crate::ffi::PyTypeObject ).as_ptr() as *mut $crate::ffi::PyTypeObject