Skip to content

Commit d57c968

Browse files
committed
fix: static code analysis
1 parent a8f13ea commit d57c968

File tree

4 files changed

+68
-52
lines changed

4 files changed

+68
-52
lines changed

backend/routes/audit.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,32 @@
22
from pydantic import BaseModel
33
from typing import List, Dict, Any, Optional
44
from services.audit_service import process_audit_question
5-
from services.azure_auth import exchange_token_obo
6-
from services.gemini_service import VisualizationConfig, AuditSummaryResponse
5+
from services.gemini_service import VisualizationConfig
76

87
router = APIRouter()
98

9+
1010
class AuditQueryRequest(BaseModel):
1111
question: str
1212

13+
1314
class AuditQueryResponse(BaseModel):
1415
sql_query: str
1516
results: List[Dict[str, Any]]
1617
summary: str
1718
visualization: Optional[VisualizationConfig] = None
1819

20+
1921
@router.post("/query", response_model=AuditQueryResponse)
2022
def query_audit_log(
21-
body: AuditQueryRequest = Body(...),
22-
authorization: str = Header(...)
23+
body: AuditQueryRequest = Body(...), authorization: str = Header(...)
2324
):
2425
if not authorization.startswith("Bearer "):
2526
raise HTTPException(status_code=401, detail="Invalid token format")
26-
27+
2728
# We might want to validate the token here even if we don't use it for the pg connection directly yet
2829
# user_token = authorization.replace("Bearer ", "")
29-
# access_token = exchange_token_obo(user_token)
30-
30+
# access_token = exchange_token_obo(user_token)
31+
3132
response = process_audit_question(body.question)
3233
return AuditQueryResponse(**response)

backend/services/audit_service.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from typing import Dict, Any
22
from services.pg_connection import get_connection
33
from services.gemini_service import generate_audit_sql, summarize_audit_results
4-
import json
54

65

76
def execute_audit_query(sql_query: str) -> list:
@@ -15,20 +14,20 @@ def execute_audit_query(sql_query: str) -> list:
1514
conn = get_connection()
1615
cur = conn.cursor()
1716
cur.execute(sql_query)
18-
17+
1918
# Get column names
2019
columns = [desc[0] for desc in cur.description]
2120
results = [dict(zip(columns, row)) for row in cur.fetchall()]
22-
21+
2322
# Serialize datetime and json objects
2423
for row in results:
2524
for key, value in row.items():
26-
if hasattr(value, 'isoformat'):
25+
if hasattr(value, "isoformat"):
2726
row[key] = value.isoformat()
2827
elif isinstance(value, dict):
2928
# Ensure dicts (like diff_data) are kept as dicts for the frontend
3029
pass
31-
30+
3231
cur.close()
3332
conn.close()
3433
return results
@@ -45,30 +44,30 @@ def process_audit_question(question: str) -> Dict[str, Any]:
4544
3. Summarize results (via Gemini)
4645
"""
4746
sql_query = generate_audit_sql(question)
48-
47+
4948
# If the generator returned an error query or invalid SQL, return it
5049
if "Error:" in sql_query:
5150
return {
5251
"sql_query": sql_query,
5352
"results": [],
54-
"summary": "Could not generate a valid query for your request."
53+
"summary": "Could not generate a valid query for your request.",
5554
}
56-
55+
5756
results = execute_audit_query(sql_query)
58-
57+
5958
# If execution failed
6059
if results and "error" in results[0]:
6160
return {
6261
"sql_query": sql_query,
6362
"results": [],
64-
"summary": f"Error executing query: {results[0]['error']}"
63+
"summary": f"Error executing query: {results[0]['error']}",
6564
}
66-
65+
6766
summary_response = summarize_audit_results(question, sql_query, results)
68-
67+
6968
return {
7069
"sql_query": sql_query,
7170
"results": results,
7271
"summary": summary_response.summary,
73-
"visualization": summary_response.visualization
72+
"visualization": summary_response.visualization,
7473
}

backend/services/gemini_service.py

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,28 @@
22
from google.genai import types
33
from models.schemas import GeneratedCode, CollectionContext, DebugSuggestionResponse
44
from pydantic import BaseModel, Field
5-
from typing import Optional, List, Any, Dict
5+
from typing import Optional, List
6+
67

78
class VisualizationConfig(BaseModel):
89
available: bool = Field(description="Whether a chart is recommended for this data")
9-
type: Optional[str] = Field(description="Type of chart: 'bar', 'line', 'pie', 'scatter'")
10+
type: Optional[str] = Field(
11+
description="Type of chart: 'bar', 'line', 'pie', 'scatter'"
12+
)
1013
x_key: Optional[str] = Field(description="Key for X-axis data")
1114
y_key: Optional[str] = Field(description="Key for Y-axis data")
1215
title: Optional[str] = Field(description="Title for the chart")
13-
data_keys: Optional[List[str]] = Field(description="Keys to include in the chart data points (e.g. ['count', 'date'])")
16+
data_keys: Optional[List[str]] = Field(
17+
description="Keys to include in the chart data points (e.g. ['count', 'date'])"
18+
)
19+
1420

1521
class AuditSummaryResponse(BaseModel):
1622
summary: str = Field(description="Markdown summary of the results")
17-
visualization: VisualizationConfig = Field(description="Configuration for data visualization")
23+
visualization: VisualizationConfig = Field(
24+
description="Configuration for data visualization"
25+
)
26+
1827

1928
PROMPT_TEMPLATE_QUERY = """
2029
You are an assistant that converts user requests into MongoDB query code.
@@ -202,7 +211,9 @@ def generate_audit_sql(user_input: str) -> str:
202211
return sql
203212

