mirror of
https://github.com/python/cpython.git
synced 2025-08-22 09:45:06 +00:00
bpo-35113: Fix inspect.getsource to return correct source for inner classes (#10307)
* Use ast module to find class definition * Add NEWS entry * Fix class with multiple children and move decorator code to the method * Fix PR comments 1. Use node.decorator_list to select decorators 2. Remove unwanted variables in ClassVisitor 3. Simplify stack management as per review * Add test for nested functions and async calls * Fix pydoc test since comments are returned now correctly * Set event loop policy as None to fix environment related change * Refactor visit_AsyncFunctionDef and tests * Refactor to use local variables and fix tests * Add patch attribution * Use self.addCleanup for asyncio * Rename ClassVisitor to ClassFinder and fix asyncio cleanup * Return first class inside conditional in case of multiple definitions. Remove decorator for class source. * Add docstring to make the test correct * Modify NEWS entry regarding decorators * Return decorators too for bpo-15856 * Move ast and the class source code to top. Use proper Exception.
This commit is contained in:
parent
ce578831a4
commit
696136b993
5 changed files with 200 additions and 23 deletions
|
@ -32,6 +32,7 @@ __author__ = ('Ka-Ping Yee <ping@lfw.org>',
|
|||
'Yury Selivanov <yselivanov@sprymix.com>')
|
||||
|
||||
import abc
|
||||
import ast
|
||||
import dis
|
||||
import collections.abc
|
||||
import enum
|
||||
|
@ -770,6 +771,42 @@ def getmodule(object, _filename=None):
|
|||
if builtinobject is object:
|
||||
return builtin
|
||||
|
||||
|
||||
class ClassFoundException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class _ClassFinder(ast.NodeVisitor):
|
||||
|
||||
def __init__(self, qualname):
|
||||
self.stack = []
|
||||
self.qualname = qualname
|
||||
|
||||
def visit_FunctionDef(self, node):
|
||||
self.stack.append(node.name)
|
||||
self.stack.append('<locals>')
|
||||
self.generic_visit(node)
|
||||
self.stack.pop()
|
||||
self.stack.pop()
|
||||
|
||||
visit_AsyncFunctionDef = visit_FunctionDef
|
||||
|
||||
def visit_ClassDef(self, node):
|
||||
self.stack.append(node.name)
|
||||
if self.qualname == '.'.join(self.stack):
|
||||
# Return the decorator for the class if present
|
||||
if node.decorator_list:
|
||||
line_number = node.decorator_list[0].lineno
|
||||
else:
|
||||
line_number = node.lineno
|
||||
|
||||
# decrement by one since lines starts with indexing by zero
|
||||
line_number -= 1
|
||||
raise ClassFoundException(line_number)
|
||||
self.generic_visit(node)
|
||||
self.stack.pop()
|
||||
|
||||
|
||||
def findsource(object):
|
||||
"""Return the entire source file and starting line number for an object.
|
||||
|
||||
|
@ -802,25 +839,15 @@ def findsource(object):
|
|||
return lines, 0
|
||||
|
||||
if isclass(object):
|
||||
name = object.__name__
|
||||
pat = re.compile(r'^(\s*)class\s*' + name + r'\b')
|
||||
# make some effort to find the best matching class definition:
|
||||
# use the one with the least indentation, which is the one
|
||||
# that's most probably not inside a function definition.
|
||||
candidates = []
|
||||
for i in range(len(lines)):
|
||||
match = pat.match(lines[i])
|
||||
if match:
|
||||
# if it's at toplevel, it's already the best one
|
||||
if lines[i][0] == 'c':
|
||||
return lines, i
|
||||
# else add whitespace to candidate list
|
||||
candidates.append((match.group(1), i))
|
||||
if candidates:
|
||||
# this will sort by whitespace, and by line number,
|
||||
# less whitespace first
|
||||
candidates.sort()
|
||||
return lines, candidates[0][1]
|
||||
qualname = object.__qualname__
|
||||
source = ''.join(lines)
|
||||
tree = ast.parse(source)
|
||||
class_finder = _ClassFinder(qualname)
|
||||
try:
|
||||
class_finder.visit(tree)
|
||||
except ClassFoundException as e:
|
||||
line_number = e.args[0]
|
||||
return lines, line_number
|
||||
else:
|
||||
raise OSError('could not find class definition')
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue