Skip to content

Commit f1c1afd

Browse files
[3.12] gh-86291: linecache: get module name from __spec__ if available (GH-22908) (GH-115731)
This allows getting source code for the __main__ module when a custom loader is used. (cherry picked from commit e976bab) Co-authored-by: Eugene Toder <[email protected]>
1 parent 5ea86f4 commit f1c1afd

File tree

3 files changed

+45
-7
lines changed

3 files changed

+45
-7
lines changed

Lib/linecache.py

+5-7
Original file line numberDiff line numberDiff line change
@@ -166,13 +166,11 @@ def lazycache(filename, module_globals):
166166
return False
167167
# Try for a __loader__, if available
168168
if module_globals and '__name__' in module_globals:
169-
name = module_globals['__name__']
170-
if (loader := module_globals.get('__loader__')) is None:
171-
if spec := module_globals.get('__spec__'):
172-
try:
173-
loader = spec.loader
174-
except AttributeError:
175-
pass
169+
spec = module_globals.get('__spec__')
170+
name = getattr(spec, 'name', None) or module_globals['__name__']
171+
loader = getattr(spec, 'loader', None)
172+
if loader is None:
173+
loader = module_globals.get('__loader__')
176174
get_source = getattr(loader, 'get_source', None)
177175

178176
if name and get_source:

Lib/test/test_linecache.py

+38
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import os.path
66
import tempfile
77
import tokenize
8+
from importlib.machinery import ModuleSpec
89
from test import support
910
from test.support import os_helper
1011

@@ -97,6 +98,16 @@ class BadUnicode_WithDeclaration(GetLineTestsBadData, unittest.TestCase):
9798
file_byte_string = b'# coding=utf-8\n\x80abc'
9899

99100

101+
class FakeLoader:
102+
def get_source(self, fullname):
103+
return f'source for {fullname}'
104+
105+
106+
class NoSourceLoader:
107+
def get_source(self, fullname):
108+
return None
109+
110+
100111
class LineCacheTests(unittest.TestCase):
101112

102113
def test_getline(self):
@@ -238,6 +249,33 @@ def raise_memoryerror(*args, **kwargs):
238249
self.assertEqual(lines3, [])
239250
self.assertEqual(linecache.getlines(FILENAME), lines)
240251

252+
def test_loader(self):
253+
filename = 'scheme://path'
254+
255+
for loader in (None, object(), NoSourceLoader()):
256+
linecache.clearcache()
257+
module_globals = {'__name__': 'a.b.c', '__loader__': loader}
258+
self.assertEqual(linecache.getlines(filename, module_globals), [])
259+
260+
linecache.clearcache()
261+
module_globals = {'__name__': 'a.b.c', '__loader__': FakeLoader()}
262+
self.assertEqual(linecache.getlines(filename, module_globals),
263+
['source for a.b.c\n'])
264+
265+
for spec in (None, object(), ModuleSpec('', FakeLoader())):
266+
linecache.clearcache()
267+
module_globals = {'__name__': 'a.b.c', '__loader__': FakeLoader(),
268+
'__spec__': spec}
269+
self.assertEqual(linecache.getlines(filename, module_globals),
270+
['source for a.b.c\n'])
271+
272+
linecache.clearcache()
273+
spec = ModuleSpec('x.y.z', FakeLoader())
274+
module_globals = {'__name__': 'a.b.c', '__loader__': spec.loader,
275+
'__spec__': spec}
276+
self.assertEqual(linecache.getlines(filename, module_globals),
277+
['source for x.y.z\n'])
278+
241279

242280
class LineCacheInvalidationTests(unittest.TestCase):
243281
def setUp(self):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
linecache: get module name from ``__spec__`` if available. This allows getting
2+
source code for the ``__main__`` module when a custom loader is used.

0 commit comments

Comments
 (0)