204213

205-
def summarize_audit_results(user_input: str, sql_query: str, results: list) -> AuditSummaryResponse:
214+
def summarize_audit_results(
215+
user_input: str, sql_query: str, results: list
216+
) -> AuditSummaryResponse:
206217
# Truncate results if too large to avoid token limits
207218
results_str = str(results)[:10000]
208219
full_prompt = PROMPT_TEMPLATE_AUDIT_SUMMARY.format(
@@ -215,20 +226,21 @@ def summarize_audit_results(user_input: str, sql_query: str, results: list) -> A
215226
config=types.GenerateContentConfig(
216227
response_mime_type="application/json",
217228
response_schema=AuditSummaryResponse,
218-
thinking_config=types.ThinkingConfig(thinking_budget=0)
229+
thinking_config=types.ThinkingConfig(thinking_budget=0),
219230
),
220231
)
221-
222-
if hasattr(response, 'parsed') and response.parsed:
232+
233+
if hasattr(response, "parsed") and response.parsed:
223234
return response.parsed
224-
235+
225236
import json
237+
226238
try:
227239
data = json.loads(response.text)
228240
return AuditSummaryResponse(**data)
229241
except Exception as e:
230242
print(f"Error parsing Gemini response: {e}")
231243
return AuditSummaryResponse(
232244
summary="Could not generate summary due to parsing error.",
233-
visualization=VisualizationConfig(available=False)
245+
visualization=VisualizationConfig(available=False),
234246
)

backend/tests/test_audit_service.py

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,51 @@
22
from unittest.mock import patch, MagicMock
33
from services.audit_service import process_audit_question
44

5+
56
class TestAuditService(unittest.TestCase):
67

7-
@patch('services.audit_service.generate_audit_sql')
8-
@patch('services.audit_service.get_connection')
9-
@patch('services.audit_service.summarize_audit_results')
10-
def test_process_audit_question_success(self, mock_summarize, mock_get_conn, mock_generate_sql):
8+
@patch("services.audit_service.generate_audit_sql")
9+
@patch("services.audit_service.get_connection")
10+
@patch("services.audit_service.summarize_audit_results")
11+
def test_process_audit_question_success(
12+
self, mock_summarize, mock_get_conn, mock_generate_sql
13+
):
1114
# Setup mocks
1215
mock_generate_sql.return_value = "SELECT * FROM write_audit_log LIMIT 5"
13-
16+
1417
mock_conn = MagicMock()
1518
mock_cursor = MagicMock()
1619
mock_get_conn.return_value = mock_conn
1720
mock_conn.cursor.return_value = mock_cursor
18-
21+
1922
# Mock DB results
20-
mock_cursor.description = [('user_email',), ('operation',)]
21-
mock_cursor.fetchall.return_value = [('test@example.com', 'insert')]
22-
23+
mock_cursor.description = [("user_email",), ("operation",)]
24+
mock_cursor.fetchall.return_value = [("test@example.com", "insert")]
25+
2326
mock_summarize.return_value = "Summary of results."
24-
27+
2528
# Execute
2629
result = process_audit_question("Show me inserts")
27-
30+
2831
# Assertions
29-
self.assertEqual(result['sql_query'], "SELECT * FROM write_audit_log LIMIT 5")
30-
self.assertEqual(len(result['results']), 1)
31-
self.assertEqual(result['results'][0]['user_email'], 'test@example.com')
32-
self.assertEqual(result['summary'], "Summary of results.")
33-
32+
self.assertEqual(result["sql_query"], "SELECT * FROM write_audit_log LIMIT 5")
33+
self.assertEqual(len(result["results"]), 1)
34+
self.assertEqual(result["results"][0]["user_email"], "test@example.com")
35+
self.assertEqual(result["summary"], "Summary of results.")
36+
3437
mock_generate_sql.assert_called_once()
3538
mock_cursor.execute.assert_called_with("SELECT * FROM write_audit_log LIMIT 5")
3639
mock_summarize.assert_called_once()
3740

38-
@patch('services.audit_service.generate_audit_sql')
41+
@patch("services.audit_service.generate_audit_sql")
3942
def test_process_audit_question_invalid_sql(self, mock_generate_sql):
4043
mock_generate_sql.return_value = "DELETE FROM write_audit_log"
41-
44+
4245
result = process_audit_question("Delete everything")
43-
44-
self.assertIn("Error executing query", result['summary'])
45-
self.assertIn("Only SELECT", result['summary'])
4646

47-
if __name__ == '__main__':
47+
self.assertIn("Error executing query", result["summary"])
48+
self.assertIn("Only SELECT", result["summary"])
49+
50+
51+
if __name__ == "__main__":
4852
unittest.main()

0 commit comments

Comments
 (0)