Export warning classes and add PyErr::warn_explicit() (#2742)

This commit is contained in:
Georg Brandl 2022-11-17 19:30:48 +01:00 committed by GitHub
parent f5735614fb
commit 1d20f2a531
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 199 additions and 7 deletions

View File

@ -0,0 +1 @@
Added exports for all built-in `Warning` classes as well as `PyErr::warn_explicit`.

View File

@ -245,6 +245,8 @@ extern "C" {
pub static mut PyExc_BytesWarning: *mut PyObject;
#[cfg_attr(PyPy, link_name = "PyPyExc_ResourceWarning")]
pub static mut PyExc_ResourceWarning: *mut PyObject;
#[cfg(Py_3_10)]
pub static mut PyExc_EncodingWarning: *mut PyObject;
}
extern "C" {

View File

@ -22,6 +22,7 @@ extern "C" {
format: *const c_char,
...
) -> c_int;
#[cfg_attr(PyPy, link_name = "PyPyErr_WarnExplicit")]
pub fn PyErr_WarnExplicit(
category: *mut PyObject,
message: *const c_char,

View File

@ -477,7 +477,28 @@ impl PyErr {
}
/// Issues a warning message.
/// May return a `PyErr` if warnings-as-errors is enabled.
///
/// May return an `Err(PyErr)` if warnings-as-errors is enabled.
///
/// Equivalent to `warnings.warn()` in Python.
///
/// The `category` should be one of the `Warning` classes available in
/// [`pyo3::exceptions`](crate::exceptions), or a subclass. The Python
/// object can be retrieved using [`PyTypeInfo::type_object()`].
///
/// Example:
/// ```rust
/// use pyo3::prelude::*;
/// use pyo3::PyTypeInfo;
///
/// # fn main() -> PyResult<()> {
/// Python::with_gil(|py| {
/// let user_warning = pyo3::exceptions::PyUserWarning::type_object(py);
/// PyErr::warn(py, user_warning, "I am warning you", 0)?;
/// Ok(())
/// })
/// # }
/// ```
pub fn warn(py: Python<'_>, category: &PyAny, message: &str, stacklevel: i32) -> PyResult<()> {
let message = CString::new(message)?;
unsafe {
@ -492,6 +513,49 @@ 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.
pub fn warn_explicit(
py: Python<'_>,
category: &PyAny,
message: &str,
filename: &str,
lineno: i32,
module: Option<&str>,
registry: Option<&PyAny>,
) -> PyResult<()> {
let message = CString::new(message)?;
let filename = CString::new(filename)?;
let module = module.map(CString::new).transpose()?;
let module_ptr = match module {
None => std::ptr::null_mut(),
Some(s) => s.as_ptr(),
};
let registry: *mut ffi::PyObject = match registry {
None => std::ptr::null_mut(),
Some(obj) => obj.as_ptr(),
};
unsafe {
error_on_minusone(
py,
ffi::PyErr_WarnExplicit(
category.as_ptr(),
message.as_ptr(),
filename.as_ptr(),
lineno,
module_ptr,
registry,
),
)
}
}
/// Clone the PyErr. This requires the GIL, which is why PyErr does not implement Clone.
///
/// # Examples
@ -769,7 +833,7 @@ fn exceptions_must_derive_from_base_exception(py: Python<'_>) -> PyErr {
mod tests {
use super::PyErrState;
use crate::exceptions;
use crate::{AsPyPointer, PyErr, Python};
use crate::{AsPyPointer, PyErr, PyTypeInfo, Python};
#[test]
fn no_error() {
@ -938,4 +1002,57 @@ mod tests {
);
});
}
#[test]
fn warnings() {
// Note: although the warning filter is interpreter global, keeping the
// GIL locked should prevent effects to be visible to other testing
// threads.
Python::with_gil(|py| {
let cls = exceptions::PyUserWarning::type_object(py);
// Reset warning filter to default state
let warnings = py.import("warnings").unwrap();
warnings.call_method0("resetwarnings").unwrap();
// First, test with ignoring the warning
warnings
.call_method1("simplefilter", ("ignore", cls))
.unwrap();
PyErr::warn(py, cls, "I am warning you", 0).unwrap();
// Test with raising
warnings
.call_method1("simplefilter", ("error", cls))
.unwrap();
PyErr::warn(py, cls, "I am warning you", 0).unwrap_err();
// Test with explicit module and specific filter
warnings.call_method0("resetwarnings").unwrap();
warnings
.call_method1("simplefilter", ("ignore", cls))
.unwrap();
warnings
.call_method1("filterwarnings", ("error", "", cls, "pyo3test"))
.unwrap();
// This has the wrong module and will not raise
PyErr::warn(py, cls, "I am warning you", 0).unwrap();
let err =
PyErr::warn_explicit(py, cls, "I am warning you", "pyo3test.py", 427, None, None)
.unwrap_err();
assert!(err
.value(py)
.getattr("args")
.unwrap()
.get_item(0)
.unwrap()
.eq("I am warning you")
.unwrap());
// Finally, reset filter again
warnings.call_method0("resetwarnings").unwrap();
});
}
}

View File

@ -1,12 +1,15 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
//! Exception types defined by Python.
//! Exception and warning types defined by Python.
//!
//! The structs in this module represent Python's built-in exceptions, while the modules comprise
//! structs representing errors defined in Python code.
//! The structs in this module represent Python's built-in exceptions and
//! warnings, while the modules comprise structs representing errors defined in
//! Python code.
//!
//! The latter are created with the [`import_exception`](crate::import_exception) macro, which you
//! can use yourself to import Python exceptions.
//! The latter are created with the
//! [`import_exception`](crate::import_exception) macro, which you can use
//! yourself to import Python classes that are ultimately derived from
//! `BaseException`.
use crate::{ffi, PyResult, Python};
use std::ffi::CStr;
@ -660,6 +663,61 @@ impl PyUnicodeDecodeError {
}
}
impl_native_exception!(PyWarning, PyExc_Warning, native_doc!("Warning"));
impl_native_exception!(PyUserWarning, PyExc_UserWarning, native_doc!("UserWarning"));
impl_native_exception!(
PyDeprecationWarning,
PyExc_DeprecationWarning,
native_doc!("DeprecationWarning")
);
impl_native_exception!(
PyPendingDeprecationWarning,
PyExc_PendingDeprecationWarning,
native_doc!("PendingDeprecationWarning")
);
impl_native_exception!(
PySyntaxWarning,
PyExc_SyntaxWarning,
native_doc!("SyntaxWarning")
);
impl_native_exception!(
PyRuntimeWarning,
PyExc_RuntimeWarning,
native_doc!("RuntimeWarning")
);
impl_native_exception!(
PyFutureWarning,
PyExc_FutureWarning,
native_doc!("FutureWarning")
);
impl_native_exception!(
PyImportWarning,
PyExc_ImportWarning,
native_doc!("ImportWarning")
);
impl_native_exception!(
PyUnicodeWarning,
PyExc_UnicodeWarning,
native_doc!("UnicodeWarning")
);
impl_native_exception!(
PyBytesWarning,
PyExc_BytesWarning,
native_doc!("BytesWarning")
);
impl_native_exception!(
PyResourceWarning,
PyExc_ResourceWarning,
native_doc!("ResourceWarning")
);
#[cfg(Py_3_10)]
impl_native_exception!(
PyEncodingWarning,
PyExc_EncodingWarning,
native_doc!("EncodingWarning")
);
#[cfg(test)]
macro_rules! test_exception {
($exc_ty:ident $(, $constructor:expr)?) => {
@ -1017,4 +1075,17 @@ mod tests {
test_exception!(PyIOError);
#[cfg(windows)]
test_exception!(PyWindowsError);
test_exception!(PyWarning);
test_exception!(PyUserWarning);
test_exception!(PyDeprecationWarning);
test_exception!(PyPendingDeprecationWarning);
test_exception!(PySyntaxWarning);
test_exception!(PyRuntimeWarning);
test_exception!(PyFutureWarning);
test_exception!(PyImportWarning);
test_exception!(PyUnicodeWarning);
test_exception!(PyBytesWarning);
#[cfg(Py_3_10)]
test_exception!(PyEncodingWarning);
}