Commit 8a232cc3 authored by Brett Cannon's avatar Brett Cannon

Add a DeprecationWarning for when warnings.showwarning() is set to a function

that lacks support for the new 'line' argument.
parent 9ae080ee
...@@ -220,7 +220,8 @@ Available Functions ...@@ -220,7 +220,8 @@ Available Functions
``warnings.showwarning``. ``warnings.showwarning``.
.. versionchanged:: 2.6 .. versionchanged:: 2.6
Added the *line* argument. Added the *line* argument. Implementations that lack the new argument
will trigger a :exc:`DeprecationWarning`.
.. function:: formatwarning(message, category, filename, lineno[, line]) .. function:: formatwarning(message, category, filename, lineno[, line])
......
...@@ -461,6 +461,32 @@ class PyWarningsDisplayTests(BaseTest, WarningsDisplayTests): ...@@ -461,6 +461,32 @@ class PyWarningsDisplayTests(BaseTest, WarningsDisplayTests):
module = py_warnings module = py_warnings
class ShowwarningDeprecationTests(BaseTest):
"""Test the deprecation of the old warnings.showwarning() API works."""
@staticmethod
def bad_showwarning(message, category, filename, lineno, file=None):
pass
def test_deprecation(self):
# message, category, filename, lineno[, file[, line]]
args = ("message", UserWarning, "file name", 42)
with test_support.catch_warning(self.module):
self.module.filterwarnings("error", category=DeprecationWarning)
self.module.showwarning = self.bad_showwarning
self.assertRaises(DeprecationWarning, self.module.warn_explicit,
*args)
class CShowwarningDeprecationTests(ShowwarningDeprecationTests):
module = c_warnings
class PyShowwarningDeprecationTests(ShowwarningDeprecationTests):
module = py_warnings
def test_main(): def test_main():
py_warnings.onceregistry.clear() py_warnings.onceregistry.clear()
c_warnings.onceregistry.clear() c_warnings.onceregistry.clear()
...@@ -471,6 +497,8 @@ def test_main(): ...@@ -471,6 +497,8 @@ def test_main():
CWCmdLineTests, PyWCmdLineTests, CWCmdLineTests, PyWCmdLineTests,
_WarningsTests, _WarningsTests,
CWarningsDisplayTests, PyWarningsDisplayTests, CWarningsDisplayTests, PyWarningsDisplayTests,
CShowwarningDeprecationTests,
PyShowwarningDeprecationTests,
) )
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
# Note: function level imports should *not* be used # Note: function level imports should *not* be used
# in this module as it may cause import lock deadlock. # in this module as it may cause import lock deadlock.
# See bug 683658. # See bug 683658.
import inspect
import linecache import linecache
import sys import sys
import types import types
...@@ -21,7 +22,7 @@ def warnpy3k(message, category=None, stacklevel=1): ...@@ -21,7 +22,7 @@ def warnpy3k(message, category=None, stacklevel=1):
category = DeprecationWarning category = DeprecationWarning
warn(message, category, stacklevel+1) warn(message, category, stacklevel+1)
def showwarning(message, category, filename, lineno, file=None, line=None): def _show_warning(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: if file is None:
file = sys.stderr file = sys.stderr
...@@ -29,6 +30,9 @@ def showwarning(message, category, filename, lineno, file=None, line=None): ...@@ -29,6 +30,9 @@ def showwarning(message, category, filename, lineno, file=None, line=None):
file.write(formatwarning(message, category, filename, lineno, line)) file.write(formatwarning(message, category, filename, lineno, line))
except IOError: except IOError:
pass # the file (probably stderr) is invalid - this warning gets lost. pass # the file (probably stderr) is invalid - this warning gets lost.
# Keep a worrking version around in case the deprecation of the old API is
# triggered.
showwarning = _show_warning
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."""
...@@ -259,6 +263,15 @@ def warn_explicit(message, category, filename, lineno, ...@@ -259,6 +263,15 @@ def warn_explicit(message, category, filename, lineno,
"Unrecognized action (%r) in warnings.filters:\n %s" % "Unrecognized action (%r) in warnings.filters:\n %s" %
(action, item)) (action, item))
# Print message and context # Print message and context
if inspect.isfunction(showwarning):
arg_spec = inspect.getargspec(showwarning)
if 'line' not in arg_spec.args:
showwarning_msg = ("functions overriding warnings.showwarning() "
"must support the 'line' argument")
if message == showwarning_msg:
_show_warning(message, category, filename, lineno)
else:
warn(showwarning_msg, DeprecationWarning)
showwarning(message, category, filename, lineno) showwarning(message, category, filename, lineno)
......
...@@ -42,7 +42,9 @@ Extension Modules ...@@ -42,7 +42,9 @@ Extension Modules
machinery in such places as the parser where use of pure Python code is not machinery in such places as the parser where use of pure Python code is not
possible. Both the ``showarning()`` and ``formatwarning()`` gain an possible. Both the ``showarning()`` and ``formatwarning()`` gain an
optional 'line' argument which is not called by default for optional 'line' argument which is not called by default for
backwards-compatibility reasons. backwards-compatibility reasons. Setting ``warnings.showwarning()`` to
an implementation that lacks support for the ``line`` argument will raise a
DeprecationWarning.
Library Library
------- -------
......
...@@ -229,8 +229,8 @@ static void ...@@ -229,8 +229,8 @@ static void
show_warning(PyObject *filename, int lineno, PyObject *text, PyObject show_warning(PyObject *filename, int lineno, PyObject *text, PyObject
*category, PyObject *sourceline) *category, PyObject *sourceline)
{ {
PyObject *f_stderr; PyObject *f_stderr;
PyObject *name; PyObject *name;
char lineno_str[128]; char lineno_str[128];
PyOS_snprintf(lineno_str, sizeof(lineno_str), ":%d: ", lineno); PyOS_snprintf(lineno_str, sizeof(lineno_str), ":%d: ", lineno);
...@@ -272,7 +272,7 @@ show_warning(PyObject *filename, int lineno, PyObject *text, PyObject ...@@ -272,7 +272,7 @@ show_warning(PyObject *filename, int lineno, PyObject *text, PyObject
} }
static PyObject * static PyObject *
warn_explicit(PyObject *category, PyObject *message, warn_explicit(PyObject *category, PyObject *message,
PyObject *filename, int lineno, PyObject *filename, int lineno,
PyObject *module, PyObject *registry, PyObject *sourceline) PyObject *module, PyObject *registry, PyObject *sourceline)
{ {
...@@ -347,12 +347,12 @@ warn_explicit(PyObject *category, PyObject *message, ...@@ -347,12 +347,12 @@ warn_explicit(PyObject *category, PyObject *message,
goto cleanup; goto cleanup;
} }
/* _once_registry[(text, category)] = 1 */ /* _once_registry[(text, category)] = 1 */
rc = update_registry(registry, text, category, 0); rc = update_registry(registry, text, category, 0);
} }
else if (strcmp(action, "module") == 0) { else if (strcmp(action, "module") == 0) {
/* registry[(text, category, 0)] = 1 */ /* registry[(text, category, 0)] = 1 */
if (registry != NULL) if (registry != NULL)
rc = update_registry(registry, text, category, 0); rc = update_registry(registry, text, category, 0);
} }
else if (strcmp(action, "default") != 0) { else if (strcmp(action, "default") != 0) {
PyObject *to_str = PyObject_Str(item); PyObject *to_str = PyObject_Str(item);
...@@ -378,15 +378,45 @@ warn_explicit(PyObject *category, PyObject *message, ...@@ -378,15 +378,45 @@ warn_explicit(PyObject *category, PyObject *message,
show_warning(filename, lineno, text, category, sourceline); show_warning(filename, lineno, text, category, sourceline);
} }
else { else {
PyObject *res; const char *msg = "functions overriding warnings.showwarning() "
"must support the 'line' argument";
res = PyObject_CallFunctionObjArgs(show_fxn, message, category, const char *text_char = PyString_AS_STRING(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");
}
defaults = PyFunction_GetDefaults(check_fxn);
/* A proper implementation of warnings.showwarning() should
have at least two default arguments. */
if ((defaults == NULL) || (PyTuple_Size(defaults) < 2)) {
if (PyErr_WarnEx(PyExc_DeprecationWarning, msg, 1) < 0)
goto cleanup;
}
res = PyObject_CallFunctionObjArgs(show_fxn, message, category,
filename, lineno_obj, filename, lineno_obj,
NULL); NULL);
Py_DECREF(show_fxn); Py_DECREF(show_fxn);
Py_XDECREF(res); Py_XDECREF(res);
if (res == NULL) if (res == NULL)
goto cleanup; goto cleanup;
}
} }
} }
else /* if (rc == -1) */ else /* if (rc == -1) */
...@@ -578,7 +608,7 @@ warnings_warn(PyObject *self, PyObject *args, PyObject *kwds) ...@@ -578,7 +608,7 @@ warnings_warn(PyObject *self, PyObject *args, PyObject *kwds)
PyObject *message, *category = NULL; PyObject *message, *category = NULL;
Py_ssize_t stack_level = 1; Py_ssize_t stack_level = 1;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|On:warn", kw_list, if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|On:warn", kw_list,
&message, &category, &stack_level)) &message, &category, &stack_level))
return NULL; return NULL;
......
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