https://github.com/abhiaagarwal/asgi-sqlalchemy
ASGI Middleware that manages the lifespan of a database request
https://github.com/abhiaagarwal/asgi-sqlalchemy
asgi fastapi python sqlalchemy
Last synced: 5 months ago
JSON representation
ASGI Middleware that manages the lifespan of a database request
- Host: GitHub
- URL: https://github.com/abhiaagarwal/asgi-sqlalchemy
- Owner: abhiaagarwal
- License: mit
- Created: 2025-01-02T04:24:48.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-01-02T16:08:08.000Z (over 1 year ago)
- Last Synced: 2025-02-08T08:48:21.344Z (over 1 year ago)
- Topics: asgi, fastapi, python, sqlalchemy
- Language: Python
- Homepage: https://pypi.org/project/asgi-sqlalchemy/
- Size: 42 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# asgi-sqlalchemy
ASGI Middleware that manages the lifespan of a database engine and a corresponding session, featuring no global state, and automatic rollbacks on unhandled exceptions. Includes FastAPI and Starlette integrations.
I wrote about my motivations for this library in-depth [here](https://abhi.rodeo/posts/programming/languages/python/fastapi/globals-in-fastapi/), but the short version is that using the ASGI lifespan protocol, we can avoid the use of global state, making database access more predictable and easier to test/mock.
## Installation:
```bash
uv add asgi-sqlalchemy
```
## Usage:
### FastAPI:
```python
from contextlib import AsyncContextManager
from collections.abc import AsyncGenerator
from typing_extensions import TypedDict
from fastapi import FastAPI
from asgi_sqlalchemy import DatabaseContext, SessionMiddleware
from asgi_sqlalchemy.fastapi import SessionDependency
class AppState(TypedDict):
db: DatabaseContext
async def lifespan() -> AsyncGenerator[AppState]:
async with DatabaseContext(...) as db:
yield {"db": db}
app = FastAPI()
app.add_middleware(SessionMiddleware)
@app.get("/db")
async def handler(session: SessionDependency) -> str:
# do something with your async session!
```
### Starlette:
```python
from contextlib import AsyncContextManager
from collections.abc import AsyncGenerator
from typing_extensions import TypedDict
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.routing import Route
from asgi_sqlalchemy import DatabaseContext, SessionMiddleware
from asgi_sqlalchemy.starlette import get_session
class AppState(TypedDict):
db: DatabaseContext
async def lifespan() -> AsyncGenerator[AppState]:
async with DatabaseContext(...) as db:
yield {"db": db}
async def handler(request: Request) -> JSONResponse:
session = await get_session(request)
# do something with your async session!
app = Starlette(routes=[Route("/db", handler)], lifespan=lifespan)
app.add_middleware(SessionMiddleware)
```
### Tests:
This library was explicitly designed to be easy to test without dependency overrides.
- For synchronous tests, use FastAPI's `TestClient`.
- For asynchronous tests, use HTTPX `AsyncClient` with `ASGITransport`, and wrap the app with `LifespanManager` so startup/shutdown events run. See the FastAPI docs: [Async Tests – In Detail](https://fastapi.tiangolo.com/advanced/async-tests/?h=lifespanmanager#in-detail).
- To “mock” your database, provide a custom lifespan in tests that yields the database object you want (real DB or test double). The middleware will inject a fresh session per request, no dependency overrides needed.
See complete examples in [`tests/test_fastapi.py`](./tests/test_fastapi.py) and [`tests/test_starlette.py`](./tests/test_starlette.py) inside the [`tests/`](./tests/) folder.