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:
parent
81ae89b611
commit
1231a4615f
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue