Skip to content

Commit ec92e18

Browse files
committed
Merge in r66135. Doing also required removing a stale DeprecationWarning along
with moving warnings.catch_warnings() over to keyword-only parameters for its constructor (as documented in the 2.6 docs).
1 parent 3a2bd7d commit ec92e18

6 files changed

Lines changed: 164 additions & 124 deletions

File tree

Doc/library/warnings.rst

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,3 +247,52 @@ Available Functions
247247
:func:`filterwarnings`, including that of the :option:`-W` command line options
248248
and calls to :func:`simplefilter`.
249249

250+
251+
Available Classes
252+
-----------------
253+
254+
.. class:: catch_warnings([\*, record=False, module=None])
255+
256+
A context manager that guards the warnings filter from being permanentally
257+
mutated. The manager returns an instance of :class:`WarningsRecorder`. The
258+
*record* argument specifies whether warnings that would typically be
259+
handled by :func:`showwarning` should instead be recorded by the
260+
:class:`WarningsRecorder` instance. This argument is typically set when
261+
testing for expected warnings behavior. The *module* argument may be a
262+
module object that is to be used instead of the :mod:`warnings` module.
263+
This argument should only be set when testing the :mod:`warnings` module
264+
or some similar use-case.
265+
266+
Typical usage of the context manager is like so::
267+
268+
def fxn():
269+
warn("fxn is deprecated", DeprecationWarning)
270+
return "spam spam bacon spam"
271+
272+
# The function 'fxn' is known to raise a DeprecationWarning.
273+
with catch_warnings() as w:
274+
warnings.filterwarning('ignore', 'fxn is deprecated', DeprecationWarning)
275+
fxn() # DeprecationWarning is temporarily suppressed.
276+
277+
.. versionadded:: 2.6
278+
279+
.. versionchanged:: 3.0
280+
281+
Constructor arguments turned into keyword-only arguments.
282+
283+
284+
.. class:: WarningsRecorder()
285+
286+
A subclass of :class:`list` that stores all warnings passed to
287+
:func:`showwarning` when returned by a :class:`catch_warnings` context
288+
manager created with its *record* argument set to ``True``. Each recorded
289+
warning is represented by an object whose attributes correspond to the
290+
arguments to :func:`showwarning`. As a convenience, a
291+
:class:`WarningsRecorder` instance has the attributes of the last
292+
recorded warning set on the :class:`WarningsRecorder` instance as well.
293+
294+
.. method:: reset()
295+
296+
Delete all recorded warnings.
297+
298+
.. versionadded:: 2.6

Lib/test/support.py

Lines changed: 2 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"is_resource_enabled", "requires", "find_unused_port", "bind_port",
2020
"fcmp", "is_jython", "TESTFN", "HOST", "FUZZ", "findfile", "verify",
2121
"vereq", "sortdict", "check_syntax_error", "open_urlresource",
22-
"WarningMessage", "catch_warning", "CleanImport", "EnvironmentVarGuard",
22+
"catch_warning", "CleanImport", "EnvironmentVarGuard",
2323
"TransientResource", "captured_output", "captured_stdout",
2424
"TransientResource", "transient_internet", "run_with_locale",
2525
"set_memlimit", "bigmemtest", "bigaddrspacetest", "BasicTestRunner",
@@ -368,47 +368,6 @@ def open_urlresource(url, *args, **kw):
368368
return open(fn, *args, **kw)
369369

370370

