Skip to content

Commit 20a02d5

Browse files
gkorlandCopilot
andcommitted
Address review: fix strict=True zip, revert serve_spa to sync, eliminate double connections
- Remove strict=True from zip(nodes, edges) in find_paths (both sync and async) — nodes intentionally has one more element than edges - Revert serve_spa to sync def so FastAPI offloads filesystem I/O to thread pool instead of blocking the event loop - Add graph_exists() method to AsyncGraphQuery to reuse the connection instead of opening a separate one via async_graph_exists() - Update all endpoints in api/index.py and tests/index.py to use g.graph_exists() on the query instance - Fix missing trailing newline in api/llm.py Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 92fa424 commit 20a02d5

4 files changed

Lines changed: 59 additions & 48 deletions

File tree

api/graph.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ def find_paths(self, src: int, dest: int) -> list[Path]:
581581
nodes = p.nodes()
582582
edges = p.edges()
583583

584-
for n, e in zip(nodes, edges, strict=True):
584+
for n, e in zip(nodes, edges):
585585
path.append(encode_node(n))
586586
path.append(encode_edge(e))
587587

@@ -670,9 +670,15 @@ class AsyncGraphQuery:
670670
"""
671671

672672
def __init__(self, name: str) -> None:
673+
self.name = name
673674
self.db = _async_db()
674675
self.g = self.db.select_graph(name)
675676

677+
async def graph_exists(self) -> bool:
678+
"""Check if this graph exists, reusing the current connection."""
679+
graphs = await self.db.list_graphs()
680+
return self.name in graphs
681+
676682
async def _query(self, q: str, params: Optional[dict] = None):
677683
return await self.g.query(q, params)
678684

@@ -744,7 +750,7 @@ async def find_paths(self, src: int, dest: int) -> list:
744750
p = row[0]
745751
nodes = p.nodes()
746752
edges = p.edges()
747-
for n, e in zip(nodes, edges, strict=True):
753+
for n, e in zip(nodes, edges):
748754
path.append(encode_node(n))
749755
path.append(encode_edge(e))
750756
path.append(encode_node(nodes[-1]))

api/index.py

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
from api.analyzers.source_analyzer import SourceAnalyzer
1313
from api.git_utils import git_utils
1414
from api.git_utils.git_graph import AsyncGitGraph
15-
from api.graph import Graph, AsyncGraphQuery, async_get_repos, async_graph_exists
15+
from api.graph import Graph, AsyncGraphQuery, async_get_repos
1616
from api.info import async_get_repo_info
1717
from api.llm import ask
1818
from api.project import Project
19-
from .auto_complete import async_prefix_search
19+
2020

2121
# Load environment variables from .env file
2222
load_dotenv()
@@ -112,35 +112,34 @@ async def graph_entities(repo: str = Query(None), _=Depends(public_or_auth)):
112112
logging.error("Missing 'repo' parameter in request.")
113113
return JSONResponse({"status": "Missing 'repo' parameter"}, status_code=400)
114114

115-
if not await async_graph_exists(repo):
116-
logging.error("Missing project %s", repo)
117-
return JSONResponse({"status": f"Missing project {repo}"}, status_code=400)
118-
115+
g = AsyncGraphQuery(repo)
119116
try:
120-
g = AsyncGraphQuery(repo)
121-
try:
122-
sub_graph = await g.get_sub_graph(500)
123-
finally:
124-
await g.close()
117+
if not await g.graph_exists():
118+
logging.error("Missing project %s", repo)
119+
return JSONResponse({"status": f"Missing project {repo}"}, status_code=400)
120+
121+
sub_graph = await g.get_sub_graph(500)
125122

126123
logging.info("Successfully retrieved sub-graph for repo: %s", repo)
127124
return {"status": "success", "entities": sub_graph}
128125

129126
except Exception as e:
130127
logging.exception("Error retrieving sub-graph for repo '%s': %s", repo, e)
131128
return JSONResponse({"status": "Internal server error"}, status_code=500)
129+
finally:
130+
await g.close()
132131

133132

134133
@app.post('/api/get_neighbors')
135134
async def get_neighbors(data: NeighborsRequest, _=Depends(public_or_auth)):
136135
"""Get neighbors of a nodes list in the graph."""
137136

138-
if not await async_graph_exists(data.repo):
139-
logging.error("Missing project %s", data.repo)
140-
return JSONResponse({"status": f"Missing project {data.repo}"}, status_code=400)
141-
142137
g = AsyncGraphQuery(data.repo)
143138
try:
139+
if not await g.graph_exists():
140+
logging.error("Missing project %s", data.repo)
141+
return JSONResponse({"status": f"Missing project {data.repo}"}, status_code=400)
142+
144143
neighbors = await g.get_neighbors(data.node_ids)
145144
finally:
146145
await g.close()
@@ -154,10 +153,14 @@ async def get_neighbors(data: NeighborsRequest, _=Depends(public_or_auth)):
154153
async def auto_complete(data: AutoCompleteRequest, _=Depends(public_or_auth)):
155154
"""Process auto-completion requests for a repository based on a prefix."""
156155

157-
if not await async_graph_exists(data.repo):
158-
return JSONResponse({"status": f"Missing project {data.repo}"}, status_code=400)
156+
g = AsyncGraphQuery(data.repo)
157+
try:
158+
if not await g.graph_exists():
159+
return JSONResponse({"status": f"Missing project {data.repo}"}, status_code=400)
159160

160-
completions = await async_prefix_search(data.repo, data.prefix)
161+
completions = await g.prefix_search(data.prefix)
162+
finally:
163+
await g.close()
161164
return {"status": "success", "completions": completions}
162165

163166

@@ -191,12 +194,12 @@ async def repo_info(data: RepoRequest, _=Depends(public_or_auth)):
191194
async def find_paths(data: FindPathsRequest, _=Depends(public_or_auth)):
192195
"""Find all paths between a source and destination node in the graph."""
193196

194-
if not await async_graph_exists(data.repo):
195-
logging.error("Missing project %s", data.repo)
196-
return JSONResponse({"status": f"Missing project {data.repo}"}, status_code=400)
197-
198197
g = AsyncGraphQuery(data.repo)
199198
try:
199+
if not await g.graph_exists():
200+
logging.error("Missing project %s", data.repo)
201+
return JSONResponse({"status": f"Missing project {data.repo}"}, status_code=400)
202+
200203
paths = await g.find_paths(data.src, data.dest)
201204
finally:
202205
await g.close()
@@ -291,7 +294,7 @@ async def list_commits(data: RepoRequest, _=Depends(public_or_auth)):
291294
INDEX_HTML = STATIC_DIR / "index.html"
292295

293296
@app.get("/{full_path:path}")
294-
async def serve_spa(full_path: str):
297+
def serve_spa(full_path: str):
295298
"""Serve React SPA — static assets or index.html catch-all."""
296299
file = (STATIC_DIR / full_path).resolve()
297300
if not file.is_relative_to(STATIC_DIR):

api/llm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,4 +270,4 @@ def _ask_sync(repo_name: str, question: str) -> str:
270270

271271
async def ask(repo_name: str, question: str) -> str:
272272
loop = asyncio.get_running_loop()
273-
return await loop.run_in_executor(None, _ask_sync, repo_name, question)
273+
return await loop.run_in_executor(None, _ask_sync, repo_name, question)

tests/index.py

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33
import logging
44
from pathlib import Path
55

6-
from api.graph import Graph, AsyncGraphQuery, async_get_repos, async_graph_exists
6+
from api.graph import Graph, AsyncGraphQuery, async_get_repos
77
from api.info import async_get_repo_info
88
from dotenv import load_dotenv
99
from fastapi import Depends, FastAPI, Header, HTTPException, Query
1010
from fastapi.responses import JSONResponse
1111
from pydantic import BaseModel
1212

1313
from api.project import Project
14-
from api.auto_complete import async_prefix_search
1514
from api.git_utils import git_utils
1615

1716
# Load environment variables from .env file
@@ -92,30 +91,29 @@ async def graph_entities(repo: str = Query(None), _=Depends(token_required)):
9291
logging.error("Missing 'repo' parameter in request.")
9392
return JSONResponse({"status": "Missing 'repo' parameter"}, status_code=400)
9493

95-
if not await async_graph_exists(repo):
96-
logging.error("Missing project %s", repo)
97-
return JSONResponse({"status": f"Missing project {repo}"}, status_code=400)
98-
94+
g = AsyncGraphQuery(repo)
9995
try:
100-
g = AsyncGraphQuery(repo)
101-
try:
102-
sub_graph = await g.get_sub_graph(500)
103-
finally:
104-
await g.close()
96+
if not await g.graph_exists():
97+
logging.error("Missing project %s", repo)
98+
return JSONResponse({"status": f"Missing project {repo}"}, status_code=400)
99+
100+
sub_graph = await g.get_sub_graph(500)
105101
logging.info("Successfully retrieved sub-graph for repo: %s", repo)
106102
return {"status": "success", "entities": sub_graph}
107103
except Exception as e:
108104
logging.error("Error retrieving sub-graph for repo '%s': %s", repo, e)
109105
return JSONResponse({"status": "Internal server error"}, status_code=500)
106+
finally:
107+
await g.close()
110108

111109
@app.post('/api/get_neighbors')
112110
async def get_neighbors(data: NeighborsRequest, _=Depends(token_required)):
113-
if not await async_graph_exists(data.repo):
114-
logging.error("Missing project %s", data.repo)
115-
return JSONResponse({"status": f"Missing project {data.repo}"}, status_code=400)
116-
117111
g = AsyncGraphQuery(data.repo)
118112
try:
113+
if not await g.graph_exists():
114+
logging.error("Missing project %s", data.repo)
115+
return JSONResponse({"status": f"Missing project {data.repo}"}, status_code=400)
116+
119117
neighbors = await g.get_neighbors(data.node_ids)
120118
finally:
121119
await g.close()
@@ -125,10 +123,14 @@ async def get_neighbors(data: NeighborsRequest, _=Depends(token_required)):
125123

126124
@app.post('/api/auto_complete')
127125
async def auto_complete(data: AutoCompleteRequest, _=Depends(token_required)):
128-
if not await async_graph_exists(data.repo):
129-
return JSONResponse({"status": f"Missing project {data.repo}"}, status_code=400)
126+
g = AsyncGraphQuery(data.repo)
127+
try:
128+
if not await g.graph_exists():
129+
return JSONResponse({"status": f"Missing project {data.repo}"}, status_code=400)
130130

131-
completions = await async_prefix_search(data.repo, data.prefix)
131+
completions = await g.prefix_search(data.prefix)
132+
finally:
133+
await g.close()
132134
return {"status": "success", "completions": completions}
133135

134136
@app.get('/api/list_repos')
@@ -151,12 +153,12 @@ async def repo_info(data: RepoRequest, _=Depends(token_required)):
151153

152154
@app.post('/api/find_paths')
153155
async def find_paths(data: FindPathsRequest, _=Depends(token_required)):
154-
if not await async_graph_exists(data.repo):
155-
logging.error("Missing project %s", data.repo)
156-
return JSONResponse({"status": f"Missing project {data.repo}"}, status_code=400)
157-
158156
g = AsyncGraphQuery(data.repo)
159157
try:
158+
if not await g.graph_exists():
159+
logging.error("Missing project %s", data.repo)
160+
return JSONResponse({"status": f"Missing project {data.repo}"}, status_code=400)
161+
160162
paths = await g.find_paths(data.src, data.dest)
161163
finally:
162164
await g.close()

0 commit comments

Comments
 (0)