Coverage for /usr/local/lib/python3.10/site-packages/opal_common-0.0.0-py3.10.egg/opal_common/middleware.py: 68%

34 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-10 11:54 +0000

1from fastapi import FastAPI, Request, status 

2from fastapi.encoders import jsonable_encoder 

3from fastapi.middleware.cors import CORSMiddleware 

4from fastapi.responses import JSONResponse 

5from opal_common.config import opal_common_config 

6from opal_common.logger import logger 

7from pydantic import BaseModel 

8 

9 

10class ErrorResponse(BaseModel): 

11 error: str 

12 

13 

14def get_response() -> JSONResponse: 

15 error = ErrorResponse(error="Uncaught server exception") 

16 json_error = jsonable_encoder(error.dict()) 

17 return JSONResponse( 

18 content=json_error, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR 

19 ) 

20 

21 

22def register_default_server_exception_handler(app: FastAPI): 

23 """Registers a default exception handler for HTTP 500 exceptions. 

24 

25 Since fastapi does not include CORS headers by default in 500 

26 exceptions, we need to include them manually. Otherwise the frontend 

27 cries on the wrong issue. 

28 """ 

29 

30 @app.exception_handler(status.HTTP_500_INTERNAL_SERVER_ERROR) 

31 async def default_server_exception_handler(request: Request, exception: Exception): 

32 response = get_response() 

33 logger.exception("Uncaught server exception: {exc}", exc=exception) 

34 

35 # Since the CORSMiddleware is not executed when an unhandled server exception 

36 # occurs, we need to manually set the CORS headers ourselves if we want the FE 

37 # to receive a proper JSON 500, opposed to a CORS error. 

38 # Setting CORS headers on server errors is a bit of a philosophical topic of 

39 # discussion in many frameworks, and it is currently not handled in FastAPI. 

40 # See dotnet core for a recent discussion, where ultimately it was 

41 # decided to return CORS headers on server failures: 

42 # https://github.com/dotnet/aspnetcore/issues/2378 

43 origin = request.headers.get("origin") 

44 

45 if origin: 45 ↛ 48line 45 didn't jump to line 48 because the condition on line 45 was never true

46 # Have the middleware do the heavy lifting for us to parse 

47 # all the config, then update our response headers 

48 cors = CORSMiddleware( 

49 app=app, 

50 allow_origins=opal_common_config.ALLOWED_ORIGINS, 

51 allow_credentials=True, 

52 allow_methods=["*"], 

53 allow_headers=["*"], 

54 ) 

55 

56 # Logic directly from Starlette's CORSMiddleware: 

57 # https://github.com/encode/starlette/blob/master/starlette/middleware/cors.py#L152 

58 

59 response.headers.update(cors.simple_headers) 

60 has_cookie = "cookie" in request.headers 

61 

62 # If request includes any cookie headers, then we must respond 

63 # with the specific origin instead of '*'. 

64 if cors.allow_all_origins and has_cookie: 

65 response.headers["Access-Control-Allow-Origin"] = origin 

66 

67 # If we only allow specific origins, then we have to mirror back 

68 # the Origin header in the response. 

69 elif not cors.allow_all_origins and cors.is_allowed_origin(origin=origin): 

70 response.headers["Access-Control-Allow-Origin"] = origin 

71 response.headers.add_vary_header("Origin") 

72 

73 return response 

74 

75 

76def configure_cors_middleware(app: FastAPI): 

77 app.add_middleware( 

78 CORSMiddleware, 

79 allow_origins=opal_common_config.ALLOWED_ORIGINS, 

80 allow_credentials=True, 

81 allow_methods=["*"], 

82 allow_headers=["*"], 

83 ) 

84 

85 

86def configure_middleware(app: FastAPI): 

87 register_default_server_exception_handler(app) 

88 configure_cors_middleware(app)