fastapi-sqla 3.3.1__py3-none-any.whl → 3.4.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastapi_sqla/async_sqla.py +54 -33
- fastapi_sqla/base.py +4 -12
- fastapi_sqla/sqla.py +57 -37
- {fastapi_sqla-3.3.1.dist-info → fastapi_sqla-3.4.2.dist-info}/METADATA +7 -7
- {fastapi_sqla-3.3.1.dist-info → fastapi_sqla-3.4.2.dist-info}/RECORD +8 -8
- {fastapi_sqla-3.3.1.dist-info → fastapi_sqla-3.4.2.dist-info}/LICENSE +0 -0
- {fastapi_sqla-3.3.1.dist-info → fastapi_sqla-3.4.2.dist-info}/WHEEL +0 -0
- {fastapi_sqla-3.3.1.dist-info → fastapi_sqla-3.4.2.dist-info}/entry_points.txt +0 -0
fastapi_sqla/async_sqla.py
CHANGED
|
@@ -3,12 +3,13 @@ from contextlib import asynccontextmanager
|
|
|
3
3
|
from typing import Annotated
|
|
4
4
|
|
|
5
5
|
import structlog
|
|
6
|
-
from fastapi import Depends, Request
|
|
6
|
+
from fastapi import Depends, Request, Response
|
|
7
7
|
from fastapi.responses import PlainTextResponse
|
|
8
8
|
from sqlalchemy import text
|
|
9
9
|
from sqlalchemy.ext.asyncio import AsyncEngine
|
|
10
10
|
from sqlalchemy.ext.asyncio import AsyncSession as SqlaAsyncSession
|
|
11
11
|
from sqlalchemy.orm.session import sessionmaker
|
|
12
|
+
from starlette.types import ASGIApp, Message, Receive, Scope, Send
|
|
12
13
|
|
|
13
14
|
from fastapi_sqla import aws_aurora_support, aws_rds_iam_support
|
|
14
15
|
from fastapi_sqla.sqla import _DEFAULT_SESSION_KEY, Base, new_engine
|
|
@@ -88,9 +89,7 @@ async def open_session(
|
|
|
88
89
|
await session.close()
|
|
89
90
|
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
request: Request, call_next, key: str = _DEFAULT_SESSION_KEY
|
|
93
|
-
):
|
|
92
|
+
class AsyncSessionMiddleware:
|
|
94
93
|
"""Middleware which injects a new sqla async session into every request.
|
|
95
94
|
|
|
96
95
|
Handles creation of session, as well as commit, rollback, and closing of session.
|
|
@@ -108,36 +107,58 @@ async def add_session_to_request(
|
|
|
108
107
|
async def get_users(session: fastapi_sqla.AsyncSession):
|
|
109
108
|
return await session.execute(...) # use your session here
|
|
110
109
|
"""
|
|
111
|
-
async with open_session(key) as session:
|
|
112
|
-
setattr(request.state, f"{_ASYNC_REQUEST_SESSION_KEY}_{key}", session)
|
|
113
|
-
response = await call_next(request)
|
|
114
|
-
|
|
115
|
-
is_dirty = bool(session.dirty or session.deleted or session.new)
|
|
116
|
-
|
|
117
|
-
# try to commit after response, so that we can return a proper 500 response
|
|
118
|
-
# and not raise a true internal server error
|
|
119
|
-
if response.status_code < 400:
|
|
120
|
-
try:
|
|
121
|
-
await session.commit()
|
|
122
|
-
except Exception:
|
|
123
|
-
logger.exception("commit failed, returning http error")
|
|
124
|
-
response = PlainTextResponse(
|
|
125
|
-
content="Internal Server Error", status_code=500
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
if response.status_code >= 400:
|
|
129
|
-
# If ever a route handler returns an http exception, we do not want the
|
|
130
|
-
# session opened by current context manager to commit anything in db.
|
|
131
|
-
if is_dirty:
|
|
132
|
-
# optimistically only log if there were uncommitted changes
|
|
133
|
-
logger.warning(
|
|
134
|
-
"http error, rolling back possibly uncommitted changes",
|
|
135
|
-
status_code=response.status_code,
|
|
136
|
-
)
|
|
137
|
-
# since this is no-op if session is not dirty, we can always call it
|
|
138
|
-
await session.rollback()
|
|
139
110
|
|
|
140
|
-
|
|
111
|
+
def __init__(self, app: ASGIApp, key: str = _DEFAULT_SESSION_KEY) -> None:
|
|
112
|
+
self.app = app
|
|
113
|
+
self.key = key
|
|
114
|
+
|
|
115
|
+
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
|
116
|
+
if scope["type"] != "http":
|
|
117
|
+
return await self.app(scope, receive, send)
|
|
118
|
+
|
|
119
|
+
async with open_session(self.key) as session:
|
|
120
|
+
request = Request(scope=scope, receive=receive, send=send)
|
|
121
|
+
setattr(request.state, f"{_ASYNC_REQUEST_SESSION_KEY}_{self.key}", session)
|
|
122
|
+
|
|
123
|
+
async def send_wrapper(message: Message) -> None:
|
|
124
|
+
if message["type"] != "http.response.start":
|
|
125
|
+
return await send(message)
|
|
126
|
+
|
|
127
|
+
response: Response | None = None
|
|
128
|
+
status_code = message["status"]
|
|
129
|
+
is_dirty = bool(session.dirty or session.deleted or session.new)
|
|
130
|
+
|
|
131
|
+
# try to commit after response, so that we can return a proper 500
|
|
132
|
+
# and not raise a true internal server error
|
|
133
|
+
if status_code < 400:
|
|
134
|
+
try:
|
|
135
|
+
await session.commit()
|
|
136
|
+
except Exception:
|
|
137
|
+
logger.exception("commit failed, returning http error")
|
|
138
|
+
status_code = 500
|
|
139
|
+
response = PlainTextResponse(
|
|
140
|
+
content="Internal Server Error", status_code=500
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
if status_code >= 400:
|
|
144
|
+
# If ever a route handler returns an http exception,
|
|
145
|
+
# we do not want the current session to commit anything in db.
|
|
146
|
+
if is_dirty:
|
|
147
|
+
# optimistically only log if there were uncommitted changes
|
|
148
|
+
logger.warning(
|
|
149
|
+
"http error, rolling back possibly uncommitted changes",
|
|
150
|
+
status_code=status_code,
|
|
151
|
+
)
|
|
152
|
+
# since this is no-op if the session is not dirty,
|
|
153
|
+
# we can always call it
|
|
154
|
+
await session.rollback()
|
|
155
|
+
|
|
156
|
+
if response:
|
|
157
|
+
return await response(scope, receive, send)
|
|
158
|
+
|
|
159
|
+
return await send(message)
|
|
160
|
+
|
|
161
|
+
await self.app(scope, receive, send_wrapper)
|
|
141
162
|
|
|
142
163
|
|
|
143
164
|
class AsyncSessionDependency:
|
fastapi_sqla/base.py
CHANGED
|
@@ -36,13 +36,9 @@ def setup_middlewares(app: FastAPI):
|
|
|
36
36
|
engines = {key: sqla.new_engine(key) for key in engine_keys}
|
|
37
37
|
for key, engine in engines.items():
|
|
38
38
|
if not _is_async_dialect(engine):
|
|
39
|
-
app.
|
|
40
|
-
functools.partial(sqla.add_session_to_request, key=key)
|
|
41
|
-
)
|
|
39
|
+
app.add_middleware(sqla.SessionMiddleware, key=key)
|
|
42
40
|
else:
|
|
43
|
-
app.
|
|
44
|
-
functools.partial(async_sqla.add_session_to_request, key=key)
|
|
45
|
-
)
|
|
41
|
+
app.add_middleware(async_sqla.AsyncSessionMiddleware, key=key)
|
|
46
42
|
|
|
47
43
|
|
|
48
44
|
@deprecated(
|
|
@@ -54,16 +50,12 @@ def setup(app: FastAPI):
|
|
|
54
50
|
for key, engine in engines.items():
|
|
55
51
|
if not _is_async_dialect(engine):
|
|
56
52
|
app.add_event_handler("startup", functools.partial(sqla.startup, key=key))
|
|
57
|
-
app.
|
|
58
|
-
functools.partial(sqla.add_session_to_request, key=key)
|
|
59
|
-
)
|
|
53
|
+
app.add_middleware(sqla.SessionMiddleware, key=key)
|
|
60
54
|
else:
|
|
61
55
|
app.add_event_handler(
|
|
62
56
|
"startup", functools.partial(async_sqla.startup, key=key)
|
|
63
57
|
)
|
|
64
|
-
app.
|
|
65
|
-
functools.partial(async_sqla.add_session_to_request, key=key)
|
|
66
|
-
)
|
|
58
|
+
app.add_middleware(async_sqla.AsyncSessionMiddleware, key=key)
|
|
67
59
|
|
|
68
60
|
|
|
69
61
|
def _get_engine_keys() -> set[str]:
|
fastapi_sqla/sqla.py
CHANGED
|
@@ -5,7 +5,7 @@ from contextlib import contextmanager
|
|
|
5
5
|
from typing import Annotated
|
|
6
6
|
|
|
7
7
|
import structlog
|
|
8
|
-
from fastapi import Depends, Request
|
|
8
|
+
from fastapi import Depends, Request, Response
|
|
9
9
|
from fastapi.concurrency import contextmanager_in_threadpool
|
|
10
10
|
from fastapi.responses import PlainTextResponse
|
|
11
11
|
from sqlalchemy import engine_from_config, text
|
|
@@ -13,6 +13,7 @@ from sqlalchemy.engine import Engine
|
|
|
13
13
|
from sqlalchemy.ext.declarative import DeferredReflection
|
|
14
14
|
from sqlalchemy.orm.session import Session as SqlaSession
|
|
15
15
|
from sqlalchemy.orm.session import sessionmaker
|
|
16
|
+
from starlette.types import ASGIApp, Message, Receive, Scope, Send
|
|
16
17
|
|
|
17
18
|
from fastapi_sqla import aws_aurora_support, aws_rds_iam_support
|
|
18
19
|
|
|
@@ -110,9 +111,7 @@ def open_session(key: str = _DEFAULT_SESSION_KEY) -> Generator[SqlaSession, None
|
|
|
110
111
|
session.close()
|
|
111
112
|
|
|
112
113
|
|
|
113
|
-
|
|
114
|
-
request: Request, call_next, key: str = _DEFAULT_SESSION_KEY
|
|
115
|
-
):
|
|
114
|
+
class SessionMiddleware:
|
|
116
115
|
"""Middleware which injects a new sqla session into every request.
|
|
117
116
|
|
|
118
117
|
Handles creation of session, as well as commit, rollback, and closing of session.
|
|
@@ -130,39 +129,60 @@ async def add_session_to_request(
|
|
|
130
129
|
def get_users(session: fastapi_sqla.Session):
|
|
131
130
|
return session.execute(...) # use your session here
|
|
132
131
|
"""
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
#
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
132
|
+
|
|
133
|
+
def __init__(self, app: ASGIApp, key: str = _DEFAULT_SESSION_KEY) -> None:
|
|
134
|
+
self.app = app
|
|
135
|
+
self.key = key
|
|
136
|
+
|
|
137
|
+
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
|
138
|
+
if scope["type"] != "http":
|
|
139
|
+
return await self.app(scope, receive, send)
|
|
140
|
+
|
|
141
|
+
async with contextmanager_in_threadpool(open_session(self.key)) as session:
|
|
142
|
+
request = Request(scope=scope, receive=receive, send=send)
|
|
143
|
+
setattr(request.state, f"{_REQUEST_SESSION_KEY}_{self.key}", session)
|
|
144
|
+
|
|
145
|
+
async def send_wrapper(message: Message) -> None:
|
|
146
|
+
if message["type"] != "http.response.start":
|
|
147
|
+
return await send(message)
|
|
148
|
+
|
|
149
|
+
response: Response | None = None
|
|
150
|
+
status_code = message["status"]
|
|
151
|
+
is_dirty = bool(session.dirty or session.deleted or session.new)
|
|
152
|
+
|
|
153
|
+
loop = asyncio.get_running_loop()
|
|
154
|
+
|
|
155
|
+
# try to commit after response, so that we can return a proper 500
|
|
156
|
+
# and not raise a true internal server error
|
|
157
|
+
if status_code < 400:
|
|
158
|
+
try:
|
|
159
|
+
await loop.run_in_executor(None, session.commit)
|
|
160
|
+
except Exception:
|
|
161
|
+
logger.exception("commit failed, returning http error")
|
|
162
|
+
status_code = 500
|
|
163
|
+
response = PlainTextResponse(
|
|
164
|
+
content="Internal Server Error", status_code=status_code
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
if status_code >= 400:
|
|
168
|
+
# If ever a route handler returns an http exception,
|
|
169
|
+
# we do not want the current session to commit anything in db.
|
|
170
|
+
if is_dirty:
|
|
171
|
+
# optimistically only log if there were uncommitted changes
|
|
172
|
+
logger.warning(
|
|
173
|
+
"http error, rolling back possibly uncommitted changes",
|
|
174
|
+
status_code=status_code,
|
|
175
|
+
)
|
|
176
|
+
# since this is no-op if the session is not dirty,
|
|
177
|
+
# we can always call it
|
|
178
|
+
await loop.run_in_executor(None, session.rollback)
|
|
179
|
+
|
|
180
|
+
if response:
|
|
181
|
+
return await response(scope, receive, send)
|
|
182
|
+
|
|
183
|
+
return await send(message)
|
|
184
|
+
|
|
185
|
+
await self.app(scope, receive, send_wrapper)
|
|
166
186
|
|
|
167
187
|
|
|
168
188
|
class SessionDependency:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fastapi-sqla
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.2
|
|
4
4
|
Summary: SQLAlchemy extension for FastAPI with support for pagination, asyncio, SQLModel, and pytest, ready for production.
|
|
5
5
|
Home-page: https://github.com/dialoguemd/fastapi-sqla
|
|
6
6
|
License: MIT
|
|
@@ -38,15 +38,15 @@ Provides-Extra: aws-rds-iam
|
|
|
38
38
|
Provides-Extra: psycopg2
|
|
39
39
|
Provides-Extra: pytest-plugin
|
|
40
40
|
Provides-Extra: sqlmodel
|
|
41
|
-
Requires-Dist: alembic (>=1.4.3,<2
|
|
42
|
-
Requires-Dist: asyncpg (>=0.28.0,<0.
|
|
43
|
-
Requires-Dist: boto3 (>=1.24.74,<2
|
|
44
|
-
Requires-Dist: deprecated (>=1.2,<2
|
|
41
|
+
Requires-Dist: alembic (>=1.4.3,<2) ; extra == "pytest-plugin"
|
|
42
|
+
Requires-Dist: asyncpg (>=0.28.0,<0.30.0) ; extra == "asyncpg"
|
|
43
|
+
Requires-Dist: boto3 (>=1.24.74,<2) ; extra == "aws-rds-iam"
|
|
44
|
+
Requires-Dist: deprecated (>=1.2,<2)
|
|
45
45
|
Requires-Dist: fastapi (>=0.95.1,<0.112)
|
|
46
|
-
Requires-Dist: psycopg2 (>=2.8.6,<3
|
|
46
|
+
Requires-Dist: psycopg2 (>=2.8.6,<3) ; extra == "psycopg2"
|
|
47
47
|
Requires-Dist: pydantic (>=1,<3)
|
|
48
48
|
Requires-Dist: sqlalchemy (>=1.3,<3)
|
|
49
|
-
Requires-Dist: sqlmodel (>=0.0.14,<0.0.
|
|
49
|
+
Requires-Dist: sqlmodel (>=0.0.14,<0.0.21) ; extra == "sqlmodel"
|
|
50
50
|
Requires-Dist: structlog (>=20,<25)
|
|
51
51
|
Project-URL: Repository, https://github.com/dialoguemd/fastapi-sqla
|
|
52
52
|
Description-Content-Type: text/markdown
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
fastapi_sqla/__init__.py,sha256=RRkwo9xZzidQ-k3BRXfqT0jtWm8d08w94XuOLteFCdU,1221
|
|
2
2
|
fastapi_sqla/_pytest_plugin.py,sha256=IQUlj-O874Sfuth254LBrEBGCrwH3rNHST9OMZl2pIk,5411
|
|
3
3
|
fastapi_sqla/async_pagination.py,sha256=3DHGUjvrpkbWMIc_BEX4GvM-_PTcn62K9z48ucTJlH0,3164
|
|
4
|
-
fastapi_sqla/async_sqla.py,sha256=
|
|
4
|
+
fastapi_sqla/async_sqla.py,sha256=L5tHuYgHMpmSdGAsgxs30K7joVmMX-06NYJ7E5WaoUo,6865
|
|
5
5
|
fastapi_sqla/aws_aurora_support.py,sha256=4dxLKOqDccgLwFqlz81L6f4HzrOXMZkY7Zuf4t_310U,838
|
|
6
6
|
fastapi_sqla/aws_rds_iam_support.py,sha256=Uw-XaiwShMMWYKCvlSqXoxvtKMblCAvbCZ1m6BYVpJk,1257
|
|
7
|
-
fastapi_sqla/base.py,sha256=
|
|
7
|
+
fastapi_sqla/base.py,sha256=40lov7wREct4q4rfXiKjlwsjFC9iFE0eDq-ghiJwGgY,2279
|
|
8
8
|
fastapi_sqla/models.py,sha256=-B1xwINpTc9rEQd3KYHEC1s5s7jdVQkJ6Gy6xpmT13c,1108
|
|
9
9
|
fastapi_sqla/pagination.py,sha256=1gfIGcmt1OFspbRgtJ8AZOZdFd14DGRc4FkDgyh5bJ8,4517
|
|
10
10
|
fastapi_sqla/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
fastapi_sqla/sqla.py,sha256=
|
|
12
|
-
fastapi_sqla-3.
|
|
13
|
-
fastapi_sqla-3.
|
|
14
|
-
fastapi_sqla-3.
|
|
15
|
-
fastapi_sqla-3.
|
|
16
|
-
fastapi_sqla-3.
|
|
11
|
+
fastapi_sqla/sqla.py,sha256=3mIGdYmyOUZaClJiHCoaZpUlzbJilH2lrMzoc2gjZN0,7335
|
|
12
|
+
fastapi_sqla-3.4.2.dist-info/LICENSE,sha256=8G0-nWLqi3xRYRrtRlTE8n1mkYJcnCRoZGUhv6ZE29c,1064
|
|
13
|
+
fastapi_sqla-3.4.2.dist-info/METADATA,sha256=XvIQHj0EyUYlDZI9nLHa2Jn5Ta7dH2d_ngdbKXImTxU,20980
|
|
14
|
+
fastapi_sqla-3.4.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
15
|
+
fastapi_sqla-3.4.2.dist-info/entry_points.txt,sha256=haa0EueKcRo8-AlJTpHBMn08wMBiULNGA53nkvaDWj0,53
|
|
16
|
+
fastapi_sqla-3.4.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|