https://github.com/hadrien/fastsqla
Async SQLAlchemy 2.0+ extension for FastAPI with SQLModel support, built-in pagination & more
https://github.com/hadrien/fastsqla
asyncio fastapi fastapi-sqla fastapi-sqlalchemy pagination sqlalchemy sqlmodel
Last synced: 2 months ago
JSON representation
Async SQLAlchemy 2.0+ extension for FastAPI with SQLModel support, built-in pagination & more
- Host: GitHub
- URL: https://github.com/hadrien/fastsqla
- Owner: hadrien
- License: mit
- Created: 2024-06-26T19:34:49.000Z (12 months ago)
- Default Branch: main
- Last Pushed: 2025-02-18T16:46:45.000Z (4 months ago)
- Last Synced: 2025-03-31T13:19:33.694Z (3 months ago)
- Topics: asyncio, fastapi, fastapi-sqla, fastapi-sqlalchemy, pagination, sqlalchemy, sqlmodel
- Language: Python
- Homepage: https://hadrien.github.io/FastSQLA/
- Size: 476 KB
- Stars: 60
- Watchers: 1
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# FastSQLA
_Async SQLAlchemy 2.0+ for FastAPI — boilerplate, pagination, and seamless session management._
[](https://pypi.org/project/FastSQLA/)
[](https://github.com/hadrien/FastSQLA/actions?query=branch%3Amain+event%3Apush)
[](https://codecov.io/gh/hadrien/FastSQLA)
[](https://conventionalcommits.org)
[](https://github.com/hadrien/FastSQLA/blob/main/LICENSE)
[](https://montrealpython.org)**Documentation**: [https://hadrien.github.io/FastSQLA/](https://hadrien.github.io/FastSQLA/)
**Github Repo:** [https://github.com/hadrien/fastsqla](https://github.com/hadrien/fastsqla)
-----------------------------------------------------------------------------------------
`FastSQLA` is an async [`SQLAlchemy 2.0+`](https://docs.sqlalchemy.org/en/20/)
extension for [`FastAPI`](https://fastapi.tiangolo.com/) with built-in pagination,
[`SQLModel`](http://sqlmodel.tiangolo.com/) support and more.It streamlines the configuration and asynchronous connection to relational databases by
providing boilerplate and intuitive helpers. Additionally, it offers built-in
customizable pagination and automatically manages the `SQLAlchemy` session lifecycle
following [`SQLAlchemy`'s best practices](https://docs.sqlalchemy.org/en/20/orm/session_basics.html#when-do-i-construct-a-session-when-do-i-commit-it-and-when-do-i-close-it).## Features
* Easy setup at app startup using
[`FastAPI` Lifespan](https://fastapi.tiangolo.com/advanced/events/#lifespan):```python
from fastapi import FastAPI
from fastsqla import lifespanapp = FastAPI(lifespan=lifespan)
```* `SQLAlchemy` async session dependency:
```python
...
from fastsqla import Session
from sqlalchemy import select
...@app.get("/heros")
async def get_heros(session:Session):
stmt = select(...)
result = await session.execute(stmt)
...
```* `SQLAlchemy` async session with an async context manager:
```python
from fastsqla import open_sessionasync def background_job():
async with open_session() as session:
stmt = select(...)
result = await session.execute(stmt)
...
```* Built-in pagination:
```python
...
from fastsqla import Page, Paginate
from sqlalchemy import select
...@app.get("/heros", response_model=Page[HeroModel])
async def get_heros(paginate:Paginate):
return await paginate(select(Hero))
```
👇 `/heros?offset=10&limit=10` 👇
```json
{
"data": [
{
"name": "The Flash",
"secret_identity": "Barry Allen",
"id": 11
},
{
"name": "Green Lantern",
"secret_identity": "Hal Jordan",
"id": 12
}
],
"meta": {
"offset": 10,
"total_items": 12,
"total_pages": 2,
"page_number": 2
}
}
```* Pagination customization:
```python
...
from fastapi import Page, new_pagination
...Paginate = new_pagination(min_page_size=5, max_page_size=500)
@app.get("/heros", response_model=Page[HeroModel])
async def get_heros(paginate:Paginate):
return paginate(select(Hero))
```
* Session lifecycle management: session is commited on request success or rollback on
failure.* [`SQLModel`](http://sqlmodel.tiangolo.com/) support:
```python
...
from fastsqla import Item, Page, Paginate, Session
from sqlmodel import Field, SQLModel
...class Hero(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str
secret_identity: str
age: int@app.get("/heroes", response_model=Page[Hero])
async def get_heroes(paginate: Paginate):
return await paginate(select(Hero))@app.get("/heroes/{hero_id}", response_model=Item[Hero])
async def get_hero(session: Session, hero_id: int):
hero = await session.get(Hero, hero_id)
if hero is None:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND)
return {"data": hero}
```## Installing
Using [uv](https://docs.astral.sh/uv/):
```bash
uv add fastsqla
```Using [pip](https://pip.pypa.io/):
```
pip install fastsqla
```## Quick Example
### `example.py`
Let's write some tiny app in `example.py`:
```python
# example.py
from http import HTTPStatusfrom fastapi import FastAPI, HTTPException
from fastsqla import Base, Item, Page, Paginate, Session, lifespan
from pydantic import BaseModel, ConfigDict
from sqlalchemy import select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Mapped, mapped_columnapp = FastAPI(lifespan=lifespan)
class Hero(Base):
__tablename__ = "hero"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(unique=True)
secret_identity: Mapped[str]
age: Mapped[int]class HeroBase(BaseModel):
name: str
secret_identity: str
age: intclass HeroModel(HeroBase):
model_config = ConfigDict(from_attributes=True)
id: int@app.get("/heros", response_model=Page[HeroModel])
async def list_heros(paginate: Paginate):
stmt = select(Hero)
return await paginate(stmt)@app.get("/heros/{hero_id}", response_model=Item[HeroModel])
async def get_hero(hero_id: int, session: Session):
hero = await session.get(Hero, hero_id)
if hero is None:
raise HTTPException(HTTPStatus.NOT_FOUND, "Hero not found")
return {"data": hero}@app.post("/heros", response_model=Item[HeroModel])
async def create_hero(new_hero: HeroBase, session: Session):
hero = Hero(**new_hero.model_dump())
session.add(hero)
try:
await session.flush()
except IntegrityError:
raise HTTPException(HTTPStatus.CONFLICT, "Duplicate hero name")
return {"data": hero}
```### Database
💡 This example uses an `SQLite` database for simplicity: `FastSQLA` is compatible with
all asynchronous db drivers that `SQLAlchemy` is compatible with.Let's create an `SQLite` database using `sqlite3` and insert 12 rows in the `hero` table:
```bash
sqlite3 db.sqlite <