1+ from hawkcatcher .types import HawkCatcherSettings
2+ from ...core import Hawk
3+ from hawkcatcher .modules .fastapi .types import FastapiSettings , FastapiAddons
4+ from starlette .types import ASGIApp , Receive , Scope , Send
5+ from starlette .requests import Request
6+ from starlette .middleware .base import BaseHTTPMiddleware
7+ from typing import Union
8+ from hawkcatcher .errors import ModuleError
9+ import asyncio
10+ from contextvars import ContextVar
11+ from fastapi import Request
12+ import asyncio
13+
14+ # Variable for saving current request, work with async tasks
15+ current_request : ContextVar [Union [Request , None ]] = ContextVar ("current_request" , default = None )
16+
17+
18+ # class for catching errors in fastapi app
19+ class HawkFastapi (Hawk ):
20+ params : FastapiSettings = {}
21+
22+ def init (self , settings : Union [str , FastapiSettings ] = None ):
23+ self .params = self .get_params (settings )
24+
25+ if self .params .get ('app_instance' ) is None :
26+ raise ModuleError ('Fastapi app instance not passed to HawkFastapi' )
27+
28+ self .params .get ('app_instance' ).add_middleware (self ._get_starlette_middleware ())
29+
30+ def _get_starlette_middleware (self ):
31+ """
32+ Create middleware for starlette to identify request exception and storing current request for manual sending
33+ """
34+
35+ # Create method to use it in middleware class with Hawk class context
36+ def send_func (err ):
37+ return self .send (err )
38+
39+ class StarletteMiddleware :
40+ def __init__ (self , app : ASGIApp ):
41+ self .app = app
42+
43+ async def __call__ (self , scope : Scope , receive : Receive , send : Send ):
44+ if scope ["type" ] == "http" :
45+ request = Request (scope , receive , send )
46+ current_request .set (request )
47+ try :
48+ await self .app (scope , receive , send )
49+ except Exception as err :
50+ return send_func (err )
51+ else :
52+ await self .app (scope , receive , send )
53+ return None
54+
55+ return StarletteMiddleware
56+
57+ def send (self , event : Exception = None , context = None , user = None ):
58+ """
59+ Method for manually send error to Hawk, make it async for starlette
60+ :param exception: exception
61+ :param context: additional context to send with error
62+ :param user: user information who faced with that event
63+ """
64+
65+ request = current_request .get ()
66+
67+ if user is None and request is not None :
68+ user = self ._set_user (request )
69+
70+ return super ().send (event , context , user )
71+
72+ def _set_addons (self ) -> Union [FastapiAddons , None ]:
73+ request = current_request .get ()
74+
75+ if request is None :
76+ return None
77+
78+ return {
79+ 'fastapi' : {
80+ 'url' : str (request .url ),
81+ 'method' : request .method ,
82+ 'headers' : dict (request .headers ),
83+ 'cookies' : dict (request .cookies ),
84+ 'params' : dict (request .query_params )
85+ }
86+ }
87+
88+ @staticmethod
89+ def get_params (settings ) -> FastapiSettings | None :
90+ hawk_params = Hawk .get_params (settings )
91+
92+ if hawk_params is None :
93+ return None
94+
95+ return {
96+ ** hawk_params ,
97+ 'app_instance' : settings .get ('app_instance' ),
98+ }
0 commit comments