Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 28 additions & 12 deletions Doc/library/inspect.rst
Original file line number Diff line number Diff line change
Expand Up @@ -750,10 +750,14 @@ Retrieving source code
.. function:: getcomments(object)

Return in a single string any lines of comments immediately preceding the
object's source code (for a class, function, or method), or at the top of the
Python source file (if the object is a module). If the object's source code
is unavailable, return ``None``. This could happen if the object has been
defined in C or the interactive shell.
object's source code (for a class, function, method, or type alias), or at the
top of the Python source file (if the object is a module). If the object's
source code is unavailable, return ``None``. This could happen if the object
has been defined in C or in an interactive shell that does not retain source
code.

.. versionchanged:: next
Added support for type aliases created with the :keyword:`type` statement.


.. function:: getfile(object)
Expand All @@ -763,6 +767,9 @@ Retrieving source code
This will fail with a :exc:`TypeError` if the object is a built-in module,
class, or function.

.. versionchanged:: next
Added support for :class:`~typing.TypeAliasType` objects.


.. function:: getmodule(object)

Expand All @@ -778,36 +785,45 @@ Retrieving source code
This will fail with a :exc:`TypeError` if the object is a built-in module,
class, or function.

.. versionchanged:: next
Added support for :class:`~typing.TypeAliasType` objects.


.. function:: getsourcelines(object)

Return a list of source lines and starting line number for an object. The
argument may be a module, class, method, function, traceback, frame, or code
object. The source code is returned as a list of the lines corresponding to the
object and the line number indicates where in the original source file the first
line of code was found. An :exc:`OSError` is raised if the source code cannot
be retrieved.
argument may be a module, class, method, function, traceback, frame, code
object, or type alias. The source code is returned as a list of the lines
corresponding to the object and the line number indicates where in the
original source file the first line of code was found. An :exc:`OSError` is
raised if the source code cannot be retrieved.
A :exc:`TypeError` is raised if the object is a built-in module, class, or
function.

.. versionchanged:: 3.3
:exc:`OSError` is raised instead of :exc:`IOError`, now an alias of the
former.

.. versionchanged:: next
Added support for type aliases created with the :keyword:`type` statement.


.. function:: getsource(object)

Return the text of the source code for an object. The argument may be a module,
class, method, function, traceback, frame, or code object. The source code is
returned as a single string. An :exc:`OSError` is raised if the source code
cannot be retrieved.
class, method, function, traceback, frame, code object, or type alias. The
source code is returned as a single string. An :exc:`OSError` is raised if the
source code cannot be retrieved.
A :exc:`TypeError` is raised if the object is a built-in module, class, or
function.

.. versionchanged:: 3.3
:exc:`OSError` is raised instead of :exc:`IOError`, now an alias of the
former.

.. versionchanged:: next
Added support for type aliases created with the :keyword:`type` statement.


.. function:: cleandoc(doc, *, dedent=True)

Expand Down
30 changes: 23 additions & 7 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@
from keyword import iskeyword
from operator import attrgetter
from collections import namedtuple, OrderedDict
from _typing import TypeAliasType
from _weakref import ref as make_weakref

