diff options
-rw-r--r-- | Doc/library/warnings.rst | 18 | ||||
-rw-r--r-- | Include/Python.h | 1 | ||||
-rw-r--r-- | Include/pyerrors.h | 10 | ||||
-rw-r--r-- | Include/sysmodule.h | 1 | ||||
-rw-r--r-- | Include/traceback.h | 1 | ||||
-rw-r--r-- | Include/warnings.h | 19 | ||||
-rw-r--r-- | Lib/test/test_support.py | 30 | ||||
-rw-r--r-- | Lib/test/test_warnings.py | 398 | ||||
-rw-r--r-- | Lib/warnings.py | 271 | ||||
-rw-r--r-- | Makefile.pre.in | 2 | ||||
-rw-r--r-- | Misc/NEWS | 5 | ||||
-rw-r--r-- | Modules/config.c.in | 4 | ||||
-rw-r--r-- | PC/config.c | 4 | ||||
-rw-r--r-- | Python/_warnings.c | 856 | ||||
-rw-r--r-- | Python/errors.c | 75 | ||||
-rw-r--r-- | Python/pythonrun.c | 75 | ||||
-rw-r--r-- | Python/sysmodule.c | 8 | ||||
-rw-r--r-- | Python/traceback.c | 60 |
18 files changed, 1459 insertions, 379 deletions
diff --git a/Doc/library/warnings.rst b/Doc/library/warnings.rst index d2e5b68..2267a0d 100644 --- a/Doc/library/warnings.rst +++ b/Doc/library/warnings.rst @@ -211,20 +211,28 @@ Available Functions is using :exc:`DeprecationWarning` as default warning class. -.. function:: showwarning(message, category, filename, lineno[, file]) +.. function:: showwarning(message, category, filename, lineno[, file[, line]]) Write a warning to a file. The default implementation calls - ``formatwarning(message, category, filename, lineno)`` and writes the resulting - string to *file*, which defaults to ``sys.stderr``. You may replace this - function with an alternative implementation by assigning to + ``formatwarning(message, category, filename, lineno, line)`` and writes the + resulting string to *file*, which defaults to ``sys.stderr``. You may replace + this function with an alternative implementation by assigning to ``warnings.showwarning``. + ..versionchanged:: 2.6 + Added the `line` argument. -.. function:: formatwarning(message, category, filename, lineno) + + + +.. function:: formatwarning(message, category, filename, lineno[, line]) Format a warning the standard way. This returns a string which may contain embedded newlines and ends in a newline. + ..versionchanged:: 2.6 + Added the `line` argument. + .. function:: filterwarnings(action[, message[, category[, module[, lineno[, append]]]]]) diff --git a/Include/Python.h b/Include/Python.h index 4cffda3..2ff4c31 100644 --- a/Include/Python.h +++ b/Include/Python.h @@ -110,6 +110,7 @@ #include "iterobject.h" #include "genobject.h" #include "descrobject.h" +#include "warnings.h" #include "weakrefobject.h" #include "codecs.h" diff --git a/Include/pyerrors.h b/Include/pyerrors.h index b687733..a4233c9 100644 --- a/Include/pyerrors.h +++ b/Include/pyerrors.h @@ -227,16 +227,6 @@ PyAPI_FUNC(PyObject *) PyErr_NewException(char *name, PyObject *base, PyObject *dict); PyAPI_FUNC(void) PyErr_WriteUnraisable(PyObject *); -/* Issue a warning or exception */ -PyAPI_FUNC(int) PyErr_WarnEx(PyObject *category, const char *msg, - Py_ssize_t stack_level); -PyAPI_FUNC(int) PyErr_WarnExplicit(PyObject *, const char *, - const char *, int, - const char *, PyObject *); -/* PyErr_Warn is only for backwards compatability and will be removed. - Use PyErr_WarnEx instead. */ -#define PyErr_Warn(category, msg) PyErr_WarnEx(category, msg, 1) - /* In sigcheck.c or signalmodule.c */ PyAPI_FUNC(int) PyErr_CheckSignals(void); PyAPI_FUNC(void) PyErr_SetInterrupt(void); diff --git a/Include/sysmodule.h b/Include/sysmodule.h index 1c9b187..536a180 100644 --- a/Include/sysmodule.h +++ b/Include/sysmodule.h @@ -23,6 +23,7 @@ PyAPI_DATA(int) _PySys_CheckInterval; PyAPI_FUNC(void) PySys_ResetWarnOptions(void); PyAPI_FUNC(void) PySys_AddWarnOption(char *); +PyAPI_FUNC(int) PySys_HasWarnOptions(void); #ifdef __cplusplus } diff --git a/Include/traceback.h b/Include/traceback.h index 0b3bfae..b77cc92 100644 --- a/Include/traceback.h +++ b/Include/traceback.h @@ -19,6 +19,7 @@ typedef struct _traceback { PyAPI_FUNC(int) PyTraceBack_Here(struct _frame *); PyAPI_FUNC(int) PyTraceBack_Print(PyObject *, PyObject *); +PyAPI_FUNC(int) Py_DisplaySourceLine(PyObject *, const char *, int); /* Reveal traceback type so we can typecheck traceback objects */ PyAPI_DATA(PyTypeObject) PyTraceBack_Type; diff --git a/Include/warnings.h b/Include/warnings.h new file mode 100644 index 0000000..e93b72c --- /dev/null +++ b/Include/warnings.h @@ -0,0 +1,19 @@ +#ifndef Py_WARNINGS_H +#define Py_WARNINGS_H +#ifdef __cplusplus +extern "C" { +#endif + +PyAPI_FUNC(void) _PyWarnings_Init(void); + +PyAPI_FUNC(int) PyErr_WarnEx(PyObject *, const char *, Py_ssize_t); +PyAPI_FUNC(int) PyErr_WarnExplicit(PyObject *, const char *, const char *, int, + const char *, PyObject *); + +/* DEPRECATED: Use PyErr_WarnEx() instead. */ +#define PyErr_Warn(category, msg) PyErr_WarnEx(category, msg, 1) + +#ifdef __cplusplus +} +#endif +#endif /* !Py_WARNINGS_H */
\ No newline at end of file diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py index 2a9474e..d2d530f 100644 --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -363,14 +363,26 @@ class WarningMessage(object): self.filename = None self.lineno = None - def _showwarning(self, message, category, filename, lineno, file=None): + def _showwarning(self, message, category, filename, lineno, file=None, + line=None): self.message = message self.category = category self.filename = filename self.lineno = lineno + self.line = line + + def reset(self): + self._showwarning(*((None,)*6)) + + def __str__(self): + return ("{message : %r, category : %r, filename : %r, lineno : %s, " + "line : %r}" % (self.message, + self.category.__name__ if self.category else None, + self.filename, self.lineno, self.line)) + @contextlib.contextmanager -def catch_warning(): +def catch_warning(module=warnings): """ Guard the warnings filter from being permanently changed and record the data of the last warning that has been issued. @@ -381,15 +393,15 @@ def catch_warning(): warnings.warn("foo") assert str(w.message) == "foo" """ - warning = WarningMessage() - original_filters = warnings.filters[:] - original_showwarning = warnings.showwarning - warnings.showwarning = warning._showwarning + warning_obj = WarningMessage() + original_filters = module.filters[:] + original_showwarning = module.showwarning + module.showwarning = warning_obj._showwarning try: - yield warning + yield warning_obj finally: - warnings.showwarning = original_showwarning - warnings.filters = original_filters + module.showwarning = original_showwarning + module.filters = original_filters class EnvironmentVarGuard(object): diff --git a/Lib/test/test_warnings.py b/Lib/test/test_warnings.py index 417a743..4c92593 100644 --- a/Lib/test/test_warnings.py +++ b/Lib/test/test_warnings.py @@ -1,4 +1,4 @@ -import warnings +from contextlib import contextmanager import linecache import os import StringIO @@ -8,112 +8,338 @@ from test import test_support import warning_tests -class TestModule(unittest.TestCase): +sys.modules['_warnings'] = 0 +if 'warnings' in sys.modules: + del sys.modules['warnings'] + +import warnings as py_warnings + +del sys.modules['_warnings'] +del sys.modules['warnings'] + +import warnings as c_warnings + +@contextmanager +def warnings_state(module): + """Use a specific warnings implementation in warning_tests.""" + global __warningregistry__ + for to_clear in (sys, warning_tests): + try: + to_clear.__warningregistry__.clear() + except AttributeError: + pass + try: + __warningregistry__.clear() + except NameError: + pass + original_warnings = warning_tests.warnings + try: + warning_tests.warnings = module + yield + finally: + warning_tests.warnings = original_warnings + + +class FilterTests(unittest.TestCase): + + """Testing the filtering functionality.""" + def setUp(self): - self.ignored = [w[2].__name__ for w in warnings.filters - if w[0]=='ignore' and w[1] is None and w[3] is None] + global __warningregistry__ + try: + __warningregistry__.clear() + except NameError: + pass - def test_warn_default_category(self): - with test_support.catch_warning() as w: - for i in range(4): - text = 'multi %d' %i # Different text on each call - warnings.warn(text) - self.assertEqual(str(w.message), text) - self.assert_(w.category is UserWarning) + def test_error(self): + with test_support.catch_warning(self.module) as w: + self.module.resetwarnings() + self.module.filterwarnings("error", category=UserWarning) + self.assertRaises(UserWarning, self.module.warn, + "FilterTests.test_error") + + def test_ignore(self): + with test_support.catch_warning(self.module) as w: + self.module.resetwarnings() + self.module.filterwarnings("ignore", category=UserWarning) + self.module.warn("FilterTests.test_ignore", UserWarning) + self.assert_(not w.message) + + def test_always(self): + with test_support.catch_warning(self.module) as w: + self.module.resetwarnings() + self.module.filterwarnings("always", category=UserWarning) + message = "FilterTests.test_always" + self.module.warn(message, UserWarning) + self.assert_(message, w.message) + w.message = None # Reset. + self.module.warn(message, UserWarning) + self.assert_(w.message, message) - def test_warn_specific_category(self): - with test_support.catch_warning() as w: - text = 'None' - for category in [DeprecationWarning, FutureWarning, - PendingDeprecationWarning, RuntimeWarning, - SyntaxWarning, UserWarning, Warning]: - if category.__name__ in self.ignored: - text = 'filtered out' + category.__name__ - warnings.warn(text, category) - self.assertNotEqual(w.message, text) + def test_default(self): + with test_support.catch_warning(self.module) as w: + self.module.resetwarnings() + self.module.filterwarnings("default", category=UserWarning) + message = UserWarning("FilterTests.test_default") + for x in xrange(2): + self.module.warn(message, UserWarning) + if x == 0: + self.assertEquals(w.message, message) + w.reset() + elif x == 1: + self.assert_(not w.message, "unexpected warning: " + str(w)) else: - text = 'unfiltered %s' % category.__name__ - warnings.warn(text, category) - self.assertEqual(str(w.message), text) - self.assert_(w.category is category) + raise ValueError("loop variant unhandled") - def test_filtering(self): + def test_module(self): + with test_support.catch_warning(self.module) as w: + self.module.resetwarnings() + self.module.filterwarnings("module", category=UserWarning) + message = UserWarning("FilterTests.test_module") + self.module.warn(message, UserWarning) + self.assertEquals(w.message, message) + w.reset() + self.module.warn(message, UserWarning) + self.assert_(not w.message, "unexpected message: " + str(w)) + + def test_once(self): + with test_support.catch_warning(self.module) as w: + self.module.resetwarnings() + self.module.filterwarnings("once", category=UserWarning) + message = UserWarning("FilterTests.test_once") + self.module.warn_explicit(message, UserWarning, "test_warnings.py", + 42) + self.assertEquals(w.message, message) + w.reset() + self.module.warn_explicit(message, UserWarning, "test_warnings.py", + 13) + self.assert_(not w.message) + self.module.warn_explicit(message, UserWarning, "test_warnings2.py", + 42) + self.assert_(not w.message) + + def test_inheritance(self): + with test_support.catch_warning(self.module) as w: + self.module.resetwarnings() + self.module.filterwarnings("error", category=Warning) + self.assertRaises(UserWarning, self.module.warn, + "FilterTests.test_inheritance", UserWarning) + + def test_ordering(self): + with test_support.catch_warning(self.module) as w: + self.module.resetwarnings() + self.module.filterwarnings("ignore", category=UserWarning) + self.module.filterwarnings("error", category=UserWarning, + append=True) + w.reset() + try: + self.module.warn("FilterTests.test_ordering", UserWarning) + except UserWarning: + self.fail("order handling for actions failed") + self.assert_(not w.message) + + def test_filterwarnings(self): # Test filterwarnings(). # Implicitly also tests resetwarnings(). - with test_support.catch_warning() as w: - warnings.filterwarnings("error", "", Warning, "", 0) - self.assertRaises(UserWarning, warnings.warn, 'convert to error') + with test_support.catch_warning(self.module) as w: + self.module.filterwarnings("error", "", Warning, "", 0) + self.assertRaises(UserWarning, self.module.warn, 'convert to error') - warnings.resetwarnings() + self.module.resetwarnings() text = 'handle normally' - warnings.warn(text) + self.module.warn(text) self.assertEqual(str(w.message), text) self.assert_(w.category is UserWarning) - warnings.filterwarnings("ignore", "", Warning, "", 0) + self.module.filterwarnings("ignore", "", Warning, "", 0) text = 'filtered out' - warnings.warn(text) + self.module.warn(text) self.assertNotEqual(str(w.message), text) - warnings.resetwarnings() - warnings.filterwarnings("error", "hex*", Warning, "", 0) - self.assertRaises(UserWarning, warnings.warn, 'hex/oct') + self.module.resetwarnings() + self.module.filterwarnings("error", "hex*", Warning, "", 0) + self.assertRaises(UserWarning, self.module.warn, 'hex/oct') text = 'nonmatching text' - warnings.warn(text) + self.module.warn(text) self.assertEqual(str(w.message), text) self.assert_(w.category is UserWarning) - def test_options(self): - # Uses the private _setoption() function to test the parsing - # of command-line warning arguments - with test_support.catch_warning(): - self.assertRaises(warnings._OptionError, - warnings._setoption, '1:2:3:4:5:6') - self.assertRaises(warnings._OptionError, - warnings._setoption, 'bogus::Warning') - self.assertRaises(warnings._OptionError, - warnings._setoption, 'ignore:2::4:-5') - warnings._setoption('error::Warning::0') - self.assertRaises(UserWarning, warnings.warn, 'convert to error') +class CFilterTests(FilterTests): + module = c_warnings + +class PyFilterTests(FilterTests): + module = py_warnings + + +class WarnTests(unittest.TestCase): + + """Test warnings.warn() and warnings.warn_explicit().""" + + def test_message(self): + with test_support.catch_warning(self.module) as w: + for i in range(4): + text = 'multi %d' %i # Different text on each call. + self.module.warn(text) + self.assertEqual(str(w.message), text) + self.assert_(w.category is UserWarning) def test_filename(self): - with test_support.catch_warning() as w: - warning_tests.inner("spam1") - self.assertEqual(os.path.basename(w.filename), "warning_tests.py") - warning_tests.outer("spam2") - self.assertEqual(os.path.basename(w.filename), "warning_tests.py") + with warnings_state(self.module): + with test_support.catch_warning(self.module) as w: + warning_tests.inner("spam1") + self.assertEqual(os.path.basename(w.filename), "warning_tests.py") + warning_tests.outer("spam2") + self.assertEqual(os.path.basename(w.filename), "warning_tests.py") def test_stacklevel(self): # Test stacklevel argument # make sure all messages are different, so the warning won't be skipped - with test_support.catch_warning() as w: - warning_tests.inner("spam3", stacklevel=1) - self.assertEqual(os.path.basename(w.filename), "warning_tests.py") - warning_tests.outer("spam4", stacklevel=1) - self.assertEqual(os.path.basename(w.filename), "warning_tests.py") + with warnings_state(self.module): + with test_support.catch_warning(self.module) as w: + warning_tests.inner("spam3", stacklevel=1) + self.assertEqual(os.path.basename(w.filename), "warning_tests.py") + warning_tests.outer("spam4", stacklevel=1) + self.assertEqual(os.path.basename(w.filename), "warning_tests.py") + + warning_tests.inner("spam5", stacklevel=2) + self.assertEqual(os.path.basename(w.filename), "test_warnings.py") + warning_tests.outer("spam6", stacklevel=2) + self.assertEqual(os.path.basename(w.filename), "warning_tests.py") + + warning_tests.inner("spam7", stacklevel=9999) + self.assertEqual(os.path.basename(w.filename), "sys") + + +class CWarnTests(WarnTests): + module = c_warnings + +class PyWarnTests(WarnTests): + module = py_warnings + + +class WCmdLineTests(unittest.TestCase): + + def test_improper_input(self): + # Uses the private _setoption() function to test the parsing + # of command-line warning arguments + with test_support.catch_warning(self.module): + self.assertRaises(self.module._OptionError, + self.module._setoption, '1:2:3:4:5:6') + self.assertRaises(self.module._OptionError, + self.module._setoption, 'bogus::Warning') + self.assertRaises(self.module._OptionError, + self.module._setoption, 'ignore:2::4:-5') + self.module._setoption('error::Warning::0') + self.assertRaises(UserWarning, self.module.warn, 'convert to error') + +class CWCmdLineTests(WCmdLineTests): + module = c_warnings + +class PyWCmdLineTests(WCmdLineTests): + module = py_warnings - warning_tests.inner("spam5", stacklevel=2) - self.assertEqual(os.path.basename(w.filename), "test_warnings.py") - warning_tests.outer("spam6", stacklevel=2) - self.assertEqual(os.path.basename(w.filename), "warning_tests.py") - warning_tests.inner("spam7", stacklevel=9999) - self.assertEqual(os.path.basename(w.filename), "sys") +class _WarningsTests(unittest.TestCase): + + """Tests specific to the _warnings module.""" + + module = c_warnings + + def test_filter(self): + # Everything should function even if 'filters' is not in warnings. + with test_support.catch_warning(self.module) as w: + self.module.filterwarnings("error", "", Warning, "", 0) + self.assertRaises(UserWarning, self.module.warn, + 'convert to error') + del self.module.filters + self.assertRaises(UserWarning, self.module.warn, + 'convert to error') + + def test_onceregistry(self): + # Replacing or removing the onceregistry should be okay. + global __warningregistry__ + message = UserWarning('onceregistry test') + try: + original_registry = self.module.onceregistry + __warningregistry__ = {} + with test_support.catch_warning(self.module) as w: + self.module.resetwarnings() + self.module.filterwarnings("once", category=UserWarning) + self.module.warn_explicit(message, UserWarning, "file", 42) + self.failUnlessEqual(w.message, message) + w.reset() + self.module.warn_explicit(message, UserWarning, "file", 42) + self.assert_(not w.message) + # Test the resetting of onceregistry. + self.module.onceregistry = {} + __warningregistry__ = {} + self.module.warn('onceregistry test') + self.failUnlessEqual(w.message.args, message.args) + # Removal of onceregistry is okay. + w.reset() + del self.module.onceregistry + __warningregistry__ = {} + self.module.warn_explicit(message, UserWarning, "file", 42) + self.failUnless(not w.message) + finally: + self.module.onceregistry = original_registry + + def test_showwarning_missing(self): + # Test that showwarning() missing is okay. + text = 'del showwarning test' + with test_support.catch_warning(self.module): + self.module.filterwarnings("always", category=UserWarning) + del self.module.showwarning + with test_support.captured_output('stderr') as stream: + self.module.warn(text) + result = stream.getvalue() + self.failUnless(text in result) + + def test_show_warning_output(self): + # With showarning() missing, make sure that output is okay. + text = 'test show_warning' + with test_support.catch_warning(self.module): + self.module.filterwarnings("always", category=UserWarning) + del self.module.showwarning + with test_support.captured_output('stderr') as stream: + warning_tests.inner(text) + result = stream.getvalue() + self.failUnlessEqual(result.count('\n'), 2, + "Too many newlines in %r" % result) + first_line, second_line = result.split('\n', 1) + expected_file = os.path.splitext(warning_tests.__file__)[0] + '.py' + path, line, warning_class, message = first_line.split(':') + line = int(line) + self.failUnlessEqual(expected_file, path) + self.failUnlessEqual(warning_class, ' ' + UserWarning.__name__) + self.failUnlessEqual(message, ' ' + text) + expected_line = ' ' + linecache.getline(path, line).strip() + '\n' + assert expected_line + self.failUnlessEqual(second_line, expected_line) class WarningsDisplayTests(unittest.TestCase): + """Test the displaying of warnings and the ability to overload functions + related to displaying warnings.""" + def test_formatwarning(self): message = "msg" category = Warning file_name = os.path.splitext(warning_tests.__file__)[0] + '.py' line_num = 3 file_line = linecache.getline(file_name, line_num).strip() - expect = "%s:%s: %s: %s\n %s\n" % (file_name, line_num, category.__name__, - message, file_line) - self.failUnlessEqual(warnings.formatwarning(message, category, - file_name, line_num), - expect) + format = "%s:%s: %s: %s\n %s\n" + expect = format % (file_name, line_num, category.__name__, message, + file_line) + self.failUnlessEqual(expect, self.module.formatwarning(message, + category, file_name, line_num)) + # Test the 'line' argument. + file_line += " for the win!" + expect = format % (file_name, line_num, category.__name__, message, + file_line) + self.failUnlessEqual(expect, self.module.formatwarning(message, + category, file_name, line_num, file_line)) def test_showwarning(self): file_name = os.path.splitext(warning_tests.__file__)[0] + '.py' @@ -122,13 +348,28 @@ class WarningsDisplayTests(unittest.TestCase): message = 'msg' category = Warning file_object = StringIO.StringIO() - expect = warnings.formatwarning(message, category, file_name, line_num) - warnings.showwarning(message, category, file_name, line_num, + expect = self.module.formatwarning(message, category, file_name, + line_num) + self.module.showwarning(message, category, file_name, line_num, file_object) self.failUnlessEqual(file_object.getvalue(), expect) + # Test 'line' argument. + expected_file_line += "for the win!" + expect = self.module.formatwarning(message, category, file_name, + line_num, expected_file_line) + file_object = StringIO.StringIO() + self.module.showwarning(message, category, file_name, line_num, + file_object, expected_file_line) + self.failUnlessEqual(expect, file_object.getvalue()) + +class CWarningsDisplayTests(WarningsDisplayTests): + module = c_warnings +class PyWarningsDisplayTests(WarningsDisplayTests): + module = py_warnings -def test_main(verbose=None): + +def test_main(): # Obscure hack so that this test passes after reloads or repeated calls # to test_main (regrtest -R). if '__warningregistry__' in globals(): @@ -137,7 +378,14 @@ def test_main(verbose=None): del warning_tests.__warningregistry__ if hasattr(sys, '__warningregistry__'): del sys.__warningregistry__ - test_support.run_unittest(TestModule, WarningsDisplayTests) + test_support.run_unittest(CFilterTests, PyFilterTests, + CWarnTests, PyWarnTests, + CWCmdLineTests, PyWCmdLineTests, + _WarningsTests, + CWarningsDisplayTests, PyWarningsDisplayTests, + ) + + if __name__ == "__main__": - test_main(verbose=True) + test_main() diff --git a/Lib/warnings.py b/Lib/warnings.py index 970d0ff..3c7357b 100644 --- a/Lib/warnings.py +++ b/Lib/warnings.py @@ -3,127 +3,13 @@ # Note: function level imports should *not* be used # in this module as it may cause import lock deadlock. # See bug 683658. -import sys, types import linecache +import sys +import types __all__ = ["warn", "showwarning", "formatwarning", "filterwarnings", "resetwarnings"] -# filters contains a sequence of filter 5-tuples -# The components of the 5-tuple are: -# - an action: error, ignore, always, default, module, or once -# - a compiled regex that must match the warning message -# - a class representing the warning category -# - a compiled regex that must match the module that is being warned -# - a line number for the line being warning, or 0 to mean any line -# If either if the compiled regexs are None, match anything. -filters = [] -defaultaction = "default" -onceregistry = {} - -def warn(message, category=None, stacklevel=1): - """Issue a warning, or maybe ignore it or raise an exception.""" - # Check if message is already a Warning object - if isinstance(message, Warning): - category = message.__class__ - # Check category argument - if category is None: - category = UserWarning - assert issubclass(category, Warning) - # Get context information - try: - caller = sys._getframe(stacklevel) - except ValueError: - globals = sys.__dict__ - lineno = 1 - else: - globals = caller.f_globals - lineno = caller.f_lineno - if '__name__' in globals: - module = globals['__name__'] - else: - module = "<string>" - filename = globals.get('__file__') - if filename: - fnl = filename.lower() - if fnl.endswith((".pyc", ".pyo")): - filename = filename[:-1] - else: - if module == "__main__": - try: - filename = sys.argv[0] - except AttributeError: - # embedded interpreters don't have sys.argv, see bug #839151 - filename = '__main__' - if not filename: - filename = module - registry = globals.setdefault("__warningregistry__", {}) - warn_explicit(message, category, filename, lineno, module, registry, - globals) - -def warn_explicit(message, category, filename, lineno, - module=None, registry=None, module_globals=None): - if module is None: - module = filename or "<unknown>" - if module[-3:].lower() == ".py": - module = module[:-3] # XXX What about leading pathname? - if registry is None: - registry = {} - if isinstance(message, Warning): - text = str(message) - category = message.__class__ - else: - text = message - message = category(message) - key = (text, category, lineno) - # Quick test for common case - if registry.get(key): - return - # Search the filters - for item in filters: - action, msg, cat, mod, ln = item - if ((msg is None or msg.match(text)) and - issubclass(category, cat) and - (mod is None or mod.match(module)) and - (ln == 0 or lineno == ln)): - break - else: - action = defaultaction - # Early exit actions - if action == "ignore": - registry[key] = 1 - return - - # Prime the linecache for formatting, in case the - # "file" is actually in a zipfile or something. - linecache.getlines(filename, module_globals) - - if action == "error": - raise message - # Other actions - if action == "once": - registry[key] = 1 - oncekey = (text, category) - if onceregistry.get(oncekey): - return - onceregistry[oncekey] = 1 - elif action == "always": - pass - elif action == "module": - registry[key] = 1 - altkey = (text, category, 0) - if registry.get(altkey): - return - registry[altkey] = 1 - elif action == "default": - registry[key] = 1 - else: - # Unrecognized actions are errors - raise RuntimeError( - "Unrecognized action (%r) in warnings.filters:\n %s" % - (action, item)) - # Print message and context - showwarning(message, category, filename, lineno) def warnpy3k(message, category=None, stacklevel=1): """Issue a deprecation warning for Python 3.x related changes. @@ -135,21 +21,22 @@ def warnpy3k(message, category=None, stacklevel=1): category = DeprecationWarning warn(message, category, stacklevel+1) -def showwarning(message, category, filename, lineno, file=None): +def showwarning(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 try: - file.write(formatwarning(message, category, filename, lineno)) + file.write(formatwarning(message, category, filename, lineno, line)) except IOError: pass # the file (probably stderr) is invalid - this warning gets lost. -def formatwarning(message, category, filename, lineno): +def formatwarning(message, category, filename, lineno, line=None): """Function to format a warning the standard way.""" s = "%s:%s: %s: %s\n" % (filename, lineno, category.__name__, message) - line = linecache.getline(filename, lineno).strip() + line = linecache.getline(filename, lineno) if line is None else line if line: - s = s + " " + line + "\n" + line = line.strip() + s += " %s\n" % line return s def filterwarnings(action, message="", category=Warning, module="", lineno=0, @@ -268,7 +155,145 @@ def _getcategory(category): raise _OptionError("invalid warning category: %r" % (category,)) return cat + +# Code typically replaced by _warnings +def warn(message, category=None, stacklevel=1): + """Issue a warning, or maybe ignore it or raise an exception.""" + # Check if message is already a Warning object + if isinstance(message, Warning): + category = message.__class__ + # Check category argument + if category is None: + category = UserWarning + assert issubclass(category, Warning) + # Get context information + try: + caller = sys._getframe(stacklevel) + except ValueError: + globals = sys.__dict__ + lineno = 1 + else: + globals = caller.f_globals + lineno = caller.f_lineno + if '__name__' in globals: + module = globals['__name__'] + else: + module = "<string>" + filename = globals.get('__file__') + if filename: + fnl = filename.lower() + if fnl.endswith((".pyc", ".pyo")): + filename = filename[:-1] + else: + if module == "__main__": + try: + filename = sys.argv[0] + except AttributeError: + # embedded interpreters don't have sys.argv, see bug #839151 + filename = '__main__' + if not filename: + filename = module + registry = globals.setdefault("__warningregistry__", {}) + warn_explicit(message, category, filename, lineno, module, registry, + globals) + +def warn_explicit(message, category, filename, lineno, + module=None, registry=None, module_globals=None): + if module is None: + module = filename or "<unknown>" + if module[-3:].lower() == ".py": + module = module[:-3] # XXX What about leading pathname? + if registry is None: + registry = {} + if isinstance(message, Warning): + text = str(message) + category = message.__class__ + else: + text = message + message = category(message) + key = (text, category, lineno) + # Quick test for common case + if registry.get(key): + return + # Search the filters + for item in filters: + action, msg, cat, mod, ln = item + if ((msg is None or msg.match(text)) and + issubclass(category, cat) and + (mod is None or mod.match(module)) and + (ln == 0 or lineno == ln)): + break + else: + action = defaultaction + # Early exit actions + if action == "ignore": + registry[key] = 1 + return + + # Prime the linecache for formatting, in case the + # "file" is actually in a zipfile or something. + linecache.getlines(filename, module_globals) + + if action == "error": + raise message + # Other actions + if action == "once": + registry[key] = 1 + oncekey = (text, category) + if onceregistry.get(oncekey): + return + onceregistry[oncekey] = 1 + elif action == "always": + pass + elif action == "module": + registry[key] = 1 + altkey = (text, category, 0) + if registry.get(altkey): + return + registry[altkey] = 1 + elif action == "default": + registry[key] = 1 + else: + # Unrecognized actions are errors + raise RuntimeError( + "Unrecognized action (%r) in warnings.filters:\n %s" % + (action, item)) + # Print message and context + showwarning(message, category, filename, lineno) + + +# filters contains a sequence of filter 5-tuples +# The components of the 5-tuple are: +# - an action: error, ignore, always, default, module, or once +# - a compiled regex that must match the warning message +# - a class representing the warning category +# - a compiled regex that must match the module that is being warned +# - a line number for the line being warning, or 0 to mean any line +# If either if the compiled regexs are None, match anything. +_warnings_defaults = False +try: + from _warnings import (filters, default_action, once_registry, + warn, warn_explicit) + defaultaction = default_action + onceregistry = once_registry + _warnings_defaults = True +except ImportError: + filters = [] + defaultaction = "default" + onceregistry = {} + + # Module initialization _processoptions(sys.warnoptions) -simplefilter("ignore", category=PendingDeprecationWarning, append=1) -simplefilter("ignore", category=ImportWarning, append=1) +if not _warnings_defaults: + simplefilter("ignore", category=PendingDeprecationWarning, append=1) + simplefilter("ignore", category=ImportWarning, append=1) + bytes_warning = sys.flags.bytes_warning + if bytes_warning > 1: + bytes_action = "error" + elif bytes_warning: + bytes_action = "default" + else: + bytes_action = "ignore" + simplefilter(bytes_action, category=BytesWarning, append=1) +del _warnings_defaults diff --git a/Makefile.pre.in b/Makefile.pre.in index af16d89..bb70301 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -245,6 +245,7 @@ ASDLGEN= $(srcdir)/Parser/asdl_c.py ########################################################################## # Python PYTHON_OBJS= \ + Python/_warnings.o \ Python/Python-ast.o \ Python/asdl.o \ Python/ast.o \ @@ -621,6 +622,7 @@ PYTHON_HEADERS= \ Include/tupleobject.h \ Include/ucnhash.h \ Include/unicodeobject.h \ + Include/warnings.h \ Include/weakrefobject.h \ pyconfig.h \ $(PARSER_HEADERS) @@ -21,6 +21,11 @@ Extensions Modules - zlib.decompressobj().flush(value) no longer crashes the interpreter when passed a value less than or equal to zero. +- Issue #1631171: Re-implement the 'warnings' module in C (the original Python + code has been kept as backup). This will allow for using the 'warning's + machinery in such places as the parser where use of pure Python code is not + possible. + Library ------- diff --git a/Modules/config.c.in b/Modules/config.c.in index f811991..399cb1e 100644 --- a/Modules/config.c.in +++ b/Modules/config.c.in @@ -28,6 +28,7 @@ extern void PyMarshal_Init(void); extern void initimp(void); extern void initgc(void); extern void init_ast(void); +extern void _PyWarnings_Init(void); struct _inittab _PyImport_Inittab[] = { @@ -51,6 +52,9 @@ struct _inittab _PyImport_Inittab[] = { /* This lives in gcmodule.c */ {"gc", initgc}, + /* This lives in _warnings.c */ + {"_warnings", _PyWarnings_Init}, + /* Sentinel */ {0, 0} }; diff --git a/PC/config.c b/PC/config.c index 9cce923..688edc9 100644 --- a/PC/config.c +++ b/PC/config.c @@ -66,6 +66,7 @@ extern void init_codecs_tw(void); extern void init_subprocess(void); extern void init_lsprof(void); extern void init_ast(void); +extern void _PyWarnings_Init(void); /* tools/freeze/makeconfig.py marker for additional "extern" */ /* -- ADDMODULE MARKER 1 -- */ @@ -160,6 +161,9 @@ struct _inittab _PyImport_Inittab[] = { {"sys", NULL}, {"exceptions", NULL}, + {"_types", init_types}, + {"_warnings", _PyWarnings_Init}, + /* Sentinel */ {0, 0} }; diff --git a/Python/_warnings.c b/Python/_warnings.c new file mode 100644 index 0000000..b815927 --- /dev/null +++ b/Python/_warnings.c @@ -0,0 +1,856 @@ +#include "Python.h" +#include "frameobject.h" + +#define MODULE_NAME "_warnings" +#define DEFAULT_ACTION_NAME "default_action" + +PyDoc_STRVAR(warnings__doc__, +MODULE_NAME " provides basic warning filtering support.\n" +"It is a helper module to speed up interpreter start-up."); + +/* Both 'filters' and 'onceregistry' can be set in warnings.py; + get_warnings_attr() will reset these variables accordingly. */ +static PyObject *_filters; /* List */ +static PyObject *_once_registry; /* Dict */ + + +static int +check_matched(PyObject *obj, PyObject *arg) +{ + PyObject *result; + int rc; + + if (obj == Py_None) + return 1; + result = PyObject_CallMethod(obj, "match", "O", arg); + if (result == NULL) + return -1; + + rc = PyObject_IsTrue(result); + Py_DECREF(result); + return rc; +} + +/* + Returns a new reference. + A NULL return value can mean false or an error. +*/ +static PyObject * +get_warnings_attr(const char *attr) +{ + static PyObject *warnings_str = NULL; + PyObject *all_modules; + PyObject *warnings_module; + int result; + + if (warnings_str == NULL) { + warnings_str = PyString_FromString("warnings"); + if (warnings_str == NULL) + return NULL; + } + + all_modules = PyImport_GetModuleDict(); + result = PyDict_Contains(all_modules, warnings_str); + if (result == -1 || result == 0) + return NULL; + + warnings_module = PyDict_GetItem(all_modules, warnings_str); + if (!PyObject_HasAttrString(warnings_module, attr)) + return NULL; + return PyObject_GetAttrString(warnings_module, attr); +} + + +PyObject * +get_once_registry(void) +{ + PyObject *registry; + + registry = get_warnings_attr("onceregistry"); + if (registry == NULL) { + if (PyErr_Occurred()) + return NULL; + return _once_registry; + } + Py_DECREF(_once_registry); + _once_registry = registry; + return registry; +} + + +/* The item is a borrowed reference. */ +static const char * +get_filter(PyObject *category, PyObject *text, Py_ssize_t lineno, + PyObject *module, PyObject **item) +{ + PyObject *action, *m, *d; + Py_ssize_t i; + PyObject *warnings_filters; + + warnings_filters = get_warnings_attr("filters"); + if (warnings_filters == NULL) { + if (PyErr_Occurred()) + return NULL; + } + else { + Py_DECREF(_filters); + _filters = warnings_filters; + } + + if (!PyList_Check(_filters)) { + PyErr_SetString(PyExc_ValueError, + MODULE_NAME ".filters must be a list"); + return NULL; + } + + /* _filters could change while we are iterating over it. */ + for (i = 0; i < PyList_GET_SIZE(_filters); i++) { + PyObject *tmp_item, *action, *msg, *cat, *mod, *ln_obj; + Py_ssize_t ln; + int is_subclass, good_msg, good_mod; + + tmp_item = *item = PyList_GET_ITEM(_filters, i); + if (PyTuple_Size(tmp_item) != 5) { + PyErr_Format(PyExc_ValueError, + MODULE_NAME ".filters item %zd isn't a 5-tuple", i); + return NULL; + } + + /* Python code: action, msg, cat, mod, ln = item */ + action = PyTuple_GET_ITEM(tmp_item, 0); + msg = PyTuple_GET_ITEM(tmp_item, 1); + cat = PyTuple_GET_ITEM(tmp_item, 2); + mod = PyTuple_GET_ITEM(tmp_item, 3); + ln_obj = PyTuple_GET_ITEM(tmp_item, 4); + + good_msg = check_matched(msg, text); + good_mod = check_matched(mod, module); + is_subclass = PyObject_IsSubclass(category, cat); + ln = PyInt_AsSsize_t(ln_obj); + if (good_msg == -1 || good_mod == -1 || is_subclass == -1 || + (ln == -1 && PyErr_Occurred())) + return NULL; + + if (good_msg && is_subclass && good_mod && (ln == 0 || lineno == ln)) + return PyString_AsString(action); + } + + m = PyImport_ImportModule(MODULE_NAME); + if (m == NULL) + return NULL; + d = PyModule_GetDict(m); + Py_DECREF(m); + if (d == NULL) + return NULL; + action = PyDict_GetItemString(d, DEFAULT_ACTION_NAME); + if (action != NULL) + return PyString_AsString(action); + + PyErr_SetString(PyExc_ValueError, + MODULE_NAME "." DEFAULT_ACTION_NAME " not found"); + return NULL; +} + +static int +already_warned(PyObject *registry, PyObject *key, int should_set) +{ + PyObject *already_warned; + + if (key == NULL) + return -1; + + already_warned = PyDict_GetItem(registry, key); + if (already_warned != NULL) { + int rc = PyObject_IsTrue(already_warned); + if (rc != 0) + return rc; + } + + /* This warning wasn't found in the registry, set it. */ + if (should_set) + return PyDict_SetItem(registry, key, Py_True); + return 0; +} + +/* New reference. */ +static PyObject * +normalize_module(PyObject *filename) +{ + PyObject *module; + const char *mod_str; + Py_ssize_t len; + + int rc = PyObject_IsTrue(filename); + if (rc == -1) + return NULL; + else if (rc == 0) + return PyString_FromString("<unknown>"); + + mod_str = PyString_AsString(filename); + if (mod_str == NULL) + return NULL; + len = PyString_Size(filename); + if (len < 0) + return NULL; + if (len >= 3 && + strncmp(mod_str + (len - 3), ".py", 3) == 0) { + module = PyString_FromStringAndSize(mod_str, len-3); + } + else { + module = filename; + Py_INCREF(module); + } + return module; +} + +static int +update_registry(PyObject *registry, PyObject *text, PyObject *category, + int add_zero) +{ + PyObject *altkey, *zero = NULL; + int rc; + + if (add_zero) { + zero = PyInt_FromLong(0); + if (zero == NULL) + return -1; + altkey = PyTuple_Pack(3, text, category, zero); + } + else + altkey = PyTuple_Pack(2, text, category); + + rc = already_warned(registry, altkey, 1); + Py_XDECREF(zero); + Py_XDECREF(altkey); + return rc; +} + +static void +show_warning(PyObject *filename, int lineno, PyObject *text, PyObject + *category, PyObject *sourceline) +{ + PyObject *f_stderr; + PyObject *name; + char lineno_str[128]; + + PyOS_snprintf(lineno_str, sizeof(lineno_str), ":%d: ", lineno); + + name = PyObject_GetAttrString(category, "__name__"); + if (name == NULL) /* XXX Can an object lack a '__name__' attribute? */ + return; + + f_stderr = PySys_GetObject("stderr"); + if (f_stderr == NULL) { + fprintf(stderr, "lost sys.stderr\n"); + Py_DECREF(name); + return; + } + + /* Print "filename:lineno: category: text\n" */ + PyFile_WriteObject(filename, f_stderr, Py_PRINT_RAW); + PyFile_WriteString(lineno_str, f_stderr); + PyFile_WriteObject(name, f_stderr, Py_PRINT_RAW); + PyFile_WriteString(": ", f_stderr); + PyFile_WriteObject(text, f_stderr, Py_PRINT_RAW); + PyFile_WriteString("\n", f_stderr); + Py_XDECREF(name); + + /* Print " source_line\n" */ + PyFile_WriteString(" ", f_stderr); + if (sourceline) { + char *source_line_str = PyString_AS_STRING(sourceline); + while (*source_line_str == ' ' || *source_line_str == '\t' || + *source_line_str == '\014') + source_line_str++; + + PyFile_WriteString(source_line_str, f_stderr); + PyFile_WriteString("\n", f_stderr); + } + else + Py_DisplaySourceLine(f_stderr, PyString_AS_STRING(filename), lineno); + PyErr_Clear(); +} + +static PyObject * +warn_explicit(PyObject *category, PyObject *message, + PyObject *filename, int lineno, + PyObject *module, PyObject *registry, PyObject *sourceline) +{ + PyObject *key = NULL, *text = NULL, *result = NULL, *lineno_obj = NULL; + PyObject *item = Py_None; + const char *action; + int rc; + + /* Normalize module. */ + if (module == NULL) { + module = normalize_module(filename); + if (module == NULL) + return NULL; + } + else + Py_INCREF(module); + + /* Normalize message. */ + Py_INCREF(message); /* DECREF'ed in cleanup. */ + rc = PyObject_IsInstance(message, PyExc_Warning); + if (rc == -1) { + goto cleanup; + } + if (rc == 1) { + text = PyObject_Str(message); + category = (PyObject*)message->ob_type; + } + else { + text = message; + message = PyObject_CallFunction(category, "O", message); + } + + lineno_obj = PyInt_FromLong(lineno); + if (lineno_obj == NULL) + goto cleanup; + + /* Create key. */ + key = PyTuple_Pack(3, text, category, lineno_obj); + if (key == NULL) + goto cleanup; + + if (registry != NULL) { + rc = already_warned(registry, key, 0); + if (rc == -1) + goto cleanup; + else if (rc == 1) + goto return_none; + /* Else this warning hasn't been generated before. */ + } + + action = get_filter(category, text, lineno, module, &item); + if (action == NULL) + goto cleanup; + + if (strcmp(action, "error") == 0) { + PyErr_SetObject(category, message); + goto cleanup; + } + + /* Store in the registry that we've been here, *except* when the action + is "always". */ + rc = 0; + if (strcmp(action, "always") != 0) { + if (registry != NULL && PyDict_SetItem(registry, key, Py_True) < 0) + goto cleanup; + else if (strcmp(action, "ignore") == 0) + goto return_none; + else if (strcmp(action, "once") == 0) { + if (registry == NULL) { + registry = get_once_registry(); + if (registry == NULL) + goto cleanup; + } + /* _once_registry[(text, category)] = 1 */ + 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); + } + else if (strcmp(action, "default") != 0) { + PyObject *to_str = PyObject_Str(item); + const char *err_str = "???"; + + if (to_str != NULL) + err_str = PyString_AS_STRING(to_str); + PyErr_Format(PyExc_RuntimeError, + "Unrecognized action (%s) in warnings.filters:\n %s", + action, err_str); + Py_XDECREF(to_str); + goto cleanup; + } + } + + 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 *result; + + result = PyObject_CallFunctionObjArgs(show_fxn, message, category, + filename, lineno_obj, + Py_None, + sourceline ? + sourceline: Py_None, + NULL); + Py_XDECREF(result); + if (result == NULL) + goto cleanup; + } + } + else /* if (rc == -1) */ + goto cleanup; + + return_none: + result = Py_None; + Py_INCREF(result); + + cleanup: + Py_XDECREF(key); + Py_XDECREF(text); + Py_XDECREF(lineno_obj); + Py_DECREF(module); + Py_DECREF(message); + return result; /* Py_None or NULL. */ +} + +/* filename, module, and registry are new refs, globals is borrowed */ +/* Returns 0 on error (no new refs), 1 on success */ +static int +setup_context(Py_ssize_t stack_level, PyObject **filename, int *lineno, + PyObject **module, PyObject **registry) +{ + PyObject *globals; + + /* Setup globals and lineno. */ + PyFrameObject *f = PyThreadState_GET()->frame; + while (--stack_level > 0 && f != NULL) { + f = f->f_back; + --stack_level; + } + + if (f == NULL) { + globals = PyThreadState_Get()->interp->sysdict; + *lineno = 1; + } + else { + globals = f->f_globals; + *lineno = PyCode_Addr2Line(f->f_code, f->f_lasti); + } + + *module = NULL; + + /* Setup registry. */ + assert(globals != NULL); + assert(PyDict_Check(globals)); + *registry = PyDict_GetItemString(globals, "__warningregistry__"); + if (*registry == NULL) { + int rc; + + *registry = PyDict_New(); + if (*registry == NULL) + return 0; + + rc = PyDict_SetItemString(globals, "__warningregistry__", *registry); + if (rc < 0) + goto handle_error; + } + else + Py_INCREF(*registry); + + /* Setup module. */ + *module = PyDict_GetItemString(globals, "__name__"); + if (*module == NULL) { + *module = PyString_FromString("<string>"); + if (*module == NULL) + goto handle_error; + } + else + Py_INCREF(*module); + + /* Setup filename. */ + *filename = PyDict_GetItemString(globals, "__file__"); + if (*filename != NULL) { + Py_ssize_t len = PyString_Size(*filename); + const char *file_str = PyString_AsString(*filename); + if (file_str == NULL || (len < 0 && PyErr_Occurred())) + goto handle_error; + + /* if filename.lower().endswith((".pyc", ".pyo")): */ + if (len >= 4 && + file_str[len-4] == '.' && + tolower(file_str[len-3]) == 'p' && + tolower(file_str[len-2]) == 'y' && + (tolower(file_str[len-1]) == 'c' || + tolower(file_str[len-1]) == 'o')) { + *filename = PyString_FromStringAndSize(file_str, len-1); + if (*filename == NULL) + goto handle_error; + } + else + Py_INCREF(*filename); + } + else { + const char *module_str = PyString_AsString(*module); + if (module_str && strcmp(module_str, "__main__") == 0) { + PyObject *argv = PySys_GetObject("argv"); + if (argv != NULL && PyList_Size(argv) > 0) { + *filename = PyList_GetItem(argv, 0); + Py_INCREF(*filename); + } + else { + /* embedded interpreters don't have sys.argv, see bug #839151 */ + *filename = PyString_FromString("__main__"); + if (*filename == NULL) + goto handle_error; + } + } + if (*filename == NULL) { + *filename = *module; + Py_INCREF(*filename); + } + } + + return 1; + + handle_error: + /* filename not XDECREF'ed here as there is no way to jump here with a + dangling reference. */ + Py_XDECREF(*registry); + Py_XDECREF(*module); + return 0; +} + +static PyObject * +get_category(PyObject *message, PyObject *category) +{ + int rc; + + /* Get category. */ + rc = PyObject_IsInstance(message, PyExc_Warning); + if (rc == -1) + return NULL; + + if (rc == 1) + category = (PyObject*)message->ob_type; + else if (category == NULL) + category = PyExc_UserWarning; + + /* Validate category. */ + rc = PyObject_IsSubclass(category, PyExc_Warning); + if (rc == -1) + return NULL; + if (rc == 0) { + PyErr_SetString(PyExc_ValueError, + "category is not a subclass of Warning"); + return NULL; + } + + return category; +} + +static PyObject * +do_warn(PyObject *message, PyObject *category, Py_ssize_t stack_level) +{ + PyObject *filename, *module, *registry, *res; + int lineno; + + if (!setup_context(stack_level, &filename, &lineno, &module, ®istry)) + return NULL; + + res = warn_explicit(category, message, filename, lineno, module, registry, + NULL); + Py_DECREF(filename); + Py_DECREF(registry); + Py_DECREF(module); + return res; +} + +static PyObject * +warnings_warn(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kw_list[] = { "message", "category", "stacklevel", 0 }; + PyObject *message, *category = NULL; + Py_ssize_t stack_level = 1; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|On:warn", kw_list, + &message, &category, &stack_level)) + return NULL; + + category = get_category(message, category); + if (category == NULL) + return NULL; + return do_warn(message, category, stack_level); +} + +static PyObject * +warnings_warn_explicit(PyObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwd_list[] = {"message", "category", "filename", "lineno", + "module", "registry", "module_globals", 0}; + PyObject *message; + PyObject *category; + PyObject *filename; + int lineno; + PyObject *module = NULL; + PyObject *registry = NULL; + PyObject *module_globals = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOOi|OOO:warn_explicit", + kwd_list, &message, &category, &filename, &lineno, &module, + ®istry, &module_globals)) + return NULL; + + if (module_globals) { + static PyObject *get_source_name = NULL; + static PyObject *splitlines_name = NULL; + PyObject *loader; + PyObject *module_name; + PyObject *source; + PyObject *source_list; + PyObject *source_line; + PyObject *returned; + + if (get_source_name == NULL) { + get_source_name = PyString_FromString("get_source"); + if (!get_source_name) + return NULL; + } + if (splitlines_name == NULL) { + splitlines_name = PyString_FromString("splitlines"); + if (!splitlines_name) + return NULL; + } + + /* Check/get the requisite pieces needed for the loader. */ + loader = PyDict_GetItemString(module_globals, "__loader__"); + module_name = PyDict_GetItemString(module_globals, "__name__"); + + if (loader == NULL || module_name == NULL) + goto standard_call; + + /* Make sure the loader implements the optional get_source() method. */ + if (!PyObject_HasAttrString(loader, "get_source")) + goto standard_call; + /* Call get_source() to get the source code. */ + source = PyObject_CallMethodObjArgs(loader, get_source_name, + module_name, NULL); + if (!source) + return NULL; + else if (source == Py_None) { + Py_DECREF(Py_None); + goto standard_call; + } + + /* Split the source into lines. */ + source_list = PyObject_CallMethodObjArgs(source, splitlines_name, + NULL); + Py_DECREF(source); + if (!source_list) + return NULL; + + /* Get the source line. */ + source_line = PyList_GetItem(source_list, lineno-1); + if (!source_line) { + Py_DECREF(source_list); + return NULL; + } + + /* Handle the warning. */ + returned = warn_explicit(category, message, filename, lineno, module, + registry, source_line); + Py_DECREF(source_list); + return returned; + } + + standard_call: + return warn_explicit(category, message, filename, lineno, module, + registry, NULL); +} + + +/* Function to issue a warning message; may raise an exception. */ +int +PyErr_WarnEx(PyObject *category, const char *text, Py_ssize_t stack_level) +{ + PyObject *res; + PyObject *message = PyString_FromString(text); + if (message == NULL) + return -1; + + if (category == NULL) + category = PyExc_RuntimeWarning; + + res = do_warn(message, category, stack_level); + Py_DECREF(message); + if (res == NULL) + return -1; + Py_DECREF(res); + + return 0; +} + +/* PyErr_Warn is only for backwards compatability and will be removed. + Use PyErr_WarnEx instead. */ + +#undef PyErr_Warn + +PyAPI_FUNC(int) +PyErr_Warn(PyObject *category, char *text) +{ + return PyErr_WarnEx(category, text, 1); +} + +/* Warning with explicit origin */ +int +PyErr_WarnExplicit(PyObject *category, const char *text, + const char *filename_str, int lineno, + const char *module_str, PyObject *registry) +{ + PyObject *res; + PyObject *message = PyString_FromString(text); + PyObject *filename = PyString_FromString(filename_str); + PyObject *module = NULL; + int ret = -1; + + if (message == NULL || filename == NULL) + goto exit; + if (module_str != NULL) { + module = PyString_FromString(module_str); + if (module == NULL) + goto exit; + } + + if (category == NULL) + category = PyExc_RuntimeWarning; + res = warn_explicit(category, message, filename, lineno, module, registry, + NULL); + if (res == NULL) + goto exit; + Py_DECREF(res); + ret = 0; + + exit: + Py_XDECREF(message); + Py_XDECREF(module); + Py_XDECREF(filename); + return ret; +} + + +PyDoc_STRVAR(warn_doc, +"Issue a warning, or maybe ignore it or raise an exception."); + +PyDoc_STRVAR(warn_explicit_doc, +"Low-level inferface to warnings functionality."); + +static PyMethodDef warnings_functions[] = { + {"warn", (PyCFunction)warnings_warn, METH_VARARGS | METH_KEYWORDS, + warn_doc}, + {"warn_explicit", (PyCFunction)warnings_warn_explicit, + METH_VARARGS | METH_KEYWORDS, warn_explicit_doc}, + // XXX(brett.cannon): add showwarning? + // XXX(brett.cannon): Reasonable to add formatwarning? + {NULL, NULL} /* sentinel */ +}; + + +static PyObject * +create_filter(PyObject *category, const char *action) +{ + static PyObject *ignore_str = NULL; + static PyObject *error_str = NULL; + static PyObject *default_str = NULL; + PyObject *action_obj = NULL; + PyObject *lineno, *result; + + if (!strcmp(action, "ignore")) { + if (ignore_str == NULL) { + ignore_str = PyString_InternFromString("ignore"); + if (ignore_str == NULL) + return NULL; + } + action_obj = ignore_str; + } + else if (!strcmp(action, "error")) { + if (error_str == NULL) { + error_str = PyString_InternFromString("error"); + if (error_str == NULL) + return NULL; + } + action_obj = error_str; + } + else if (!strcmp(action, "default")) { + if (default_str == NULL) { + default_str = PyString_InternFromString("default"); + if (default_str == NULL) + return NULL; + } + action_obj = default_str; + } + else { + Py_FatalError("unknown action"); + } + + /* This assumes the line number is zero for now. */ + lineno = PyInt_FromLong(0); + if (lineno == NULL) + return NULL; + result = PyTuple_Pack(5, action_obj, Py_None, category, Py_None, lineno); + Py_DECREF(lineno); + return result; +} + +static PyObject * +init_filters(void) +{ + PyObject *filters = PyList_New(3); + const char *bytes_action; + if (filters == NULL) + return NULL; + + PyList_SET_ITEM(filters, 0, + create_filter(PyExc_PendingDeprecationWarning, "ignore")); + PyList_SET_ITEM(filters, 1, create_filter(PyExc_ImportWarning, "ignore")); + if (Py_BytesWarningFlag > 1) + bytes_action = "error"; + else if (Py_BytesWarningFlag) + bytes_action = "default"; + else + bytes_action = "ignore"; + PyList_SET_ITEM(filters, 2, create_filter(PyExc_BytesWarning, + bytes_action)); + + if (PyList_GET_ITEM(filters, 0) == NULL || + PyList_GET_ITEM(filters, 1) == NULL || + PyList_GET_ITEM(filters, 2) == NULL) { + Py_DECREF(filters); + return NULL; + } + + return filters; +} + + +PyMODINIT_FUNC +_PyWarnings_Init(void) +{ + PyObject *m, *default_action; + + m = Py_InitModule3(MODULE_NAME, warnings_functions, warnings__doc__); + if (m == NULL) + return; + + _filters = init_filters(); + if (_filters == NULL) + return; + Py_INCREF(_filters); + if (PyModule_AddObject(m, "filters", _filters) < 0) + return; + + _once_registry = PyDict_New(); + if (_once_registry == NULL) + return; + Py_INCREF(_once_registry); + if (PyModule_AddObject(m, "once_registry", _once_registry) < 0) + return; + + default_action = PyString_InternFromString("default"); + if (default_action == NULL) + return; + if (PyModule_AddObject(m, DEFAULT_ACTION_NAME, default_action) < 0) + return; +} diff --git a/Python/errors.c b/Python/errors.c index 1c29160..8951d57 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -641,81 +641,6 @@ PyErr_WriteUnraisable(PyObject *obj) extern PyObject *PyModule_GetWarningsModule(void); -/* Function to issue a warning message; may raise an exception. */ -int -PyErr_WarnEx(PyObject *category, const char *message, Py_ssize_t stack_level) -{ - PyObject *dict, *func = NULL; - PyObject *warnings_module = PyModule_GetWarningsModule(); - - if (warnings_module != NULL) { - dict = PyModule_GetDict(warnings_module); - if (dict != NULL) - func = PyDict_GetItemString(dict, "warn"); - } - if (func == NULL) { - PySys_WriteStderr("warning: %s\n", message); - return 0; - } - else { - PyObject *res; - - if (category == NULL) - category = PyExc_RuntimeWarning; - res = PyObject_CallFunction(func, "sOn", - message, category, stack_level); - if (res == NULL) - return -1; - Py_DECREF(res); - return 0; - } -} - -/* PyErr_Warn is only for backwards compatability and will be removed. - Use PyErr_WarnEx instead. */ - -#undef PyErr_Warn - -PyAPI_FUNC(int) -PyErr_Warn(PyObject *category, char *message) -{ - return PyErr_WarnEx(category, message, 1); -} - -/* Warning with explicit origin */ -int -PyErr_WarnExplicit(PyObject *category, const char *message, - const char *filename, int lineno, - const char *module, PyObject *registry) -{ - PyObject *mod, *dict, *func = NULL; - - mod = PyImport_ImportModuleNoBlock("warnings"); - if (mod != NULL) { - dict = PyModule_GetDict(mod); - func = PyDict_GetItemString(dict, "warn_explicit"); - Py_DECREF(mod); - } - if (func == NULL) { - PySys_WriteStderr("warning: %s\n", message); - return 0; - } - else { - PyObject *res; - - if (category == NULL) - category = PyExc_RuntimeWarning; - if (registry == NULL) - registry = Py_None; - res = PyObject_CallFunction(func, "sOsizO", message, category, - filename, lineno, module, registry); - if (res == NULL) - return -1; - Py_DECREF(res); - return 0; - } -} - /* Set file and line information for the current exception. If the exception is not a SyntaxError, also sets additional attributes diff --git a/Python/pythonrun.c b/Python/pythonrun.c index 6cc9f6d..a9b7f81 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -83,38 +83,12 @@ int Py_IgnoreEnvironmentFlag; /* e.g. PYTHONPATH, PYTHONHOME */ true divisions (which they will be in 2.3). */ int _Py_QnewFlag = 0; -/* Reference to 'warnings' module, to avoid importing it - on the fly when the import lock may be held. See 683658/771097 -*/ -static PyObject *warnings_module = NULL; - -/* Returns a borrowed reference to the 'warnings' module, or NULL. - If the module is returned, it is guaranteed to have been obtained - without acquiring the import lock -*/ -PyObject *PyModule_GetWarningsModule(void) -{ - PyObject *typ, *val, *tb; - PyObject *all_modules; - /* If we managed to get the module at init time, just use it */ - if (warnings_module) - return warnings_module; - /* If it wasn't available at init time, it may be available - now in sys.modules (common scenario is frozen apps: import - at init time fails, but the frozen init code sets up sys.path - correctly, then does an implicit import of warnings for us - */ - /* Save and restore any exceptions */ - PyErr_Fetch(&typ, &val, &tb); - - all_modules = PySys_GetObject("modules"); - if (all_modules) { - warnings_module = PyDict_GetItemString(all_modules, "warnings"); - /* We keep a ref in the global */ - Py_XINCREF(warnings_module); - } - PyErr_Restore(typ, val, tb); - return warnings_module; +/* PyModule_GetWarningsModule is no longer necessary as of 2.6 +since _warnings is builtin. This API should not be used. */ +PyObject * +PyModule_GetWarningsModule(void) +{ + return PyImport_ImportModule("warnings"); } static int initialized = 0; @@ -244,6 +218,15 @@ Py_InitializeEx(int install_sigs) if (install_sigs) initsigs(); /* Signal handling stuff, including initintr() */ + + /* Initialize warnings. */ + _PyWarnings_Init(); + if (PySys_HasWarnOptions()) { + PyObject *warnings_module = PyImport_ImportModule("warnings"); + if (!warnings_module) + PyErr_Clear(); + Py_XDECREF(warnings_module); + } initmain(); /* Module __main__ */ if (!Py_NoSiteFlag) @@ -254,30 +237,6 @@ Py_InitializeEx(int install_sigs) _PyGILState_Init(interp, tstate); #endif /* WITH_THREAD */ - warnings_module = PyImport_ImportModule("warnings"); - if (!warnings_module) { - PyErr_Clear(); - } - else { - PyObject *o; - char *action[8]; - - if (Py_BytesWarningFlag > 1) - *action = "error"; - else if (Py_BytesWarningFlag) - *action = "default"; - else - *action = "ignore"; - - o = PyObject_CallMethod(warnings_module, - "simplefilter", "sO", - *action, PyExc_BytesWarning); - if (o == NULL) - Py_FatalError("Py_Initialize: can't initialize" - "warning filter for BytesWarning."); - Py_DECREF(o); - } - #if defined(Py_USING_UNICODE) && defined(HAVE_LANGINFO_H) && defined(CODESET) /* On Unix, set the file system encoding according to the user's preference, if the CODESET names a well-known @@ -397,10 +356,6 @@ Py_Finalize(void) /* Disable signal handling */ PyOS_FiniInterrupts(); - /* drop module references we saved */ - Py_XDECREF(warnings_module); - warnings_module = NULL; - /* Clear type lookup cache */ PyType_ClearCache(); diff --git a/Python/sysmodule.c b/Python/sysmodule.c index e733cee..d6ccd17 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -940,6 +940,12 @@ PySys_AddWarnOption(char *s) } } +int +PySys_HasWarnOptions(void) +{ + return warnoptions ? 1 : 0; +} + /* XXX This doc string is too long to be a single string literal in VC++ 5.0. Two literals concatenated works just fine. If you have a K&R compiler or other abomination that however *does* understand longer strings, @@ -1165,6 +1171,7 @@ static PyStructSequence_Field flags_fields[] = { /* {"unbuffered", "-u"}, */ {"unicode", "-U"}, /* {"skip_first", "-x"}, */ + {"bytes_warning", "-b"}, {0} }; @@ -1211,6 +1218,7 @@ make_flags(void) /* SetFlag(saw_unbuffered_flag); */ SetFlag(Py_UnicodeFlag); /* SetFlag(skipfirstline); */ + SetFlag(Py_BytesWarningFlag); #undef SetFlag if (PyErr_Occurred()) { diff --git a/Python/traceback.c b/Python/traceback.c index 877ca5a..7433997 100644 --- a/Python/traceback.c +++ b/Python/traceback.c @@ -122,16 +122,16 @@ PyTraceBack_Here(PyFrameObject *frame) return 0; } -static int -tb_displayline(PyObject *f, char *filename, int lineno, char *name) +int +Py_DisplaySourceLine(PyObject *f, const char *filename, int lineno) { int err = 0; - FILE *xfp; + FILE *xfp = NULL; char linebuf[2000]; int i; char namebuf[MAXPATHLEN+1]; - if (filename == NULL || name == NULL) + if (filename == NULL) return -1; /* This is needed by Emacs' compile command */ #define FMT " File \"%.500s\", line %d, in %.500s\n" @@ -139,7 +139,7 @@ tb_displayline(PyObject *f, char *filename, int lineno, char *name) if (xfp == NULL) { /* Search tail of filename in sys.path before giving up */ PyObject *path; - char *tail = strrchr(filename, SEP); + const char *tail = strrchr(filename, SEP); if (tail == NULL) tail = filename; else @@ -175,14 +175,14 @@ tb_displayline(PyObject *f, char *filename, int lineno, char *name) } } } - PyOS_snprintf(linebuf, sizeof(linebuf), FMT, filename, lineno, name); - err = PyFile_WriteString(linebuf, f); - if (xfp == NULL) - return err; - else if (err != 0) { - fclose(xfp); - return err; - } + + if (xfp == NULL) + return err; + if (err != 0) { + fclose(xfp); + return err; + } + for (i = 0; i < lineno; i++) { char* pLastChar = &linebuf[sizeof(linebuf)-2]; do { @@ -200,22 +200,38 @@ tb_displayline(PyObject *f, char *filename, int lineno, char *name) char *p = linebuf; while (*p == ' ' || *p == '\t' || *p == '\014') p++; - err = PyFile_WriteString(" ", f); - if (err == 0) { - err = PyFile_WriteString(p, f); - if (err == 0 && strchr(p, '\n') == NULL) - err = PyFile_WriteString("\n", f); - } + err = PyFile_WriteString(p, f); + if (err == 0 && strchr(p, '\n') == NULL) + err = PyFile_WriteString("\n", f); } fclose(xfp); return err; } static int -tb_printinternal(PyTracebackObject *tb, PyObject *f, int limit) +tb_displayline(PyObject *f, const char *filename, int lineno, const char *name) +{ + int err = 0; + char linebuf[2000]; + + if (filename == NULL || name == NULL) + return -1; + /* This is needed by Emacs' compile command */ +#define FMT " File \"%.500s\", line %d, in %.500s\n" + PyOS_snprintf(linebuf, sizeof(linebuf), FMT, filename, lineno, name); + err = PyFile_WriteString(linebuf, f); + if (err != 0) + return err; + + err = PyFile_WriteString(" ", f); + return Py_DisplaySourceLine(f, filename, lineno); +} + +static int +tb_printinternal(PyTracebackObject *tb, PyObject *f, long limit) { int err = 0; - int depth = 0; + long depth = 0; PyTracebackObject *tb1 = tb; while (tb1 != NULL) { depth++; @@ -242,7 +258,7 @@ PyTraceBack_Print(PyObject *v, PyObject *f) { int err; PyObject *limitv; - int limit = 1000; + long limit = 1000; if (v == NULL) return 0; if (!PyTraceBack_Check(v)) { |