Fix #770: Explorer backend routes return proper error status codes instead of 200 OK on failure

- routes/temporal.py: temporal_patterns raises HTTPException(500) instead of
  silently returning an empty-but-valid TemporalPatternResponse on exception
- routes/analytics.py: preserves existing partial-success body shape
  (frontend already parses this), but sets response.status_code = 207 when
  any individual metric computation fails, so callers get a real signal
  instead of an indistinguishable 200
- routes/ontology.py: POST /create now raises HTTPException(500) on
  generation failure instead of silently falling back to a partial/minimal
  ontology and returning 200 with a misleading nodes_added count

Verified via git stash comparison that pre-existing test suite failures
(58 errors, Starlette TestClient/httpx version mismatch) are unrelated to
this change - identical failure count on modified and unmodified code.

Closes #770
This commit is contained in:
Sameer6305
2026-07-23 15:09:44 +05:30
parent 6e4ff0c7c5
commit 443a9b78d7
3 changed files with 15 additions and 5 deletions
+10 -2
View File
@@ -1,11 +1,11 @@
"""
"""
Analytics routes for graph metrics and validation.
"""
import asyncio
from typing import Optional
from fastapi import APIRouter, Depends, Query
from fastapi import APIRouter, Depends, Query, HTTPException, Response
from ..dependencies import get_session
from ..schemas import AnalyticsResponse, ValidationIssue, ValidationReportResponse
@@ -16,6 +16,7 @@ router = APIRouter(prefix="/api/analytics", tags=["Analytics"])
@router.get("", response_model=AnalyticsResponse)
async def get_analytics(
response: Response,
metrics: Optional[str] = Query(
None,
description="Comma-separated metrics to compute: centrality,community,connectivity",
@@ -25,6 +26,7 @@ async def get_analytics(
requested = set((metrics or "centrality,community,connectivity").split(","))
graph_dict = await asyncio.to_thread(session.build_graph_dict)
result: dict = {}
any_failed = False
if "centrality" in requested and session.centrality is not None:
try:
@@ -34,6 +36,7 @@ async def get_analytics(
)
except Exception as exc:
result["centrality"] = {"error": str(exc)}
any_failed = True
if "community" in requested and session.community is not None:
try:
@@ -43,6 +46,7 @@ async def get_analytics(
)
except Exception as exc:
result["community"] = {"error": str(exc)}
any_failed = True
if "connectivity" in requested and session.connectivity is not None:
try:
@@ -52,6 +56,10 @@ async def get_analytics(
)
except Exception as exc:
result["connectivity"] = {"error": str(exc)}
any_failed = True
if any_failed:
response.status_code = 207
return AnalyticsResponse(**result)
+2
View File
@@ -1402,6 +1402,7 @@ async def create_ontology(
except Exception as exc:
logger.exception("Failed to generate ontology from sample data; falling back to minimal ontology.")
logger.warning(f"OntologyEngine.from_data error: {exc}")
raise HTTPException(status_code=500, detail=str(exc))
elif body.mode == "text" and body.schema_text:
try:
@@ -1472,6 +1473,7 @@ async def create_ontology(
except Exception as exc:
logger.exception("Failed to generate ontology from schema text; falling back to minimal ontology.")
logger.warning(f"OntologyEngine.from_text error: {exc}")
raise HTTPException(status_code=500, detail=str(exc))
nodes_added = await asyncio.to_thread(session.add_nodes, nodes)
edges_added = await asyncio.to_thread(session.add_edges, edges)
+3 -3
View File
@@ -1,4 +1,4 @@
"""
"""
Temporal routes for snapshots, diffs, and pattern detection.
"""
@@ -8,7 +8,7 @@ import re
from datetime import datetime, timedelta, timezone, UTC
from typing import List, Optional
from fastapi import APIRouter, Depends, Query
from fastapi import APIRouter, Depends, Query, HTTPException
from pydantic import BaseModel
from ..dependencies import get_session
@@ -117,7 +117,7 @@ async def temporal_patterns(
return TemporalPatternResponse(patterns=[])
except Exception as exc:
logger.warning("temporal_patterns failed: %s", exc, exc_info=True)
return TemporalPatternResponse(patterns=[])
raise HTTPException(status_code=500, detail=str(exc))
@router.get("/bounds", response_model=TemporalBoundsResponse)