Kaydet (Commit) ec92e181 authored tarafından Brett Cannon's avatar Brett Cannon

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).
üst 3a2bd7d5
...@@ -247,3 +247,52 @@ Available Functions ...@@ -247,3 +247,52 @@ Available Functions
:func:`filterwarnings`, including that of the :option:`-W` command line options :func:`filterwarnings`, including that of the :option:`-W` command line options
and calls to :func:`simplefilter`. and calls to :func:`simplefilter`.
Available Classes
-----------------
.. class:: catch_warnings([\*, record=False, module=None])
A context manager that guards the warnings filter from being permanentally
mutated. The manager returns an instance of :class:`WarningsRecorder`. The
*record* argument specifies whether warnings that would typically be
handled by :func:`showwarning` should instead be recorded by the
:class:`WarningsRecorder` instance. This argument is typically set when
testing for expected warnings behavior. The *module* argument may be a
module object that is to be used instead of the :mod:`warnings` module.
This argument should only be set when testing the :mod:`warnings` module
or some similar use-case.
Typical usage of the context manager is like so::
def fxn():
warn("fxn is deprecated", DeprecationWarning)
return "spam spam bacon spam"
# The function 'fxn' is known to raise a DeprecationWarning.
with catch_warnings() as w:
warnings.filterwarning('ignore', 'fxn is deprecated', DeprecationWarning)
fxn() # DeprecationWarning is temporarily suppressed.
.. versionadded:: 2.6
.. versionchanged:: 3.0
Constructor arguments turned into keyword-only arguments.
.. class:: WarningsRecorder()
A subclass of :class:`list` that stores all warnings passed to
:func:`showwarning` when returned by a :class:`catch_warnings` context
manager created with its *record* argument set to ``True``. Each recorded
warning is represented by an object whose attributes correspond to the
arguments to :func:`showwarning`. As a convenience, a
:class:`WarningsRecorder` instance has the attributes of the last
recorded warning set on the :class:`WarningsRecorder` instance as well.
.. method:: reset()
Delete all recorded warnings.
.. versionadded:: 2.6
...@@ -19,7 +19,7 @@ __all__ = ["Error", "TestFailed", "TestSkipped", "ResourceDenied", "import_modul ...@@ -19,7 +19,7 @@ __all__ = ["Error", "TestFailed", "TestSkipped", "ResourceDenied", "import_modul
"is_resource_enabled", "requires", "find_unused_port", "bind_port", "is_resource_enabled", "requires", "find_unused_port", "bind_port",
"fcmp", "is_jython", "TESTFN", "HOST", "FUZZ", "findfile", "verify", "fcmp", "is_jython", "TESTFN", "HOST", "FUZZ", "findfile", "verify",
"vereq", "sortdict", "check_syntax_error", "open_urlresource", "vereq", "sortdict", "check_syntax_error", "open_urlresource",
"WarningMessage", "catch_warning", "CleanImport", "EnvironmentVarGuard", "catch_warning", "CleanImport", "EnvironmentVarGuard",
"TransientResource", "captured_output", "captured_stdout", "TransientResource", "captured_output", "captured_stdout",
"TransientResource", "transient_internet", "run_with_locale", "TransientResource", "transient_internet", "run_with_locale",
"set_memlimit", "bigmemtest", "bigaddrspacetest", "BasicTestRunner", "set_memlimit", "bigmemtest", "bigaddrspacetest", "BasicTestRunner",
...@@ -368,47 +368,6 @@ def open_urlresource(url, *args, **kw): ...@@ -368,47 +368,6 @@ def open_urlresource(url, *args, **kw):
return open(fn, *args, **kw) return open(fn, *args, **kw)
class WarningMessage(object):
"Holds the result of a single showwarning() call"
_WARNING_DETAILS = "message category filename lineno line".split()
def __init__(self, message, category, filename, lineno, line=None):
for attr in self._WARNING_DETAILS:
setattr(self, attr, locals()[attr])
self._category_name = category.__name__ if category else None
def __str__(self):
return ("{message : %r, category : %r, filename : %r, lineno : %s, "
"line : %r}" % (self.message, self._category_name,
self.filename, self.lineno, self.line))
class WarningRecorder(object):
"Records the result of any showwarning calls"
def __init__(self):
self.warnings = []
self._set_last(None)
def _showwarning(self, message, category, filename, lineno,
file=None, line=None):
wm = WarningMessage(message, category, filename, lineno, line)
self.warnings.append(wm)
self._set_last(wm)
def _set_last(self, last_warning):
if last_warning is None:
for attr in WarningMessage._WARNING_DETAILS:
setattr(self, attr, None)
else:
for attr in WarningMessage._WARNING_DETAILS:
setattr(self, attr, getattr(last_warning, attr))
def reset(self):
self.warnings = []
self._set_last(None)
def __str__(self):
return '[%s]' % (', '.join(map(str, self.warnings)))
@contextlib.contextmanager
def catch_warning(module=warnings, record=True): def catch_warning(module=warnings, record=True):
"""Guard the warnings filter from being permanently changed and """Guard the warnings filter from being permanently changed and
optionally record the details of any warnings that are issued. optionally record the details of any warnings that are issued.
...@@ -419,20 +378,7 @@ def catch_warning(module=warnings, record=True): ...@@ -419,20 +378,7 @@ def catch_warning(module=warnings, record=True):
warnings.warn("foo") warnings.warn("foo")
assert str(w.message) == "foo" assert str(w.message) == "foo"
""" """
original_filters = module.filters return warnings.catch_warnings(record=record, module=module)
original_showwarning = module.showwarning
if record:
recorder = WarningRecorder()
module.showwarning = recorder._showwarning
else:
recorder = None
try:
# Replace the filters with a copy of the original
module.filters = module.filters[:]
yield recorder
finally:
module.showwarning = original_showwarning
module.filters = original_filters
class CleanImport(object): class CleanImport(object):
......
...@@ -79,20 +79,19 @@ class FilterTests(object): ...@@ -79,20 +79,19 @@ class FilterTests(object):
"FilterTests.test_error") "FilterTests.test_error")
def test_ignore(self): def test_ignore(self):
with support.catch_warning(self.module) as w: with support.catch_warning(module=self.module) as w:
self.module.resetwarnings() self.module.resetwarnings()
self.module.filterwarnings("ignore", category=UserWarning) self.module.filterwarnings("ignore", category=UserWarning)
self.module.warn("FilterTests.test_ignore", UserWarning) self.module.warn("FilterTests.test_ignore", UserWarning)
self.assert_(not w.message) self.assertEquals(len(w), 0)
def test_always(self): def test_always(self):
with support.catch_warning(self.module) as w: with support.catch_warning(module=self.module) as w:
self.module.resetwarnings() self.module.resetwarnings()
self.module.filterwarnings("always", category=UserWarning) self.module.filterwarnings("always", category=UserWarning)
message = "FilterTests.test_always" message = "FilterTests.test_always"
self.module.warn(message, UserWarning) self.module.warn(message, UserWarning)
self.assert_(message, w.message) self.assert_(message, w.message)
w.message = None # Reset.
self.module.warn(message, UserWarning) self.module.warn(message, UserWarning)
self.assert_(w.message, message) self.assert_(w.message, message)
...@@ -107,7 +106,7 @@ class FilterTests(object): ...@@ -107,7 +106,7 @@ class FilterTests(object):
self.assertEquals(w.message, message) self.assertEquals(w.message, message)
w.reset() w.reset()
elif x == 1: elif x == 1:
self.assert_(not w.message, "unexpected warning: " + str(w)) self.assert_(not len(w), "unexpected warning: " + str(w))
else: else:
raise ValueError("loop variant unhandled") raise ValueError("loop variant unhandled")
...@@ -120,7 +119,7 @@ class FilterTests(object): ...@@ -120,7 +119,7 @@ class FilterTests(object):
self.assertEquals(w.message, message) self.assertEquals(w.message, message)
w.reset() w.reset()
self.module.warn(message, UserWarning) self.module.warn(message, UserWarning)
self.assert_(not w.message, "unexpected message: " + str(w)) self.assert_(not len(w), "unexpected message: " + str(w))
def test_once(self): def test_once(self):
with support.catch_warning(self.module) as w: with support.catch_warning(self.module) as w:
...@@ -133,10 +132,10 @@ class FilterTests(object): ...@@ -133,10 +132,10 @@ class FilterTests(object):
w.reset() w.reset()
self.module.warn_explicit(message, UserWarning, "test_warnings.py", self.module.warn_explicit(message, UserWarning, "test_warnings.py",
13) 13)
self.assert_(not w.message) self.assertEquals(len(w), 0)
self.module.warn_explicit(message, UserWarning, "test_warnings2.py", self.module.warn_explicit(message, UserWarning, "test_warnings2.py",
42) 42)
self.assert_(not w.message) self.assertEquals(len(w), 0)
def test_inheritance(self): def test_inheritance(self):
with support.catch_warning(self.module) as w: with support.catch_warning(self.module) as w:
...@@ -156,7 +155,7 @@ class FilterTests(object): ...@@ -156,7 +155,7 @@ class FilterTests(object):
self.module.warn("FilterTests.test_ordering", UserWarning) self.module.warn("FilterTests.test_ordering", UserWarning)
except UserWarning: except UserWarning:
self.fail("order handling for actions failed") self.fail("order handling for actions failed")
self.assert_(not w.message) self.assertEquals(len(w), 0)
def test_filterwarnings(self): def test_filterwarnings(self):
# Test filterwarnings(). # Test filterwarnings().
...@@ -317,7 +316,6 @@ class WarnTests(unittest.TestCase): ...@@ -317,7 +316,6 @@ class WarnTests(unittest.TestCase):
None, Warning, None, 1, registry=42) None, Warning, None, 1, registry=42)
class CWarnTests(BaseTest, WarnTests): class CWarnTests(BaseTest, WarnTests):
module = c_warnings module = c_warnings
...@@ -377,7 +375,7 @@ class _WarningsTests(BaseTest): ...@@ -377,7 +375,7 @@ class _WarningsTests(BaseTest):
self.failUnlessEqual(w.message, message) self.failUnlessEqual(w.message, message)
w.reset() w.reset()
self.module.warn_explicit(message, UserWarning, "file", 42) self.module.warn_explicit(message, UserWarning, "file", 42)
self.assert_(not w.message) self.assertEquals(len(w), 0)
# Test the resetting of onceregistry. # Test the resetting of onceregistry.
self.module.onceregistry = {} self.module.onceregistry = {}
__warningregistry__ = {} __warningregistry__ = {}
...@@ -388,7 +386,7 @@ class _WarningsTests(BaseTest): ...@@ -388,7 +386,7 @@ class _WarningsTests(BaseTest):
del self.module.onceregistry del self.module.onceregistry
__warningregistry__ = {} __warningregistry__ = {}
self.module.warn_explicit(message, UserWarning, "file", 42) self.module.warn_explicit(message, UserWarning, "file", 42)
self.failUnless(not w.message) self.assertEquals(len(w), 0)
finally: finally:
self.module.onceregistry = original_registry self.module.onceregistry = original_registry
...@@ -487,45 +485,45 @@ class CWarningsDisplayTests(BaseTest, WarningsDisplayTests): ...@@ -487,45 +485,45 @@ class CWarningsDisplayTests(BaseTest, WarningsDisplayTests):
class PyWarningsDisplayTests(BaseTest, WarningsDisplayTests): class PyWarningsDisplayTests(BaseTest, WarningsDisplayTests):
module = py_warnings module = py_warnings
class WarningsSupportTests(object): class CatchWarningTests(BaseTest):
"""Test the warning tools from test support module"""
def test_catch_warning_restore(self): """Test catch_warnings()."""
def test_catch_warnings_restore(self):
wmod = self.module wmod = self.module
orig_filters = wmod.filters orig_filters = wmod.filters
orig_showwarning = wmod.showwarning orig_showwarning = wmod.showwarning
with support.catch_warning(wmod): with support.catch_warning(module=wmod):
wmod.filters = wmod.showwarning = object() wmod.filters = wmod.showwarning = object()
self.assert_(wmod.filters is orig_filters) self.assert_(wmod.filters is orig_filters)
self.assert_(wmod.showwarning is orig_showwarning) self.assert_(wmod.showwarning is orig_showwarning)
with support.catch_warning(wmod, record=False): with support.catch_warning(module=wmod, record=False):
wmod.filters = wmod.showwarning = object() wmod.filters = wmod.showwarning = object()
self.assert_(wmod.filters is orig_filters) self.assert_(wmod.filters is orig_filters)
self.assert_(wmod.showwarning is orig_showwarning) self.assert_(wmod.showwarning is orig_showwarning)
def test_catch_warning_recording(self): def test_catch_warnings_recording(self):
wmod = self.module wmod = self.module
with support.catch_warning(wmod) as w: with support.catch_warning(module=wmod) as w:
self.assertEqual(w.warnings, []) self.assertEqual(w, [])
wmod.simplefilter("always") wmod.simplefilter("always")
wmod.warn("foo") wmod.warn("foo")
self.assertEqual(str(w.message), "foo") self.assertEqual(str(w.message), "foo")
wmod.warn("bar") wmod.warn("bar")
self.assertEqual(str(w.message), "bar") self.assertEqual(str(w.message), "bar")
self.assertEqual(str(w.warnings[0].message), "foo") self.assertEqual(str(w[0].message), "foo")
self.assertEqual(str(w.warnings[1].message), "bar") self.assertEqual(str(w[1].message), "bar")
w.reset() w.reset()
self.assertEqual(w.warnings, []) self.assertEqual(w, [])
orig_showwarning = wmod.showwarning orig_showwarning = wmod.showwarning
with support.catch_warning(wmod, record=False) as w: with support.catch_warning(module=wmod, record=False) as w:
self.assert_(w is None) self.assert_(w is None)
self.assert_(wmod.showwarning is orig_showwarning) self.assert_(wmod.showwarning is orig_showwarning)
class CCatchWarningTests(CatchWarningTests):
class CWarningsSupportTests(BaseTest, WarningsSupportTests):
module = c_warnings module = c_warnings
class PyWarningsSupportTests(BaseTest, WarningsSupportTests): class PyCatchWarningTests(CatchWarningTests):
module = py_warnings module = py_warnings
...@@ -539,7 +537,7 @@ def test_main(): ...@@ -539,7 +537,7 @@ def test_main():
CWCmdLineTests, PyWCmdLineTests, CWCmdLineTests, PyWCmdLineTests,
_WarningsTests, _WarningsTests,
CWarningsDisplayTests, PyWarningsDisplayTests, CWarningsDisplayTests, PyWarningsDisplayTests,
CWarningsSupportTests, PyWarningsSupportTests, CCatchWarningTests, PyCatchWarningTests,
) )
......
...@@ -254,6 +254,76 @@ def warn_explicit(message, category, filename, lineno, ...@@ -254,6 +254,76 @@ def warn_explicit(message, category, filename, lineno,
showwarning(message, category, filename, lineno) showwarning(message, category, filename, lineno)
class WarningMessage(object):
"""Holds the result of a single showwarning() call."""
_WARNING_DETAILS = ("message", "category", "filename", "lineno", "file",
"line")
def __init__(self, message, category, filename, lineno, file=None,
line=None):
local_values = locals()
for attr in self._WARNING_DETAILS:
setattr(self, attr, local_values[attr])
self._category_name = category.__name__ if category else None
def __str__(self):
return ("{message : %r, category : %r, filename : %r, lineno : %s, "
"line : %r}" % (self.message, self._category_name,
self.filename, self.lineno, self.line))
class WarningsRecorder(list):
"""Record the result of various showwarning() calls."""
def showwarning(self, *args, **kwargs):
self.append(WarningMessage(*args, **kwargs))
def __getattr__(self, attr):
return getattr(self[-1], attr)
def reset(self):
del self[:]
class catch_warnings(object):
"""Guard the warnings filter from being permanently changed and optionally
record the details of any warnings that are issued.
Context manager returns an instance of warnings.WarningRecorder which is a
list of WarningMessage instances. Attributes on WarningRecorder are
redirected to the last created WarningMessage instance.
"""
def __init__(self, *, record=False, module=None):
"""Specify whether to record warnings and if an alternative module
should be used other than sys.modules['warnings'].
For compatibility with Python 3.0, please consider all arguments to be
keyword-only.
"""
self._recorder = WarningsRecorder() if record else None
self._module = sys.modules['warnings'] if module is None else module
def __enter__(self):
self._filters = self._module.filters
self._module.filters = self._filters[:]
self._showwarning = self._module.showwarning
if self._recorder is not None:
self._recorder.reset() # In case the instance is being reused.
self._module.showwarning = self._recorder.showwarning
return self._recorder
def __exit__(self, *exc_info):
self._module.filters = self._filters
self._module.showwarning = self._showwarning
# filters contains a sequence of filter 5-tuples # filters contains a sequence of filter 5-tuples
# The components of the 5-tuple are: # The components of the 5-tuple are:
# - an action: error, ignore, always, default, module, or once # - an action: error, ignore, always, default, module, or once
......
...@@ -60,6 +60,9 @@ C API ...@@ -60,6 +60,9 @@ C API
Library Library
------- -------
- Issue 3602: As part of the merge of r66135, make the parameters on
warnings.catch_warnings() keyword-only. Also remove a DeprecationWarning.
- The deprecation warnings for the camelCase threading API names were removed. - The deprecation warnings for the camelCase threading API names were removed.
Extension Modules Extension Modules
......
...@@ -386,49 +386,23 @@ warn_explicit(PyObject *category, PyObject *message, ...@@ -386,49 +386,23 @@ warn_explicit(PyObject *category, PyObject *message,
show_warning(filename, lineno, text, category, sourceline); show_warning(filename, lineno, text, category, sourceline);
} }
else { else {
const char *msg = "functions overriding warnings.showwarning() " PyObject *res;
"must support the 'line' argument";
const char *text_char = _PyUnicode_AsString(text);
if (strcmp(msg, text_char) == 0) {
/* Prevent infinite recursion by using built-in implementation
of showwarning(). */
show_warning(filename, lineno, text, category, sourceline);
}
else {
PyObject *check_fxn;
PyObject *defaults;
PyObject *res;
if (PyMethod_Check(show_fxn))
check_fxn = PyMethod_Function(show_fxn);
else if (PyFunction_Check(show_fxn))
check_fxn = show_fxn;
else {
PyErr_SetString(PyExc_TypeError,
"warnings.showwarning() must be set to a "
"function or method");
Py_DECREF(show_fxn);
goto cleanup;
}
defaults = PyFunction_GetDefaults(check_fxn); if (!PyMethod_Check(show_fxn) && !PyFunction_Check(show_fxn)) {
/* A proper implementation of warnings.showwarning() should PyErr_SetString(PyExc_TypeError,
have at least two default arguments. */ "warnings.showwarning() must be set to a "
if ((defaults == NULL) || (PyTuple_Size(defaults) < 2)) { "function or method");
if (PyErr_WarnEx(PyExc_DeprecationWarning, msg, 1) < 0) {
Py_DECREF(show_fxn);
goto cleanup;
}
}
res = PyObject_CallFunctionObjArgs(show_fxn, message, category,
filename, lineno_obj,
NULL);
Py_DECREF(show_fxn); Py_DECREF(show_fxn);
Py_XDECREF(res); goto cleanup;
if (res == NULL)
goto cleanup;
} }
res = PyObject_CallFunctionObjArgs(show_fxn, message, category,
filename, lineno_obj,
NULL);
Py_DECREF(show_fxn);
Py_XDECREF(res);
if (res == NULL)
goto cleanup;
} }
} }
else /* if (rc == -1) */ else /* if (rc == -1) */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment