From 883b65d0a9a326421cf2d7c5522f9bfbb7885ecb Mon Sep 17 00:00:00 2001 From: Eugene Toder Date: Fri, 23 Oct 2020 00:07:53 -0400 Subject: [PATCH] bpo-42125: linecache: get module name from __spec__ if available This allows getting source code for the __main__ module when a custom loader is used. --- Lib/linecache.py | 12 +++--- Lib/test/test_linecache.py | 38 +++++++++++++++++++ .../2020-12-15-22-30-49.bpo-42125.UGyseY.rst | 2 + 3 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2020-12-15-22-30-49.bpo-42125.UGyseY.rst diff --git a/Lib/linecache.py b/Lib/linecache.py index 513b17e999880b5..ac930e4d1090438 100644 --- a/Lib/linecache.py +++ b/Lib/linecache.py @@ -166,13 +166,11 @@ def lazycache(filename, module_globals): return False # Try for a __loader__, if available if module_globals and '__name__' in module_globals: - name = module_globals['__name__'] - if (loader := module_globals.get('__loader__')) is None: - if spec := module_globals.get('__spec__'): - try: - loader = spec.loader - except AttributeError: - pass + spec = module_globals.get('__spec__') + name = getattr(spec, 'name', None) or module_globals['__name__'] + loader = getattr(spec, 'loader', None) + if loader is None: + loader = module_globals.get('__loader__') get_source = getattr(loader, 'get_source', None) if name and get_source: diff --git a/Lib/test/test_linecache.py b/Lib/test/test_linecache.py index cfc6ba89e774c06..7468fafc1124e40 100644 --- a/Lib/test/test_linecache.py +++ b/Lib/test/test_linecache.py @@ -5,6 +5,7 @@ import os.path import tempfile import tokenize +from importlib.machinery import ModuleSpec from test import support from test.support import os_helper @@ -97,6 +98,16 @@ class BadUnicode(GetLineTestsBadData, unittest.TestCase): file_byte_string = b'\x80abc' +class FakeLoader: + def get_source(self, fullname): + return f'source for {fullname}' + + +class NoSourceLoader: + def get_source(self, fullname): + return None + + class LineCacheTests(unittest.TestCase): def test_getline(self): @@ -238,6 +249,33 @@ def raise_memoryerror(*args, **kwargs): self.assertEqual(lines3, []) self.assertEqual(linecache.getlines(FILENAME), lines) + def test_loader(self): + filename = 'scheme://path' + + for loader in (None, object(), NoSourceLoader()): + linecache.clearcache() + module_globals = {'__name__': 'a.b.c', '__loader__': loader} + self.assertEqual(linecache.getlines(filename, module_globals), []) + + linecache.clearcache() + module_globals = {'__name__': 'a.b.c', '__loader__': FakeLoader()} + self.assertEqual(linecache.getlines(filename, module_globals), + ['source for a.b.c\n']) + + for spec in (None, object(), ModuleSpec('', FakeLoader())): + linecache.clearcache() + module_globals = {'__name__': 'a.b.c', '__loader__': FakeLoader(), + '__spec__': spec} + self.assertEqual(linecache.getlines(filename, module_globals), + ['source for a.b.c\n']) + + linecache.clearcache() + spec = ModuleSpec('x.y.z', FakeLoader()) + module_globals = {'__name__': 'a.b.c', '__loader__': spec.loader, + '__spec__': spec} + self.assertEqual(linecache.getlines(filename, module_globals), + ['source for x.y.z\n']) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2020-12-15-22-30-49.bpo-42125.UGyseY.rst b/Misc/NEWS.d/next/Library/2020-12-15-22-30-49.bpo-42125.UGyseY.rst new file mode 100644 index 000000000000000..6a3cb9b10cfd3b6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-12-15-22-30-49.bpo-42125.UGyseY.rst @@ -0,0 +1,2 @@ +linecache: get module name from `__spec__` if available. This allows getting +source code for the `__main__` module when a custom loader is used.