@@ -90,6 +90,7 @@ def warns(
9090 expected_warning : type [Warning ] | tuple [type [Warning ], ...] = ...,
9191 * ,
9292 match : str | Pattern [str ] | None = ...,
93+ keep_ignores : bool = ...,
9394) -> WarningsChecker : ...
9495
9596
@@ -106,6 +107,7 @@ def warns(
106107 expected_warning : type [Warning ] | tuple [type [Warning ], ...] = Warning ,
107108 * args : Any ,
108109 match : str | Pattern [str ] | None = None ,
110+ keep_ignores : bool = False ,
109111 ** kwargs : Any ,
110112) -> WarningsChecker | Any :
111113 r"""Assert that code raises a particular class of warning.
@@ -140,6 +142,22 @@ def warns(
140142 ...
141143 Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted...
142144
145+ You may also set the keyword argument ``keep_ignores`` to avoid catching warnings
146+ which were filtered out, in pytest configuration or otherwise::
147+
148+ >>> warnings.simplefilter("ignore", category=FutureWarning)
149+ >>> with pytest.warns(UserWarning, keep_ignores=True):
150+ ... warnings.warn("ignore this warning", UserWarning)
151+ Traceback (most recent call last):
152+ ...
153+ Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted...
154+
155+ >>> with pytest.warns(RuntimeWarning):
156+ >>> warnings.simplefilter("ignore", category=FutureWarning)
157+ >>> with pytest.warns(UserWarning, keep_ignores=True):
158+ ... warnings.warn("ignore this warning", UserWarning)
159+ warnings.warn("keep this warning", RuntimeWarning)
160+
143161 **Using with** ``pytest.mark.parametrize``
144162
145163 When using :ref:`pytest.mark.parametrize ref` it is possible to parametrize tests
@@ -157,7 +175,12 @@ def warns(
157175 f"Unexpected keyword arguments passed to pytest.warns: { argnames } "
158176 "\n Use context-manager form instead?"
159177 )
160- return WarningsChecker (expected_warning , match_expr = match , _ispytest = True )
178+ return WarningsChecker (
179+ expected_warning ,
180+ match_expr = match ,
181+ keep_ignores = keep_ignores ,
182+ _ispytest = True ,
183+ )
161184 else :
162185 func = args [0 ]
163186 if not callable (func ):
@@ -179,11 +202,12 @@ class WarningsRecorder(warnings.catch_warnings): # type:ignore[type-arg]
179202
180203 """
181204
182- def __init__ (self , * , _ispytest : bool = False ) -> None :
205+ def __init__ (self , * , keep_ignores : bool = False , _ispytest : bool = False ) -> None :
183206 check_ispytest (_ispytest )
184207 super ().__init__ (record = True )
185208 self ._entered = False
186209 self ._list : list [warnings .WarningMessage ] = []
210+ self ._keep_ignores = keep_ignores
187211
188212 @property
189213 def list (self ) -> list [warnings .WarningMessage ]:
@@ -233,7 +257,20 @@ def __enter__(self) -> Self:
233257 # record=True means it's None.
234258 assert _list is not None
235259 self ._list = _list
236- warnings .simplefilter ("always" )
260+
261+ if self ._keep_ignores :
262+ for action , message , category , module , lineno in reversed (warnings .filters ):
263+ if isinstance (module , re .Pattern ):
264+ module = getattr (module , "pattern" , None ) # type: ignore[unreachable]
265+ warnings .filterwarnings (
266+ action = "always" if action != "ignore" else "ignore" ,
267+ message = message or "" ,
268+ category = category ,
269+ module = module or "" ,
270+ lineno = lineno ,
271+ )
272+ else :
273+ warnings .simplefilter ("always" )
237274 return self
238275
239276 def __exit__ (
@@ -259,11 +296,12 @@ def __init__(
259296 self ,
260297 expected_warning : type [Warning ] | tuple [type [Warning ], ...] = Warning ,
261298 match_expr : str | Pattern [str ] | None = None ,
299+ keep_ignores : bool = False ,
262300 * ,
263301 _ispytest : bool = False ,
264302 ) -> None :
265303 check_ispytest (_ispytest )
266- super ().__init__ (_ispytest = True )
304+ super ().__init__ (keep_ignores = keep_ignores , _ispytest = True )
267305
268306 msg = "exceptions must be derived from Warning, not %s"
269307 if isinstance (expected_warning , tuple ):
0 commit comments