371-
class WarningMessage(object):
372-
"Holds the result of a single showwarning() call"
373-
_WARNING_DETAILS = "message category filename lineno line".split()
374-
def __init__(self, message, category, filename, lineno, line=None):
375-
for attr in self._WARNING_DETAILS:
376-
setattr(self, attr, locals()[attr])
377-
self._category_name = category.__name__ if category else None
378-
379-
def __str__(self):
380-
return ("{message : %r, category : %r, filename : %r, lineno : %s, "
381-
"line : %r}" % (self.message, self._category_name,
382-
self.filename, self.lineno, self.line))
383-
384-
class WarningRecorder(object):
385-
"Records the result of any showwarning calls"
386-
def __init__(self):
387-
self.warnings = []
388-
self._set_last(None)
389-
390-
def _showwarning(self, message, category, filename, lineno,
391-
file=None, line=None):
392-
wm = WarningMessage(message, category, filename, lineno, line)
393-
self.warnings.append(wm)
394-
self._set_last(wm)
395-
396-
def _set_last(self, last_warning):
397-
if last_warning is None:
398-
for attr in WarningMessage._WARNING_DETAILS:
399-
setattr(self, attr, None)
400-
else:
401-
for attr in WarningMessage._WARNING_DETAILS:
402-
setattr(self, attr, getattr(last_warning, attr))
403-
404-
def reset(self):
405-
self.warnings = []
406-
self._set_last(None)
407-
408-
def __str__(self):
409-
return '[%s]' % (', '.join(map(str, self.warnings)))
410-
411-
@contextlib.contextmanager
412371
def catch_warning(module=warnings, record=True):
413372
"""Guard the warnings filter from being permanently changed and
414373
optionally record the details of any warnings that are issued.
@@ -419,20 +378,7 @@ def catch_warning(module=warnings, record=True):
419378
warnings.warn("foo")
420379
assert str(w.message) == "foo"
421380
"""
422-
original_filters = module.filters
423-
original_showwarning = module.showwarning
424-
if record:
425-
recorder = WarningRecorder()
426-
module.showwarning = recorder._showwarning
427-
else:
428-
recorder = None
429-
try:
430-
# Replace the filters with a copy of the original
431-
module.filters = module.filters[:]
432-
yield recorder
433-
finally:
434-
module.showwarning = original_showwarning
435-
module.filters = original_filters
381+
return warnings.catch_warnings(record=record, module=module)
436382

437383

438384
class CleanImport(object):

Lib/test/test_warnings.py

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -79,20 +79,19 @@ def test_error(self):
7979
"FilterTests.test_error")
8080

8181
def test_ignore(self):
82-
with support.catch_warning(self.module) as w:
82+
with support.catch_warning(module=self.module) as w:
8383
self.module.resetwarnings()
8484
self.module.filterwarnings("ignore", category=UserWarning)
8585
self.module.warn("FilterTests.test_ignore", UserWarning)
86-
self.assert_(not w.message)
86+
self.assertEquals(len(w), 0)
8787

8888
def test_always(self):
89-
with support.catch_warning(self.module) as w:
89+
with support.catch_warning(module=self.module) as w:
9090
self.module.resetwarnings()
9191
self.module.filterwarnings("always", category=UserWarning)
9292
message = "FilterTests.test_always"
9393
self.module.warn(message, UserWarning)
9494
self.assert_(message, w.message)
95-
w.message = None # Reset.
9695
self.module.warn(message, UserWarning)
9796
self.assert_(w.message, message)
9897

@@ -107,7 +106,7 @@ def test_default(self):
107106
self.assertEquals(w.message, message)
108107
w.reset()
109108
elif x == 1:
110-
self.assert_(not w.message, "unexpected warning: " + str(w))
109+
self.assert_(not len(w), "unexpected warning: " + str(w))
111110
else:
112111
raise ValueError("loop variant unhandled")
113112

@@ -120,7 +119,7 @@ def test_module(self):
120119
self.assertEquals(w.message, message)
121120
w.reset()
122121
self.module.warn(message, UserWarning)
123-
self.assert_(not w.message, "unexpected message: " + str(w))
122+
self.assert_(not len(w), "unexpected message: " + str(w))
124123

125124
def test_once(self):
126125
with support.catch_warning(self.module) as w:
@@ -133,10 +132,10 @@ def test_once(self):
133132
w.reset()
134133
self.module.warn_explicit(message, UserWarning, "test_warnings.py",
135134
13)
136-
self.assert_(not w.message)
135+
self.assertEquals(len(w), 0)
137136
self.module.warn_explicit(message, UserWarning, "test_warnings2.py",
138137
42)
139-
self.assert_(not w.message)
138+
self.assertEquals(len(w), 0)
140139

