Skip to content

Commit 06a47c4

Browse files
authored
Global exception handling (#212)
* exception handler file and api keys code * refactored org code * credentials code * onboarding code * project code * threads and responses * users code * document, collection, handlers testing * simplification * removing test * credentials code update * small fix * fixes * small fixes * validation error fix * last fixes * main alignment * api key code * formatting * fixing exceptions q * small fixes * missed a positional argument * increasing test coverage
1 parent 671d384 commit 06a47c4

27 files changed

+450
-446
lines changed

backend/app/api/deps.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -123,18 +123,6 @@ def get_current_active_superuser_org(current_user: CurrentUserOrg) -> User:
123123
return current_user
124124

125125

126-
async def http_exception_handler(request: Request, exc: HTTPException):
127-
"""
128-
Global handler for HTTPException to return standardized response format.
129-
"""
130-
return JSONResponse(
131-
status_code=exc.status_code,
132-
content=APIResponse.failure_response(exc.detail).model_dump()
133-
# TEMPORARY: Keep "detail" for backward compatibility
134-
| {"detail": exc.detail},
135-
)
136-
137-
138126
def verify_user_project_organization(
139127
db: SessionDep,
140128
current_user: CurrentUserOrg,

backend/app/api/routes/api_keys.py

Lines changed: 45 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
from app.crud.project import validate_project
1313
from app.models import APIKeyPublic, User
1414
from app.utils import APIResponse
15+
from app.core.exception_handlers import HTTPException
1516

1617
router = APIRouter(prefix="/apikeys", tags=["API Keys"])
1718

1819

19-
# Create API Key
2020
@router.post("/", response_model=APIResponse[APIKeyPublic])
2121
def create_key(
2222
project_id: int,
@@ -27,31 +27,26 @@ def create_key(
2727
"""
2828
Generate a new API key for the user's organization.
2929
"""
30-
try:
31-
# Validate organization
32-
project = validate_project(session, project_id)
33-
34-
existing_api_key = get_api_key_by_project_user(session, project_id, user_id)
35-
if existing_api_key:
36-
raise HTTPException(
37-
status_code=400,
38-
detail="API Key already exists for this user and project.",
39-
)
40-
41-
# Create and return API key
42-
api_key = create_api_key(
43-
session,
44-
organization_id=project.organization_id,
45-
user_id=user_id,
46-
project_id=project_id,
30+
# Validate organization
31+
project = validate_project(session, project_id)
32+
33+
existing_api_key = get_api_key_by_project_user(session, project_id, user_id)
34+
if existing_api_key:
35+
raise HTTPException(
36+
status_code=400,
37+
detail="API Key already exists for this user and project.",
4738
)
48-
return APIResponse.success_response(api_key)
4939

50-
except ValueError as e:
51-
raise HTTPException(status_code=400, detail=str(e))
40+
# Create and return API key
41+
api_key = create_api_key(
42+
session,
43+
organization_id=project.organization_id,
44+
user_id=user_id,
45+
project_id=project_id,
46+
)
47+
return APIResponse.success_response(api_key)
5248

5349

54-
# List API Keys
5550
@router.get("/", response_model=APIResponse[list[APIKeyPublic]])
5651
def list_keys(
5752
project_id: int,
@@ -62,27 +57,29 @@ def list_keys(
6257
Retrieve all API keys for the given project. Superusers get all keys;
6358
regular users get only their own.
6459
"""
65-
try:
66-
# Validate project
67-
project = validate_project(session=session, project_id=project_id)
68-
69-
if current_user.is_superuser:
70-
# Superuser: fetch all API keys for the project
71-
api_keys = get_api_keys_by_project(session=session, project_id=project_id)
72-
else:
73-
# Regular user: fetch only their own API key
74-
user_api_key = get_api_key_by_project_user(
75-
session=session, project_id=project_id, user_id=current_user.id
76-
)
77-
api_keys = [user_api_key] if user_api_key else []
60+
# Validate project
61+
project = validate_project(session=session, project_id=project_id)
62+
63+
if current_user.is_superuser:
64+
# Superuser: fetch all API keys for the project
65+
api_keys = get_api_keys_by_project(session=session, project_id=project_id)
66+
else:
67+
# Regular user: fetch only their own API key
68+
user_api_key = get_api_key_by_project_user(
69+
session=session, project_id=project_id, user_id=current_user.id
70+
)
71+
api_keys = [user_api_key] if user_api_key else []
7872

79-
return APIResponse.success_response(api_keys)
73+
# Raise an exception if no API keys are found for the project
74+
if not api_keys:
75+
raise HTTPException(
76+
status_code=404,
77+
detail="No API keys found for this project.",
78+
)
8079

81-
except ValueError as e:
82-
raise HTTPException(status_code=400, detail=str(e))
80+
return APIResponse.success_response(api_keys)
8381

8482

85-
# Get API Key by ID
8683
@router.get("/{api_key_id}", response_model=APIResponse[APIKeyPublic])
8784
def get_key(
8885
api_key_id: int,
@@ -94,12 +91,11 @@ def get_key(
9491
"""
9592
api_key = get_api_key(session, api_key_id)
9693
if not api_key:
97-
raise HTTPException(status_code=404, detail="API Key does not exist")
94+
raise HTTPException(404, "API Key does not exist")
9895

9996
return APIResponse.success_response(api_key)
10097

10198

102-
# Revoke API Key (Soft Delete)
10399
@router.delete("/{api_key_id}", response_model=APIResponse[dict])
104100
def revoke_key(
105101
api_key_id: int,
@@ -109,8 +105,11 @@ def revoke_key(
109105
"""
110106
Soft delete an API key (revoke access).
111107
"""
112-
try:
113-
delete_api_key(session, api_key_id)
114-
return APIResponse.success_response({"message": "API key revoked successfully"})
115-
except ValueError as e:
116-
raise HTTPException(status_code=400, detail=str(e))
108+
api_key = get_api_key(session, api_key_id)
109+
110+
if not api_key:
111+
raise HTTPException(404, "API key not found or already deleted")
112+
113+
delete_api_key(session, api_key_id)
114+
115+
return APIResponse.success_response({"message": "API key revoked successfully"})

backend/app/api/routes/collections.py

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -317,15 +317,7 @@ def collection_info(
317317
collection_id: UUID = FastPath(description="Collection to retrieve"),
318318
):
319319
collection_crud = CollectionCrud(session, current_user.id)
320-
try:
321-
data = collection_crud.read_one(collection_id)
322-
except NoResultFound as err:
323-
raise HTTPException(status_code=404, detail=str(err))
324-
except MultipleResultsFound as err:
325-
raise HTTPException(status_code=503, detail=str(err))
326-
except Exception as err:
327-
raise_from_unknown(err)
328-
320+
data = collection_crud.read_one(collection_id)
329321
return APIResponse.success_response(data)
330322

331323

@@ -339,13 +331,7 @@ def list_collections(
339331
current_user: CurrentUser,
340332
):
341333
collection_crud = CollectionCrud(session, current_user.id)
342-
try:
343-
data = collection_crud.read_all()
344-
except (ValueError, SQLAlchemyError) as err:
345-
raise HTTPException(status_code=403, detail=str(err))
346-
except Exception as err:
347-
raise_from_unknown(err)
348-
334+
data = collection_crud.read_all()
349335
return APIResponse.success_response(data)
350336

351337

@@ -363,10 +349,6 @@ def collection_documents(
363349
):
364350
collection_crud = CollectionCrud(session, current_user.id)
365351
document_collection_crud = DocumentCollectionCrud(session)
366-
try:
367-
collection = collection_crud.read_one(collection_id)
368-
data = document_collection_crud.read(collection, skip, limit)
369-
except (SQLAlchemyError, ValueError) as err:
370-
raise HTTPException(status_code=400, detail=str(err))
371-
352+
collection = collection_crud.read_one(collection_id)
353+
data = document_collection_crud.read(collection, skip, limit)
372354
return APIResponse.success_response(data)

0 commit comments

Comments
 (0)