Skip to content

Commit fb34096

Browse files
authored
bpo-24792: Fix zipimporter masking the cause of import errors (GH-22204)
zipimport's _unmarshal_code swallows import errors and then _get_module_code doesn't know the cause of the error, and returns the generic, and sometimes incorrect, 'could not find...'. Automerge-Triggered-By: GH:brettcannon
1 parent e8d2264 commit fb34096

File tree

5 files changed

+748
-737
lines changed

5 files changed

+748
-737
lines changed

Doc/library/zipimport.rst

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ zipimporter Objects
121121
.. method:: get_code(fullname)
122122

123123
Return the code object for the specified module. Raise
124-
:exc:`ZipImportError` if the module couldn't be found.
124+
:exc:`ZipImportError` if the module couldn't be imported.
125125

126126

127127
.. method:: get_data(pathname)
@@ -137,7 +137,7 @@ zipimporter Objects
137137

138138
Return the value ``__file__`` would be set to if the specified module
139139
was imported. Raise :exc:`ZipImportError` if the module couldn't be
140-
found.
140+
imported.
141141

142142
.. versionadded:: 3.1
143143

@@ -159,14 +159,13 @@ zipimporter Objects
159159
.. method:: load_module(fullname)
160160

161161
Load the module specified by *fullname*. *fullname* must be the fully
162-
qualified (dotted) module name. It returns the imported module, or raises
163-
:exc:`ZipImportError` if it wasn't found.
162+
qualified (dotted) module name. Returns the imported module on success,
163+
raises :exc:`ZipImportError` on failure.
164164

165165
.. deprecated:: 3.10
166166

167167
Use :meth:`exec_module` instead.
168168

169-
170169
.. attribute:: archive
171170

172171
The file name of the importer's associated ZIP file, without a possible

Lib/test/test_zipimport.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -242,10 +242,10 @@ def testBadMagic2(self):
242242
files = {TESTMOD + pyc_ext: (NOW, badmagic_pyc)}
243243
try:
244244
self.doTest(".py", files, TESTMOD)
245-
except ImportError:
246-
pass
247-
else:
248-
self.fail("expected ImportError; import from bad pyc")
245+
self.fail("This should not be reached")
246+
except zipimport.ZipImportError as exc:
247+
self.assertIsInstance(exc.__cause__, ImportError)
248+
self.assertIn("magic number", exc.__cause__.msg)
249249

250250
def testBadMTime(self):
251251
badtime_pyc = bytearray(test_pyc)

Lib/zipimport.py

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ def get_code(self, fullname):
185185
"""get_code(fullname) -> code object.
186186
187187
Return the code object for the specified module. Raise ZipImportError
188-
if the module couldn't be found.
188+
if the module couldn't be imported.
189189
"""
190190
code, ispackage, modpath = _get_module_code(self, fullname)
191191
return code
@@ -215,7 +215,8 @@ def get_data(self, pathname):
215215
def get_filename(self, fullname):
216216
"""get_filename(fullname) -> filename string.
217217
218-
Return the filename for the specified module.
218+
Return the filename for the specified module or raise ZipImportError
219+
if it couldn't be imported.
219220
"""
220221
# Deciding the filename requires working out where the code
221222
# would come from if the module was actually loaded
@@ -267,7 +268,7 @@ def load_module(self, fullname):
267268
268269
Load the module specified by 'fullname'. 'fullname' must be the
269270
fully qualified (dotted) module name. It returns the imported
270-
module, or raises ZipImportError if it wasn't found.
271+
module, or raises ZipImportError if it could not be imported.
271272
272273
Deprecated since Python 3.10. Use exec_module() instead.
273274
"""
@@ -613,20 +614,15 @@ def _eq_mtime(t1, t2):
613614

614615

615616
# Given the contents of a .py[co] file, unmarshal the data
616-
# and return the code object. Return None if it the magic word doesn't
617-
# match, or if the recorded .py[co] metadata does not match the source,
618-
# (we do this instead of raising an exception as we fall back
619-
# to .py if available and we don't want to mask other errors).
617+
# and return the code object. Raises ImportError it the magic word doesn't
618+
# match, or if the recorded .py[co] metadata does not match the source.
620619
def _unmarshal_code(self, pathname, fullpath, fullname, data):
621620
exc_details = {
622621
'name': fullname,
623622
'path': fullpath,
624623
}
625624

626-
try:
627-
flags = _bootstrap_external._classify_pyc(data, fullname, exc_details)
628-
except ImportError:
629-
return None
625+
flags = _bootstrap_external._classify_pyc(data, fullname, exc_details)
630626

631627
hash_based = flags & 0b1 != 0
632628
if hash_based:
@@ -640,11 +636,8 @@ def _unmarshal_code(self, pathname, fullpath, fullname, data):
640636
source_bytes,
641637
)
642638

643-
try:
644-
_bootstrap_external._validate_hash_pyc(
645-
data, source_hash, fullname, exc_details)
646-
except ImportError:
647-
return None
639+
_bootstrap_external._validate_hash_pyc(
640+
data, source_hash, fullname, exc_details)
648641
else:
649642
source_mtime, source_size = \
650643
_get_mtime_and_size_of_source(self, fullpath)
@@ -730,6 +723,7 @@ def _get_pyc_source(self, path):
730723
# 'fullname'.
731724
def _get_module_code(self, fullname):
732725
path = _get_module_path(self, fullname)
726+
import_error = None
733727
for suffix, isbytecode, ispackage in _zip_searchorder:
734728
fullpath = path + suffix
735729
_bootstrap._verbose_message('trying {}{}{}', self.archive, path_sep, fullpath, verbosity=2)
@@ -740,8 +734,12 @@ def _get_module_code(self, fullname):
740734
else:
741735
modpath = toc_entry[0]
742736
data = _get_data(self.archive, toc_entry)
737+
code = None
743738
if isbytecode:
744-
code = _unmarshal_code(self, modpath, fullpath, fullname, data)
739+
try:
740+
code = _unmarshal_code(self, modpath, fullpath, fullname, data)
741+
except ImportError as exc:
742+
import_error = exc
745743
else:
746744
code = _compile_source(modpath, data)
747745
if code is None:
@@ -751,4 +749,8 @@ def _get_module_code(self, fullname):
751749
modpath = toc_entry[0]
752750
return code, ispackage, modpath
753751
else:
754-
raise ZipImportError(f"can't find module {fullname!r}", name=fullname)
752+
if import_error:
753+
msg = f"module load failed: {import_error}"
754+
raise ZipImportError(msg, name=fullname) from import_error
755+
else:
756+
raise ZipImportError(f"can't find module {fullname!r}", name=fullname)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed bug where :mod:`zipimporter` sometimes reports an incorrect cause of import errors.

0 commit comments

Comments
 (0)