Skip to content

Commit 0a90562

Browse files
committed
gh-117500: Add a new route in linecache to fetch interactive source code
Signed-off-by: Pablo Galindo <pablogsal@gmail.com>
1 parent 56eda25 commit 0a90562

File tree

5 files changed

+48
-14
lines changed

5 files changed

+48
-14
lines changed

Lib/inspect.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -968,6 +968,8 @@ def findsource(object):
968968
module = getmodule(object, file)
969969
if module:
970970
lines = linecache.getlines(file, module.__dict__)
971+
if not lines and file.startswith('<') and hasattr(object, "__code__"):
972+
lines = linecache._getlines_from_code(object.__code__, module.__dict__)
971973
else:
972974
lines = linecache.getlines(file)
973975
if not lines:

Lib/linecache.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
# The cache. Maps filenames to either a thunk which will provide source code,
1212
# or a tuple (size, mtime, lines, fullname) once loaded.
1313
cache = {}
14+
_interactive_cache = {}
1415

1516

1617
def clearcache():
@@ -44,6 +45,21 @@ def getlines(filename, module_globals=None):
4445
return []
4546

4647

48+
def _getline_from_code(filename, lineno, module_globals=None):
49+
lines = _getlines_from_code(filename, module_globals)
50+
if 1 <= lineno <= len(lines):
51+
return lines[lineno - 1]
52+
return ''
53+
54+
55+
def _getlines_from_code(code, module_globals=None):
56+
if code in _interactive_cache:
57+
entry = _interactive_cache[code]
58+
if len(entry) != 1:
59+
return _interactive_cache[code][2]
60+
return []
61+
62+
4763
def checkcache(filename=None):
4864
"""Discard cache entries that are out of date.
4965
(This is not checked upon each call!)"""
@@ -196,8 +212,14 @@ def get_lines(name=name, *args, **kwargs):
196212

197213

198214
def _register_code(code, string, name):
199-
cache[code] = (
200-
len(string),
201-
None,
202-
[line + '\n' for line in string.splitlines()],
203-
name)
215+
entry = (len(string),
216+
None,
217+
[line + '\n' for line in string.splitlines()],
218+
name)
219+
stack = [code]
220+
while stack:
221+
code = stack.pop()
222+
for const in code.co_consts:
223+
if isinstance(const, type(code)):
224+
stack.append(const)
225+
_interactive_cache[code] = entry

Lib/test/test_io.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4558,11 +4558,11 @@ def test_check_encoding_warning(self):
45584558
''')
45594559
proc = assert_python_ok('-X', 'warn_default_encoding', '-c', code)
45604560
warnings = proc.err.splitlines()
4561-
self.assertEqual(len(warnings), 4)
4561+
self.assertEqual(len(warnings), 2)
45624562
self.assertTrue(
45634563
warnings[0].startswith(b"<string>:5: EncodingWarning: "))
45644564
self.assertTrue(
4565-
warnings[2].startswith(b"<string>:8: EncodingWarning: "))
4565+
warnings[1].startswith(b"<string>:8: EncodingWarning: "))
45664566

45674567
def test_text_encoding(self):
45684568
# PEP 597, bpo-47000. io.text_encoding() returns "locale" or "utf-8"

Lib/traceback.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -288,11 +288,11 @@ class FrameSummary:
288288
"""
289289

290290
__slots__ = ('filename', 'lineno', 'end_lineno', 'colno', 'end_colno',
291-
'name', '_lines', '_lines_dedented', 'locals')
291+
'name', '_lines', '_lines_dedented', 'locals', '_frame')
292292

293293
def __init__(self, filename, lineno, name, *, lookup_line=True,
294294
locals=None, line=None,
295-
end_lineno=None, colno=None, end_colno=None):
295+
end_lineno=None, colno=None, end_colno=None, **kwargs):
296296
"""Construct a FrameSummary.
297297
298298
:param lookup_line: If True, `linecache` is consulted for the source
@@ -308,6 +308,7 @@ def __init__(self, filename, lineno, name, *, lookup_line=True,
308308
self.colno = colno
309309
self.end_colno = end_colno
310310
self.name = name
311+
self._frame = kwargs.get("_frame")
311312
self._lines = line
312313
self._lines_dedented = None
313314
if lookup_line:
@@ -347,7 +348,12 @@ def _set_lines(self):
347348
lines = []
348349
for lineno in range(self.lineno, self.end_lineno + 1):
349350
# treat errors (empty string) and empty lines (newline) as the same
350-
lines.append(linecache.getline(self.filename, lineno).rstrip())
351+
line = None
352+
if self._frame is not None and self.filename.startswith("<"):
353+
line = linecache._getline_from_code(self._frame.f_code, lineno, self._frame.f_globals).rstrip()
354+
if line is None:
355+
line = linecache.getline(self.filename, lineno).rstrip()
356+
lines.append(line)
351357
self._lines = "\n".join(lines) + "\n"
352358

353359
@property
@@ -480,9 +486,13 @@ def _extract_from_extended_frame_gen(klass, frame_gen, *, limit=None,
480486
f_locals = f.f_locals
481487
else:
482488
f_locals = None
483-
result.append(FrameSummary(
484-
filename, lineno, name, lookup_line=False, locals=f_locals,
485-
end_lineno=end_lineno, colno=colno, end_colno=end_colno))
489+
result.append(
490+
FrameSummary(filename, lineno, name,
491+
lookup_line=False, locals=f_locals,
492+
end_lineno=end_lineno, colno=colno, end_colno=end_colno,
493+
_frame=f,
494+
)
495+
)
486496
for filename in fnames:
487497
linecache.checkcache(filename)
488498

Python/pythonrun.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1383,7 +1383,7 @@ run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,
13831383

13841384
PyObject* result = PyObject_CallFunction(
13851385
print_tb_func, "OOO",
1386-
interactive_filename,
1386+
co,
13871387
interactive_src,
13881388
filename
13891389
);

0 commit comments

Comments
 (0)