diff -r 0f251e1b877d Lib/test/test_warnings/__init__.py --- a/Lib/test/test_warnings/__init__.py Tue Mar 15 14:28:04 2016 +0100 +++ b/Lib/test/test_warnings/__init__.py Tue Mar 15 15:27:58 2016 +0100 @@ -651,6 +651,17 @@ class _WarningsTests(BaseTest, unittest. result = stream.getvalue() self.assertIn(text, result) + def test_showmsg_missing(self): + # Test that showmsg() missing is okay. + text = 'del showmsg test' + with original_warnings.catch_warnings(module=self.module): + self.module.filterwarnings("always", category=UserWarning) + del self.module.showmsg + with support.captured_output('stderr') as stream: + self.module.warn(text) + result = stream.getvalue() + self.assertIn(text, result) + def test_showwarning_not_callable(self): with original_warnings.catch_warnings(module=self.module): self.module.filterwarnings("always", category=UserWarning) diff -r 0f251e1b877d Lib/warnings.py --- a/Lib/warnings.py Tue Mar 15 14:28:04 2016 +0100 +++ b/Lib/warnings.py Tue Mar 15 15:27:58 2016 +0100 @@ -6,24 +6,65 @@ import sys "formatwarning", "filterwarnings", "simplefilter", "resetwarnings", "catch_warnings"] - def showwarning(message, category, filename, lineno, file=None, line=None): """Hook to write a warning to a file; replace if you like.""" + msg = WarningMessage(message, category, filename, lineno, file, line) + showmsg(msg) + +def formatwarning(message, category, filename, lineno, line=None): + """Function to format a warning the standard way.""" + msg = WarningMessage(message, category, filename, lineno, None, line) + return formatmsg(msg) + +# Keep references to check if the functions were replaced +_showwarning = showwarning +_formatwarning = formatwarning + +def showmsg(msg): + """Hook to write a warning to a file; replace if you like.""" + showwarning = globals().get('showwarning', _showwarning) + if showwarning is not _showwarning: + # Backward-compatibility with applications overrinding + # warnings.showwarning(): call the overriden function + if not callable(showwarning): + raise TypeError("warnings.showwarning() must be set to a " + "function or method") + + showwarning(msg.message, msg.category, msg.filename, msg.lineno, + msg.file, msg.line) + return + + file = msg.file if file is None: file = sys.stderr if file is None: - # sys.stderr is None when run with pythonw.exe - warnings get lost + # sys.stderr is None when run with pythonw.exe: + # warnings get lost return + text = formatmsg(msg) try: - file.write(formatwarning(message, category, filename, lineno, line)) + file.write(text) except OSError: - pass # the file (probably stderr) is invalid - this warning gets lost. + # the file (probably stderr) is invalid - this warning gets lost. + pass -def formatwarning(message, category, filename, lineno, line=None): +def formatmsg(msg): """Function to format a warning the standard way.""" + formatwarning = globals().get('formatwarning', _formatwarning) + if formatwarning is not _formatwarning: + # Backward-compatibility with applications overrinding + # warnings.formatwarning(): call the overriden function + return formatwarning(msg.message, msg.category, + msg.filename, msg.lineno, line=msg.line) + import linecache - s = "%s:%s: %s: %s\n" % (filename, lineno, category.__name__, message) - line = linecache.getline(filename, lineno) if line is None else line + s = ("%s:%s: %s: %s\n" + % (msg.filename, msg.lineno, msg.category.__name__, + msg.message)) + if msg.line is None: + line = linecache.getline(msg.filename, msg.lineno) + else: + line = msg.line if line: line = line.strip() s += " %s\n" % line @@ -293,17 +334,13 @@ def warn_explicit(message, category, fil raise RuntimeError( "Unrecognized action (%r) in warnings.filters:\n %s" % (action, item)) - if not callable(showwarning): - raise TypeError("warnings.showwarning() must be set to a " - "function or method") # Print message and context - showwarning(message, category, filename, lineno) + msg = WarningMessage(message, category, filename, lineno) + showmsg(msg) class WarningMessage(object): - """Holds the result of a single showwarning() call.""" - _WARNING_DETAILS = ("message", "category", "filename", "lineno", "file", "line") @@ -366,11 +403,12 @@ class catch_warnings(object): self._module.filters = self._filters[:] self._module._filters_mutated() self._showwarning = self._module.showwarning + self._showmsg = self._module.showmsg if self._record: log = [] - def showwarning(*args, **kwargs): - log.append(WarningMessage(*args, **kwargs)) - self._module.showwarning = showwarning + def showmsg(msg): + log.append(msg) + self._module.showmsg = showmsg return log else: return None @@ -381,6 +419,7 @@ class catch_warnings(object): self._module.filters = self._filters self._module._filters_mutated() self._module.showwarning = self._showwarning + self._module.showmsg = self._showmsg # filters contains a sequence of filter 5-tuples diff -r 0f251e1b877d Python/_warnings.c --- a/Python/_warnings.c Tue Mar 15 14:28:04 2016 +0100 +++ b/Python/_warnings.c Tue Mar 15 15:27:58 2016 +0100 @@ -359,6 +359,56 @@ error: PyErr_Clear(); } +static int +call_show_warning(PyObject *category, PyObject *text, PyObject *message, + PyObject *filename, int lineno, PyObject *lineno_obj, + PyObject *sourceline) +{ + PyObject *show_fn, *msg, *res, *warnmsg_cls = NULL; + + show_fn = get_warnings_attr("showmsg"); + if (show_fn == NULL) { + if (PyErr_Occurred()) + return -1; + show_warning(filename, lineno, text, category, sourceline); + return 0; + } + + if (!PyCallable_Check(show_fn)) { + PyErr_SetString(PyExc_TypeError, + "warnings.showmsg() must be set to a callable"); + goto error; + } + + warnmsg_cls = get_warnings_attr("WarningMessage"); + if (warnmsg_cls == NULL) { + PyErr_SetString(PyExc_RuntimeError, + "unable to get warnings.WarningMessage"); + goto error; + } + + msg = PyObject_CallFunctionObjArgs(warnmsg_cls, message, category, + filename, lineno_obj, + NULL); + Py_DECREF(warnmsg_cls); + if (msg == NULL) + goto error; + + res = PyObject_CallFunctionObjArgs(show_fn, msg, NULL); + Py_DECREF(show_fn); + Py_DECREF(msg); + + if (res == NULL) + return -1; + + Py_DECREF(res); + return 0; + +error: + Py_XDECREF(show_fn); + return -1; +} + static PyObject * warn_explicit(PyObject *category, PyObject *message, PyObject *filename, int lineno, @@ -470,31 +520,9 @@ warn_explicit(PyObject *category, PyObje if (rc == 1) /* Already warned for this module. */ goto return_none; if (rc == 0) { - PyObject *show_fxn = get_warnings_attr("showwarning"); - if (show_fxn == NULL) { - if (PyErr_Occurred()) - goto cleanup; - show_warning(filename, lineno, text, category, sourceline); - } - else { - PyObject *res; - - if (!PyCallable_Check(show_fxn)) { - PyErr_SetString(PyExc_TypeError, - "warnings.showwarning() must be set to a " - "callable"); - Py_DECREF(show_fxn); - 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; - } + if (call_show_warning(category, text, message, filename, lineno, + lineno_obj, sourceline) < 0) + goto cleanup; } else /* if (rc == -1) */ goto cleanup;