fastapi-async-sqlalchemy 0.6.1__py3-none-any.whl → 0.7.0.dev2__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_async_sqlalchemy/__init__.py +1 -1
- fastapi_async_sqlalchemy/middleware.py +86 -22
- {fastapi_async_sqlalchemy-0.6.1.dist-info → fastapi_async_sqlalchemy-0.7.0.dev2.dist-info}/METADATA +26 -6
- fastapi_async_sqlalchemy-0.7.0.dev2.dist-info/RECORD +9 -0
- {fastapi_async_sqlalchemy-0.6.1.dist-info → fastapi_async_sqlalchemy-0.7.0.dev2.dist-info}/WHEEL +1 -1
- fastapi_async_sqlalchemy-0.6.1.dist-info/RECORD +0 -9
- {fastapi_async_sqlalchemy-0.6.1.dist-info → fastapi_async_sqlalchemy-0.7.0.dev2.dist-info}/LICENSE +0 -0
- {fastapi_async_sqlalchemy-0.6.1.dist-info → fastapi_async_sqlalchemy-0.7.0.dev2.dist-info}/top_level.txt +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
from contextvars import ContextVar
|
|
2
3
|
from typing import Dict, Optional, Union
|
|
3
4
|
|
|
4
5
|
from sqlalchemy.engine import Engine
|
|
5
6
|
from sqlalchemy.engine.url import URL
|
|
6
|
-
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
|
|
7
|
+
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
|
7
8
|
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
|
|
8
9
|
from starlette.requests import Request
|
|
9
10
|
from starlette.types import ASGIApp
|
|
@@ -11,17 +12,22 @@ from starlette.types import ASGIApp
|
|
|
11
12
|
from fastapi_async_sqlalchemy.exceptions import MissingSessionError, SessionNotInitialisedError
|
|
12
13
|
|
|
13
14
|
try:
|
|
14
|
-
from sqlalchemy.ext.asyncio import async_sessionmaker
|
|
15
|
+
from sqlalchemy.ext.asyncio import async_sessionmaker # noqa: F811
|
|
15
16
|
except ImportError:
|
|
16
17
|
from sqlalchemy.orm import sessionmaker as async_sessionmaker
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
def create_middleware_and_session_proxy():
|
|
20
21
|
_Session: Optional[async_sessionmaker] = None
|
|
22
|
+
_session: ContextVar[Optional[AsyncSession]] = ContextVar("_session", default=None)
|
|
23
|
+
_multi_sessions_ctx: ContextVar[bool] = ContextVar("_multi_sessions_context", default=False)
|
|
24
|
+
_task_session_ctx: ContextVar[Optional[AsyncSession]] = ContextVar(
|
|
25
|
+
"_task_session_ctx", default=None
|
|
26
|
+
)
|
|
27
|
+
_commit_on_exit_ctx: ContextVar[bool] = ContextVar("_commit_on_exit_ctx", default=False)
|
|
21
28
|
# Usage of context vars inside closures is not recommended, since they are not properly
|
|
22
29
|
# garbage collected, but in our use case context var is created on program startup and
|
|
23
30
|
# is used throughout the whole its lifecycle.
|
|
24
|
-
_session: ContextVar[Optional[AsyncSession]] = ContextVar("_session", default=None)
|
|
25
31
|
|
|
26
32
|
class SQLAlchemyMiddleware(BaseHTTPMiddleware):
|
|
27
33
|
def __init__(
|
|
@@ -61,38 +67,96 @@ def create_middleware_and_session_proxy():
|
|
|
61
67
|
if _Session is None:
|
|
62
68
|
raise SessionNotInitialisedError
|
|
63
69
|
|
|
64
|
-
|
|
65
|
-
if
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
multi_sessions = _multi_sessions_ctx.get()
|
|
71
|
+
if multi_sessions:
|
|
72
|
+
"""In this case, we need to create a new session for each task.
|
|
73
|
+
We also need to commit the session on exit if commit_on_exit is True.
|
|
74
|
+
This is useful when we need to run multiple queries in parallel.
|
|
75
|
+
For example, when we need to run multiple queries in parallel in a route handler.
|
|
76
|
+
Example:
|
|
77
|
+
```python
|
|
78
|
+
async with db(multi_sessions=True):
|
|
79
|
+
async def execute_query(query):
|
|
80
|
+
return await db.session.execute(text(query))
|
|
81
|
+
|
|
82
|
+
tasks = [
|
|
83
|
+
asyncio.create_task(execute_query("SELECT 1")),
|
|
84
|
+
asyncio.create_task(execute_query("SELECT 2")),
|
|
85
|
+
asyncio.create_task(execute_query("SELECT 3")),
|
|
86
|
+
asyncio.create_task(execute_query("SELECT 4")),
|
|
87
|
+
asyncio.create_task(execute_query("SELECT 5")),
|
|
88
|
+
asyncio.create_task(execute_query("SELECT 6")),
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
await asyncio.gather(*tasks)
|
|
92
|
+
```
|
|
93
|
+
"""
|
|
94
|
+
commit_on_exit = _commit_on_exit_ctx.get()
|
|
95
|
+
session = _task_session_ctx.get()
|
|
96
|
+
if session is None:
|
|
97
|
+
session = _Session()
|
|
98
|
+
_task_session_ctx.set(session)
|
|
99
|
+
|
|
100
|
+
async def cleanup():
|
|
101
|
+
try:
|
|
102
|
+
if commit_on_exit:
|
|
103
|
+
await session.commit()
|
|
104
|
+
except Exception:
|
|
105
|
+
await session.rollback()
|
|
106
|
+
raise
|
|
107
|
+
finally:
|
|
108
|
+
await session.close()
|
|
109
|
+
_task_session_ctx.set(None)
|
|
110
|
+
|
|
111
|
+
task = asyncio.current_task()
|
|
112
|
+
if task is not None:
|
|
113
|
+
task.add_done_callback(lambda t: asyncio.create_task(cleanup()))
|
|
114
|
+
return session
|
|
115
|
+
else:
|
|
116
|
+
session = _session.get()
|
|
117
|
+
if session is None:
|
|
118
|
+
raise MissingSessionError
|
|
119
|
+
return session
|
|
69
120
|
|
|
70
121
|
class DBSession(metaclass=DBSessionMeta):
|
|
71
|
-
def __init__(
|
|
122
|
+
def __init__(
|
|
123
|
+
self,
|
|
124
|
+
session_args: Dict = None,
|
|
125
|
+
commit_on_exit: bool = False,
|
|
126
|
+
multi_sessions: bool = False,
|
|
127
|
+
):
|
|
72
128
|
self.token = None
|
|
129
|
+
self.multi_sessions_token = None
|
|
130
|
+
self.commit_on_exit_token = None
|
|
73
131
|
self.session_args = session_args or {}
|
|
74
132
|
self.commit_on_exit = commit_on_exit
|
|
133
|
+
self.multi_sessions = multi_sessions
|
|
75
134
|
|
|
76
135
|
async def __aenter__(self):
|
|
77
136
|
if not isinstance(_Session, async_sessionmaker):
|
|
78
137
|
raise SessionNotInitialisedError
|
|
79
138
|
|
|
80
|
-
|
|
139
|
+
if self.multi_sessions:
|
|
140
|
+
self.multi_sessions_token = _multi_sessions_ctx.set(True)
|
|
141
|
+
self.commit_on_exit_token = _commit_on_exit_ctx.set(self.commit_on_exit)
|
|
142
|
+
else:
|
|
143
|
+
self.token = _session.set(_Session(**self.session_args))
|
|
81
144
|
return type(self)
|
|
82
145
|
|
|
83
146
|
async def __aexit__(self, exc_type, exc_value, traceback):
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
147
|
+
if self.multi_sessions:
|
|
148
|
+
_multi_sessions_ctx.reset(self.multi_sessions_token)
|
|
149
|
+
_commit_on_exit_ctx.reset(self.commit_on_exit_token)
|
|
150
|
+
else:
|
|
151
|
+
session = _session.get()
|
|
152
|
+
try:
|
|
153
|
+
if exc_type is not None:
|
|
154
|
+
await session.rollback()
|
|
155
|
+
elif self.commit_on_exit:
|
|
156
|
+
await session.commit()
|
|
157
|
+
finally:
|
|
158
|
+
await session.close()
|
|
159
|
+
_session.reset(self.token)
|
|
96
160
|
|
|
97
161
|
return SQLAlchemyMiddleware, DBSession
|
|
98
162
|
|
{fastapi_async_sqlalchemy-0.6.1.dist-info → fastapi_async_sqlalchemy-0.7.0.dev2.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fastapi-async-sqlalchemy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0.dev2
|
|
4
4
|
Summary: SQLAlchemy middleware for FastAPI
|
|
5
5
|
Home-page: https://github.com/h0rn3t/fastapi-async-sqlalchemy.git
|
|
6
6
|
Author: Eugene Shershen
|
|
@@ -8,7 +8,7 @@ Author-email: h0rn3t.null@gmail.com
|
|
|
8
8
|
License: MIT
|
|
9
9
|
Project-URL: Code, https://github.com/h0rn3t/fastapi-async-sqlalchemy
|
|
10
10
|
Project-URL: Issue tracker, https://github.com/h0rn3t/fastapi-async-sqlalchemy/issues
|
|
11
|
-
Classifier: Development Status ::
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
12
|
Classifier: Environment :: Web Environment
|
|
13
13
|
Classifier: Framework :: AsyncIO
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
@@ -19,6 +19,7 @@ Classifier: Programming Language :: Python :: 3.8
|
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.9
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.10
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
23
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
23
24
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
24
25
|
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
@@ -50,8 +51,8 @@ Provides SQLAlchemy middleware for FastAPI using AsyncSession and async engine.
|
|
|
50
51
|
pip install fastapi-async-sqlalchemy
|
|
51
52
|
```
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
|
|
55
|
+
It also works with ```sqlmodel```
|
|
55
56
|
|
|
56
57
|
|
|
57
58
|
### Examples
|
|
@@ -159,9 +160,10 @@ app.add_middleware(
|
|
|
159
160
|
routes.py
|
|
160
161
|
|
|
161
162
|
```python
|
|
163
|
+
import asyncio
|
|
164
|
+
|
|
162
165
|
from fastapi import APIRouter
|
|
163
|
-
from sqlalchemy import column
|
|
164
|
-
from sqlalchemy import table
|
|
166
|
+
from sqlalchemy import column, table, text
|
|
165
167
|
|
|
166
168
|
from databases import first_db, second_db
|
|
167
169
|
|
|
@@ -179,4 +181,22 @@ async def get_files_from_first_db():
|
|
|
179
181
|
async def get_files_from_second_db():
|
|
180
182
|
result = await second_db.session.execute(foo.select())
|
|
181
183
|
return result.fetchall()
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@router.get("/concurrent-queries")
|
|
187
|
+
async def parallel_select():
|
|
188
|
+
async with first_db(multi_sessions=True):
|
|
189
|
+
async def execute_query(query):
|
|
190
|
+
return await first_db.session.execute(text(query))
|
|
191
|
+
|
|
192
|
+
tasks = [
|
|
193
|
+
asyncio.create_task(execute_query("SELECT 1")),
|
|
194
|
+
asyncio.create_task(execute_query("SELECT 2")),
|
|
195
|
+
asyncio.create_task(execute_query("SELECT 3")),
|
|
196
|
+
asyncio.create_task(execute_query("SELECT 4")),
|
|
197
|
+
asyncio.create_task(execute_query("SELECT 5")),
|
|
198
|
+
asyncio.create_task(execute_query("SELECT 6")),
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
await asyncio.gather(*tasks)
|
|
182
202
|
```
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
fastapi_async_sqlalchemy/__init__.py,sha256=uBiMzBBYVuNC52ekhbIasBTLsIhSvVyXY2KXD8nOWuQ,143
|
|
2
|
+
fastapi_async_sqlalchemy/exceptions.py,sha256=dH3xjPE7B6kgq8L3LvxK-MsGZ_ZnW2WH2drLkK2oD-4,887
|
|
3
|
+
fastapi_async_sqlalchemy/middleware.py,sha256=z3lPmnIihKyRfy34qE2z-38KuxFSXnVVcy_rg_S6IwM,6900
|
|
4
|
+
fastapi_async_sqlalchemy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
fastapi_async_sqlalchemy-0.7.0.dev2.dist-info/LICENSE,sha256=OhSfEMY0WqZhq41M6mLmkuFloAG7ZJYKfryF2jyxkfY,1108
|
|
6
|
+
fastapi_async_sqlalchemy-0.7.0.dev2.dist-info/METADATA,sha256=ANbR70mznxKwilOJ4OmYPVQ0OjmntfTdXr-SFcbL2eU,6831
|
|
7
|
+
fastapi_async_sqlalchemy-0.7.0.dev2.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
|
|
8
|
+
fastapi_async_sqlalchemy-0.7.0.dev2.dist-info/top_level.txt,sha256=po-P5Tif35GmAaUR0mgZr5uBwN91iAgszaKI_r4ZTZA,25
|
|
9
|
+
fastapi_async_sqlalchemy-0.7.0.dev2.dist-info/RECORD,,
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
fastapi_async_sqlalchemy/__init__.py,sha256=9-OgxL-kLIvuXMLhRxyBsGN02_KN_CuINboiWANtvtg,138
|
|
2
|
-
fastapi_async_sqlalchemy/exceptions.py,sha256=dH3xjPE7B6kgq8L3LvxK-MsGZ_ZnW2WH2drLkK2oD-4,887
|
|
3
|
-
fastapi_async_sqlalchemy/middleware.py,sha256=pZLizk7Gvqf6X3FB83vlORL-aaA_gobPkFpGEGL9xF4,3781
|
|
4
|
-
fastapi_async_sqlalchemy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
fastapi_async_sqlalchemy-0.6.1.dist-info/LICENSE,sha256=OhSfEMY0WqZhq41M6mLmkuFloAG7ZJYKfryF2jyxkfY,1108
|
|
6
|
-
fastapi_async_sqlalchemy-0.6.1.dist-info/METADATA,sha256=CFYtBOEhWWl5_S9WIb38KR-DTIHI0RBnmOkT5J2eCGI,6162
|
|
7
|
-
fastapi_async_sqlalchemy-0.6.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
8
|
-
fastapi_async_sqlalchemy-0.6.1.dist-info/top_level.txt,sha256=po-P5Tif35GmAaUR0mgZr5uBwN91iAgszaKI_r4ZTZA,25
|
|
9
|
-
fastapi_async_sqlalchemy-0.6.1.dist-info/RECORD,,
|
{fastapi_async_sqlalchemy-0.6.1.dist-info → fastapi_async_sqlalchemy-0.7.0.dev2.dist-info}/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|