Spaces:
Sleeping
Sleeping
| import ast | |
| from pathlib import Path | |
| from app.models.code import ( | |
| ImportInfo, | |
| FunctionInfo, | |
| ClassInfo, | |
| PythonFileInfo, | |
| CallInfo, | |
| ) | |
| class FunctionCallVisitor(ast.NodeVisitor): | |
| def __init__(self): | |
| self.calls = [] | |
| def visit_Call(self, node: ast.Call): | |
| name = self._get_call_name(node.func) | |
| if name is not None: | |
| self.calls.append( | |
| CallInfo( | |
| name=name, | |
| line=node.lineno, | |
| ) | |
| ) | |
| self.generic_visit(node) | |
| def _get_call_name(self, node): | |
| if isinstance(node, ast.Name): | |
| return node.id | |
| if isinstance(node, ast.Attribute): | |
| parts = [] | |
| while isinstance(node, ast.Attribute): | |
| parts.append(node.attr) | |
| node = node.value | |
| if isinstance(node, ast.Name): | |
| parts.append(node.id) | |
| return ".".join(reversed(parts)) | |
| return None | |
| class ParserService: | |
| def _module_name(self, file_path: Path): | |
| parts = list(file_path.parts) | |
| if "app" in parts: | |
| index = parts.index("app") | |
| parts = parts[index:] | |
| parts[-1] = parts[-1].replace(".py", "") | |
| return ".".join(parts) | |
| def parse_file(self, file_path: Path) -> PythonFileInfo: | |
| module_name = self._module_name(file_path) | |
| source = file_path.read_text( | |
| encoding="utf-8", | |
| errors="ignore", | |
| ) | |
| lines = source.splitlines() | |
| tree = ast.parse(source) | |
| imports = [] | |
| classes = [] | |
| functions = [] | |
| for node in tree.body: | |
| if isinstance(node, ast.Import): | |
| for alias in node.names: | |
| imports.append( | |
| ImportInfo( | |
| module=alias.name, | |
| name=alias.name, | |
| alias=alias.asname, | |
| ) | |
| ) | |
| elif isinstance(node, ast.ImportFrom): | |
| for alias in node.names: | |
| imports.append( | |
| ImportInfo( | |
| module=node.module, | |
| name=alias.name, | |
| alias=alias.asname, | |
| ) | |
| ) | |
| elif isinstance(node, ast.FunctionDef): | |
| functions.append( | |
| self._parse_function( | |
| node, | |
| module_name, | |
| lines=lines, | |
| ) | |
| ) | |
| elif isinstance(node, ast.ClassDef): | |
| methods = [] | |
| for child in node.body: | |
| if isinstance(child, ast.FunctionDef): | |
| methods.append( | |
| self._parse_function( | |
| node=child, | |
| module_name=module_name, | |
| lines=lines, | |
| class_name=node.name, | |
| ) | |
| ) | |
| class_source = self._get_source_code( | |
| node, | |
| lines, | |
| ) | |
| classes.append( | |
| ClassInfo( | |
| name=node.name, | |
| qualified_name=f"{module_name}.{node.name}", | |
| line=node.lineno, | |
| end_line=node.end_lineno, | |
| source_code=class_source, | |
| methods=methods, | |
| ) | |
| ) | |
| return PythonFileInfo( | |
| path=str(file_path), | |
| imports=imports, | |
| classes=classes, | |
| functions=functions, | |
| ) | |
| def _get_source_code( | |
| self, | |
| node, | |
| lines, | |
| ) -> str: | |
| return "\n".join( | |
| lines[node.lineno - 1 : node.end_lineno] | |
| ) | |
| def _parse_function( | |
| self, | |
| node, | |
| module_name, | |
| lines, | |
| class_name=None, | |
| ): | |
| visitor = FunctionCallVisitor() | |
| visitor.visit(node) | |
| if class_name: | |
| qualified = f"{module_name}.{class_name}.{node.name}" | |
| else: | |
| qualified = f"{module_name}.{node.name}" | |
| print("module_name:", repr(module_name)) | |
| print("class_name:", repr(class_name)) | |
| print("node.name:", repr(node.name)) | |
| print("qualified:", repr(qualified)) | |
| source_code = self._get_source_code( | |
| node, | |
| lines, | |
| ) | |
| return FunctionInfo( | |
| name=node.name, | |
| qualified_name=qualified, | |
| line=node.lineno, | |
| end_line=node.end_lineno, | |
| docstring=ast.get_docstring(node), | |
| source_code=source_code, | |
| calls=visitor.calls, | |
| ) |