@@ -477,7 +477,28 @@ impl PyErr {
477
477
}
478
478
479
479
/// 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
+ /// ```
481
502
pub fn warn ( py : Python < ' _ > , category : & PyAny , message : & str , stacklevel : i32 ) -> PyResult < ( ) > {
482
503
let message = CString :: new ( message) ?;
483
504
unsafe {
@@ -492,6 +513,49 @@ impl PyErr {
492
513
}
493
514
}
494
515
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
+
495
559
/// Clone the PyErr. This requires the GIL, which is why PyErr does not implement Clone.
496
560
///
497
561
/// # Examples
@@ -769,7 +833,7 @@ fn exceptions_must_derive_from_base_exception(py: Python<'_>) -> PyErr {
769
833
mod tests {
770
834
use super :: PyErrState ;
771
835
use crate :: exceptions;
772
- use crate :: { AsPyPointer , PyErr , Python } ;
836
+ use crate :: { AsPyPointer , PyErr , PyTypeInfo , Python } ;
773
837
774
838
#[ test]
775
839
fn no_error ( ) {
@@ -938,4 +1002,57 @@ mod tests {
938
1002
) ;
939
1003
} ) ;
940
1004
}
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
+ }
941
1058
}
0 commit comments