[Python-checkins] cpython: Close #15425: Eliminate more importlib related traceback noise

nick.coghlan python-checkins at python.org
Sun Jul 29 12:30:52 CEST 2012


http://hg.python.org/cpython/rev/75a30a478dc7
changeset:   78314:75a30a478dc7
user:        Nick Coghlan <ncoghlan at gmail.com>
date:        Sun Jul 29 20:30:36 2012 +1000
summary:
  Close #15425: Eliminate more importlib related traceback noise

files:
  Lib/importlib/_bootstrap.py |    11 +-
  Lib/test/test_import.py     |    68 +
  Misc/NEWS                   |     3 +
  Python/import.c             |    30 +-
  Python/importlib.h          |  8325 +++++++++++-----------
  5 files changed, 4281 insertions(+), 4156 deletions(-)


diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py
--- a/Lib/importlib/_bootstrap.py
+++ b/Lib/importlib/_bootstrap.py
@@ -1472,7 +1472,7 @@
     parent = name.rpartition('.')[0]
     if parent:
         if parent not in sys.modules:
-            import_(parent)
+            _recursive_import(import_, parent)
         # Crazy side-effects!
         if name in sys.modules:
             return sys.modules[name]
@@ -1550,6 +1550,12 @@
     _lock_unlock_module(name)
     return module
 
