|
|
""" |
|
|
This module provides functionality for caching and looking up fully qualified function |
|
|
and class names from Python source files by line number. |
|
|
|
|
|
It uses Python's tokenize module to parse source files and tracks function/class |
|
|
definitions along with their nesting to build fully qualified names (e.g. 'class.method' |
|
|
or 'module.function'). The results are cached in a two-level dictionary mapping: |
|
|
|
|
|
filename -> (line_number -> fully_qualified_name) |
|
|
|
|
|
Example usage: |
|
|
name = get_funcname("myfile.py", 42) # Returns name of function/class at line 42 |
|
|
clearcache() # Clear the cache if file contents have changed |
|
|
|
|
|
The parsing is done lazily when a file is first accessed. Invalid Python files or |
|
|
IO errors are handled gracefully by returning empty cache entries. |
|
|
""" |
|
|
|
|
|
import tokenize |
|
|
from typing import Optional |
|
|
|
|
|
|
|
|
cache: dict[str, dict[int, str]] = {} |
|
|
|
|
|
|
|
|
def clearcache() -> None: |
|
|
cache.clear() |
|
|
|
|
|
|
|
|
def _add_file(filename: str) -> None: |
|
|
try: |
|
|
with tokenize.open(filename) as f: |
|
|
tokens = list(tokenize.generate_tokens(f.readline)) |
|
|
except (OSError, tokenize.TokenError): |
|
|
cache[filename] = {} |
|
|
return |
|
|
|
|
|
|
|
|
|
|
|
result: dict[int, str] = {} |
|
|
|
|
|
cur_name = "" |
|
|
cur_indent = 0 |
|
|
significant_indents: list[int] = [] |
|
|
|
|
|
for i, token in enumerate(tokens): |
|
|
if token.type == tokenize.INDENT: |
|
|
cur_indent += 1 |
|
|
elif token.type == tokenize.DEDENT: |
|
|
cur_indent -= 1 |
|
|
|
|
|
if significant_indents and cur_indent == significant_indents[-1]: |
|
|
significant_indents.pop() |
|
|
|
|
|
cur_name = cur_name.rpartition(".")[0] |
|
|
elif ( |
|
|
token.type == tokenize.NAME |
|
|
and i + 1 < len(tokens) |
|
|
and tokens[i + 1].type == tokenize.NAME |
|
|
and (token.string == "class" or token.string == "def") |
|
|
): |
|
|
|
|
|
significant_indents.append(cur_indent) |
|
|
if cur_name: |
|
|
cur_name += "." |
|
|
cur_name += tokens[i + 1].string |
|
|
result[token.start[0]] = cur_name |
|
|
|
|
|
cache[filename] = result |
|
|
|
|
|
|
|
|
def get_funcname(filename: str, lineno: int) -> Optional[str]: |
|
|
if filename not in cache: |
|
|
_add_file(filename) |
|
|
return cache[filename].get(lineno, None) |
|
|
|