77from types import ModuleType
88from typing import TYPE_CHECKING
99
10- from fastapi .responses import JSONResponse
11- from pydantic import BaseModel # pylint: disable=E0611
12- from starlette .routing import _DefaultLifespan
13-
1410from tortoise import Tortoise , connections
1511from tortoise .exceptions import DoesNotExist , IntegrityError
1612from tortoise .log import logger
1713
1814if TYPE_CHECKING :
1915 from fastapi import FastAPI , Request
2016
17+
2118if sys .version_info >= (3 , 11 ):
2219 from typing import Self
2320else :
2421 from typing_extensions import Self
2522
2623
27- class HTTPNotFoundError (BaseModel ):
28- detail : str
24+ def tortoise_exception_handlers () -> dict :
25+ from fastapi .responses import JSONResponse
26+
27+ async def doesnotexist_exception_handler (request : "Request" , exc : DoesNotExist ):
28+ return JSONResponse (status_code = 404 , content = {"detail" : str (exc )})
29+
30+ async def integrityerror_exception_handler (request : "Request" , exc : IntegrityError ):
31+ return JSONResponse (
32+ status_code = 422 ,
33+ content = {"detail" : [{"loc" : [], "msg" : str (exc ), "type" : "IntegrityError" }]},
34+ )
35+
36+ return {
37+ DoesNotExist : doesnotexist_exception_handler ,
38+ IntegrityError : integrityerror_exception_handler ,
39+ }
2940
3041
3142class RegisterTortoise (AbstractAsyncContextManager ):
@@ -122,17 +133,22 @@ def __init__(
122133 self ._create_db = _create_db
123134
124135 if add_exception_handlers and app is not None :
136+ from starlette .middleware .exceptions import ExceptionMiddleware
137+
138+ warnings .warn (
139+ "Setting `add_exception_handlers` to be true is deprecated, "
140+ "use `FastAPI(exception_handlers=tortoise_exception_handlers())` instead."
141+ "See more about it on https://tortoise.github.io/examples/fastapi" ,
142+ DeprecationWarning ,
143+ )
144+ original_call_func = ExceptionMiddleware .__call__
125145
126- @app .exception_handler (DoesNotExist )
127- async def doesnotexist_exception_handler (request : "Request" , exc : DoesNotExist ):
128- return JSONResponse (status_code = 404 , content = {"detail" : str (exc )})
146+ async def wrap_middleware_call (self , * args , ** kw ) -> None :
147+ if DoesNotExist not in self ._exception_handlers :
148+ self ._exception_handlers .update (tortoise_exception_handlers ())
149+ await original_call_func (self , * args , ** kw )
129150
130- @app .exception_handler (IntegrityError )
131- async def integrityerror_exception_handler (request : "Request" , exc : IntegrityError ):
132- return JSONResponse (
133- status_code = 422 ,
134- content = {"detail" : [{"loc" : [], "msg" : str (exc ), "type" : "IntegrityError" }]},
135- )
151+ ExceptionMiddleware .__call__ = wrap_middleware_call # type:ignore
136152
137153 async def init_orm (self ) -> None : # pylint: disable=W0612
138154 await Tortoise .init (
@@ -166,8 +182,7 @@ async def __aexit__(self, *args, **kw) -> None:
166182
167183 def __await__ (self ) -> Generator [None , None , Self ]:
168184 async def _self () -> Self :
169- await self .init_orm ()
170- return self
185+ return await self .__aenter__ ()
171186
172187 return _self ().__await__ ()
173188
@@ -182,8 +197,9 @@ def register_tortoise(
182197 add_exception_handlers : bool = False ,
183198) -> None :
184199 """
185- Registers ``startup`` and ``shutdown`` events to set-up and tear-down Tortoise-ORM
186- inside a FastAPI application.
200+ Registers Tortoise-ORM with set-up at the beginning of FastAPI application's lifespan
201+ (which allow user to read/write data from/to db inside the lifespan function),
202+ and tear-down at the end of that lifespan.
187203
188204 You can configure using only one of ``config``, ``config_file``
189205 and ``(db_url, modules)``.
@@ -245,40 +261,26 @@ def register_tortoise(
245261 ConfigurationError
246262 For any configuration error
247263 """
248- orm = RegisterTortoise (
249- app ,
250- config ,
251- config_file ,
252- db_url ,
253- modules ,
254- generate_schemas ,
255- add_exception_handlers ,
256- )
257- if isinstance (lifespan := app .router .lifespan_context , _DefaultLifespan ):
258- # Leave on_event here to compare with old versions
259- # So people can upgrade tortoise-orm in running project without changing any code
260-
261- @app .on_event ("startup" )
262- async def init_orm () -> None : # pylint: disable=W0612
263- await orm .init_orm ()
264-
265- @app .on_event ("shutdown" )
266- async def close_orm () -> None : # pylint: disable=W0612
267- await orm .close_orm ()
268-
269- else :
270- # If custom lifespan was passed to app, register tortoise in it
271- warnings .warn (
272- "`register_tortoise` function is deprecated, "
273- "use the `RegisterTortoise` class instead."
274- "See more about it on https://tortoise.github.io/examples/fastapi" ,
275- DeprecationWarning ,
276- )
277-
278- @asynccontextmanager
279- async def orm_lifespan (app_instance : "FastAPI" ):
280- async with orm :
281- async with lifespan (app_instance ):
282- yield
283-
284- app .router .lifespan_context = orm_lifespan
264+ from fastapi .routing import _merge_lifespan_context
265+
266+ # Leave this function here to compare with old versions
267+ # So people can upgrade tortoise-orm in running project without changing any code
268+
269+ @asynccontextmanager
270+ async def orm_lifespan (app_instance : "FastAPI" ):
271+ async with RegisterTortoise (
272+ app_instance ,
273+ config ,
274+ config_file ,
275+ db_url ,
276+ modules ,
277+ generate_schemas ,
278+ ):
279+ yield
280+
281+ original_lifespan = app .router .lifespan_context
282+ app .router .lifespan_context = _merge_lifespan_context (orm_lifespan , original_lifespan )
283+
284+ if add_exception_handlers :
285+ for exp_type , endpoint in tortoise_exception_handlers ().items ():
286+ app .exception_handler (exp_type )(endpoint )
0 commit comments