+def _recursive_import(import_, name):
+    """Common exit point for recursive calls to the import machinery
+
+    This simplifies the process of stripping importlib from tracebacks
+    """
+    return import_(name)
 
 def _handle_fromlist(module, fromlist, import_):
     """Figure out what __import__ should return.
@@ -1569,7 +1575,8 @@
                 fromlist.extend(module.__all__)
         for x in fromlist:
             if not hasattr(module, x):
-                import_('{}.{}'.format(module.__name__, x))
+                _recursive_import(import_,
+                                  '{}.{}'.format(module.__name__, x))
     return module
 
 
diff --git a/Lib/test/test_import.py b/Lib/test/test_import.py
--- a/Lib/test/test_import.py
+++ b/Lib/test/test_import.py
@@ -844,6 +844,74 @@
             self.fail("ZeroDivisionError should have been raised")
         self.assert_traceback(tb, [__file__, 'foo.py', 'bar.py'])
 
+    # A few more examples from issue #15425
+    def test_syntax_error(self):
+        self.create_module("foo", "invalid syntax is invalid")
+        try:
+            import foo
+        except SyntaxError as e:
+            tb = e.__traceback__
+        else:
+            self.fail("SyntaxError should have been raised")
+        self.assert_traceback(tb, [__file__])
+
+    def _setup_broken_package(self, parent, child):
+        pkg_name = "_parent_foo"
+        def cleanup():
+            rmtree(pkg_name)
+            unload(pkg_name)
+        os.mkdir(pkg_name)
+        self.addCleanup(cleanup)
+        # Touch the __init__.py
+        init_path = os.path.join(pkg_name, '__init__.py')
+        with open(init_path, 'w') as f:
+            f.write(parent)
+        bar_path = os.path.join(pkg_name, 'bar.py')
+        with open(bar_path, 'w') as f:
+            f.write(child)
+        importlib.invalidate_caches()
+        return init_path, bar_path
+
+    def test_broken_submodule(self):
+        init_path, bar_path = self._setup_broken_package("", "1/0")
+        try:
+            import _parent_foo.bar
+        except ZeroDivisionError as e:
+            tb = e.__traceback__
+        else:
+            self.fail("ZeroDivisionError should have been raised")
+        self.assert_traceback(tb, [__file__, bar_path])
+
+    def test_broken_from(self):
+        init_path, bar_path = self._setup_broken_package("", "1/0")
+        try:
+            from _parent_foo import bar
+        except ZeroDivisionError as e:
+            tb = e.__traceback__
+        else:
+            self.fail("ImportError should have been raised")
+        self.assert_traceback(tb, [__file__, bar_path])
+
+    def test_broken_parent(self):
+        init_path, bar_path = self._setup_broken_package("1/0", "")
+        try:
+            import _parent_foo.bar
+        except ZeroDivisionError as e:
+            tb = e.__traceback__
+        else:
+            self.fail("ZeroDivisionError should have been raised")
+        self.assert_traceback(tb, [__file__, init_path])
+
+    def test_broken_parent_from(self):
+        init_path, bar_path = self._setup_broken_package("1/0", "")
+        try:
+            from _parent_foo import bar
+        except ZeroDivisionError as e:
+            tb = e.__traceback__
+        else:
+            self.fail("ZeroDivisionError should have been raised")
+        self.assert_traceback(tb, [__file__, init_path])
+
     @cpython_only
     def test_import_bug(self):
         # We simulate a bug in importlib and check that it's not stripped
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,9 @@
 Core and Builtins
 -----------------
 
+- Issue #15425: Eliminated traceback noise from more situations involving
+  importlib
+
 - Issue #14578: Support modules registered in the Windows registry again.
 
 - Issue #15466: Stop using TYPE_INT64 in marshal, to make importlib.h
diff --git a/Python/import.c b/Python/import.c
--- a/Python/import.c
+++ b/Python/import.c
@@ -1154,14 +1154,27 @@
 {
     const char *importlib_filename = "<frozen importlib._bootstrap>";
     const char *exec_funcname = "_exec_module";
+    const char *get_code_funcname = "get_code";
+    const char *recursive_import = "_recursive_import";
     int always_trim = 0;
+    int trim_get_code = 0;
     int in_importlib = 0;
     PyObject *exception, *value, *base_tb, *tb;
     PyObject **prev_link, **outer_link = NULL;
 
     /* Synopsis: if it's an ImportError, we trim all importlib chunks
-       from the traceback.  Otherwise, we trim only those chunks which
-       end with a call to "_exec_module". */
+       from the traceback.  If it's a SyntaxError, we trim any chunks that
+       end with a call to "get_code", We always trim chunks
+       which end with a call to "_exec_module". */
+
+    /* Thanks to issue 15425, we also strip any chunk ending with
+     * _recursive_import. This is used when making a recursive call to the
+     * full import machinery which means the inner stack gets stripped early
+     * and the normal heuristics won't fire properly for outer frames. A
+     * more elegant mechanism would be nice, as this one can misfire if
+     * builtins.__import__ has been replaced with a custom implementation.
+     * However, the current approach at least gets the job done.
+     */
 
     PyErr_Fetch(&exception, &value, &base_tb);
     if (!exception || Py_VerboseFlag)
@@ -1169,6 +1182,9 @@
     if (PyType_IsSubtype((PyTypeObject *) exception,
                          (PyTypeObject *) PyExc_ImportError))
         always_trim = 1;
+    if (PyType_IsSubtype((PyTypeObject *) exception,
+                         (PyTypeObject *) PyExc_SyntaxError))
+        trim_get_code = 1;
 
     prev_link = &base_tb;
     tb = base_tb;
@@ -1191,8 +1207,14 @@
 
         if (in_importlib &&
             (always_trim ||
-             PyUnicode_CompareWithASCIIString(code->co_name,
-                                              exec_funcname) == 0)) {
+             (PyUnicode_CompareWithASCIIString(code->co_name,
+                                              exec_funcname) == 0) ||
+             (PyUnicode_CompareWithASCIIString(code->co_name,
+                                              recursive_import) == 0) ||
+             (trim_get_code &&
+              PyUnicode_CompareWithASCIIString(code->co_name,
+                                              get_code_funcname) == 0)
+           )) {
             PyObject *tmp = *outer_link;
             *outer_link = next;
             Py_XINCREF(next);
diff --git a/Python/importlib.h b/Python/importlib.h
--- a/Python/importlib.h
+++ b/Python/importlib.h
[stripped]

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list