Skip to content

Commit 139a982

Browse files
committed
added the chat endpoint
1 parent 22d5a46 commit 139a982

File tree

12 files changed

+482
-153
lines changed

12 files changed

+482
-153
lines changed

code_graph/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .info import *
2+
from .llm import ask
23
from .graph import *
34
from .project import *
45
from .entities import *

code_graph/analyzers/python/analyzer.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def process_class_definition(self, node: Node, path: Path) -> tuple[Class, list[
7575

7676
return (c, inherited_classes)
7777

78-
def process_function_definition(self, node: Node, path: Path) -> Function:
78+
def process_function_definition(self, node: Node, path: Path, source_code: str) -> Function:
7979
"""
8080
Processes a function definition node from the syntax tree and extracts relevant information.
8181
@@ -154,7 +154,8 @@ def process_function_definition(self, node: Node, path: Path) -> Function:
154154
ret_type = return_type.text.decode('utf-8') if return_type else None
155155

156156
# Create Function object
157-
f = Function(str(path), function_name, docstring, ret_type, '', start_line, end_line)
157+
src = source_code[node.start_byte:node.end_byte]
158+
f = Function(str(path), function_name, docstring, ret_type, src, start_line, end_line)
158159

159160
# Add arguments to Function object
160161
for arg in args:
@@ -163,7 +164,7 @@ def process_function_definition(self, node: Node, path: Path) -> Function:
163164
return f
164165

165166
def first_pass_traverse(self, parent: Union[File,Class,Function], node: Node,
166-
path: Path, graph: Graph) -> None:
167+
path: Path, graph: Graph, source_code: str) -> None:
167168
"""
168169
Recursively traverses a syntax tree node, processes class and function definitions,
169170
and connects them in a graph representation.
@@ -198,7 +199,7 @@ def first_pass_traverse(self, parent: Union[File,Class,Function], node: Node,
198199
graph.add_class(entity)
199200

200201
elif node.type == "function_definition":
201-
entity = self.process_function_definition(node, path)
202+
entity = self.process_function_definition(node, path, source_code)
202203
# Add Function object to the graph
203204
graph.add_function(entity)
204205

@@ -209,7 +210,7 @@ def first_pass_traverse(self, parent: Union[File,Class,Function], node: Node,
209210

210211
# Recursivly visit child nodes
211212
for child in node.children:
212-
self.first_pass_traverse(parent, child, path, graph)
213+
self.first_pass_traverse(parent, child, path, graph, source_code)
213214

214215
def first_pass(self, path: Path, f: io.TextIOWrapper, graph:Graph) -> None:
215216
"""
@@ -231,11 +232,16 @@ def first_pass(self, path: Path, f: io.TextIOWrapper, graph:Graph) -> None:
231232
graph.add_file(file)
232233

233234
# Parse file
234-
content = f.read()
235-
tree = self.parser.parse(content)
235+
source_code = f.read()
236+
tree = self.parser.parse(source_code)
237+
try:
238+
source_code = source_code.decode('utf-8')
239+
except Exception as e:
240+
logger.error(f"Failed decoding source code: {e}")
241+
source_code = ''
236242

237243
# Walk thought the AST
238-
self.first_pass_traverse(file, tree.root_node, path, graph)
244+
self.first_pass_traverse(file, tree.root_node, path, graph, source_code)
239245

240246
def process_function_call(self, node) -> Optional[str]:
241247
"""
@@ -324,7 +330,7 @@ def process_inheritance(self, cls: Class, super_classes: list[str],
324330
graph.connect_entities('INHERITS', cls.id, _super_class.id)
325331

326332
def second_pass_traverse(self, parent: Union[File, Class, Function],
327-
node: Node, path: Path, graph: Graph) -> None:
333+
node: Node, path: Path, graph: Graph, source_code: str) -> None:
328334
"""
329335
Traverse the AST nodes during the second pass and process each node accordingly.
330336
@@ -341,7 +347,9 @@ def second_pass_traverse(self, parent: Union[File, Class, Function],
341347
parent = cls
342348

343349
elif node.type == "function_definition":
344-
func = self.process_function_definition(node, path)
350+
# TODO: simply extract function name, no need to parse entire function
351+
# see C analyzer
352+
func = self.process_function_definition(node, path, source_code)
345353
parent = graph.get_function_by_name(func.name)
346354
elif node.type == "call":
347355
callee = self.process_function_call(node)
@@ -350,7 +358,7 @@ def second_pass_traverse(self, parent: Union[File, Class, Function],
350358

351359
# Recursivly visit child nodes
352360
for child in node.children:
353-
self.second_pass_traverse(parent, child, path, graph)
361+
self.second_pass_traverse(parent, child, path, graph, source_code)
354362

355363
def second_pass(self, path: Path, f: io.TextIOWrapper, graph: Graph) -> None:
356364
"""
@@ -375,10 +383,10 @@ def second_pass(self, path: Path, f: io.TextIOWrapper, graph: Graph) -> None:
375383

376384
try:
377385
# Parse file
378-
content = f.read()
379-
tree = self.parser.parse(content)
386+
source_code = f.read()
387+
tree = self.parser.parse(source_code)
380388

381389
# Walk thought the AST
382-
self.second_pass_traverse(file, tree.root_node, path, graph)
390+
self.second_pass_traverse(file, tree.root_node, path, graph, source_code)
383391
except Exception as e:
384392
logger.error(f"Failed to process file {path}: {e}")

code_graph/analyzers/source_analyzer.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
from .python.analyzer import PythonAnalyzer
1212

1313
import logging
14-
logger = logging.getLogger('code_graph')
14+
# Configure logging
15+
logging.basicConfig(level=logging.DEBUG, format='%(filename)s - %(asctime)s - %(levelname)s - %(message)s')
1516

1617
# List of available analyzers
1718
analyzers = {'.c': CAnalyzer(),
@@ -41,11 +42,11 @@ def first_pass(self, ignore: List[str], executor: concurrent.futures.Executor) -
4142
if dirpath in ignore:
4243
# in-place clear dirnames to prevent os.walk from recursing into
4344
# any of the nested directories
44-
logger.info(f'ignoring directory: {dirpath}')
45+
logging.info(f'ignoring directory: {dirpath}')
4546
dirnames[:] = []
4647
continue
4748

48-
logger.info(f'Processing directory: {dirpath}')
49+
logging.info(f'Processing directory: {dirpath}')
4950

5051
# Process each file in the current directory
5152
for filename in filenames:
@@ -54,11 +55,10 @@ def first_pass(self, ignore: List[str], executor: concurrent.futures.Executor) -
5455
# Skip none supported files
5556
ext = file_path.suffix
5657
if ext not in analyzers:
57-
logger.info(f"Skipping none supported file {file_path}")
58+
logging.info(f"Skipping none supported file {file_path}")
5859
continue
5960

60-
logger.info(f'Processing file: {file_path}')
61-
print(f'Processing file: {file_path}')
61+
logging.info(f'Processing file: {file_path}')
6262

6363
def process_file(path: Path) -> None:
6464
with open(path, 'rb') as f:
@@ -89,11 +89,11 @@ def second_pass(self, ignore: List[str], executor: concurrent.futures.Executor)
8989
if dirpath in ignore:
9090
# in-place clear dirnames to prevent os.walk from recursing into
9191
# any of the nested directories
92-
logger.info(f'ignoring directory: {dirpath}')
92+
logging.info(f'ignoring directory: {dirpath}')
9393
dirnames[:] = []
9494
continue
9595

96-
logger.info(f'Processing directory: {dirpath}')
96+
logging.info(f'Processing directory: {dirpath}')
9797

9898
# Process each file in the current directory
9999
for filename in filenames:
@@ -104,7 +104,7 @@ def second_pass(self, ignore: List[str], executor: concurrent.futures.Executor)
104104
if ext not in analyzers:
105105
continue
106106

107-
logger.info(f'Processing file: {file_path}')
107+
logging.info(f'Processing file: {file_path}')
108108

109109
def process_file(path: Path) -> None:
110110
with open(path, 'rb') as f:
@@ -119,8 +119,8 @@ def process_file(path: Path) -> None:
119119

120120
def analyze_file(self, path: Path, graph: Graph) -> None:
121121
ext = path.suffix
122-
print(f"analyze_file: path: {path}")
123-
print(f"analyze_file: ext: {ext}")
122+
logging.info(f"analyze_file: path: {path}")
123+
logging.info(f"analyze_file: ext: {ext}")
124124
if ext not in analyzers:
125125
return
126126

@@ -145,6 +145,9 @@ def analyze(self, path: str, g: Graph, ignore: Optional[List[str]] = []) -> None
145145
ignore (List(str)): List of paths to skip
146146
"""
147147

148+
# Save original working directory for later restore
149+
original_dir = Path.cwd()
150+
148151
# change working directory to path
149152
os.chdir(path)
150153

@@ -154,7 +157,10 @@ def analyze(self, path: str, g: Graph, ignore: Optional[List[str]] = []) -> None
154157
# Analyze source files
155158
self.analyze_sources(ignore)
156159

157-
logger.info("Done analyzing path")
160+
logging.info("Done analyzing path")
161+
162+
# Restore original working dir
163+
os.chdir(original_dir)
158164

159165
def analyze_local_repository(self, path: str, ignore: Optional[List[str]] = []) -> Graph:
160166
"""

code_graph/app.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -387,8 +387,6 @@ def repo_info():
387387
'info': stats
388388
}
389389

390-
print(f"response: {response}")
391-
392390
return jsonify(response), 200
393391

394392
@app.route('/find_paths', methods=['POST'])
@@ -479,6 +477,29 @@ def unreachable_entities():
479477

480478
return jsonify(response), 200
481479

480+
@app.route('/chat', methods=['POST'])
481+
def chat():
482+
# Get JSON data from the request
483+
data = request.get_json()
484+
485+
# Validate 'repo' parameter
486+
repo = data.get('repo')
487+
if repo is None:
488+
return jsonify({'status': f'Missing mandatory parameter "repo"'}), 400
489+
490+
# Get optional 'label' and 'relation' parameters
491+
msg = data.get('msg')
492+
if msg is None:
493+
return jsonify({'status': f'Missing mandatory parameter "msg"'}), 400
494+
495+
answer = ask(repo, msg)
496+
497+
# Create and return a successful response
498+
response = { 'status': 'success', 'response': answer }
499+
500+
return jsonify(response), 200
501+
502+
482503
return app
483504

484505
if __name__ == '__main__':

code_graph/git_utils/git_graph.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import os
2+
import logging
23
from falkordb import FalkorDB, Node
34
from typing import List, Optional
45

6+
# Configure logging
7+
logging.basicConfig(level=logging.DEBUG, format='%(filename)s - %(asctime)s - %(levelname)s - %(message)s')
8+
59
class GitGraph():
610
"""
711
Represents a git commit graph
@@ -40,6 +44,9 @@ def add_commit(self, commit_hash: str, author: str, message: str, date: int) ->
4044
"""
4145
Add a new commit to the graph
4246
"""
47+
48+
logging.info(f"Adding commit {commit_hash}: {message}")
49+
4350
q = "MERGE (c:Commit {hash: $hash, author: $author, message: $message, date: $date})"
4451
params = {'hash': commit_hash, 'author': author, 'message': message, 'date': date}
4552
self.g.query(q, params)
@@ -55,6 +62,8 @@ def list_commits(self) -> List[Node]:
5562
return [self._commit_from_node(row[0]) for row in result_set]
5663

5764
def get_commits(self, hashes: List[str]) -> List[dict]:
65+
logging.info(f"Searching for commits {hashes}")
66+
5867
q = """MATCH (c:Commit)
5968
WHERE c.hash IN $hashes
6069
RETURN c"""
@@ -67,13 +76,17 @@ def get_commits(self, hashes: List[str]) -> List[dict]:
6776
commit = self._commit_from_node(row[0])
6877
commits.append(commit)
6978

79+
logging.info(f"retrived commits: {commits}")
7080
return commits
7181

7282
def connect_commits(self, child: str, parent: str) -> None:
7383
"""
7484
connect commits via both PARENT and CHILD edges
7585
"""
7686

87+
logging.info(f"Connecting commits {child} -PARENT-> {parent}")
88+
logging.info(f"Connecting commits {parent} -CHILD-> {child}")
89+
7790
q = """MATCH (child :Commit {hash: $child_hash}), (parent :Commit {hash: $parent_hash})
7891
MERGE (child)-[:PARENT]->(parent)
7992
MERGE (parent)-[:CHILD]->(child)"""

0 commit comments

Comments
 (0)