-
-
Notifications
You must be signed in to change notification settings - Fork 83
fix : add centralized error handling and strong input validation #153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -19,34 +19,52 @@ | |||||||||||||||||||||
| app (FastAPI): The FastAPI application instance. | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| from fastapi import FastAPI | ||||||||||||||||||||||
| from app.routes.routes import router as article_router | ||||||||||||||||||||||
| from fastapi.middleware.cors import CORSMiddleware | ||||||||||||||||||||||
| from app.logging.logging_config import setup_logger | ||||||||||||||||||||||
| from fastapi import FastAPI, Request, HTTPException | ||||||||||||||||||||||
| from fastapi.responses import JSONResponse | ||||||||||||||||||||||
| from fastapi.exceptions import RequestValidationError | ||||||||||||||||||||||
| import logging | ||||||||||||||||||||||
| import uuid | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| app = FastAPI() | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| logger = logging.getLogger(__name__) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
Comment on lines
+28
to
+31
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Register the APIRouter; otherwise all API endpoints return 404.
🔧 Proposed fix+from app.routes.routes import router
+
app = FastAPI()
+app.include_router(router)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| # Handle HTTPExceptions (400, 404, etc.) | ||||||||||||||||||||||
| @app.exception_handler(HTTPException) | ||||||||||||||||||||||
| async def http_exception_handler(request: Request, exc: HTTPException): | ||||||||||||||||||||||
| return JSONResponse( | ||||||||||||||||||||||
| status_code=exc.status_code, | ||||||||||||||||||||||
| content={ | ||||||||||||||||||||||
| "error": exc.detail, | ||||||||||||||||||||||
| "code": f"HTTP_{exc.status_code}" | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Handle ALL unhandled exceptions (500s) | ||||||||||||||||||||||
| @app.exception_handler(Exception) | ||||||||||||||||||||||
| async def global_exception_handler(request: Request, exc: Exception): | ||||||||||||||||||||||
| trace_id = str(uuid.uuid4()) # Unique ID for debugging | ||||||||||||||||||||||
| logger.error(f"[{trace_id}] Unhandled exception: {exc}", exc_info=True) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Setup logger for this module | ||||||||||||||||||||||
| logger = setup_logger(__name__) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| app = FastAPI( | ||||||||||||||||||||||
| title="Perspective API", | ||||||||||||||||||||||
| version="1.0.0", | ||||||||||||||||||||||
| description=("An API to generate alternative perspectives on biased articles"), | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| app.add_middleware( | ||||||||||||||||||||||
| CORSMiddleware, | ||||||||||||||||||||||
| allow_origins=["*"], | ||||||||||||||||||||||
| allow_credentials=True, | ||||||||||||||||||||||
| allow_methods=["*"], | ||||||||||||||||||||||
| allow_headers=["*"], | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| app.include_router(article_router, prefix="/api", tags=["Articles"]) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if __name__ == "__main__": | ||||||||||||||||||||||
| import uvicorn | ||||||||||||||||||||||
| import os | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| port = int(os.environ.get("PORT", 7860)) | ||||||||||||||||||||||
| logger.info(f" Server is running on http://localhost:{port}") | ||||||||||||||||||||||
| uvicorn.run(app, host="0.0.0.0", port=port) | ||||||||||||||||||||||
| return JSONResponse( | ||||||||||||||||||||||
| status_code=500, | ||||||||||||||||||||||
| content={ | ||||||||||||||||||||||
| "error": "An internal server error occurred", | ||||||||||||||||||||||
| "code": "INTERNAL_SERVER_ERROR", | ||||||||||||||||||||||
| "trace_id": trace_id # Return to frontend for easier debugging | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| from fastapi.exceptions import RequestValidationError | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @app.exception_handler(RequestValidationError) | ||||||||||||||||||||||
| async def validation_exception_handler(request: Request, exc: RequestValidationError): | ||||||||||||||||||||||
| errors = exc.errors() | ||||||||||||||||||||||
| return JSONResponse( | ||||||||||||||||||||||
| status_code=400, | ||||||||||||||||||||||
| content={ | ||||||||||||||||||||||
| "error": errors[0]['msg'] if errors else "Invalid input", | ||||||||||||||||||||||
| "code": "VALIDATION_ERROR", | ||||||||||||||||||||||
| "details": errors | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: AOSSIE-Org/Perspective
Length of output: 2427
🏁 Script executed:
Repository: AOSSIE-Org/Perspective
Length of output: 2975
🏁 Script executed:
# Check the routes.py file to see error handlers and context cat -n backend/app/routes/routes.pyRepository: AOSSIE-Org/Perspective
Length of output: 4566
🏁 Script executed:
Repository: AOSSIE-Org/Perspective
Length of output: 1009
🏁 Script executed:
Repository: AOSSIE-Org/Perspective
Length of output: 462
🏁 Script executed:
Repository: AOSSIE-Org/Perspective
Length of output: 1238
🏁 Script executed:
Repository: AOSSIE-Org/Perspective
Length of output: 2362
Add explicit validation for empty article content to ensure deterministic 400 handling.
The scraper pipeline can return
{"cleaned_text": "", "keywords": []}on failure. Sincecheck_bias()receives the entire dict and only checksif not text:(dict truthiness), an emptycleaned_textvalue bypasses validation and causes inconsistent behavior. Both endpoints must validate thatcleaned_textis actually populated.Also remove unnecessary tuple wrapping when passing
str(request.url)toasyncio.to_thread().Suggested fix
`@router.post`("/bias") async def bias_detection(request: URLRequest): try: - content = await asyncio.to_thread(run_scraper_pipeline, (str(request.url))) + content = await asyncio.to_thread(run_scraper_pipeline, str(request.url)) + if not content.get("cleaned_text"): + raise ValueError("Unable to extract article content from URL") bias_score = await asyncio.to_thread(check_bias, (content)) logger.info(f"Bias detection result: {bias_score}") return bias_score @@ `@router.post`("/process") async def run_pipelines(request: URLRequest): try: - article_text = await asyncio.to_thread(run_scraper_pipeline, (str(request.url))) + article_text = await asyncio.to_thread(run_scraper_pipeline, str(request.url)) + if not article_text.get("cleaned_text"): + raise ValueError("Unable to extract article content from URL") logger.debug(f"Scraper output: {json.dumps(article_text, indent=2, ensure_ascii=False)}") data = await asyncio.to_thread(run_langgraph_workflow, (article_text)) return data🤖 Prompt for AI Agents