summaryrefslogtreecommitdiffstats
path: root/Lib/inspect.py
diff options
context:
space:
mode:
authorKarthikeyan Singaravelan <tir.karthi@gmail.com>2020-04-18 16:19:32 (GMT)
committerGitHub <noreply@github.com>2020-04-18 16:19:32 (GMT)
commit696136b993e11b37c4f34d729a0375e5ad544ade (patch)
tree19b1085d34db9d665b8f131e3f99bd0c2a40fcb6 /Lib/inspect.py
parentce578831a4e573eac422a488930100bc5380f227 (diff)
downloadcpython-696136b993e11b37c4f34d729a0375e5ad544ade.zip
cpython-696136b993e11b37c4f34d729a0375e5ad544ade.tar.gz
cpython-696136b993e11b37c4f34d729a0375e5ad544ade.tar.bz2
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.
Diffstat (limited to 'Lib/inspect.py')
-rw-r--r--Lib/inspect.py65
1 files changed, 46 insertions, 19 deletions
diff --git a/Lib/inspect.py b/Lib/inspect.py
index 6f7d5cd..ad7e8cb 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -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')