Skip to content

SQLAlchemy extension for FastAPI with built-in pagination and asyncio support

License

Notifications You must be signed in to change notification settings

hadrien/FastSQLA

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🚀 FastSQLA

PyPI - Version Conventional Commits codecov

FastSQLA is an SQLAlchemy extension for FastAPI. It supports asynchronous SQLAlchemy sessions and includes built-in custimizable pagination.

Features

Automatic SQLAlchemy configuration at app startup.

Using FastAPI Lifespan:

from fastapi import FastAPI
from fastsqla import lifespan

app = FastAPI(lifespan=lifespan)
Async SQLAlchemy session as a FastAPI dependency.
...
from fastsqla import Session
from sqlalchemy import select
...

@app.get("/heros")
async def get_heros(session:Session):
    stmt = select(...)
    result = await session.execute(stmt)
    ...
Built-in pagination.
...
from fastsqla import Page, Paginate
from sqlalchemy import select
...

@app.get("/heros", response_model=Page[HeroModel])
async def get_heros(paginate:Paginate):
    return paginate(select(Hero))
Allows pagination customization.
...
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))

And more ...

Installing

Using uv:

uv add fastsqla

Using pip:

pip install fastsqla

Quick Example

# example.py
from http import HTTPStatus

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, ConfigDict
from sqlalchemy import select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Mapped, mapped_column

from fastsqla import Base, Item, Page, Paginate, Session, lifespan

app = 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]


class HeroBase(BaseModel):
    name: str
    secret_identity: str


class HeroModel(HeroBase):
    model_config = ConfigDict(from_attributes=True)
    id: int


@app.get("/heros", response_model=Page[HeroModel])
async def list_users(paginate: Paginate):
    return await paginate(select(Hero))


@app.get("/heros/{hero_id}", response_model=Item[HeroModel])
async def get_user(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_user(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}

Note

Sqlite is used for the sake of the example. FastSQLA is compatible with all async db drivers that SQLAlchemy is compatible with.

Create an sqlite3 db:
sqlite3 db.sqlite <<EOF
CREATE TABLE hero (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    name            TEXT NOT NULL UNIQUE, -- Hero name (e.g., Superman)
    secret_identity TEXT NOT NULL         -- Secret identity (e.g., Clark Kent)
);

-- Insert heroes with hero name and secret identity
INSERT INTO hero (name, secret_identity) VALUES ('Superman', 'Clark Kent');
INSERT INTO hero (name, secret_identity) VALUES ('Batman', 'Bruce Wayne');
INSERT INTO hero (name, secret_identity) VALUES ('Wonder Woman', 'Diana Prince');
INSERT INTO hero (name, secret_identity) VALUES ('Iron Man', 'Tony Stark');
INSERT INTO hero (name, secret_identity) VALUES ('Spider-Man', 'Peter Parker');
INSERT INTO hero (name, secret_identity) VALUES ('Captain America', 'Steve Rogers');
INSERT INTO hero (name, secret_identity) VALUES ('Black Widow', 'Natasha Romanoff');
INSERT INTO hero (name, secret_identity) VALUES ('Thor', 'Thor Odinson');
INSERT INTO hero (name, secret_identity) VALUES ('Scarlet Witch', 'Wanda Maximoff');
INSERT INTO hero (name, secret_identity) VALUES ('Doctor Strange', 'Stephen Strange');
INSERT INTO hero (name, secret_identity) VALUES ('The Flash', 'Barry Allen');
INSERT INTO hero (name, secret_identity) VALUES ('Green Lantern', 'Hal Jordan');
EOF
Install dependencies & run the app
pip install uvicorn aiosqlite fastsqla
sqlalchemy_url=sqlite+aiosqlite:///db.sqlite?check_same_thread=false uvicorn example:app

Execute GET /heros?offset=10:

curl -X 'GET' \
'http://127.0.0.1:8000/heros?offset=10&limit=10' \
-H 'accept: application/json'

Returns:

{
  "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
  }
}

About

SQLAlchemy extension for FastAPI with built-in pagination and asyncio support

Resources

License

Stars

Watchers

Forks

Languages