Skip to content

Commit b56945e

Browse files
committed
refactor(routes): replace HTTPException with custom exceptions
Update API routes to use custom exception hierarchy: - Replace HTTPException with ValidationException for input validation - Replace HTTPException with ModelInferenceException for generation errors - Replace HTTPException with QueryNotFoundException for missing queries - Replace HTTPException with AuthenticationException for auth failures - Let global exception handlers convert to proper HTTP responses - Remove unused HTTPException and status imports Part of Issue #7: Error Handling & Resilience
1 parent 2413d54 commit b56945e

1 file changed

Lines changed: 42 additions & 50 deletions

File tree

app/routes.py

Lines changed: 42 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,18 @@
77
from datetime import datetime
88
from typing import Any
99

10-
from fastapi import APIRouter, HTTPException, Request, status
10+
from fastapi import APIRouter, Request
1111
from pydantic import BaseModel, Field
1212

1313
from app import __version__
1414
from app.config import get_settings
15+
from app.exceptions import (
16+
AuthenticationException,
17+
ModelInferenceException,
18+
QueryNotFoundException,
19+
SchemaNotFoundException,
20+
ValidationException,
21+
)
1522
from app.logging_config import get_logger
1623
from app.security import limiter, validate_database_id, validate_natural_language_query
1724
from db.connection import get_database
@@ -186,12 +193,9 @@ async def generate_sql(request: Request, query_request: QueryRequest) -> QueryRe
186193
errors=query_errors,
187194
database_id=query_request.database_id,
188195
)
189-
raise HTTPException(
190-
status_code=status.HTTP_400_BAD_REQUEST,
191-
detail={
192-
"error": "Invalid query",
193-
"messages": query_errors,
194-
},
196+
raise ValidationException(
197+
message="Invalid natural language query",
198+
validation_errors=[{"field": "query", "error": e} for e in query_errors],
195199
)
196200

197201
# Validate database ID (Phase 2.2: Security Implementation)
@@ -202,9 +206,9 @@ async def generate_sql(request: Request, query_request: QueryRequest) -> QueryRe
202206
error=db_error,
203207
database_id=query_request.database_id,
204208
)
205-
raise HTTPException(
206-
status_code=status.HTTP_400_BAD_REQUEST,
207-
detail={"error": db_error},
209+
raise ValidationException(
210+
message=db_error or "Invalid database ID",
211+
validation_errors=[{"field": "database_id", "error": db_error}],
208212
)
209213

210214
logger.info(
@@ -256,15 +260,21 @@ async def generate_sql(request: Request, query_request: QueryRequest) -> QueryRe
256260
warnings=result.warnings,
257261
)
258262

263+
except ModelInferenceException:
264+
# Let custom exception handlers deal with this
265+
raise
266+
except SchemaNotFoundException:
267+
# Let custom exception handlers deal with this
268+
raise
259269
except Exception as e:
260270
logger.error(
261271
"generate_sql_error",
262272
error=str(e),
263273
database_id=query_request.database_id,
264274
)
265-
raise HTTPException(
266-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
267-
detail={"error": "SQL generation failed", "message": str(e)},
275+
raise ModelInferenceException(
276+
message=f"SQL generation failed: {str(e)}",
277+
details={"database_id": query_request.database_id},
268278
) from e
269279

270280

@@ -292,9 +302,9 @@ async def validate_sql(
292302
error=db_error,
293303
database_id=validation_request.database_id,
294304
)
295-
raise HTTPException(
296-
status_code=status.HTTP_400_BAD_REQUEST,
297-
detail={"error": db_error},
305+
raise ValidationException(
306+
message=db_error or "Invalid database ID",
307+
validation_errors=[{"field": "database_id", "error": db_error}],
298308
)
299309

300310
logger.info(
@@ -364,9 +374,9 @@ async def get_schema(request: Request, database_id: str) -> SchemaResponse:
364374
error=db_error,
365375
database_id=database_id,
366376
)
367-
raise HTTPException(
368-
status_code=status.HTTP_400_BAD_REQUEST,
369-
detail={"error": db_error},
377+
raise ValidationException(
378+
message=db_error or "Invalid database ID",
379+
validation_errors=[{"field": "database_id", "error": db_error}],
370380
)
371381

372382
logger.info("get_schema_request", database_id=database_id)
@@ -418,9 +428,9 @@ async def register_schema(
418428
error=db_error,
419429
database_id=schema_request.database_id,
420430
)
421-
raise HTTPException(
422-
status_code=status.HTTP_400_BAD_REQUEST,
423-
detail={"error": db_error},
431+
raise ValidationException(
432+
message=db_error or "Invalid database ID",
433+
validation_errors=[{"field": "database_id", "error": db_error}],
424434
)
425435

426436
logger.info(
@@ -485,10 +495,7 @@ async def get_reasoning_trace(
485495
history = engine.get_query_history(query_id)
486496

487497
if history is None:
488-
raise HTTPException(
489-
status_code=status.HTTP_404_NOT_FOUND,
490-
detail={"error": "Query not found", "query_id": query_id},
491-
)
498+
raise QueryNotFoundException(query_id=query_id)
492499

493500
# Convert reasoning trace to response model
494501
reasoning_trace = [
@@ -513,14 +520,12 @@ async def get_reasoning_trace(
513520
created_at=history.created_at,
514521
)
515522

516-
except HTTPException:
523+
except QueryNotFoundException:
517524
raise
518525
except Exception as e:
519526
logger.error("get_reasoning_trace_error", error=str(e), query_id=query_id)
520-
raise HTTPException(
521-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
522-
detail={"error": "Failed to retrieve reasoning trace", "message": str(e)},
523-
) from e
527+
# Let global exception handler deal with unhandled exceptions
528+
raise
524529

525530

526531
@router.post("/agent/retry", response_model=QueryResponse)
@@ -541,7 +546,6 @@ async def retry_query(request: Request, retry_request: RetryRequest) -> QueryRes
541546
)
542547

543548
from app.agent import get_agent_engine
544-
from app.exceptions import QueryNotFoundException
545549

546550
try:
547551
engine = await get_agent_engine()
@@ -580,25 +584,17 @@ async def retry_query(request: Request, retry_request: RetryRequest) -> QueryRes
580584
warnings=result.warnings,
581585
)
582586

583-
except QueryNotFoundException as e:
584-
raise HTTPException(
585-
status_code=status.HTTP_404_NOT_FOUND,
586-
detail={
587-
"error": "Query not found",
588-
"message": str(e),
589-
"query_id": retry_request.query_id,
590-
},
591-
) from e
587+
except QueryNotFoundException:
588+
# Let custom exception handler deal with this
589+
raise
592590
except Exception as e:
593591
logger.error(
594592
"retry_query_error",
595593
error=str(e),
596594
query_id=retry_request.query_id,
597595
)
598-
raise HTTPException(
599-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
600-
detail={"error": "Retry failed", "message": str(e)},
601-
) from e
596+
# Let global exception handler deal with unhandled exceptions
597+
raise
602598

603599

604600
# =============================================================================
@@ -656,11 +652,7 @@ async def login(request: Request, credentials: LoginRequest) -> TokenResponse:
656652
)
657653

658654
logger.warning("authentication_failed", username=credentials.username)
659-
raise HTTPException(
660-
status_code=status.HTTP_401_UNAUTHORIZED,
661-
detail="Incorrect username or password",
662-
headers={"WWW-Authenticate": "Bearer"},
663-
)
655+
raise AuthenticationException(message="Incorrect username or password")
664656

665657

666658
# =============================================================================

0 commit comments

Comments
 (0)