https://github.com/python/cpython/commit/6bd96894269be4754a811fb8ea1e3b627a676562
commit: 6bd96894269be4754a811fb8ea1e3b627a676562
branch: main
author: Tian Gao <gaogaotiant...@hotmail.com>
committer: gaogaotiantian <gaogaotiant...@hotmail.com>
date: 2025-04-02T19:50:01-04:00
summary:

gh-60115: Support frozen modules for linecache.getline() (#131638)

files:
A Misc/NEWS.d/next/Library/2025-03-23-18-39-07.gh-issue-60115.AWdcmq.rst
M Doc/library/linecache.rst
M Doc/whatsnew/3.14.rst
M Lib/linecache.py
M Lib/test/test_linecache.py

diff --git a/Doc/library/linecache.rst b/Doc/library/linecache.rst
index 88c6079a05b7fa..e766a9280946d3 100644
--- a/Doc/library/linecache.rst
+++ b/Doc/library/linecache.rst
@@ -30,6 +30,10 @@ The :mod:`linecache` module defines the following functions:
 
    .. index:: triple: module; search; path
 
+   If *filename* indicates a frozen module (starting with ``'<frozen '``), the 
function
+   will attepmt to get the real file name from ``module_globals['__file__']`` 
if
+   *module_globals* is not ``None``.
+
    If a file named *filename* is not found, the function first checks
    for a :pep:`302` ``__loader__`` in *module_globals*.
    If there is such a loader and it defines a ``get_source`` method,
@@ -38,6 +42,10 @@ The :mod:`linecache` module defines the following functions:
    Finally, if *filename* is a relative filename,
    it is looked up relative to the entries in the module search path, 
``sys.path``.
 
+   .. versionchanged:: 3.14
+
+      Support *filename* of frozen modules.
+
 
 .. function:: clearcache()
 
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index 5c0f2829809e3e..108768de086bb2 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -706,6 +706,13 @@ json
   (Contributed by Trey Hunner in :gh:`122873`.)
 
 
+linecache
+---------
+
+* :func:`linecache.getline` can retrieve source code for frozen modules.
+  (Contributed by Tian Gao in :gh:`131638`.)
+
+
 mimetypes
 ---------
 
diff --git a/Lib/linecache.py b/Lib/linecache.py
index dc02de19eb62cb..87d7d6fda657e4 100644
--- a/Lib/linecache.py
+++ b/Lib/linecache.py
@@ -63,6 +63,16 @@ def _getlines_from_code(code):
     return []
 
 
+def _source_unavailable(filename):
+    """Return True if the source code is unavailable for such file name."""
+    return (
+        not filename
+        or (filename.startswith('<')
+            and filename.endswith('>')
+            and not filename.startswith('<frozen '))
+    )
+
+
 def checkcache(filename=None):
     """Discard cache entries that are out of date.
     (This is not checked upon each call!)"""
@@ -118,10 +128,17 @@ def updatecache(filename, module_globals=None):
     if filename in cache:
         if len(cache[filename]) != 1:
             cache.pop(filename, None)
-    if not filename or (filename.startswith('<') and filename.endswith('>')):
+    if _source_unavailable(filename):
         return []
 
-    fullname = filename
+    if filename.startswith('<frozen ') and module_globals is not None:
+        # This is a frozen module, so we need to use the filename
+        # from the module globals.
+        fullname = module_globals.get('__file__')
+        if fullname is None:
+            return []
+    else:
+        fullname = filename
     try:
         stat = os.stat(fullname)
     except OSError:
diff --git a/Lib/test/test_linecache.py b/Lib/test/test_linecache.py
index e23e1cc942856b..e4aa41ebb43762 100644
--- a/Lib/test/test_linecache.py
+++ b/Lib/test/test_linecache.py
@@ -281,6 +281,19 @@ def test_loader(self):
         self.assertEqual(linecache.getlines(filename, module_globals),
                          ['source for x.y.z\n'])
 
+    def test_frozen(self):
+        filename = '<frozen fakemodule>'
+        module_globals = {'__file__': FILENAME}
+        empty = linecache.getlines(filename)
+        self.assertEqual(empty, [])
+        lines = linecache.getlines(filename, module_globals)
+        self.assertGreater(len(lines), 0)
+        lines_cached = linecache.getlines(filename)
+        self.assertEqual(lines, lines_cached)
+        linecache.clearcache()
+        empty = linecache.getlines(filename)
+        self.assertEqual(empty, [])
+
     def test_invalid_names(self):
         for name, desc in [
             ('\x00', 'NUL bytes filename'),
diff --git 
a/Misc/NEWS.d/next/Library/2025-03-23-18-39-07.gh-issue-60115.AWdcmq.rst 
b/Misc/NEWS.d/next/Library/2025-03-23-18-39-07.gh-issue-60115.AWdcmq.rst
new file mode 100644
index 00000000000000..6287e996ae1b01
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-03-23-18-39-07.gh-issue-60115.AWdcmq.rst
@@ -0,0 +1 @@
+Support frozen modules for :func:`linecache.getline`.

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to