-
-
Notifications
You must be signed in to change notification settings - Fork 31.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
bpo-35113: Fix inspect.getsource to return correct source for inner c…
…lasses (#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.
- Loading branch information
Showing
5 changed files
with
200 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,7 @@ | |
'Yury Selivanov <[email protected]>') | ||
|
||
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') | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 3 additions & 0 deletions
3
Misc/NEWS.d/next/Library/2018-11-03-16-18-20.bpo-35113.vwvWKG.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
:meth:`inspect.getsource` now returns correct source code for inner class | ||
with same name as module level class. Decorators are also returned as part | ||
of source of the class. Patch by Karthikeyan Singaravelan. |