Skip to content

Commit 4f6b7c8

Browse files
committed
feat: add code understanding service
- Add CodeUnderstandingService using Python AST - Implement parse_python_code, extract_functions, extract_classes - Implement get_file_structure - Follow TDD principles - All tests passing Infrastructure layer and core services progressing
1 parent f5cd683 commit 4f6b7c8

3 files changed

Lines changed: 211 additions & 4 deletions

File tree

src/agently/core/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
"""
2-
Core Services Layer - Core Capabilities
3-
"""
1+
"""Core Services Layer - Core Capabilities"""
42

5-
__all__ = []
3+
from agently.core.code_understanding import CodeUnderstandingService
4+
5+
__all__ = ["CodeUnderstandingService"]
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"""Code understanding service using Python AST"""
2+
3+
import ast
4+
from pathlib import Path
5+
from typing import Any, List, Dict
6+
7+
from agently.logging import LoggingMixin
8+
9+
10+
class CodeUnderstandingService(LoggingMixin):
11+
"""Service for understanding code structure"""
12+
13+
def parse_python_code(self, file_path: Path) -> ast.AST:
14+
"""Parse Python code into AST
15+
16+
Args:
17+
file_path: Path to Python file
18+
19+
Returns:
20+
AST of the Python code
21+
22+
Raises:
23+
SyntaxError: If code has syntax errors
24+
"""
25+
self.logger.info("Parsing Python code", file_path=str(file_path))
26+
code = file_path.read_text()
27+
return ast.parse(code)
28+
29+
def extract_functions(self, file_path: Path) -> List[ast.FunctionDef]:
30+
"""Extract function definitions from code
31+
32+
Args:
33+
file_path: Path to Python file
34+
35+
Returns:
36+
List of function definitions
37+
"""
38+
self.logger.info("Extracting functions", file_path=str(file_path))
39+
tree = self.parse_python_code(file_path)
40+
functions = []
41+
42+
# First, get all top-level functions
43+
for node in tree.body:
44+
if isinstance(node, ast.FunctionDef):
45+
functions.append(node)
46+
elif isinstance(node, ast.ClassDef):
47+
# Get methods from classes
48+
for item in node.body:
49+
if isinstance(item, ast.FunctionDef):
50+
functions.append(item)
51+
52+
return functions
53+
54+
def extract_classes(self, file_path: Path) -> List[ast.ClassDef]:
55+
"""Extract class definitions from code
56+
57+
Args:
58+
file_path: Path to Python file
59+
60+
Returns:
61+
List of class definitions
62+
"""
63+
self.logger.info("Extracting classes", file_path=str(file_path))
64+
tree = self.parse_python_code(file_path)
65+
classes = []
66+
67+
for node in ast.walk(tree):
68+
if isinstance(node, ast.ClassDef):
69+
classes.append(node)
70+
71+
return classes
72+
73+
def get_file_structure(self, file_path: Path) -> Dict[str, Any]:
74+
"""Get complete structure of a Python file
75+
76+
Args:
77+
file_path: Path to Python file
78+
79+
Returns:
80+
Dictionary with classes, functions, imports
81+
"""
82+
self.logger.info("Getting file structure", file_path=str(file_path))
83+
tree = self.parse_python_code(file_path)
84+
85+
structure: Dict[str, Any] = {
86+
"classes": [],
87+
"functions": [],
88+
"imports": [],
89+
}
90+
91+
structure["functions"] = [f.name for f in self.extract_functions(file_path)]
92+
for node in ast.walk(tree):
93+
if isinstance(node, ast.ClassDef):
94+
structure["classes"].append(node.name)
95+
elif isinstance(node, (ast.Import, ast.ImportFrom)):
96+
if isinstance(node, ast.Import):
97+
for alias in node.names:
98+
structure["imports"].append(alias.name)
99+
elif isinstance(node, ast.ImportFrom):
100+
structure["imports"].append(node.module)
101+
102+
return structure
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
"""Tests for code understanding service"""
2+
3+
from pathlib import Path
4+
5+
import pytest
6+
7+
from agently.core.code_understanding import CodeUnderstandingService
8+
9+
10+
class TestCodeUnderstandingService:
11+
"""Test CodeUnderstandingService class"""
12+
13+
def test_parse_python_code(self, tmp_path: Path):
14+
"""Test parsing Python code"""
15+
# Setup
16+
code_file = tmp_path / "test.py"
17+
code = """
18+
def hello_world():
19+
print("Hello, World!")
20+
"""
21+
code_file.write_text(code)
22+
23+
# Test
24+
service = CodeUnderstandingService()
25+
ast = service.parse_python_code(code_file)
26+
27+
# Assert
28+
assert ast is not None
29+
assert hasattr(ast, "body")
30+
assert len(ast.body) > 0
31+
32+
def test_extract_functions(self, tmp_path: Path):
33+
"""Test extracting functions from code"""
34+
# Setup
35+
code_file = tmp_path / "test.py"
36+
code = """
37+
def function_one():
38+
pass
39+
40+
def function_two(arg1, arg2):
41+
return arg1 + arg2
42+
43+
class MyClass:
44+
def method_one(self):
45+
pass
46+
"""
47+
code_file.write_text(code)
48+
49+
# Test
50+
service = CodeUnderstandingService()
51+
functions = service.extract_functions(code_file)
52+
53+
# Assert
54+
assert len(functions) >= 3 # 2 functions + 1 class method
55+
function_names = [f.name for f in functions]
56+
assert "function_one" in function_names
57+
assert "function_two" in function_names
58+
assert "method_one" in function_names
59+
60+
def test_extract_classes(self, tmp_path: Path):
61+
"""Test extracting classes from code"""
62+
# Setup
63+
code_file = tmp_path / "test.py"
64+
code = """
65+
class ClassOne:
66+
pass
67+
68+
class ClassTwo:
69+
pass
70+
"""
71+
code_file.write_text(code)
72+
73+
# Test
74+
service = CodeUnderstandingService()
75+
classes = service.extract_classes(code_file)
76+
77+
# Assert
78+
assert len(classes) == 2
79+
class_names = [c.name for c in classes]
80+
assert "ClassOne" in class_names
81+
assert "ClassTwo" in class_names
82+
83+
def test_get_file_structure(self, tmp_path: Path):
84+
"""Test getting file structure"""
85+
# Setup
86+
code_file = tmp_path / "test.py"
87+
code = """
88+
class MyClass:
89+
def method_one(self):
90+
pass
91+
92+
def function_one():
93+
pass
94+
"""
95+
code_file.write_text(code)
96+
97+
# Test
98+
service = CodeUnderstandingService()
99+
structure = service.get_file_structure(code_file)
100+
101+
# Assert
102+
assert "classes" in structure
103+
assert "functions" in structure
104+
assert len(structure["classes"]) >= 1
105+
assert len(structure["functions"]) >= 1

0 commit comments

Comments
 (0)