Add _showwarnmsg() and _formatwarnmsg() to warnings
authorVictor Stinner <victor.stinner@gmail.com>
Fri, 18 Mar 2016 23:47:17 +0000 (00:47 +0100)
committerVictor Stinner <victor.stinner@gmail.com>
Fri, 18 Mar 2016 23:47:17 +0000 (00:47 +0100)
Issue #26568: add new  _showwarnmsg() and _formatwarnmsg() functions to the
warnings module.

The C function warn_explicit() now calls warnings._showwarnmsg() with a
warnings.WarningMessage as parameter, instead of calling warnings.showwarning()
with multiple parameters.

_showwarnmsg() calls warnings.showwarning() if warnings.showwarning() was
replaced. Same for _formatwarnmsg(): call warnings.formatwarning() if it was
replaced.

Lib/test/test_warnings/__init__.py
Lib/warnings.py
Python/_warnings.c

index cea9c574abfabf5f1f89ab704449521e4afae071..70eae4c35e7df272a1140c3f2b0dea656d780a6e 100644 (file)
@@ -651,6 +651,17 @@ class _WarningsTests(BaseTest, unittest.TestCase):
                 result = stream.getvalue()
         self.assertIn(text, result)
 
+    def test_showwarnmsg_missing(self):
+        # Test that _showwarnmsg() missing is okay.
+        text = 'del _showwarnmsg test'
+        with original_warnings.catch_warnings(module=self.module):
+            self.module.filterwarnings("always", category=UserWarning)
+            del self.module._showwarnmsg
+            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)
index 1d4fb208f831c8ec92472114e5127bfbf09c0554..f54726a45fc8b7025759dc8f91e48a0c5403b838 100644 (file)
@@ -6,24 +6,63 @@ __all__ = ["warn", "warn_explicit", "showwarning",
            "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)
+    _showwarnmsg(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 _formatwarnmsg(msg)
+
+# Keep references to check if the functions were replaced
+_showwarning = showwarning
+_formatwarning = formatwarning
+
+def _showwarnmsg(msg):
+    """Hook to write a warning to a file; replace if you like."""
+    showwarning = globals().get('showwarning', _showwarning)
+    if showwarning is not _showwarning:
+        # warnings.showwarning() was replaced
+        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 = _formatwarnmsg(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 _formatwarnmsg(msg):
     """Function to format a warning the standard way."""
+    formatwarning = globals().get('formatwarning', _formatwarning)
+    if formatwarning is not _formatwarning:
+        # warnings.formatwarning() was replaced
+        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 +332,13 @@ def warn_explicit(message, category, filename, lineno,
         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)
+    _showwarnmsg(msg)
 
 
 class WarningMessage(object):
 
-    """Holds the result of a single showwarning() call."""
-
     _WARNING_DETAILS = ("message", "category", "filename", "lineno", "file",
                         "line")
 
@@ -366,11 +401,12 @@ class catch_warnings(object):
         self._module.filters = self._filters[:]
         self._module._filters_mutated()
         self._showwarning = self._module.showwarning
+        self._showwarnmsg = self._module._showwarnmsg
         if self._record:
             log = []
-            def showwarning(*args, **kwargs):
-                log.append(WarningMessage(*args, **kwargs))
-            self._module.showwarning = showwarning
+            def showarnmsg(msg):
+                log.append(msg)
+            self._module._showwarnmsg = showarnmsg
             return log
         else:
             return None
@@ -381,6 +417,7 @@ class catch_warnings(object):
         self._module.filters = self._filters
         self._module._filters_mutated()
         self._module.showwarning = self._showwarning
+        self._module._showwarnmsg = self._showwarnmsg
 
 
 # filters contains a sequence of filter 5-tuples
index daa13551ec9fbb3477f7e5153d22dafefd39cd04..a8c3703926471875b7033080d1585c00614ba90a 100644 (file)
@@ -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("_showwarnmsg");
+    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._showwarnmsg() 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, PyObject *message,
     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;