From 456f09a7e7448056edbfd915fba1610249458e96 Mon Sep 17 00:00:00 2001 From: ShreyasW-Microsoft Date: Fri, 26 Jun 2026 20:51:17 +0530 Subject: [PATCH 1/3] fix upload bug --- .../common/telemetry/patch_instrumentor.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/backend/common/telemetry/patch_instrumentor.py b/src/backend/common/telemetry/patch_instrumentor.py index f2baca23..71e67c33 100644 --- a/src/backend/common/telemetry/patch_instrumentor.py +++ b/src/backend/common/telemetry/patch_instrumentor.py @@ -30,6 +30,54 @@ def _fixed_response_to_str(response_format: Any) -> Optional[str]: return str(response_format) +def _patch_fastapi_route_details(): + """ + Patch opentelemetry-instrumentation-fastapi's _get_route_details. + + The bug: _get_route_details() iterates over app.routes and reads + `starlette_route.path`. With FastAPI >= 0.119.0, app.include_router() + leaves an `_IncludedRouter` object in app.routes that has no `.path` + attribute. This raises: + AttributeError: '_IncludedRouter' object has no attribute 'path' + + It is triggered on requests where no concrete route matches FULL, + most notably CORS preflight `OPTIONS` requests from the frontend, + causing a 500 on the preflight and blocking the real request. + + The fix: replace the module-level _get_route_details with a version + that uses getattr(..., "path", None) and ignores routes whose + matches() raises. _get_default_span_details references this function + as a module global, so patching the module attribute is sufficient. + """ + try: + import opentelemetry.instrumentation.fastapi as fastapi_instr + from starlette.routing import Match + except ImportError: + logger.debug("opentelemetry.instrumentation.fastapi not installed, skipping patch") + return + + try: + def _safe_get_route_details(scope: Any) -> Optional[str]: + app = scope["app"] + route = None + for starlette_route in app.routes: + try: + match, _ = starlette_route.matches(scope) + except Exception: # noqa: BLE001 + continue + if match == Match.FULL: + route = getattr(starlette_route, "path", None) + break + if match == Match.PARTIAL: + route = getattr(starlette_route, "path", None) + return route + + fastapi_instr._get_route_details = _safe_get_route_details + logger.info("Patched opentelemetry.instrumentation.fastapi._get_route_details") + except Exception: # noqa: BLE001 + logger.exception("Failed to patch opentelemetry.instrumentation.fastapi route details") + + def patch_instrumentors(): """ Patch Azure AI telemetry instrumentors to handle dict response_format. @@ -60,3 +108,8 @@ def patch_instrumentors(): logger.info("Patched azure.ai.projects instrumentor") except ImportError: logger.debug("azure.ai.projects telemetry not installed, skipping patch") + + # Patch opentelemetry-instrumentation-fastapi to tolerate routes without a + # `.path` attribute (e.g. FastAPI >= 0.119.0 `_IncludedRouter`), which + # otherwise crashes span-name resolution on CORS preflight OPTIONS requests. + _patch_fastapi_route_details() From 7902c7b179faf5ab354acf3728136803c446a341 Mon Sep 17 00:00:00 2001 From: ShreyasW-Microsoft Date: Mon, 29 Jun 2026 18:39:09 +0530 Subject: [PATCH 2/3] fix test --- src/tests/backend/app_test.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/tests/backend/app_test.py b/src/tests/backend/app_test.py index ea93d4e7..6d10805b 100644 --- a/src/tests/backend/app_test.py +++ b/src/tests/backend/app_test.py @@ -30,7 +30,13 @@ async def test_health_check(app: FastAPI): @pytest.mark.asyncio async def test_backend_routes_exist(app: FastAPI): """Ensure /api routes are available (smoke test).""" - # Check available routes include /api prefix from backend_router - routes = [route.path for route in app.router.routes] - backend_routes = [r for r in routes if r.startswith("/api")] + # Check available routes include /api prefix from backend_router. + # Newer FastAPI/Starlette versions wrap included routers in objects + # (e.g. _IncludedRouter / Mount) that expose `prefix` instead of `path`, + # so fall back to either attribute when collecting route identifiers. + routes = [ + getattr(route, "path", None) or getattr(route, "prefix", "") + for route in app.router.routes + ] + backend_routes = [r for r in routes if r and r.startswith("/api")] assert backend_routes, "No backend routes found under /api prefix" From 77e21db5bb23e69da10c1dbbb77df938b8776c54 Mon Sep 17 00:00:00 2001 From: ShreyasW-Microsoft Date: Mon, 29 Jun 2026 18:47:31 +0530 Subject: [PATCH 3/3] fix tests --- src/tests/backend/app_test.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/tests/backend/app_test.py b/src/tests/backend/app_test.py index 6d10805b..0f3030a8 100644 --- a/src/tests/backend/app_test.py +++ b/src/tests/backend/app_test.py @@ -30,13 +30,12 @@ async def test_health_check(app: FastAPI): @pytest.mark.asyncio async def test_backend_routes_exist(app: FastAPI): """Ensure /api routes are available (smoke test).""" - # Check available routes include /api prefix from backend_router. - # Newer FastAPI/Starlette versions wrap included routers in objects - # (e.g. _IncludedRouter / Mount) that expose `prefix` instead of `path`, - # so fall back to either attribute when collecting route identifiers. - routes = [ - getattr(route, "path", None) or getattr(route, "prefix", "") - for route in app.router.routes - ] - backend_routes = [r for r in routes if r and r.startswith("/api")] - assert backend_routes, "No backend routes found under /api prefix" + # Use the OpenAPI schema rather than walking `app.router.routes`, because + # newer FastAPI versions wrap included routers in an `_IncludedRouter` + # object (no `.path` attribute) which makes direct route iteration fragile. + # The OpenAPI `paths` mapping is the stable public surface and always + # contains the fully-resolved paths including the `/api` prefix added by + # `app.include_router(backend_router, prefix="/api", ...)`. + paths = app.openapi().get("paths", {}) + backend_paths = [p for p in paths if p.startswith("/api")] + assert backend_paths, "No backend routes found under /api prefix"