141140
def test_inheritance(self):
142141
with support.catch_warning(self.module) as w:
@@ -156,7 +155,7 @@ def test_ordering(self):
156155
self.module.warn("FilterTests.test_ordering", UserWarning)
157156
except UserWarning:
158157
self.fail("order handling for actions failed")
159-
self.assert_(not w.message)
158+
self.assertEquals(len(w), 0)
160159

161160
def test_filterwarnings(self):
162161
# Test filterwarnings().
@@ -317,7 +316,6 @@ def test_warn_explicit_type_errors(self):
317316
None, Warning, None, 1, registry=42)
318317

319318

320-
321319
class CWarnTests(BaseTest, WarnTests):
322320
module = c_warnings
323321

@@ -377,7 +375,7 @@ def test_onceregistry(self):
377375
self.failUnlessEqual(w.message, message)
378376
w.reset()
379377
self.module.warn_explicit(message, UserWarning, "file", 42)
380-
self.assert_(not w.message)
378+
self.assertEquals(len(w), 0)
381379
# Test the resetting of onceregistry.
382380
self.module.onceregistry = {}
383381
__warningregistry__ = {}
@@ -388,7 +386,7 @@ def test_onceregistry(self):
388386
del self.module.onceregistry
389387
__warningregistry__ = {}
390388
self.module.warn_explicit(message, UserWarning, "file", 42)
391-
self.failUnless(not w.message)
389+
self.assertEquals(len(w), 0)
392390
finally:
393391
self.module.onceregistry = original_registry
394392

@@ -487,45 +485,45 @@ class CWarningsDisplayTests(BaseTest, WarningsDisplayTests):
487485
class PyWarningsDisplayTests(BaseTest, WarningsDisplayTests):
488486
module = py_warnings
489487

490-
class WarningsSupportTests(object):
491-
"""Test the warning tools from test support module"""
488+
class CatchWarningTests(BaseTest):
492489

493-
def test_catch_warning_restore(self):
490+
"""Test catch_warnings()."""
491+
492+
def test_catch_warnings_restore(self):
494493
wmod = self.module
495494
orig_filters = wmod.filters
496495
orig_showwarning = wmod.showwarning
497-
with support.catch_warning(wmod):
496+
with support.catch_warning(module=wmod):
498497
wmod.filters = wmod.showwarning = object()
499498
self.assert_(wmod.filters is orig_filters)
500499
self.assert_(wmod.showwarning is orig_showwarning)
501-
with support.catch_warning(wmod, record=False):
500+
with support.catch_warning(module=wmod, record=False):
502501
wmod.filters = wmod.showwarning = object()
503502
self.assert_(wmod.filters is orig_filters)
504503
self.assert_(wmod.showwarning is orig_showwarning)
505504

506-
def test_catch_warning_recording(self):
505+
def test_catch_warnings_recording(self):
507506
wmod = self.module
508-
with support.catch_warning(wmod) as w:
509-
self.assertEqual(w.warnings, [])
507+
with support.catch_warning(module=wmod) as w:
508+
self.assertEqual(w, [])
510509
wmod.simplefilter("always")
511510
wmod.warn("foo")
512511
self.assertEqual(str(w.message), "foo")
513512
wmod.warn("bar")
514513
self.assertEqual(str(w.message), "bar")
515-
self.assertEqual(str(w.warnings[0].message), "foo")
516-
self.assertEqual(str(w.warnings[1].message), "bar")
514+
self.assertEqual(str(w[0].message), "foo")
515+
self.assertEqual(str(w[1].message), "bar")
517516
w.reset()
518-
self.assertEqual(w.warnings, [])
517+
self.assertEqual(w, [])
519518
orig_showwarning = wmod.showwarning
520-
with support.catch_warning(wmod, record=False) as w:
519+
with support.catch_warning(module=wmod, record=False) as w:
521520
self.assert_(w is None)
522521
self.assert_(wmod.showwarning is orig_showwarning)
523522

