7
7
from types import ModuleType
8
8
from typing import TYPE_CHECKING
9
9
10
- from fastapi .responses import JSONResponse
11
- from pydantic import BaseModel # pylint: disable=E0611
12
- from starlette .routing import _DefaultLifespan
13
-
14
10
from tortoise import Tortoise , connections
15
11
from tortoise .exceptions import DoesNotExist , IntegrityError
16
12
from tortoise .log import logger
17
13
18
14
if TYPE_CHECKING :
19
15
from fastapi import FastAPI , Request
20
16
17
+
21
18
if sys .version_info >= (3 , 11 ):
22
19
from typing import Self
23
20
else :
24
21
from typing_extensions import Self
25
22
26
23
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
+ }
29
40
30
41
31
42
class RegisterTortoise (AbstractAsyncContextManager ):
@@ -122,17 +133,22 @@ def __init__(
122
133
self ._create_db = _create_db
123
134
124
135
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__
125
145
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 )
129
150
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
136
152
137
153
async def init_orm (self ) -> None : # pylint: disable=W0612
138
154
await Tortoise .init (
@@ -166,8 +182,7 @@ async def __aexit__(self, *args, **kw) -> None:
166
182
167
183
def __await__ (self ) -> Generator [None , None , Self ]:
168
184
async def _self () -> Self :
169
- await self .init_orm ()
170
- return self
185
+ return await self .__aenter__ ()
171
186
172
187
return _self ().__await__ ()
173
188
@@ -182,8 +197,9 @@ def register_tortoise(
182
197
add_exception_handlers : bool = False ,
183
198
) -> None :
184
199
"""
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.
187
203
188
204
You can configure using only one of ``config``, ``config_file``
189
205
and ``(db_url, modules)``.
@@ -245,40 +261,26 @@ def register_tortoise(
245
261
ConfigurationError
246
262
For any configuration error
247
263
"""
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