# Create constants for the compiler flags in Include/cpython/code.h
Expand Down Expand Up @@ -872,8 +873,18 @@ def getfile(object):
object = object.f_code
if iscode(object):
return object.co_filename
raise TypeError('module, class, method, function, traceback, frame, or '
'code object was expected, got {}'.format(
if isinstance(object, TypeAliasType):
evaluator = object.evaluate_value
if isfunction(evaluator):
return getfile(evaluator)
module = sys.modules.get(object.__module__)
if getattr(module, '__file__', None):
return module.__file__
if object.__module__ == '__main__':
raise OSError('source code not available')
raise TypeError(f'module not available for {object!r}')
raise TypeError('module, class, method, function, traceback, frame, '
'code object, or type alias was expected, got {}'.format(
type(object).__name__))

def getmodulename(path):
Expand Down Expand Up @@ -984,9 +995,14 @@ def findsource(object):
"""Return the entire source file and starting line number for an object.

The argument may be a module, class, method, function, traceback, frame,
or code object. The source code is returned as a list of all the lines
in the file and the line number indexes a line in that list. An OSError
is raised if the source code cannot be retrieved."""
code object, or type alias. The source code is returned as a list of all
the lines in the file and the line number indexes a line in that list.
An OSError is raised if the source code cannot be retrieved."""

if isinstance(object, TypeAliasType):
evaluator = object.evaluate_value
if isfunction(evaluator):
object = evaluator

file = getsourcefile(object)
if file:
Expand Down Expand Up @@ -1168,7 +1184,7 @@ def getsourcelines(object):
"""Return a list of source lines and starting line number for an object.

The argument may be a module, class, method, function, traceback, frame,
or code object. The source code is returned as a list of the lines
code object, or type alias. The source code is returned as a list of the lines
corresponding to the object and the line number indicates where in the
original source file the first line of code was found. An OSError is
raised if the source code cannot be retrieved."""
Expand All @@ -1189,7 +1205,7 @@ def getsource(object):
"""Return the text of the source code for an object.

The argument may be a module, class, method, function, traceback, frame,
or code object. The source code is returned as a single string. An
code object, or type alias. The source code is returned as a single string. An
OSError is raised if the source code cannot be retrieved."""
lines, lnum = getsourcelines(object)
return ''.join(lines)
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_inspect/inspect_fodder.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,6 @@ async def asyncf(self):
# a closing parenthesis with the opening paren being in another line
(
); after_closing = lambda: 1

# What is their airspeed?
type Sparrow = African | European
17 changes: 17 additions & 0 deletions Lib/test/test_inspect/inspect_fodder2.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,4 +400,21 @@ def func394():
def func400():
return 401

# A multiline generic alias.
type GenericAlias[
T,
] = (
list[T]
| tuple[T, ...]
)

class TypeAliases:
# A nested alias.
type Nested = MissingName

def make_type_alias():
# A local alias.
type Local = MissingName
return Local

pass # end of file
68 changes: 68 additions & 0 deletions Lib/test/test_inspect/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import subprocess
import time
import types
import typing
import tempfile
import textwrap
import unicodedata
Expand Down Expand Up @@ -812,6 +813,7 @@ def test_getcomments(self):
self.assertEqual(inspect.getcomments(mod), '# line 1\n')
self.assertEqual(inspect.getcomments(mod.StupidGit), '# line 20\n')
self.assertEqual(inspect.getcomments(mod2.cls160), '# line 159\n')
self.assertEqual(inspect.getcomments(mod.Sparrow), '# What is their airspeed?\n')
# If the object source file is not available, return None.
co = compile('x=1', '_non_existing_filename.py', 'exec')
self.assertIsNone(inspect.getcomments(co))
Expand Down Expand Up @@ -851,6 +853,7 @@ def test_getsource(self):
self.assertSourceEqual(mod.StupidGit, 21, 51)
self.assertSourceEqual(mod.lobbest, 75, 76)
self.assertSourceEqual(mod.after_closing, 120, 120)
self.assertSourceEqual(mod.Sparrow, 123, 123)

def test_getsourcefile(self):
self.assertEqual(normcase(inspect.getsourcefile(mod.spam)), modfile)
Expand All @@ -864,6 +867,31 @@ def test_getsourcefile(self):
finally:
del linecache.cache[co.co_filename]

def test_getsourcefile_type_alias(self):
self.assertEqual(normcase(inspect.getsourcefile(mod.Sparrow)),
normcase(mod.__file__))
self.assertEqual(normcase(inspect.getfile(mod.Sparrow)),
normcase(mod.__file__))

WrongModule = typing.TypeAliasType("WrongModule", int)
WrongModule.__module__ = "types"
self.assertEqual(normcase(inspect.getsourcefile(WrongModule)), normcase(types.__file__))
self.assertEqual(normcase(inspect.getfile(WrongModule)), normcase(types.__file__))

WrongModule.__module__ = "non-existing module"
with self.assertRaises(TypeError):
inspect.getsourcefile(WrongModule)
with self.assertRaises(TypeError):
inspect.getfile(WrongModule)

def test_getsource_on_generated_type_alias(self):
Alias = typing.TypeAliasType("Alias", int)
self.assertEqual(inspect.getsourcefile(Alias), __file__)
self.assertEqual(inspect.getfile(Alias), __file__)
self.assertRaises(OSError, inspect.getsource, Alias)
self.assertRaises(OSError, inspect.getsourcelines, Alias)
self.assertIsNone(inspect.getcomments(Alias))

def test_getsource_empty_file(self):
with temp_cwd() as cwd:
with open('empty_file.py', 'w'):
Expand Down Expand Up @@ -1135,6 +1163,20 @@ def test_class_async_method(self):
class TestBuggyCases(GetSourceBase):
fodderModule = mod2

def test_type_aliases(self):
cases = (
(mod2.GenericAlias, 404, 409, '# A multiline generic alias.\n'),
(mod2.TypeAliases.Nested, 413, 413, '# A nested alias.\n'),
(mod2.make_type_alias(), 417, 417, '# A local alias.\n'),
)
for alias, start, end, comments in cases:
with self.subTest(alias=alias):
self.assertSourceEqual(alias, start, end)
self.assertEqual(inspect.getsourcelines(alias),
(self.sourcerange(start, end).splitlines(True),
start))
self.assertEqual(inspect.getcomments(alias), comments)

def test_with_comment(self):
self.assertSourceEqual(mod2.with_comment, 58, 59)

Expand Down Expand Up @@ -6920,6 +6962,32 @@ def f():
expected = "The source is: <<<def f():\n print(0)\n return 1 + 2\n>>>"
self.assertIn(expected, output)

@unittest.skipIf(not has_subprocess_support, "test requires subprocess")
def test_getsource_type_alias(self):
output = self.run_on_interactive_mode(textwrap.dedent("""\
type Alias = MissingName
import inspect
print(f"The source is: <<<{inspect.getsource(Alias)}>>>")
print(f"The lines are: {inspect.getsourcelines(Alias)!r}")
"""))

self.assertIn("The source is: <<<type Alias = MissingName\n>>>", output)
self.assertIn("The lines are: (['type Alias = MissingName\\n'], 1)", output)

@unittest.skipIf(not has_subprocess_support, "test requires subprocess")
def test_getcomments_type_alias(self):
output = self.run_on_interactive_mode(textwrap.dedent("""\
def f():
# A local alias.
type Alias = MissingName
return Alias

import inspect
print(f"The comments are: <<<{inspect.getcomments(f())}>>>")
"""))

self.assertIn("The comments are: <<<# A local alias.\n>>>", output)


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Support type alias objects in :func:`inspect.getcomments`,
:func:`inspect.getfile`, :func:`inspect.getsourcefile`,
:func:`inspect.getsourcelines`, and :func:`inspect.getsource`.
Loading