Skip to main content

FastSQLA

Async SQLAlchemy 2.0+ for FastAPI — boilerplate, pagination, and seamless session management.

PyPI - Version GitHub Actions Workflow Status Codecov Conventional Commits GitHub License 🍁 With love from Canada

Documentation: https://hadrien.github.io/FastSQLA/

Github Repo: https://github.com/hadrien/fastsqla


FastSQLA is an async SQLAlchemy 2.0+ extension for FastAPI with built-in pagination, SQLModel 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.

Features

  • Easy setup at app startup using FastAPI Lifespan:

    from fastapi import FastAPI
    from fastsqla import lifespan
    
    app = FastAPI(lifespan=lifespan)
    
  • SQLAlchemy async session 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)
        ...
    
  • SQLAlchemy async session with an async context manager:

    from fastsqla import open_session
    
    async def background_job():
        async with open_session() as session:
            stmt = select(...)
            result = await session.execute(stmt)
            ...
    
  • Built-in pagination with offset/limit:

    ...
    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 👇

    {
      "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
      }
    }
    
  • Forward cursor pagination for "Load more" lists:

    from fastsqla.cursor import Page, Paginate
    
    @app.get("/heros")
    async def get_heros(paginate: Paginate[Hero]) -> Page[HeroModel]:
        return await paginate(select(Hero).order_by(Hero.id))
    

    👇 /heros?limit=2&next_cursor=eyJ2IjoxLCJvcmRlciI6W1siaGVybyIsImlkIixmYWxzZSwiaW50Il1dLCJ2YWx1ZXMiOlsyXX0 👇

    {
      "data": [
        {
          "name": "Superman",
          "secret_identity": "Clark Kent",
          "id": 3
        },
        {
          "name": "Batman",
          "secret_identity": "Bruce Wayne",
          "id": 4
        }
      ],
      "meta": {
        "next_cursor": "eyJ2IjoxLCJvcmRlciI6W1siaGVybyIsImlkIixmYWxzZSwiaW50Il1dLCJ2YWx1ZXMiOls0XX0"
      }
    }
    

    Pass meta.next_cursor as next_cursor in the next query string. A null cursor means the end of the results.

  • Pagination customization:

    from typing import Annotated
    
    from fastapi import Depends
    from fastsqla import Page, PaginateType, new_pagination
    
    CustomPaginate = Annotated[
        PaginateType[HeroModel],
        Depends(new_pagination(default_page_size=5, max_page_size=500)),
    ]
    
    @app.get("/heroes", response_model=Page[HeroModel])
    async def get_heroes(paginate: CustomPaginate):
        return await paginate(select(Hero))
    
  • Session lifecycle management: session is commited on request success or rollback on failure.

  • SQLModel support:

    ...
    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:

uv add fastsqla

Using pip:

pip install fastsqla

Quick Example

example.py

Let's write some tiny app in example.py:

# example.py
from http import HTTPStatus

from 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_column


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]
    age: Mapped[int]


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


class 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:

sqlite3 db.sqlite <<EOF
-- Create Table hero
CREATE TABLE hero (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    name            TEXT NOT NULL UNIQUE, -- Unique hero name (e.g., Superman)
    secret_identity TEXT NOT NULL,        -- Secret identity (e.g., Clark Kent)
    age             INTEGER NOT NULL      -- Age of the hero (e.g., 30)
);

-- Insert heroes with their name, secret identity, and age
INSERT INTO hero (name, secret_identity, age) VALUES ('Superman',        'Clark Kent',       30);
INSERT INTO hero (name, secret_identity, age) VALUES ('Batman',          'Bruce Wayne',      35);
INSERT INTO hero (name, secret_identity, age) VALUES ('Wonder Woman',    'Diana Prince',     30);
INSERT INTO hero (name, secret_identity, age) VALUES ('Iron Man',        'Tony Stark',       45);
INSERT INTO hero (name, secret_identity, age) VALUES ('Spider-Man',      'Peter Parker',     25);
INSERT INTO hero (name, secret_identity, age) VALUES ('Captain America', 'Steve Rogers',     100);
INSERT INTO hero (name, secret_identity, age) VALUES ('Black Widow',     'Natasha Romanoff', 35);
INSERT INTO hero (name, secret_identity, age) VALUES ('Thor',            'Thor Odinson',     1500);
INSERT INTO hero (name, secret_identity, age) VALUES ('Scarlet Witch',   'Wanda Maximoff',   30);
INSERT INTO hero (name, secret_identity, age) VALUES ('Doctor Strange',  'Stephen Strange',  40);
INSERT INTO hero (name, secret_identity, age) VALUES ('The Flash',       'Barry Allen',      28);
INSERT INTO hero (name, secret_identity, age) VALUES ('Green Lantern',   'Hal Jordan',       35);
EOF

Run the app

Let's install required dependencies:

pip install uvicorn aiosqlite fastsqla

Let's run the app:

sqlalchemy_url=sqlite+aiosqlite:///db.sqlite?check_same_thread=false \
  uvicorn example:app

Check the result

Execute GET /heros?offset=10&limit=10 using curl:

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

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

You can also check the generated openapi doc by opening your browser to http://127.0.0.1:8000/docs.

OpenAPI generated documentation of the example API

License

This project is licensed under the terms of the MIT license.

For coding agents and LLMs

FastSQLA publishes agent-readable documentation alongside the website:

  • llms.txt is the concise documentation index.
  • llms-full.txt contains the complete documentation in one file.
  • Every indexed page has a Markdown twin, such as setup/index.md.
  • Context7 serves the current documentation and FastSQLA-specific usage rules.

The repository also bundles Agent Skills for setup, session management, and pagination. Install all three as one plugin:

Claude Code

claude plugin marketplace add hadrien/FastSQLA
claude plugin install fastsqla@fastsqla

Codex

codex plugin marketplace add hadrien/FastSQLA
codex plugin add fastsqla@fastsqla

Release files for FastSQLA 0.9.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for FastSQLA 0.9.2
File Size Uploaded
fastsqla-0.9.2.tar.gz 16.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for FastSQLA 0.9.2
File Interpreter ABI Platform
fastsqla-0.9.2-py3-none-any.whl Python 3 none any Details

Total release size: 31.9 kB

Release files / fastsqla-0.9.2.tar.gz

Download URL fastsqla-0.9.2.tar.gz
Size 16.2 kB
Tags Source
SHA-256 checksum
How to use checksums
acb1f4f773e928c309c4f385e9c1633794080f0bc6752f1ec721226f1709835b
BLAKE2b-256 checksum
How to use checksums
ba75940c06beaece0a82969f4a266730cbea7e2ee77609a2a313a01cdae69045
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release files / fastsqla-0.9.2-py3-none-any.whl

Download URL fastsqla-0.9.2-py3-none-any.whl
Size 15.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6670b68fffef4846935fa0db4a3d14c957d1d2c69095219acb07b13b0e879778
BLAKE2b-256 checksum
How to use checksums
e7a5ec27e40097fa2c8568c1ba9b86342534a277600aa875da1e2453c33e5ef7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.9.2 This release

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.8

2 release files

0.8.7

2 release files

0.8.6

2 release files

0.8.5

2 release files

0.8.4

2 release files

0.8.2

2 release files

0.8.1

2 release files

0.8.0

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.6

2 release files

0.4.5

2 release files

0.4.4

2 release files

0.3.0

2 release files

0.2.4

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page