diff --git a/Doc/library/warnings.rst b/Doc/library/warnings.rst index 94d47380c57..87c4fd6c003 100644 --- a/Doc/library/warnings.rst +++ b/Doc/library/warnings.rst @@ -220,7 +220,8 @@ Available Functions ``warnings.showwarning``. .. 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]) diff --git a/Lib/test/test_warnings.py b/Lib/test/test_warnings.py index 5df0b90a5d5..c854d7ade42 100644 --- a/Lib/test/test_warnings.py +++ b/Lib/test/test_warnings.py @@ -461,6 +461,32 @@ class PyWarningsDisplayTests(BaseTest, WarningsDisplayTests): 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(): py_warnings.onceregistry.clear() c_warnings.onceregistry.clear() @@ -471,6 +497,8 @@ def test_main(): CWCmdLineTests, PyWCmdLineTests, _WarningsTests, CWarningsDisplayTests, PyWarningsDisplayTests, + CShowwarningDeprecationTests, + PyShowwarningDeprecationTests, ) diff --git a/Lib/warnings.py b/Lib/warnings.py index 3c7357baf0c..cefa961b06e 100644 --- a/Lib/warnings.py +++ b/Lib/warnings.py @@ -3,6 +3,7 @@ # Note: function level imports should *not* be used # in this module as it may cause import lock deadlock. # See bug 683658. +import inspect import linecache import sys import types @@ -21,7 +22,7 @@ def warnpy3k(message, category=None, stacklevel=1): category = DeprecationWarning 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.""" if file is None: file = sys.stderr @@ -29,6 +30,9 @@ def showwarning(message, category, filename, lineno, file=None, line=None): file.write(formatwarning(message, category, filename, lineno, line)) except IOError: 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): """Function to format a warning the standard way.""" @@ -259,6 +263,15 @@ def warn_explicit(message, category, filename, lineno, "Unrecognized action (%r) in warnings.filters:\n %s" % (action, item)) # 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) diff --git a/Misc/NEWS b/Misc/NEWS index 85152387970..9c3743fb2af 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -42,7 +42,9 @@ Extension Modules machinery in such places as the parser where use of pure Python code is not possible. Both the ``showarning()`` and ``formatwarning()`` gain an 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 ------- diff --git a/Python/_warnings.c b/Python/_warnings.c index d843af6285b..3e7dda7db8f 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -229,8 +229,8 @@ static void show_warning(PyObject *filename, int lineno, PyObject *text, PyObject *category, PyObject *sourceline) { - PyObject *f_stderr; - PyObject *name; + PyObject *f_stderr; + PyObject *name; char lineno_str[128]; PyOS_snprintf(lineno_str, sizeof(lineno_str), ":%d: ", lineno); @@ -272,7 +272,7 @@ show_warning(PyObject *filename, int lineno, PyObject *text, PyObject } static PyObject * -warn_explicit(PyObject *category, PyObject *message, +warn_explicit(PyObject *category, PyObject *message, PyObject *filename, int lineno, PyObject *module, PyObject *registry, PyObject *sourceline) { @@ -347,12 +347,12 @@ warn_explicit(PyObject *category, PyObject *message, goto cleanup; } /* _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) { /* registry[(text, category, 0)] = 1 */ if (registry != NULL) - rc = update_registry(registry, text, category, 0); + rc = update_registry(registry, text, category, 0); } else if (strcmp(action, "default") != 0) { PyObject *to_str = PyObject_Str(item); @@ -378,15 +378,45 @@ warn_explicit(PyObject *category, PyObject *message, show_warning(filename, lineno, text, category, sourceline); } else { - PyObject *res; - - res = PyObject_CallFunctionObjArgs(show_fxn, message, category, + const char *msg = "functions overriding warnings.showwarning() " + "must support the 'line' argument"; + 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, NULL); - Py_DECREF(show_fxn); - Py_XDECREF(res); - if (res == NULL) - goto cleanup; + Py_DECREF(show_fxn); + Py_XDECREF(res); + if (res == NULL) + goto cleanup; + } } } else /* if (rc == -1) */ @@ -578,7 +608,7 @@ warnings_warn(PyObject *self, PyObject *args, PyObject *kwds) PyObject *message, *category = NULL; 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)) return NULL;