4141 JSONResponse ,
4242 PlainTextResponse ,
4343)
44+ from fastapi .routing import APIRoute , APIRouter
4445from fastapi .security import OAuth2PasswordRequestForm
4546from fastapi_pagination import add_pagination
4647from fastapi_users import FastAPIUsers
47- from fastapi_versioning import VersionedFastAPI
4848from jose import jwt
4949from jose .exceptions import JWTError
5050from kernelci .api .models import (
@@ -2300,12 +2300,19 @@ async def purge_handler(
23002300 return await purge_old_nodes (age_days = days , batch_size = batch_size )
23012301
23022302
2303- versioned_app = VersionedFastAPI (
2304- app ,
2305- version_format = "{major}" ,
2306- prefix_format = "/v{major}" ,
2307- enable_latest = True ,
2308- default_version = (0 , 0 ),
2303+ # Build versioned app using include_router instead of fastapi-versioning.
2304+ # VersionedFastAPI (fastapi-versioning==0.10.0) is incompatible with
2305+ # FastAPI >= 0.118.0: it transplants route objects by directly appending
2306+ # to sub-app router.routes, which bypasses FastAPI's request_response
2307+ # wrapper and causes 'fastapi_inner_astack not found in request scope'.
2308+ # Using include_router re-creates APIRoute objects properly.
2309+ _api_router = APIRouter ()
2310+ for _route in app .routes :
2311+ if isinstance (_route , APIRoute ):
2312+ _api_router .routes .append (_route )
2313+
2314+ versioned_app = FastAPI (
2315+ title = app .title ,
23092316 on_startup = [
23102317 pubsub_startup ,
23112318 create_indexes ,
@@ -2315,9 +2322,10 @@ async def purge_handler(
23152322 ],
23162323)
23172324
2325+ versioned_app .include_router (_api_router , prefix = "/v0" )
2326+ versioned_app .include_router (_api_router , prefix = "/latest" )
2327+
23182328
2319- # traceback_exception_handler is a global exception handler that will be
2320- # triggered for all exceptions that are not handled by specific exception
23212329def traceback_exception_handler (request : Request , exc : Exception ):
23222330 """Global exception handler to print traceback"""
23232331 print (f"Exception: { exc } " )
@@ -2328,29 +2336,15 @@ def traceback_exception_handler(request: Request, exc: Exception):
23282336 )
23292337
23302338
2331- # Workaround to use global exception handlers for versioned API.
2332- # The issue has already been reported here:
2333- # https://github.com/DeanWay/fastapi-versioning/issues/30
2334- for sub_app in versioned_app .routes :
2335- if hasattr (sub_app .app , "add_exception_handler" ):
2336- sub_app .app .add_exception_handler (
2337- ValueError , value_error_exception_handler
2338- )
2339- sub_app .app .add_exception_handler (
2340- errors .InvalidId , invalid_id_exception_handler
2341- )
2342- # print traceback for all other exceptions
2343- sub_app .app .add_exception_handler (
2344- Exception , traceback_exception_handler
2345- )
2339+ versioned_app .add_exception_handler (ValueError , value_error_exception_handler )
2340+ versioned_app .add_exception_handler (
2341+ errors .InvalidId , invalid_id_exception_handler
2342+ )
2343+ versioned_app .add_exception_handler (Exception , traceback_exception_handler )
23462344
23472345
23482346class VersionRedirectMiddleware :
2349- """Pure ASGI middleware to redirect requests with version prefix.
2350-
2351- Avoids BaseHTTPMiddleware which can corrupt the request scope
2352- (fastapi_inner_astack not found in request scope).
2353- """
2347+ """Pure ASGI middleware to redirect requests with version prefix."""
23542348
23552349 def __init__ (self , app ):
23562350 self .app = app
0 commit comments