mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
Close #15486: Simplify the mechanism used to remove importlib frames from tracebacks when they just introduce irrelevant noise
This commit is contained in:
parent
73a74dad3f
commit
42c0766a53
4 changed files with 3503 additions and 3616 deletions
|
@ -297,8 +297,20 @@ def _lock_unlock_module(name):
|
||||||
else:
|
else:
|
||||||
lock.release()
|
lock.release()
|
||||||
|
|
||||||
|
# Frame stripping magic ###############################################
|
||||||
|
|
||||||
# Finder/loader utility code ##################################################
|
def _call_with_frames_removed(f, *args, **kwds):
|
||||||
|
"""remove_importlib_frames in import.c will always remove sequences
|
||||||
|
of importlib frames that end with a call to this function
|
||||||
|
|
||||||
|
Use it instead of a normal call in places where including the importlib
|
||||||
|
frames introduces unwanted noise into the traceback (e.g. when executing
|
||||||
|
module code)
|
||||||
|
"""
|
||||||
|
return f(*args, **kwds)
|
||||||
|
|
||||||
|
|
||||||
|
# Finder/loader utility code ###############################################
|
||||||
|
|
||||||
"""Magic word to reject .pyc files generated by other Python versions.
|
"""Magic word to reject .pyc files generated by other Python versions.
|
||||||
It should change for each incompatible change to the bytecode.
|
It should change for each incompatible change to the bytecode.
|
||||||
|
@ -629,19 +641,12 @@ class BuiltinImporter:
|
||||||
"""Load a built-in module."""
|
"""Load a built-in module."""
|
||||||
is_reload = fullname in sys.modules
|
is_reload = fullname in sys.modules
|
||||||
try:
|
try:
|
||||||
return cls._exec_module(fullname)
|
return _call_with_frames_removed(_imp.init_builtin, fullname)
|
||||||
except:
|
except:
|
||||||
if not is_reload and fullname in sys.modules:
|
if not is_reload and fullname in sys.modules:
|
||||||
del sys.modules[fullname]
|
del sys.modules[fullname]
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _exec_module(cls, fullname):
|
|
||||||
"""Helper for load_module, allowing to isolate easily (when
|
|
||||||
looking at a traceback) whether an error comes from executing
|
|
||||||
an imported module's code."""
|
|
||||||
return _imp.init_builtin(fullname)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@_requires_builtin
|
@_requires_builtin
|
||||||
def get_code(cls, fullname):
|
def get_code(cls, fullname):
|
||||||
|
@ -687,7 +692,7 @@ class FrozenImporter:
|
||||||
"""Load a frozen module."""
|
"""Load a frozen module."""
|
||||||
is_reload = fullname in sys.modules
|
is_reload = fullname in sys.modules
|
||||||
try:
|
try:
|
||||||
m = cls._exec_module(fullname)
|
m = _call_with_frames_removed(_imp.init_frozen, fullname)
|
||||||
# Let our own module_repr() method produce a suitable repr.
|
# Let our own module_repr() method produce a suitable repr.
|
||||||
del m.__file__
|
del m.__file__
|
||||||
return m
|
return m
|
||||||
|
@ -714,13 +719,6 @@ class FrozenImporter:
|
||||||
"""Return if the frozen module is a package."""
|
"""Return if the frozen module is a package."""
|
||||||
return _imp.is_frozen_package(fullname)
|
return _imp.is_frozen_package(fullname)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _exec_module(cls, fullname):
|
|
||||||
"""Helper for load_module, allowing to isolate easily (when
|
|
||||||
looking at a traceback) whether an error comes from executing
|
|
||||||
an imported module's code."""
|
|
||||||
return _imp.init_frozen(fullname)
|
|
||||||
|
|
||||||
|
|
||||||
class WindowsRegistryImporter:
|
class WindowsRegistryImporter:
|
||||||
|
|
||||||
|
@ -850,15 +848,9 @@ class _LoaderBasics:
|
||||||
else:
|
else:
|
||||||
module.__package__ = module.__package__.rpartition('.')[0]
|
module.__package__ = module.__package__.rpartition('.')[0]
|
||||||
module.__loader__ = self
|
module.__loader__ = self
|
||||||
self._exec_module(code_object, module.__dict__)
|
_call_with_frames_removed(exec, code_object, module.__dict__)
|
||||||
return module
|
return module
|
||||||
|
|
||||||
def _exec_module(self, code_object, module_dict):
|
|
||||||
"""Helper for _load_module, allowing to isolate easily (when
|
|
||||||
looking at a traceback) whether an error comes from executing
|
|
||||||
an imported module's code."""
|
|
||||||
exec(code_object, module_dict)
|
|
||||||
|
|
||||||
|
|
||||||
class SourceLoader(_LoaderBasics):
|
class SourceLoader(_LoaderBasics):
|
||||||
|
|
||||||
|
@ -956,7 +948,8 @@ class SourceLoader(_LoaderBasics):
|
||||||
raise ImportError(msg.format(bytecode_path),
|
raise ImportError(msg.format(bytecode_path),
|
||||||
name=fullname, path=bytecode_path)
|
name=fullname, path=bytecode_path)
|
||||||
source_bytes = self.get_data(source_path)
|
source_bytes = self.get_data(source_path)
|
||||||
code_object = compile(source_bytes, source_path, 'exec',
|
code_object = _call_with_frames_removed(compile,
|
||||||
|
source_bytes, source_path, 'exec',
|
||||||
dont_inherit=True)
|
dont_inherit=True)
|
||||||
_verbose_message('code object from {}', source_path)
|
_verbose_message('code object from {}', source_path)
|
||||||
if (not sys.dont_write_bytecode and bytecode_path is not None and
|
if (not sys.dont_write_bytecode and bytecode_path is not None and
|
||||||
|
@ -1093,7 +1086,8 @@ class ExtensionFileLoader:
|
||||||
"""Load an extension module."""
|
"""Load an extension module."""
|
||||||
is_reload = fullname in sys.modules
|
is_reload = fullname in sys.modules
|
||||||
try:
|
try:
|
||||||
module = self._exec_module(fullname, self.path)
|
module = _call_with_frames_removed(_imp.load_dynamic,
|
||||||
|
fullname, self.path)
|
||||||
_verbose_message('extension module loaded from {!r}', self.path)
|
_verbose_message('extension module loaded from {!r}', self.path)
|
||||||
return module
|
return module
|
||||||
except:
|
except:
|
||||||
|
@ -1113,12 +1107,6 @@ class ExtensionFileLoader:
|
||||||
"""Return None as extension modules have no source code."""
|
"""Return None as extension modules have no source code."""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _exec_module(self, fullname, path):
|
|
||||||
"""Helper for load_module, allowing to isolate easily (when
|
|
||||||
looking at a traceback) whether an error comes from executing
|
|
||||||
an imported module's code."""
|
|
||||||
return _imp.load_dynamic(fullname, path)
|
|
||||||
|
|
||||||
|
|
||||||
class _NamespacePath:
|
class _NamespacePath:
|
||||||
"""Represents a namespace package's path. It uses the module name
|
"""Represents a namespace package's path. It uses the module name
|
||||||
|
@ -1472,7 +1460,7 @@ def _find_and_load_unlocked(name, import_):
|
||||||
parent = name.rpartition('.')[0]
|
parent = name.rpartition('.')[0]
|
||||||
if parent:
|
if parent:
|
||||||
if parent not in sys.modules:
|
if parent not in sys.modules:
|
||||||
_recursive_import(import_, parent)
|
_call_with_frames_removed(import_, parent)
|
||||||
# Crazy side-effects!
|
# Crazy side-effects!
|
||||||
if name in sys.modules:
|
if name in sys.modules:
|
||||||
return sys.modules[name]
|
return sys.modules[name]
|
||||||
|
@ -1550,13 +1538,6 @@ def _gcd_import(name, package=None, level=0):
|
||||||
_lock_unlock_module(name)
|
_lock_unlock_module(name)
|
||||||
return module
|
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_):
|
def _handle_fromlist(module, fromlist, import_):
|
||||||
"""Figure out what __import__ should return.
|
"""Figure out what __import__ should return.
|
||||||
|
|
||||||
|
@ -1575,7 +1556,7 @@ def _handle_fromlist(module, fromlist, import_):
|
||||||
fromlist.extend(module.__all__)
|
fromlist.extend(module.__all__)
|
||||||
for x in fromlist:
|
for x in fromlist:
|
||||||
if not hasattr(module, x):
|
if not hasattr(module, x):
|
||||||
_recursive_import(import_,
|
_call_with_frames_removed(import_,
|
||||||
'{}.{}'.format(module.__name__, x))
|
'{}.{}'.format(module.__name__, x))
|
||||||
return module
|
return module
|
||||||
|
|
||||||
|
|
|
@ -785,11 +785,13 @@ class ImportTracebackTests(unittest.TestCase):
|
||||||
sys.path[:] = self.old_path
|
sys.path[:] = self.old_path
|
||||||
rmtree(TESTFN)
|
rmtree(TESTFN)
|
||||||
|
|
||||||
def create_module(self, mod, contents):
|
def create_module(self, mod, contents, ext=".py"):
|
||||||
with open(os.path.join(TESTFN, mod + ".py"), "w") as f:
|
fname = os.path.join(TESTFN, mod + ext)
|
||||||
|
with open(fname, "w") as f:
|
||||||
f.write(contents)
|
f.write(contents)
|
||||||
self.addCleanup(unload, mod)
|
self.addCleanup(unload, mod)
|
||||||
importlib.invalidate_caches()
|
importlib.invalidate_caches()
|
||||||
|
return fname
|
||||||
|
|
||||||
def assert_traceback(self, tb, files):
|
def assert_traceback(self, tb, files):
|
||||||
deduped_files = []
|
deduped_files = []
|
||||||
|
|
|
@ -1153,9 +1153,7 @@ static void
|
||||||
remove_importlib_frames(void)
|
remove_importlib_frames(void)
|
||||||
{
|
{
|
||||||
const char *importlib_filename = "<frozen importlib._bootstrap>";
|
const char *importlib_filename = "<frozen importlib._bootstrap>";
|
||||||
const char *exec_funcname = "_exec_module";
|
const char *remove_frames = "_call_with_frames_removed";
|
||||||
const char *get_code_funcname = "get_code";
|
|
||||||
const char *recursive_import = "_recursive_import";
|
|
||||||
int always_trim = 0;
|
int always_trim = 0;
|
||||||
int trim_get_code = 0;
|
int trim_get_code = 0;
|
||||||
int in_importlib = 0;
|
int in_importlib = 0;
|
||||||
|
@ -1163,18 +1161,8 @@ remove_importlib_frames(void)
|
||||||
PyObject **prev_link, **outer_link = NULL;
|
PyObject **prev_link, **outer_link = NULL;
|
||||||
|
|
||||||
/* Synopsis: if it's an ImportError, we trim all importlib chunks
|
/* Synopsis: if it's an ImportError, we trim all importlib chunks
|
||||||
from the traceback. If it's a SyntaxError, we trim any chunks that
|
from the traceback. We always trim chunks
|
||||||
end with a call to "get_code", We always trim chunks
|
which end with a call to "_call_with_frames_removed". */
|
||||||
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);
|
PyErr_Fetch(&exception, &value, &base_tb);
|
||||||
if (!exception || Py_VerboseFlag)
|
if (!exception || Py_VerboseFlag)
|
||||||
|
@ -1207,14 +1195,8 @@ remove_importlib_frames(void)
|
||||||
|
|
||||||
if (in_importlib &&
|
if (in_importlib &&
|
||||||
(always_trim ||
|
(always_trim ||
|
||||||
(PyUnicode_CompareWithASCIIString(code->co_name,
|
|
||||||
exec_funcname) == 0) ||
|
|
||||||
(PyUnicode_CompareWithASCIIString(code->co_name,
|
|
||||||
recursive_import) == 0) ||
|
|
||||||
(trim_get_code &&
|
|
||||||
PyUnicode_CompareWithASCIIString(code->co_name,
|
PyUnicode_CompareWithASCIIString(code->co_name,
|
||||||
get_code_funcname) == 0)
|
remove_frames) == 0)) {
|
||||||
)) {
|
|
||||||
PyObject *tmp = *outer_link;
|
PyObject *tmp = *outer_link;
|
||||||
*outer_link = next;
|
*outer_link = next;
|
||||||
Py_XINCREF(next);
|
Py_XINCREF(next);
|
||||||
|
|
7020
Python/importlib.h
7020
Python/importlib.h
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue