Skip to content

Commit 1d20f2a

Browse files
authored
Export warning classes and add PyErr::warn_explicit() (#2742)
1 parent f573561 commit 1d20f2a

File tree

5 files changed

+199
-7
lines changed

5 files changed

+199
-7
lines changed

newsfragments/2742.added.md

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

pyo3-ffi/src/pyerrors.rs

+2
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,8 @@ extern "C" {
245245
pub static mut PyExc_BytesWarning: *mut PyObject;
246246
#[cfg_attr(PyPy, link_name = "PyPyExc_ResourceWarning")]
247247
pub static mut PyExc_ResourceWarning: *mut PyObject;
248+
#[cfg(Py_3_10)]
249+
pub static mut PyExc_EncodingWarning: *mut PyObject;
248250
}
249251

250252
extern "C" {

pyo3-ffi/src/warnings.rs

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ extern "C" {
2222
format: *const c_char,
2323
...
2424
) -> c_int;
25+
#[cfg_attr(PyPy, link_name = "PyPyErr_WarnExplicit")]
2526
pub fn PyErr_WarnExplicit(
2627
category: *mut PyObject,
2728
message: *const c_char,

src/err/mod.rs

+119-2
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,28 @@ impl PyErr {
477477
}
478478

479479
/// Issues a warning message.
480-
/// May return a `PyErr` if warnings-as-errors is enabled.
480+
///
481+
/// May return an `Err(PyErr)` if warnings-as-errors is enabled.
482+
///
483+
/// Equivalent to `warnings.warn()` in Python.
484+
///
485+
/// The `category` should be one of the `Warning` classes available in
486+
/// [`pyo3::exceptions`](crate::exceptions), or a subclass. The Python
487+
/// object can be retrieved using [`PyTypeInfo::type_object()`].
488+
///
489+
/// Example:
490+
/// ```rust
491+
/// use pyo3::prelude::*;
492+
/// use pyo3::PyTypeInfo;
493+
///
494+
/// # fn main() -> PyResult<()> {
495+
/// Python::with_gil(|py| {
496+
/// let user_warning = pyo3::exceptions::PyUserWarning::type_object(py);
497+
/// PyErr::warn(py, user_warning, "I am warning you", 0)?;
498+
/// Ok(())
499+
/// })
500+
/// # }
501+
/// ```
481502
pub fn warn(py: Python<'_>, category: &PyAny, message: &str, stacklevel: i32) -> PyResult<()> {
482503
let message = CString::new(message)?;
483504
unsafe {
@@ -492,6 +513,49 @@ impl PyErr {
492513
}
493514
}
494515

516+
/// Issues a warning message, with more control over the warning attributes.
517+
///
518+
/// May return a `PyErr` if warnings-as-errors is enabled.
519+
///
520+
/// Equivalent to `warnings.warn_explicit()` in Python.
521+
///
522+
/// The `category` should be one of the `Warning` classes available in
523+
/// [`pyo3::exceptions`](crate::exceptions), or a subclass.
524+
pub fn warn_explicit(
525+
py: Python<'_>,
526+
category: &PyAny,
527+
message: &str,
528+
filename: &str,
529+
lineno: i32,
530+
module: Option<&str>,
531+
registry: Option<&PyAny>,
532+
) -> PyResult<()> {
533+
let message = CString::new(message)?;
534+
let filename = CString::new(filename)?;
535+
let module = module.map(CString::new).transpose()?;
536+
let module_ptr = match module {
537+
None => std::ptr::null_mut(),
538+
Some(s) => s.as_ptr(),
539+
};
540+
let registry: *mut ffi::PyObject = match registry {
541+
None => std::ptr::null_mut(),
542+
Some(obj) => obj.as_ptr(),
543+
};
544+
unsafe {
545+
error_on_minusone(
546+
py,
547+
ffi::PyErr_WarnExplicit(
548+
category.as_ptr(),
549+
message.as_ptr(),
550+
filename.as_ptr(),
551+
lineno,
552+
module_ptr,
553+
registry,
554+
),
555+
)
556+
}
557+
}
558+
495559
/// Clone the PyErr. This requires the GIL, which is why PyErr does not implement Clone.
496560
///
497561
/// # Examples
@@ -769,7 +833,7 @@ fn exceptions_must_derive_from_base_exception(py: Python<'_>) -> PyErr {
769833
mod tests {
770834
use super::PyErrState;
771835
use crate::exceptions;
772-
use crate::{AsPyPointer, PyErr, Python};
836+
use crate::{AsPyPointer, PyErr, PyTypeInfo, Python};
773837

774838
#[test]
775839
fn no_error() {
@@ -938,4 +1002,57 @@ mod tests {
9381002
);
9391003
});
9401004
}
1005+
1006+
#[test]
1007+
fn warnings() {
1008+
// Note: although the warning filter is interpreter global, keeping the
1009+
// GIL locked should prevent effects to be visible to other testing
1010+
// threads.
1011+
Python::with_gil(|py| {
1012+
let cls = exceptions::PyUserWarning::type_object(py);
1013+
1014+
// Reset warning filter to default state
1015+
let warnings = py.import("warnings").unwrap();
1016+
warnings.call_method0("resetwarnings").unwrap();
1017+
1018+
// First, test with ignoring the warning
1019+
warnings
1020+
.call_method1("simplefilter", ("ignore", cls))
1021+
.unwrap();
1022+
PyErr::warn(py, cls, "I am warning you", 0).unwrap();
1023+
1024+
// Test with raising
1025+
warnings
1026+
.call_method1("simplefilter", ("error", cls))
1027+
.unwrap();
1028+
PyErr::warn(py, cls, "I am warning you", 0).unwrap_err();
1029+
1030+
// Test with explicit module and specific filter
1031+
warnings.call_method0("resetwarnings").unwrap();
1032+
warnings
1033+
.call_method1("simplefilter", ("ignore", cls))
1034+
.unwrap();
1035+
warnings
1036+
.call_method1("filterwarnings", ("error", "", cls, "pyo3test"))
1037+
.unwrap();
1038+
1039+
// This has the wrong module and will not raise
1040+
PyErr::warn(py, cls, "I am warning you", 0).unwrap();
1041+
1042+
let err =
1043+
PyErr::warn_explicit(py, cls, "I am warning you", "pyo3test.py", 427, None, None)
1044+
.unwrap_err();
1045+
assert!(err
1046+
.value(py)
1047+
.getattr("args")
1048+
.unwrap()
1049+
.get_item(0)
1050+
.unwrap()
1051+
.eq("I am warning you")
1052+
.unwrap());
1053+
1054+
// Finally, reset filter again
1055+
warnings.call_method0("resetwarnings").unwrap();
1056+
});
1057+
}
9411058
}

src/exceptions.rs

+76-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
// Copyright (c) 2017-present PyO3 Project and Contributors
22

3-
//! Exception types defined by Python.
3+
//! Exception and warning types defined by Python.
44
//!
5-
//! The structs in this module represent Python's built-in exceptions, while the modules comprise
6-
//! structs representing errors defined in Python code.
5+
//! The structs in this module represent Python's built-in exceptions and
6+
//! warnings, while the modules comprise structs representing errors defined in
7+
//! Python code.
78
//!
8-
//! The latter are created with the [`import_exception`](crate::import_exception) macro, which you
9-
//! can use yourself to import Python exceptions.
9+
//! The latter are created with the
10+
//! [`import_exception`](crate::import_exception) macro, which you can use
11+
//! yourself to import Python classes that are ultimately derived from
12+
//! `BaseException`.
1013
1114
use crate::{ffi, PyResult, Python};
1215
use std::ffi::CStr;
@@ -660,6 +663,61 @@ impl PyUnicodeDecodeError {
660663
}
661664
}
662665

666+
impl_native_exception!(PyWarning, PyExc_Warning, native_doc!("Warning"));
667+
impl_native_exception!(PyUserWarning, PyExc_UserWarning, native_doc!("UserWarning"));
668+
impl_native_exception!(
669+
PyDeprecationWarning,
670+
PyExc_DeprecationWarning,
671+
native_doc!("DeprecationWarning")
672+
);
673+
impl_native_exception!(
674+
PyPendingDeprecationWarning,
675+
PyExc_PendingDeprecationWarning,
676+
native_doc!("PendingDeprecationWarning")
677+
);
678+
impl_native_exception!(
679+
PySyntaxWarning,
680+
PyExc_SyntaxWarning,
681+
native_doc!("SyntaxWarning")
682+
);
683+
impl_native_exception!(
684+
PyRuntimeWarning,
685+
PyExc_RuntimeWarning,
686+
native_doc!("RuntimeWarning")
687+
);
688+
impl_native_exception!(
689+
PyFutureWarning,
690+
PyExc_FutureWarning,
691+
native_doc!("FutureWarning")
692+
);
693+
impl_native_exception!(
694+
PyImportWarning,
695+
PyExc_ImportWarning,
696+
native_doc!("ImportWarning")
697+
);
698+
impl_native_exception!(
699+
PyUnicodeWarning,
700+
PyExc_UnicodeWarning,
701+
native_doc!("UnicodeWarning")
702+
);
703+
impl_native_exception!(
704+
PyBytesWarning,
705+
PyExc_BytesWarning,
706+
native_doc!("BytesWarning")
707+
);
708+
impl_native_exception!(
709+
PyResourceWarning,
710+
PyExc_ResourceWarning,
711+
native_doc!("ResourceWarning")
712+
);
713+
714+
#[cfg(Py_3_10)]
715+
impl_native_exception!(
716+
PyEncodingWarning,
717+
PyExc_EncodingWarning,
718+
native_doc!("EncodingWarning")
719+
);
720+
663721
#[cfg(test)]
664722
macro_rules! test_exception {
665723
($exc_ty:ident $(, $constructor:expr)?) => {
@@ -1017,4 +1075,17 @@ mod tests {
10171075
test_exception!(PyIOError);
10181076
#[cfg(windows)]
10191077
test_exception!(PyWindowsError);
1078+
1079+
test_exception!(PyWarning);
1080+
test_exception!(PyUserWarning);
1081+
test_exception!(PyDeprecationWarning);
1082+
test_exception!(PyPendingDeprecationWarning);
1083+
test_exception!(PySyntaxWarning);
1084+
test_exception!(PyRuntimeWarning);
1085+
test_exception!(PyFutureWarning);
1086+
test_exception!(PyImportWarning);
1087+
test_exception!(PyUnicodeWarning);
1088+
test_exception!(PyBytesWarning);
1089+
#[cfg(Py_3_10)]
1090+
test_exception!(PyEncodingWarning);
10201091
}

0 commit comments

Comments
 (0)