Skip to content

Commit 0ecb0df

Browse files
gpsheadclaude
andcommitted
gh-157146: Accept relative archive paths and tighten the lookup
zipimporter does not absolutize its sys.path entry, so a relative entry gives its modules a relative __file__ and a relative key in sys.path_importer_cache; the isabs() guard made linecache skip exactly that case. Build the cache entry once for both the loader and the archive lookups, and only import importlib.util once there is data to decode. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016oQeaTzLMBouqehAe3W2EU
1 parent 44ede9e commit 0ecb0df

2 files changed

Lines changed: 23 additions & 20 deletions

File tree

Lib/linecache.py

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ def updatecache(filename, module_globals=None):
144144
lazy_entry = entry if entry is not None and len(entry) == 1 else None
145145
if lazy_entry is None:
146146
lazy_entry = _make_lazycache_entry(filename, module_globals)
147+
data = None
147148
if lazy_entry is not None:
148149
try:
149150
data = lazy_entry[0]()
@@ -154,18 +155,10 @@ def updatecache(filename, module_globals=None):
154155
# No luck, the PEP302 loader cannot find the source
155156
# for this module.
156157
return []
157-
entry = (
158-
len(data),
159-
None,
160-
[line + '\n' for line in data.splitlines()],
161-
fullname
162-
)
163-
cache[filename] = entry
164-
return entry[2]
165-
166-
# The file may be inside an archive on the module search path, such
167-
# as a zip file.
168-
data = _read_from_archive(fullname)
158+
if data is None:
159+
# The file may be inside an archive on the module search path,
160+
# such as a zip file.
161+
data = _read_from_archive(fullname)
169162
if data is not None:
170163
entry = (
171164
len(data),
@@ -220,11 +213,8 @@ def _read_from_archive(filename):
220213
look for a finder registered for one of them. Return None if the file
221214
is not in such an archive.
222215
"""
223-
import importlib.util
224216
import os
225217
import sys
226-
if not os.path.isabs(filename):
227-
return None
228218
path = filename
229219
while True:
230220
parent = os.path.dirname(path)
@@ -239,6 +229,7 @@ def _read_from_archive(filename):
239229
data = get_data(filename)
240230
except (ImportError, OSError):
241231
continue
232+
import importlib.util
242233
try:
243234
return importlib.util.decode_source(data)
244235
except (UnicodeDecodeError, SyntaxError):

Lib/test/test_linecache.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -444,12 +444,24 @@ def test_getlines_without_module_globals(self):
444444
self.assertEqual(linecache.getline(filename, 4),
445445
' return "from the zip"\n')
446446
self.assertEqual(linecache.getline(filename, 5), '')
447-
448-
def test_getline_from_code_object(self):
449447
code = self.zipmod.f.__code__
450-
self.assertEqual(
451-
linecache.getline(code.co_filename, code.co_firstlineno),
452-
'def f():\n')
448+
self.assertEqual(code.co_filename, filename)
449+
self.assertEqual(linecache.getline(filename, code.co_firstlineno),
450+
'def f():\n')
451+
452+
def test_relative_archive_path(self):
453+
# A relative sys.path entry gives its modules a relative __file__.
454+
tmpdir, zip_base = os.path.split(self.zip_name)
455+
self.addCleanup(sys.path_importer_cache.pop, zip_base, None)
456+
self.addCleanup(zipimport._zip_directory_cache.pop, zip_base, None)
457+
sys.path.insert(0, zip_base)
458+
self.addCleanup(sys.path.remove, zip_base)
459+
with os_helper.change_cwd(tmpdir):
460+
zippkg = importlib.import_module('zippkg')
461+
self.assertEqual(zippkg.__file__,
462+
os.path.join(zip_base, 'zippkg', '__init__.py'))
463+
self.assertEqual(linecache.getlines(zippkg.__file__),
464+
['value = 42\n'])
453465

454466
def test_package(self):
455467
zippkg = importlib.import_module('zippkg')

0 commit comments

Comments
 (0)