|
32 | 32 | 'Yury Selivanov <yselivanov@sprymix.com>')
|
33 | 33 |
|
34 | 34 | import abc
|
| 35 | +import ast |
35 | 36 | import dis
|
36 | 37 | import collections.abc
|
37 | 38 | import enum
|
@@ -769,6 +770,42 @@ def getmodule(object, _filename=None):
|
769 | 770 | if builtinobject is object:
|
770 | 771 | return builtin
|
771 | 772 |
|
| 773 | + |
| 774 | +class ClassFoundException(Exception): |
| 775 | + pass |
| 776 | + |
| 777 | + |
| 778 | +class _ClassFinder(ast.NodeVisitor): |
| 779 | + |
| 780 | + def __init__(self, qualname): |
| 781 | + self.stack = [] |
| 782 | + self.qualname = qualname |
| 783 | + |
| 784 | + def visit_FunctionDef(self, node): |
| 785 | + self.stack.append(node.name) |
| 786 | + self.stack.append('<locals>') |
| 787 | + self.generic_visit(node) |
| 788 | + self.stack.pop() |
| 789 | + self.stack.pop() |
| 790 | + |
| 791 | + visit_AsyncFunctionDef = visit_FunctionDef |
| 792 | + |
| 793 | + def visit_ClassDef(self, node): |
| 794 | + self.stack.append(node.name) |
| 795 | + if self.qualname == '.'.join(self.stack): |
| 796 | + # Return the decorator for the class if present |
| 797 | + if node.decorator_list: |
| 798 | + line_number = node.decorator_list[0].lineno |
| 799 | + else: |
| 800 | + line_number = node.lineno |
| 801 | + |
| 802 | + # decrement by one since lines starts with indexing by zero |
| 803 | + line_number -= 1 |
| 804 | + raise ClassFoundException(line_number) |
| 805 | + self.generic_visit(node) |
| 806 | + self.stack.pop() |
| 807 | + |
| 808 | + |
772 | 809 | def findsource(object):
|
773 | 810 | """Return the entire source file and starting line number for an object.
|
774 | 811 |
|
@@ -801,47 +838,14 @@ def findsource(object):
|
801 | 838 | return lines, 0
|
802 | 839 |
|
803 | 840 | if isclass(object):
|
804 |
| - # Lazy import ast because it's relatively heavy and |
805 |
| - # it's not used for other than this part. |
806 |
| - import ast |
807 |
| - |
808 |
| - class ClassFinder(ast.NodeVisitor): |
809 |
| - |
810 |
| - def visit_FunctionDef(self, node): |
811 |
| - stack.append(node.name) |
812 |
| - stack.append('<locals>') |
813 |
| - self.generic_visit(node) |
814 |
| - stack.pop() |
815 |
| - stack.pop() |
816 |
| - |
817 |
| - visit_AsyncFunctionDef = visit_FunctionDef |
818 |
| - |
819 |
| - def visit_ClassDef(self, node): |
820 |
| - nonlocal line_number |
821 |
| - stack.append(node.name) |
822 |
| - if qualname == '.'.join(stack): |
823 |
| - # Return the decorator for the class if present |
824 |
| - if node.decorator_list: |
825 |
| - line_number = node.decorator_list[0].lineno |
826 |
| - else: |
827 |
| - line_number = node.lineno |
828 |
| - |
829 |
| - # decrement by one since lines starts with indexing by zero |
830 |
| - line_number -= 1 |
831 |
| - raise StopIteration(line_number) |
832 |
| - self.generic_visit(node) |
833 |
| - stack.pop() |
834 |
| - |
835 |
| - stack = [] |
836 |
| - line_number = None |
837 | 841 | qualname = object.__qualname__
|
838 | 842 | source = ''.join(lines)
|
839 | 843 | tree = ast.parse(source)
|
840 |
| - class_finder = ClassFinder() |
| 844 | + class_finder = _ClassFinder(qualname) |
841 | 845 | try:
|
842 | 846 | class_finder.visit(tree)
|
843 |
| - except StopIteration as e: |
844 |
| - line_number = e.value |
| 847 | + except ClassFoundException as e: |
| 848 | + line_number = e.args[0] |
845 | 849 | return lines, line_number
|
846 | 850 | else:
|
847 | 851 | raise OSError('could not find class definition')
|
|
0 commit comments