fastapi-async-sqlalchemy 0.7.0.dev5__tar.gz → 0.7.1.post1__tar.gz
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-0.7.0.dev5 → fastapi_async_sqlalchemy-0.7.1.post1}/PKG-INFO +2 -4
- fastapi_async_sqlalchemy-0.7.1.post1/fastapi_async_sqlalchemy/__init__.py +22 -0
- fastapi_async_sqlalchemy-0.7.1.post1/fastapi_async_sqlalchemy/middleware.py +211 -0
- {fastapi_async_sqlalchemy-0.7.0.dev5 → fastapi_async_sqlalchemy-0.7.1.post1}/fastapi_async_sqlalchemy.egg-info/PKG-INFO +2 -4
- {fastapi_async_sqlalchemy-0.7.0.dev5 → fastapi_async_sqlalchemy-0.7.1.post1}/fastapi_async_sqlalchemy.egg-info/SOURCES.txt +10 -1
- {fastapi_async_sqlalchemy-0.7.0.dev5 → fastapi_async_sqlalchemy-0.7.1.post1}/pyproject.toml +7 -1
- {fastapi_async_sqlalchemy-0.7.0.dev5 → fastapi_async_sqlalchemy-0.7.1.post1}/setup.py +1 -3
- {fastapi_async_sqlalchemy-0.7.0.dev5 → fastapi_async_sqlalchemy-0.7.1.post1}/tests/test_additional_coverage.py +34 -29
- fastapi_async_sqlalchemy-0.7.1.post1/tests/test_coverage_improvements.py +285 -0
- fastapi_async_sqlalchemy-0.7.1.post1/tests/test_custom_engine_branch.py +107 -0
- fastapi_async_sqlalchemy-0.7.1.post1/tests/test_edge_cases_coverage.py +305 -0
- fastapi_async_sqlalchemy-0.7.1.post1/tests/test_import_fallback_simulation.py +192 -0
- fastapi_async_sqlalchemy-0.7.1.post1/tests/test_import_fallbacks.py +82 -0
- fastapi_async_sqlalchemy-0.7.1.post1/tests/test_maximum_coverage.py +440 -0
- fastapi_async_sqlalchemy-0.7.1.post1/tests/test_multi_sessions_cleanup.py +89 -0
- fastapi_async_sqlalchemy-0.7.1.post1/tests/test_multisession_pool.py +82 -0
- fastapi_async_sqlalchemy-0.7.1.post1/tests/test_type_hints_compatibility.py +213 -0
- fastapi_async_sqlalchemy-0.7.0.dev5/fastapi_async_sqlalchemy/__init__.py +0 -9
- fastapi_async_sqlalchemy-0.7.0.dev5/fastapi_async_sqlalchemy/middleware.py +0 -172
- {fastapi_async_sqlalchemy-0.7.0.dev5 → fastapi_async_sqlalchemy-0.7.1.post1}/LICENSE +0 -0
- {fastapi_async_sqlalchemy-0.7.0.dev5 → fastapi_async_sqlalchemy-0.7.1.post1}/README.md +0 -0
- {fastapi_async_sqlalchemy-0.7.0.dev5 → fastapi_async_sqlalchemy-0.7.1.post1}/fastapi_async_sqlalchemy/exceptions.py +0 -0
- {fastapi_async_sqlalchemy-0.7.0.dev5 → fastapi_async_sqlalchemy-0.7.1.post1}/fastapi_async_sqlalchemy/py.typed +0 -0
- {fastapi_async_sqlalchemy-0.7.0.dev5 → fastapi_async_sqlalchemy-0.7.1.post1}/fastapi_async_sqlalchemy.egg-info/dependency_links.txt +0 -0
- {fastapi_async_sqlalchemy-0.7.0.dev5 → fastapi_async_sqlalchemy-0.7.1.post1}/fastapi_async_sqlalchemy.egg-info/not-zip-safe +0 -0
- {fastapi_async_sqlalchemy-0.7.0.dev5 → fastapi_async_sqlalchemy-0.7.1.post1}/fastapi_async_sqlalchemy.egg-info/requires.txt +0 -0
- {fastapi_async_sqlalchemy-0.7.0.dev5 → fastapi_async_sqlalchemy-0.7.1.post1}/fastapi_async_sqlalchemy.egg-info/top_level.txt +0 -0
- {fastapi_async_sqlalchemy-0.7.0.dev5 → fastapi_async_sqlalchemy-0.7.1.post1}/setup.cfg +0 -0
- {fastapi_async_sqlalchemy-0.7.0.dev5 → fastapi_async_sqlalchemy-0.7.1.post1}/tests/test_coverage_boost.py +0 -0
- {fastapi_async_sqlalchemy-0.7.0.dev5 → fastapi_async_sqlalchemy-0.7.1.post1}/tests/test_session.py +0 -0
- {fastapi_async_sqlalchemy-0.7.0.dev5 → fastapi_async_sqlalchemy-0.7.1.post1}/tests/test_sqlmodel.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-async-sqlalchemy
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.1.post1
|
|
4
4
|
Summary: SQLAlchemy middleware for FastAPI
|
|
5
5
|
Home-page: https://github.com/h0rn3t/fastapi-async-sqlalchemy.git
|
|
6
6
|
Author: Eugene Shershen
|
|
@@ -14,8 +14,6 @@ Classifier: Framework :: AsyncIO
|
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: License :: OSI Approved :: MIT License
|
|
16
16
|
Classifier: Operating System :: OS Independent
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
19
17
|
Classifier: Programming Language :: Python :: 3.9
|
|
20
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
21
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -25,7 +23,7 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
|
25
23
|
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
26
24
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
27
25
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
28
|
-
Requires-Python: >=3.
|
|
26
|
+
Requires-Python: >=3.9
|
|
29
27
|
Description-Content-Type: text/markdown
|
|
30
28
|
License-File: LICENSE
|
|
31
29
|
Requires-Dist: starlette>=0.13.6
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from fastapi_async_sqlalchemy.middleware import (
|
|
2
|
+
SQLAlchemyMiddleware,
|
|
3
|
+
create_middleware_and_session_proxy,
|
|
4
|
+
db,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
# Export DBSessionMeta type for type hints (Issue #18)
|
|
8
|
+
# Note: DBSessionMeta is the metaclass of db, created dynamically.
|
|
9
|
+
# It can be used in runtime type checks (isinstance, type(db) is DBSessionMeta)
|
|
10
|
+
# but mypy may show warnings when used in type annotations due to its dynamic nature.
|
|
11
|
+
DBSessionMeta = type(db)
|
|
12
|
+
DBSessionType = DBSessionMeta # Alternative name for backwards compatibility
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"db",
|
|
16
|
+
"SQLAlchemyMiddleware",
|
|
17
|
+
"create_middleware_and_session_proxy",
|
|
18
|
+
"DBSessionMeta",
|
|
19
|
+
"DBSessionType",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
__version__ = "0.7.1.post1"
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from contextvars import ContextVar
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Optional, Union
|
|
5
|
+
|
|
6
|
+
from sqlalchemy.engine.url import URL
|
|
7
|
+
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine
|
|
8
|
+
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
|
|
9
|
+
from starlette.requests import Request
|
|
10
|
+
from starlette.types import ASGIApp
|
|
11
|
+
|
|
12
|
+
from fastapi_async_sqlalchemy.exceptions import (
|
|
13
|
+
MissingSessionError,
|
|
14
|
+
SessionNotInitialisedError,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from sqlalchemy.ext.asyncio import async_sessionmaker
|
|
19
|
+
except ImportError:
|
|
20
|
+
from sqlalchemy.orm import sessionmaker as async_sessionmaker # type: ignore
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from sqlmodel.ext.asyncio.session import AsyncSession as SQLModelAsyncSession
|
|
24
|
+
|
|
25
|
+
DefaultAsyncSession: type[AsyncSession] = SQLModelAsyncSession # type: ignore
|
|
26
|
+
except ImportError:
|
|
27
|
+
DefaultAsyncSession: type[AsyncSession] = AsyncSession # type: ignore
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(slots=True)
|
|
31
|
+
class MultiSessionState:
|
|
32
|
+
"""State for multi_sessions mode."""
|
|
33
|
+
|
|
34
|
+
tracked: set[AsyncSession] = field(default_factory=set)
|
|
35
|
+
task_sessions: dict[int, AsyncSession] = field(default_factory=dict)
|
|
36
|
+
cleanup_tasks: list[asyncio.Task] = field(default_factory=list)
|
|
37
|
+
parent_task_id: int = 0
|
|
38
|
+
commit_on_exit: bool = False
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def create_middleware_and_session_proxy() -> tuple:
|
|
42
|
+
_Session: Optional[async_sessionmaker] = None
|
|
43
|
+
_session: ContextVar[Optional[AsyncSession]] = ContextVar("_session", default=None)
|
|
44
|
+
_multi_state: ContextVar[Optional[MultiSessionState]] = ContextVar("_multi_state", default=None)
|
|
45
|
+
|
|
46
|
+
class _SQLAlchemyMiddleware(BaseHTTPMiddleware):
|
|
47
|
+
__test__ = False # Prevent pytest from collecting this as a test class
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
app: ASGIApp,
|
|
52
|
+
db_url: Optional[Union[str, URL]] = None,
|
|
53
|
+
custom_engine: Optional[AsyncEngine] = None,
|
|
54
|
+
engine_args: Optional[dict] = None,
|
|
55
|
+
session_args: Optional[dict] = None,
|
|
56
|
+
commit_on_exit: bool = False,
|
|
57
|
+
):
|
|
58
|
+
super().__init__(app)
|
|
59
|
+
self.commit_on_exit = commit_on_exit
|
|
60
|
+
engine_args = engine_args or {}
|
|
61
|
+
session_args = session_args or {}
|
|
62
|
+
|
|
63
|
+
if not custom_engine and not db_url:
|
|
64
|
+
raise ValueError("You need to pass a db_url or a custom_engine parameter.")
|
|
65
|
+
if custom_engine:
|
|
66
|
+
engine = custom_engine
|
|
67
|
+
else:
|
|
68
|
+
engine = create_async_engine(db_url, **engine_args)
|
|
69
|
+
|
|
70
|
+
nonlocal _Session
|
|
71
|
+
_Session = async_sessionmaker(
|
|
72
|
+
engine,
|
|
73
|
+
class_=DefaultAsyncSession,
|
|
74
|
+
expire_on_commit=False,
|
|
75
|
+
**session_args,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint):
|
|
79
|
+
async with DBSession(commit_on_exit=self.commit_on_exit):
|
|
80
|
+
return await call_next(request)
|
|
81
|
+
|
|
82
|
+
class DBSessionMeta(type):
|
|
83
|
+
@property
|
|
84
|
+
def session(self) -> AsyncSession:
|
|
85
|
+
"""Return an instance of Session local to the current async context."""
|
|
86
|
+
if _Session is None:
|
|
87
|
+
raise SessionNotInitialisedError
|
|
88
|
+
|
|
89
|
+
state = _multi_state.get()
|
|
90
|
+
if state is not None:
|
|
91
|
+
task = asyncio.current_task()
|
|
92
|
+
if task is None:
|
|
93
|
+
raise RuntimeError("Cannot get current task")
|
|
94
|
+
task_id = id(task)
|
|
95
|
+
|
|
96
|
+
if task_id in state.task_sessions:
|
|
97
|
+
return state.task_sessions[task_id]
|
|
98
|
+
|
|
99
|
+
session = _Session()
|
|
100
|
+
state.task_sessions[task_id] = session
|
|
101
|
+
state.tracked.add(session)
|
|
102
|
+
|
|
103
|
+
# Add cleanup callback only for child tasks
|
|
104
|
+
if task_id != state.parent_task_id:
|
|
105
|
+
|
|
106
|
+
def cleanup_callback(_task):
|
|
107
|
+
try:
|
|
108
|
+
loop = asyncio.get_running_loop()
|
|
109
|
+
if loop.is_closed():
|
|
110
|
+
return
|
|
111
|
+
except RuntimeError:
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
async def cleanup():
|
|
115
|
+
try:
|
|
116
|
+
if state.commit_on_exit:
|
|
117
|
+
try:
|
|
118
|
+
await session.commit()
|
|
119
|
+
except Exception:
|
|
120
|
+
await session.rollback()
|
|
121
|
+
finally:
|
|
122
|
+
await session.close()
|
|
123
|
+
state.tracked.discard(session)
|
|
124
|
+
state.task_sessions.pop(task_id, None)
|
|
125
|
+
|
|
126
|
+
t = loop.create_task(cleanup())
|
|
127
|
+
state.cleanup_tasks.append(t)
|
|
128
|
+
|
|
129
|
+
task.add_done_callback(cleanup_callback)
|
|
130
|
+
|
|
131
|
+
return session
|
|
132
|
+
else:
|
|
133
|
+
session = _session.get()
|
|
134
|
+
if session is None:
|
|
135
|
+
raise MissingSessionError
|
|
136
|
+
return session
|
|
137
|
+
|
|
138
|
+
class DBSession(metaclass=DBSessionMeta):
|
|
139
|
+
def __init__(
|
|
140
|
+
self,
|
|
141
|
+
session_args: Optional[dict] = None,
|
|
142
|
+
commit_on_exit: bool = False,
|
|
143
|
+
multi_sessions: bool = False,
|
|
144
|
+
):
|
|
145
|
+
self.token = None
|
|
146
|
+
self.multi_state_token = None
|
|
147
|
+
self.session_args = session_args or {}
|
|
148
|
+
self.commit_on_exit = commit_on_exit
|
|
149
|
+
self.multi_sessions = multi_sessions
|
|
150
|
+
|
|
151
|
+
async def __aenter__(self):
|
|
152
|
+
if not isinstance(_Session, async_sessionmaker):
|
|
153
|
+
raise SessionNotInitialisedError
|
|
154
|
+
|
|
155
|
+
if self.multi_sessions:
|
|
156
|
+
state = MultiSessionState(
|
|
157
|
+
parent_task_id=id(asyncio.current_task()),
|
|
158
|
+
commit_on_exit=self.commit_on_exit,
|
|
159
|
+
)
|
|
160
|
+
self.multi_state_token = _multi_state.set(state)
|
|
161
|
+
else:
|
|
162
|
+
self.token = _session.set(_Session(**self.session_args))
|
|
163
|
+
return type(self)
|
|
164
|
+
|
|
165
|
+
async def __aexit__(self, exc_type, exc_value, traceback):
|
|
166
|
+
if self.multi_sessions:
|
|
167
|
+
state = _multi_state.get()
|
|
168
|
+
|
|
169
|
+
# Wait for cleanup tasks
|
|
170
|
+
if state.cleanup_tasks:
|
|
171
|
+
await asyncio.sleep(0)
|
|
172
|
+
await asyncio.gather(*state.cleanup_tasks, return_exceptions=True)
|
|
173
|
+
|
|
174
|
+
# Clean up remaining sessions
|
|
175
|
+
for session in list(state.tracked):
|
|
176
|
+
try:
|
|
177
|
+
if exc_type is not None:
|
|
178
|
+
await session.rollback()
|
|
179
|
+
elif self.commit_on_exit:
|
|
180
|
+
try:
|
|
181
|
+
await session.commit()
|
|
182
|
+
except Exception:
|
|
183
|
+
await session.rollback()
|
|
184
|
+
except Exception:
|
|
185
|
+
pass
|
|
186
|
+
finally:
|
|
187
|
+
try:
|
|
188
|
+
await session.close()
|
|
189
|
+
except Exception:
|
|
190
|
+
pass
|
|
191
|
+
|
|
192
|
+
_multi_state.reset(self.multi_state_token)
|
|
193
|
+
else:
|
|
194
|
+
session = _session.get()
|
|
195
|
+
try:
|
|
196
|
+
if exc_type is not None:
|
|
197
|
+
await session.rollback()
|
|
198
|
+
elif self.commit_on_exit:
|
|
199
|
+
try:
|
|
200
|
+
await session.commit()
|
|
201
|
+
except Exception:
|
|
202
|
+
await session.rollback()
|
|
203
|
+
raise
|
|
204
|
+
finally:
|
|
205
|
+
await session.close()
|
|
206
|
+
_session.reset(self.token)
|
|
207
|
+
|
|
208
|
+
return _SQLAlchemyMiddleware, DBSession
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
SQLAlchemyMiddleware, db = create_middleware_and_session_proxy()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-async-sqlalchemy
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.1.post1
|
|
4
4
|
Summary: SQLAlchemy middleware for FastAPI
|
|
5
5
|
Home-page: https://github.com/h0rn3t/fastapi-async-sqlalchemy.git
|
|
6
6
|
Author: Eugene Shershen
|
|
@@ -14,8 +14,6 @@ Classifier: Framework :: AsyncIO
|
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: License :: OSI Approved :: MIT License
|
|
16
16
|
Classifier: Operating System :: OS Independent
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
19
17
|
Classifier: Programming Language :: Python :: 3.9
|
|
20
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
21
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -25,7 +23,7 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
|
25
23
|
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
|
|
26
24
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
27
25
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
28
|
-
Requires-Python: >=3.
|
|
26
|
+
Requires-Python: >=3.9
|
|
29
27
|
Description-Content-Type: text/markdown
|
|
30
28
|
License-File: LICENSE
|
|
31
29
|
Requires-Dist: starlette>=0.13.6
|
|
@@ -14,5 +14,14 @@ fastapi_async_sqlalchemy.egg-info/requires.txt
|
|
|
14
14
|
fastapi_async_sqlalchemy.egg-info/top_level.txt
|
|
15
15
|
tests/test_additional_coverage.py
|
|
16
16
|
tests/test_coverage_boost.py
|
|
17
|
+
tests/test_coverage_improvements.py
|
|
18
|
+
tests/test_custom_engine_branch.py
|
|
19
|
+
tests/test_edge_cases_coverage.py
|
|
20
|
+
tests/test_import_fallback_simulation.py
|
|
21
|
+
tests/test_import_fallbacks.py
|
|
22
|
+
tests/test_maximum_coverage.py
|
|
23
|
+
tests/test_multi_sessions_cleanup.py
|
|
24
|
+
tests/test_multisession_pool.py
|
|
17
25
|
tests/test_session.py
|
|
18
|
-
tests/test_sqlmodel.py
|
|
26
|
+
tests/test_sqlmodel.py
|
|
27
|
+
tests/test_type_hints_compatibility.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.ruff]
|
|
2
2
|
line-length = 100
|
|
3
|
-
target-version = "
|
|
3
|
+
target-version = "py39"
|
|
4
4
|
exclude = [
|
|
5
5
|
".git",
|
|
6
6
|
".venv",
|
|
@@ -31,3 +31,9 @@ line-ending = "auto"
|
|
|
31
31
|
[tool.ruff.lint.isort]
|
|
32
32
|
combine-as-imports = true
|
|
33
33
|
split-on-trailing-comma = true
|
|
34
|
+
|
|
35
|
+
[tool.pytest.ini_options]
|
|
36
|
+
filterwarnings = [
|
|
37
|
+
"ignore::DeprecationWarning",
|
|
38
|
+
"ignore:The garbage collector is trying to clean up:sqlalchemy.exc.SAWarning",
|
|
39
|
+
]
|
|
@@ -26,7 +26,7 @@ setup(
|
|
|
26
26
|
packages=["fastapi_async_sqlalchemy"],
|
|
27
27
|
package_data={"fastapi_async_sqlalchemy": ["py.typed"]},
|
|
28
28
|
zip_safe=False,
|
|
29
|
-
python_requires=">=3.
|
|
29
|
+
python_requires=">=3.9",
|
|
30
30
|
install_requires=["starlette>=0.13.6", "SQLAlchemy>=1.4.19"],
|
|
31
31
|
classifiers=[
|
|
32
32
|
"Development Status :: 5 - Production/Stable",
|
|
@@ -35,8 +35,6 @@ setup(
|
|
|
35
35
|
"Intended Audience :: Developers",
|
|
36
36
|
"License :: OSI Approved :: MIT License",
|
|
37
37
|
"Operating System :: OS Independent",
|
|
38
|
-
"Programming Language :: Python :: 3.7",
|
|
39
|
-
"Programming Language :: Python :: 3.8",
|
|
40
38
|
"Programming Language :: Python :: 3.9",
|
|
41
39
|
"Programming Language :: Python :: 3.10",
|
|
42
40
|
"Programming Language :: Python :: 3.11",
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Additional tests to reach target coverage of 97.22%
|
|
3
3
|
"""
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
from fastapi import FastAPI
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def test_commit_on_exit_parameter():
|
|
9
9
|
"""Test commit_on_exit parameter in middleware initialization"""
|
|
10
10
|
from sqlalchemy.ext.asyncio import create_async_engine
|
|
11
|
+
|
|
11
12
|
from fastapi_async_sqlalchemy.middleware import create_middleware_and_session_proxy
|
|
12
|
-
|
|
13
|
+
|
|
13
14
|
SQLAlchemyMiddleware, db = create_middleware_and_session_proxy()
|
|
14
15
|
app = FastAPI()
|
|
15
|
-
|
|
16
|
+
|
|
16
17
|
# Test commit_on_exit=True
|
|
17
18
|
custom_engine = create_async_engine("sqlite+aiosqlite://")
|
|
18
19
|
middleware = SQLAlchemyMiddleware(app, custom_engine=custom_engine, commit_on_exit=True)
|
|
19
20
|
assert middleware.commit_on_exit is True
|
|
20
|
-
|
|
21
|
+
|
|
21
22
|
# Test commit_on_exit=False (default)
|
|
22
23
|
middleware2 = SQLAlchemyMiddleware(app, custom_engine=custom_engine, commit_on_exit=False)
|
|
23
24
|
assert middleware2.commit_on_exit is False
|
|
@@ -26,33 +27,30 @@ def test_commit_on_exit_parameter():
|
|
|
26
27
|
def test_exception_classes_simple():
|
|
27
28
|
"""Test exception classes are properly defined"""
|
|
28
29
|
from fastapi_async_sqlalchemy.exceptions import MissingSessionError, SessionNotInitialisedError
|
|
29
|
-
|
|
30
|
+
|
|
30
31
|
# Test exception instantiation without parameters
|
|
31
32
|
missing_error = MissingSessionError()
|
|
32
33
|
assert isinstance(missing_error, Exception)
|
|
33
|
-
|
|
34
|
+
|
|
34
35
|
init_error = SessionNotInitialisedError()
|
|
35
36
|
assert isinstance(init_error, Exception)
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
def test_middleware_properties():
|
|
39
40
|
"""Test middleware properties and methods"""
|
|
40
|
-
from fastapi_async_sqlalchemy.middleware import create_middleware_and_session_proxy
|
|
41
|
-
from sqlalchemy.ext.asyncio import create_async_engine
|
|
42
41
|
from fastapi import FastAPI
|
|
43
|
-
|
|
42
|
+
from sqlalchemy.ext.asyncio import create_async_engine
|
|
43
|
+
|
|
44
|
+
from fastapi_async_sqlalchemy.middleware import create_middleware_and_session_proxy
|
|
45
|
+
|
|
44
46
|
SQLAlchemyMiddleware, db = create_middleware_and_session_proxy()
|
|
45
47
|
app = FastAPI()
|
|
46
|
-
|
|
48
|
+
|
|
47
49
|
# Test middleware properties
|
|
48
50
|
custom_engine = create_async_engine("sqlite+aiosqlite://")
|
|
49
|
-
middleware = SQLAlchemyMiddleware(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
commit_on_exit=True
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
assert hasattr(middleware, 'commit_on_exit')
|
|
51
|
+
middleware = SQLAlchemyMiddleware(app, custom_engine=custom_engine, commit_on_exit=True)
|
|
52
|
+
|
|
53
|
+
assert hasattr(middleware, "commit_on_exit")
|
|
56
54
|
assert middleware.commit_on_exit is True
|
|
57
55
|
|
|
58
56
|
|
|
@@ -60,41 +58,48 @@ def test_basic_imports():
|
|
|
60
58
|
"""Test basic imports and module structure"""
|
|
61
59
|
# Test main module imports
|
|
62
60
|
from fastapi_async_sqlalchemy import SQLAlchemyMiddleware, db
|
|
61
|
+
|
|
63
62
|
assert SQLAlchemyMiddleware is not None
|
|
64
63
|
assert db is not None
|
|
65
|
-
|
|
64
|
+
|
|
66
65
|
# Test exception imports
|
|
67
66
|
from fastapi_async_sqlalchemy.exceptions import MissingSessionError, SessionNotInitialisedError
|
|
68
|
-
|
|
67
|
+
|
|
68
|
+
assert MissingSessionError is not None
|
|
69
69
|
assert SessionNotInitialisedError is not None
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
# Test middleware module imports
|
|
72
|
-
from fastapi_async_sqlalchemy.middleware import
|
|
72
|
+
from fastapi_async_sqlalchemy.middleware import (
|
|
73
|
+
DefaultAsyncSession,
|
|
74
|
+
create_middleware_and_session_proxy,
|
|
75
|
+
)
|
|
76
|
+
|
|
73
77
|
assert create_middleware_and_session_proxy is not None
|
|
74
78
|
assert DefaultAsyncSession is not None
|
|
75
79
|
|
|
76
80
|
|
|
77
81
|
def test_middleware_factory_different_instances():
|
|
78
82
|
"""Test creating multiple middleware/db instances"""
|
|
79
|
-
from fastapi_async_sqlalchemy.middleware import create_middleware_and_session_proxy
|
|
80
83
|
from fastapi import FastAPI
|
|
81
84
|
from sqlalchemy.ext.asyncio import create_async_engine
|
|
82
|
-
|
|
85
|
+
|
|
86
|
+
from fastapi_async_sqlalchemy.middleware import create_middleware_and_session_proxy
|
|
87
|
+
|
|
83
88
|
# Create first instance
|
|
84
89
|
SQLAlchemyMiddleware1, db1 = create_middleware_and_session_proxy()
|
|
85
|
-
|
|
90
|
+
|
|
86
91
|
# Create second instance
|
|
87
92
|
SQLAlchemyMiddleware2, db2 = create_middleware_and_session_proxy()
|
|
88
|
-
|
|
93
|
+
|
|
89
94
|
# They should be different instances
|
|
90
95
|
assert SQLAlchemyMiddleware1 is not SQLAlchemyMiddleware2
|
|
91
96
|
assert db1 is not db2
|
|
92
|
-
|
|
97
|
+
|
|
93
98
|
# Test both instances work
|
|
94
99
|
app = FastAPI()
|
|
95
100
|
engine = create_async_engine("sqlite+aiosqlite://")
|
|
96
|
-
|
|
101
|
+
|
|
97
102
|
middleware1 = SQLAlchemyMiddleware1(app, custom_engine=engine)
|
|
98
103
|
middleware2 = SQLAlchemyMiddleware2(app, custom_engine=engine)
|
|
99
|
-
|
|
100
|
-
assert middleware1 is not middleware2
|
|
104
|
+
|
|
105
|
+
assert middleware1 is not middleware2
|