524-
525-
class CWarningsSupportTests(BaseTest, WarningsSupportTests):
523+
class CCatchWarningTests(CatchWarningTests):
526524
module = c_warnings
527525

528-
class PyWarningsSupportTests(BaseTest, WarningsSupportTests):
526+
class PyCatchWarningTests(CatchWarningTests):
529527
module = py_warnings
530528

531529

@@ -539,7 +537,7 @@ def test_main():
539537
CWCmdLineTests, PyWCmdLineTests,
540538
_WarningsTests,
541539
CWarningsDisplayTests, PyWarningsDisplayTests,
542-
CWarningsSupportTests, PyWarningsSupportTests,
540+
CCatchWarningTests, PyCatchWarningTests,
543541
)
544542

545543

Lib/warnings.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,76 @@ def warn_explicit(message, category, filename, lineno,
254254
showwarning(message, category, filename, lineno)
255255

256256

257+
class WarningMessage(object):
258+
259+
"""Holds the result of a single showwarning() call."""
260+
261+
_WARNING_DETAILS = ("message", "category", "filename", "lineno", "file",
262+
"line")
263+
264+
def __init__(self, message, category, filename, lineno, file=None,
265+
line=None):
266+
local_values = locals()
267+
for attr in self._WARNING_DETAILS:
268+
setattr(self, attr, local_values[attr])
269+
self._category_name = category.__name__ if category else None
270+
271+
def __str__(self):
272+
return ("{message : %r, category : %r, filename : %r, lineno : %s, "
273+
"line : %r}" % (self.message, self._category_name,
274+
self.filename, self.lineno, self.line))
275+
276+
277+
class WarningsRecorder(list):
278+
279+
"""Record the result of various showwarning() calls."""
280+
281+
def showwarning(self, *args, **kwargs):
282+
self.append(WarningMessage(*args, **kwargs))
283+
284+
def __getattr__(self, attr):
285+
return getattr(self[-1], attr)
286+
287+
def reset(self):
288+
del self[:]
289+
290+
291+
class catch_warnings(object):
292+
293+
"""Guard the warnings filter from being permanently changed and optionally
294+
record the details of any warnings that are issued.
295+
296+
Context manager returns an instance of warnings.WarningRecorder which is a
297+
list of WarningMessage instances. Attributes on WarningRecorder are
298+
redirected to the last created WarningMessage instance.
299+
300+
"""
301+
302+
def __init__(self, *, record=False, module=None):
303+
"""Specify whether to record warnings and if an alternative module
304+
should be used other than sys.modules['warnings'].
305+
306+
For compatibility with Python 3.0, please consider all arguments to be
307+
keyword-only.
308+
309+
"""
310+
self._recorder = WarningsRecorder() if record else None
311+
self._module = sys.modules['warnings'] if module is None else module
312+
313+
def __enter__(self):
314+
self._filters = self._module.filters
315+
self._module.filters = self._filters[:]
316+
self._showwarning = self._module.showwarning
317+
if self._recorder is not None:
318+
self._recorder.reset() # In case the instance is being reused.
319+
self._module.showwarning = self._recorder.showwarning
320+
return self._recorder
321+
322+
def __exit__(self, *exc_info):
323+
self._module.filters = self._filters
324+
self._module.showwarning = self._showwarning
325+
326+
257327
# filters contains a sequence of filter 5-tuples
258328
# The components of the 5-tuple are:
259329
# - an action: error, ignore, always, default, module, or once

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ C API
6060
Library
6161
-------
6262

63+
- Issue 3602: As part of the merge of r66135, make the parameters on
64+
warnings.catch_warnings() keyword-only. Also remove a DeprecationWarning.
65+
6366
- The deprecation warnings for the camelCase threading API names were removed.
6467

6568
Extension Modules

0 commit comments

Comments
 (0)