Add _showwarnmsg() and _formatwarnmsg() to warnings

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.
This commit is contained in:
Victor Stinner 2016-03-19 00:47:17 +01:00
parent 81ae89b611
commit 1231a4615f
3 changed files with 122 additions and 46 deletions

View File

@ -651,6 +651,17 @@ class _WarningsTests(BaseTest, unittest.TestCase):
result = stream.getvalue() result = stream.getvalue()
self.assertIn(text, result) 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): def test_showwarning_not_callable(self):
with original_warnings.catch_warnings(module=self.module): with original_warnings.catch_warnings(module=self.module):
self.module.filterwarnings("always", category=UserWarning) self.module.filterwarnings("always", category=UserWarning)

View File

@ -6,24 +6,63 @@ __all__ = ["warn", "warn_explicit", "showwarning",
"formatwarning", "filterwarnings", "simplefilter", "formatwarning", "filterwarnings", "simplefilter",
"resetwarnings", "catch_warnings"] "resetwarnings", "catch_warnings"]
def showwarning(message, category, filename, lineno, file=None, line=None): def showwarning(message, category, filename, lineno, file=None, line=None):
"""Hook to write a warning to a file; replace if you like.""" """Hook to write a warning to a file; replace if you like."""
if file is None: msg = WarningMessage(message, category, filename, lineno, file, line)
file = sys.stderr _showwarnmsg(msg)
if file is None:
# sys.stderr is None when run with pythonw.exe - warnings get lost
return
try:
file.write(formatwarning(message, category, filename, lineno, line))
except OSError:
pass # the file (probably stderr) is invalid - this warning gets lost.
def formatwarning(message, category, filename, lineno, line=None): def formatwarning(message, category, filename, lineno, line=None):
"""Function to format a warning the standard way.""" """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
return
text = _formatwarnmsg(msg)
try:
file.write(text)
except OSError:
# the file (probably stderr) is invalid - this warning gets lost.
pass
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 import linecache
s = "%s:%s: %s: %s\n" % (filename, lineno, category.__name__, message) s = ("%s:%s: %s: %s\n"
line = linecache.getline(filename, lineno) if line is None else line % (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: if line:
line = line.strip() line = line.strip()
s += " %s\n" % line s += " %s\n" % line
@ -293,17 +332,13 @@ def warn_explicit(message, category, filename, lineno,
raise RuntimeError( raise RuntimeError(
"Unrecognized action (%r) in warnings.filters:\n %s" % "Unrecognized action (%r) in warnings.filters:\n %s" %
(action, item)) (action, item))
if not callable(showwarning):
raise TypeError("warnings.showwarning() must be set to a "
"function or method")
# Print message and context # Print message and context
showwarning(message, category, filename, lineno) msg = WarningMessage(message, category, filename, lineno)
_showwarnmsg(msg)
class WarningMessage(object): class WarningMessage(object):
"""Holds the result of a single showwarning() call."""
_WARNING_DETAILS = ("message", "category", "filename", "lineno", "file", _WARNING_DETAILS = ("message", "category", "filename", "lineno", "file",
"line") "line")
@ -366,11 +401,12 @@ class catch_warnings(object):
self._module.filters = self._filters[:] self._module.filters = self._filters[:]
self._module._filters_mutated() self._module._filters_mutated()
self._showwarning = self._module.showwarning self._showwarning = self._module.showwarning
self._showwarnmsg = self._module._showwarnmsg
if self._record: if self._record:
log = [] log = []
def showwarning(*args, **kwargs): def showarnmsg(msg):
log.append(WarningMessage(*args, **kwargs)) log.append(msg)
self._module.showwarning = showwarning self._module._showwarnmsg = showarnmsg
return log return log
else: else:
return None return None
@ -381,6 +417,7 @@ class catch_warnings(object):
self._module.filters = self._filters self._module.filters = self._filters
self._module._filters_mutated() self._module._filters_mutated()
self._module.showwarning = self._showwarning self._module.showwarning = self._showwarning
self._module._showwarnmsg = self._showwarnmsg
# filters contains a sequence of filter 5-tuples # filters contains a sequence of filter 5-tuples

View File

@ -359,6 +359,56 @@ error:
PyErr_Clear(); 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 * static PyObject *
warn_explicit(PyObject *category, PyObject *message, warn_explicit(PyObject *category, PyObject *message,
PyObject *filename, int lineno, PyObject *filename, int lineno,
@ -470,31 +520,9 @@ warn_explicit(PyObject *category, PyObject *message,
if (rc == 1) /* Already warned for this module. */ if (rc == 1) /* Already warned for this module. */
goto return_none; goto return_none;
if (rc == 0) { if (rc == 0) {
PyObject *show_fxn = get_warnings_attr("showwarning"); if (call_show_warning(category, text, message, filename, lineno,
if (show_fxn == NULL) { lineno_obj, sourceline) < 0)
if (PyErr_Occurred())
goto cleanup; 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;
}
} }
else /* if (rc == -1) */ else /* if (rc == -1) */
goto cleanup; goto cleanup;