Skip to content

Commit f935277

Browse files
committed
auto-complete
1 parent 589fdcf commit f935277

File tree

4 files changed

+128
-42
lines changed

4 files changed

+128
-42
lines changed

code_graph/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
from .analyzers.source_analyzer import *
1+
from .graph import *
22
from .entities import *
33
from .git_utils import *
4-
from .graph import Graph
54
from .code_coverage import *
5+
from .analyzers.source_analyzer import *
6+
from. auto_complete import auto_complete

code_graph/auto_complete.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from typing import List
2+
from .graph import Graph
3+
from .entities import Function
4+
5+
def auto_complete(repo: str, prefix: str) -> str:
6+
g = Graph(repo)
7+
return g.prefix_search(prefix)
8+

code_graph/graph.py

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,24 @@
33
from typing import List, Optional
44
from falkordb import FalkorDB, Node, QueryResult
55

6+
FALKORDB_HOST = "localhost"
7+
FALKORDB_PORT = 6379
8+
FALKORDB_USERNAME = None
9+
FALKORDB_PASSWORD = None
10+
11+
def list_repos() -> List[str]:
12+
"""
13+
List processed repositories
14+
"""
15+
16+
db = FalkorDB(host=FALKORDB_HOST, port=FALKORDB_PORT,
17+
username=FALKORDB_USERNAME, password=FALKORDB_PASSWORD)
18+
19+
graphs = db.list_graphs()
20+
print(f"graphs: {graphs}")
21+
graphs = [g for g in graphs if not g.endswith('_git')]
22+
return graphs
23+
624
class Graph():
725
"""
826
Represents a connection to a graph database using FalkorDB.
@@ -34,6 +52,12 @@ def __init__(self, name: str, host: str = 'localhost', port: int = 6379,
3452
except Exception:
3553
pass
3654

55+
# index Function using full-text search
56+
try:
57+
self.g.create_node_fulltext_index("Searchable", "name")
58+
except Exception:
59+
pass
60+
3761
def clone(self, clone: str) -> "Graph":
3862
"""
3963
Create a copy of the graph under the name clone
@@ -71,7 +95,7 @@ def add_class(self, c: Class) -> None:
7195
c (Class): The Class object to be added.
7296
"""
7397

74-
q = """MERGE (c:Class {name: $name, path: $path, src_start: $src_start,
98+
q = """MERGE (c:Class:Searchable {name: $name, path: $path, src_start: $src_start,
7599
src_end: $src_end})
76100
SET c.doc = $doc
77101
RETURN ID(c)"""
@@ -133,7 +157,7 @@ def add_function(self, func: Function) -> None:
133157
func (Function): The Function object to be added.
134158
"""
135159

136-
q = """MERGE (f:Function {path: $path, name: $name,
160+
q = """MERGE (f:Function:Searchable {path: $path, name: $name,
137161
src_start: $src_start, src_end: $src_end})
138162
SET f.args = $args, f.ret_type = $ret_type, f.src = $src, f.doc = $doc
139163
RETURN ID(f)"""
@@ -151,7 +175,6 @@ def add_function(self, func: Function) -> None:
151175
'ret_type': func.ret_type
152176
}
153177

154-
155178
res = self.g.query(q, params)
156179
func.id = res.result_set[0][0]
157180

@@ -215,6 +238,53 @@ def get_function_by_name(self, name: str) -> Optional[Function]:
215238

216239
return self._function_from_node(res[0][0])
217240

241+
def prefix_search(self, prefix: str) -> str:
242+
"""
243+
Search for entities by prefix using a full-text search on the graph.
244+
The search is limited to 10 nodes. Each node's name and labels are retrieved,
245+
and the results are sorted based on their labels.
246+
247+
Args:
248+
prefix (str): The prefix string to search for in the graph database.
249+
250+
Returns:
251+
str: A list of entity names and corresponding labels, sorted by label.
252+
If no results are found or an error occurs, an empty list is returned.
253+
"""
254+
255+
# Append a wildcard '*' to the prefix for full-text search.
256+
search_prefix = f"{prefix}*"
257+
258+
# Cypher query to perform full-text search and limit the result to 10 nodes.
259+
# The 'CALL db.idx.fulltext.queryNodes' method searches for nodes labeled 'Searchable'
260+
# that match the given prefix, collects the nodes, and returns the result.
261+
query = """
262+
CALL db.idx.fulltext.queryNodes('Searchable', $prefix)
263+
YIELD node
264+
WITH node
265+
LIMIT 10
266+
RETURN collect(node)
267+
"""
268+
269+
try:
270+
# Execute the query using the provided graph database connection.
271+
completions = self.g.query(query, {'prefix': search_prefix}).result_set[0][0]
272+
273+
# Remove label Searchable from each node
274+
for node in completions:
275+
node.labels.remove('Searchable')
276+
277+
# Sort the results by the label for better organization.
278+
completions = sorted(completions, key=lambda x: x.labels)
279+
280+
return completions
281+
282+
except Exception as e:
283+
# Log any errors encountered during the search and return an empty list.
284+
logging.error(f"Error while searching for entities with prefix '{prefix}': {e}")
285+
return []
286+
287+
218288
def get_function(self, func_id: int) -> Optional[Function]:
219289
q = """MATCH (f:Function)
220290
WHERE ID(f) = $func_id
@@ -269,7 +339,7 @@ def add_file(self, file: File) -> None:
269339
file_ext (str): Extension of the file.
270340
"""
271341

272-
q = """MERGE (f:File {path: $path, name: $name, ext: $ext})
342+
q = """MERGE (f:File:Searchable {path: $path, name: $name, ext: $ext})
273343
RETURN ID(f)"""
274344
params = {'path': file.path, 'name': file.name, 'ext': file.ext}
275345

@@ -400,7 +470,7 @@ def add_struct(self, s: Struct) -> None:
400470
s (Struct): The Struct object to be added.
401471
"""
402472

403-
q = """MERGE (s:Struct {name: $name, path: $path, src_start: $src_start,
473+
q = """MERGE (s:Struct:Searchable {name: $name, path: $path, src_start: $src_start,
404474
src_end: $src_end})
405475
SET s.doc = $doc, s.fields = $fields
406476
RETURN ID(s)"""
@@ -484,3 +554,4 @@ def rerun_query(self, q: str, params: dict) -> QueryResult:
484554
"""
485555

486556
return self.g.query(q, params)
557+

main.py

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -32,41 +32,6 @@ def extract_org_name_from_url(url: str) -> Optional[tuple[str, str]]:
3232

3333
return (components[n-2], components[n-1])
3434

35-
def get_current_head_commit_hash(repo_url: str) -> str:
36-
import git
37-
38-
with tempfile.TemporaryDirectory() as temp_dir:
39-
repo = git.Repo.clone_from(repo_url, temp_dir)
40-
commit_hash = repo.head.commit.hexsha
41-
return commit_hash
42-
43-
def save_repository_metadata(repo_url: str, repo_name: str):
44-
r = redis.Redis(host=FALKORDB_HOST, port=FALKORDB_PORT,
45-
username=FALKORDB_USERNAME, password=FALKORDB_PASSWORD,
46-
decode_responses=True)
47-
48-
key = f'{repo_name}_metadata'
49-
metadata = {
50-
'repo_url': repo_url,
51-
'repo_name': repo_name,
52-
'date_create': str(datetime.datetime.today().replace(microsecond=0)),
53-
'commit': get_current_head_commit_hash(repo_url)}
54-
55-
r.hset(key, mapping=metadata)
56-
57-
@app.route('/list_repos', methods=['GET'])
58-
def list_repos():
59-
r = redis.Redis(host=FALKORDB_HOST, port=FALKORDB_PORT,
60-
username=FALKORDB_USERNAME, password=FALKORDB_PASSWORD,
61-
decode_responses=True)
62-
keys = r.keys('*_metadata')[:20]
63-
64-
repos = []
65-
for key in keys:
66-
repos.append(r.hgetall(key))
67-
68-
return jsonify({'repos': repos}), 200
69-
7035
@app.route('/graph_entities', methods=['GET'])
7136
def graph_entities():
7237
# Access the 'graph_id' parameter from the GET request
@@ -297,6 +262,47 @@ def process_switch_commit():
297262

298263
return jsonify(response), 200
299264

265+
@app.route('/auto_complete', methods=['POST'])
266+
def process_auto_complete():
267+
# Get JSON data from the request
268+
data = request.get_json()
269+
270+
# path to local repository
271+
repo = data.get('repo')
272+
if repo is None:
273+
return jsonify({'status': f'Missing mandatory parameter "repo"'}), 400
274+
275+
prefix = data.get('prefix')
276+
if prefix is None:
277+
return jsonify({'status': f'Missing mandatory parameter "prefix"'}), 400
278+
279+
completions = auto_complete(repo, prefix)
280+
# Create a response
281+
response = {
282+
'status': 'success',
283+
'completions': completions
284+
}
285+
286+
return jsonify(response), 200
287+
288+
289+
@app.route('/list_repos', methods=['GET'])
290+
def process_list_repos():
291+
print("Hello!")
292+
# Get JSON data from the request
293+
294+
repos = list_repos()
295+
print(f"repos: {repos}")
296+
297+
# Create a response
298+
response = {
299+
'status': 'success',
300+
'repositories': repos
301+
}
302+
303+
return jsonify(response), 200
304+
305+
300306
if __name__ == '__main__':
301307
app.run(debug=True)
302308

0 commit comments

Comments
 (0)