CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
firstlinefinder.py105 linesDownload Raw Back to misc
1"""
2This module provides helper functions to find the first line of a function
3body.
4"""
5
6import ast
7import inspect
8import textwrap
9
10
11class FindDefFirstLine(ast.NodeVisitor):
12    """
13    Attributes
14    ----------
15    first_stmt_line : int or None
16        This stores the first statement line number if the definition is found.
17        Or, ``None`` if the definition is not found.
18    """
19
20    def __init__(self, name, firstlineno):
21        """
22        Parameters
23        ----------
24        code :
25            The function's code object.
26        """
27        self._co_name = name
28        self._co_firstlineno = firstlineno
29        self.first_stmt_line = None
30
31    def _visit_children(self, node):
32        for child in ast.iter_child_nodes(node):
33            super().visit(child)
34
35    def visit_FunctionDef(self, node: ast.FunctionDef):
36        if node.name == self._co_name:
37            # Name of function matches.
38
39            # The `def` line may match co_firstlineno.
40            possible_start_lines = set([node.lineno])
41            if node.decorator_list:
42                # Has decorators.
43                # The first decorator line may match co_firstlineno.
44                first_decor = node.decorator_list[0]
45                possible_start_lines.add(first_decor.lineno)
46            # Does the first lineno match?
47            if self._co_firstlineno in possible_start_lines:
48                # Yes, we found the function.
49                # So, use the first statement line as the first line.
50                if node.body:
51                    first_stmt = node.body[0]
52                    if _is_docstring(first_stmt):
53                        # Skip docstring
54                        first_stmt = node.body[1]
55                    self.first_stmt_line = first_stmt.lineno
56                    return
57                else:
58                    # This is probably unreachable.
59                    # Function body cannot be bare. It must at least have
60                    # A const string for docstring or a `pass`.
61                    pass
62        self._visit_children(node)
63
64
65def _is_docstring(node):
66    if isinstance(node, ast.Expr):
67        if (isinstance(node.value, ast.Constant)
68                and isinstance(node.value.value, str)):
69            return True
70    return False
71
72
73def get_func_body_first_lineno(pyfunc):
74    """
75    Look up the first line of function body using the file in
76    ``pyfunc.__code__.co_filename``.
77
78    Returns
79    -------
80    lineno : int; or None
81        The first line number of the function body; or ``None`` if the first
82        line cannot be determined.
83    """
84    co = pyfunc.__code__
85    try:
86        with open(co.co_filename) as fin:
87            source = fin.read()
88            offset = 0
89    except (FileNotFoundError, OSError):
90        try:
91            lines, offset = inspect.getsourcelines(pyfunc)
92            source = "".join(lines)
93            offset = offset - 1
94        except (OSError, TypeError):
95            return None
96
97    tree = ast.parse(textwrap.dedent(source))
98    finder = FindDefFirstLine(co.co_name, co.co_firstlineno - offset)
99    finder.visit(tree)
100    if finder.first_stmt_line:
101        return finder.first_stmt_line + offset
102    else:
103        # No first line found.
104        return None
105 
Aluode/PerceptionLabPortable · CoolFace