FastSQLA


NameFastSQLA JSON
Version 0.2.4 PyPI version JSON
download
home_pageNone
SummarySQLAlchemy extension for FastAPI that supports asynchronous sessions and includes built-in pagination.
upload_time2025-01-27 19:29:00
maintainerNone
docs_urlNone
authorNone
requires_python>=3.12
licenseMIT License
keywords fastapi sqlalchemy asyncio
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 🚀 FastSQLA

[![PyPI - Version](https://img.shields.io/pypi/v/FastSQLA?color=brightgreen)](https://pypi.org/project/FastSQLA/)
[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-brightgreen.svg)](https://conventionalcommits.org)
[![codecov](https://codecov.io/gh/hadrien/fastsqla/graph/badge.svg?token=XK3YT60MWK)](https://codecov.io/gh/hadrien/fastsqla)

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

## Features

<details>
    <summary>Automatic SQLAlchemy configuration at app startup.</summary>

  Using [`FastAPI` Lifespan](https://fastapi.tiangolo.com/advanced/events/#lifespan):
```python
from fastapi import FastAPI
from fastsqla import lifespan

app = FastAPI(lifespan=lifespan)
```
</details>
<details>
    <summary>Async SQLAlchemy session as a FastAPI dependency.</summary>

```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)
    ...
```
</details>
<details>
    <summary>Built-in pagination.</summary>

```python
...
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))
```
</details>
<details>
    <summary>Allows pagination customization.</summary>

```python
...
from fastapi import 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))
```
</details>

And more ...
<!-- <details><summary></summary></details> -->

## Installing

Using [uv](https://docs.astral.sh/uv/):
```bash
uv add fastsqla
```

Using [pip](https://pip.pypa.io/):
```
pip install fastsqla
```

## Quick Example

```python
# 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.

<details>
    <summary>Create an <code>sqlite3</code> db:</summary>

```bash
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
```

</details>

<details>
    <summary>Install dependencies & run the app</summary>

```bash
pip install uvicorn aiosqlite fastsqla
sqlalchemy_url=sqlite+aiosqlite:///db.sqlite?check_same_thread=false uvicorn example:app
```

</details>

Execute `GET /heros?offset=10`:

```bash
curl -X 'GET' \
'http://127.0.0.1:8000/heros?offset=10&limit=10' \
-H 'accept: application/json'
```
Returns:
```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
  }
}
```

[`FastAPI`]: https://fastapi.tiangolo.com/
[`SQLAlchemy`]: http://sqlalchemy.org/

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "FastSQLA",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.12",
    "maintainer_email": null,
    "keywords": "FastAPI, SQLAlchemy, AsyncIO",
    "author": null,
    "author_email": "Hadrien David <bonjour@hadriendavid.com>",
    "download_url": "https://files.pythonhosted.org/packages/5f/91/ad1b298d7be88c30964c45490160de1f2bc1505ccbd21b144e1b3b73d20e/fastsqla-0.2.4.tar.gz",
    "platform": null,
    "description": "# \ud83d\ude80 FastSQLA\n\n[![PyPI - Version](https://img.shields.io/pypi/v/FastSQLA?color=brightgreen)](https://pypi.org/project/FastSQLA/)\n[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-brightgreen.svg)](https://conventionalcommits.org)\n[![codecov](https://codecov.io/gh/hadrien/fastsqla/graph/badge.svg?token=XK3YT60MWK)](https://codecov.io/gh/hadrien/fastsqla)\n\n`FastSQLA` is an [`SQLAlchemy`] extension for [`FastAPI`].\nIt supports asynchronous `SQLAlchemy` sessions and includes built-in custimizable\npagination.\n\n## Features\n\n<details>\n    <summary>Automatic SQLAlchemy configuration at app startup.</summary>\n\n  Using [`FastAPI` Lifespan](https://fastapi.tiangolo.com/advanced/events/#lifespan):\n```python\nfrom fastapi import FastAPI\nfrom fastsqla import lifespan\n\napp = FastAPI(lifespan=lifespan)\n```\n</details>\n<details>\n    <summary>Async SQLAlchemy session as a FastAPI dependency.</summary>\n\n```python\n...\nfrom fastsqla import Session\nfrom sqlalchemy import select\n...\n\n@app.get(\"/heros\")\nasync def get_heros(session:Session):\n    stmt = select(...)\n    result = await session.execute(stmt)\n    ...\n```\n</details>\n<details>\n    <summary>Built-in pagination.</summary>\n\n```python\n...\nfrom fastsqla import Page, Paginate\nfrom sqlalchemy import select\n...\n\n@app.get(\"/heros\", response_model=Page[HeroModel])\nasync def get_heros(paginate:Paginate):\n    return paginate(select(Hero))\n```\n</details>\n<details>\n    <summary>Allows pagination customization.</summary>\n\n```python\n...\nfrom fastapi import new_pagination\n...\n\nPaginate = new_pagination(min_page_size=5, max_page_size=500)\n\n@app.get(\"/heros\", response_model=Page[HeroModel])\nasync def get_heros(paginate:Paginate):\n    return paginate(select(Hero))\n```\n</details>\n\nAnd more ...\n<!-- <details><summary></summary></details> -->\n\n## Installing\n\nUsing [uv](https://docs.astral.sh/uv/):\n```bash\nuv add fastsqla\n```\n\nUsing [pip](https://pip.pypa.io/):\n```\npip install fastsqla\n```\n\n## Quick Example\n\n```python\n# example.py\nfrom http import HTTPStatus\n\nfrom fastapi import FastAPI, HTTPException\nfrom pydantic import BaseModel, ConfigDict\nfrom sqlalchemy import select\nfrom sqlalchemy.exc import IntegrityError\nfrom sqlalchemy.orm import Mapped, mapped_column\n\nfrom fastsqla import Base, Item, Page, Paginate, Session, lifespan\n\napp = FastAPI(lifespan=lifespan)\n\n\nclass Hero(Base):\n    __tablename__ = \"hero\"\n    id: Mapped[int] = mapped_column(primary_key=True)\n    name: Mapped[str] = mapped_column(unique=True)\n    secret_identity: Mapped[str]\n\n\nclass HeroBase(BaseModel):\n    name: str\n    secret_identity: str\n\n\nclass HeroModel(HeroBase):\n    model_config = ConfigDict(from_attributes=True)\n    id: int\n\n\n@app.get(\"/heros\", response_model=Page[HeroModel])\nasync def list_users(paginate: Paginate):\n    return await paginate(select(Hero))\n\n\n@app.get(\"/heros/{hero_id}\", response_model=Item[HeroModel])\nasync def get_user(hero_id: int, session: Session):\n    hero = await session.get(Hero, hero_id)\n    if hero is None:\n        raise HTTPException(HTTPStatus.NOT_FOUND, \"Hero not found\")\n    return {\"data\": hero}\n\n\n@app.post(\"/heros\", response_model=Item[HeroModel])\nasync def create_user(new_hero: HeroBase, session: Session):\n    hero = Hero(**new_hero.model_dump())\n    session.add(hero)\n    try:\n        await session.flush()\n    except IntegrityError:\n        raise HTTPException(HTTPStatus.CONFLICT, \"Duplicate hero name\")\n    return {\"data\": hero}\n```\n\n> [!NOTE]\n> Sqlite is used for the sake of the example.\n> FastSQLA is compatible with all async db drivers that SQLAlchemy is compatible with.\n\n<details>\n    <summary>Create an <code>sqlite3</code> db:</summary>\n\n```bash\nsqlite3 db.sqlite <<EOF\nCREATE TABLE hero (\n    id              INTEGER PRIMARY KEY AUTOINCREMENT,\n    name            TEXT NOT NULL UNIQUE, -- Hero name (e.g., Superman)\n    secret_identity TEXT NOT NULL         -- Secret identity (e.g., Clark Kent)\n);\n\n-- Insert heroes with hero name and secret identity\nINSERT INTO hero (name, secret_identity) VALUES ('Superman', 'Clark Kent');\nINSERT INTO hero (name, secret_identity) VALUES ('Batman', 'Bruce Wayne');\nINSERT INTO hero (name, secret_identity) VALUES ('Wonder Woman', 'Diana Prince');\nINSERT INTO hero (name, secret_identity) VALUES ('Iron Man', 'Tony Stark');\nINSERT INTO hero (name, secret_identity) VALUES ('Spider-Man', 'Peter Parker');\nINSERT INTO hero (name, secret_identity) VALUES ('Captain America', 'Steve Rogers');\nINSERT INTO hero (name, secret_identity) VALUES ('Black Widow', 'Natasha Romanoff');\nINSERT INTO hero (name, secret_identity) VALUES ('Thor', 'Thor Odinson');\nINSERT INTO hero (name, secret_identity) VALUES ('Scarlet Witch', 'Wanda Maximoff');\nINSERT INTO hero (name, secret_identity) VALUES ('Doctor Strange', 'Stephen Strange');\nINSERT INTO hero (name, secret_identity) VALUES ('The Flash', 'Barry Allen');\nINSERT INTO hero (name, secret_identity) VALUES ('Green Lantern', 'Hal Jordan');\nEOF\n```\n\n</details>\n\n<details>\n    <summary>Install dependencies & run the app</summary>\n\n```bash\npip install uvicorn aiosqlite fastsqla\nsqlalchemy_url=sqlite+aiosqlite:///db.sqlite?check_same_thread=false uvicorn example:app\n```\n\n</details>\n\nExecute `GET /heros?offset=10`:\n\n```bash\ncurl -X 'GET' \\\n'http://127.0.0.1:8000/heros?offset=10&limit=10' \\\n-H 'accept: application/json'\n```\nReturns:\n```json\n{\n  \"data\": [\n    {\n      \"name\": \"The Flash\",\n      \"secret_identity\": \"Barry Allen\",\n      \"id\": 11\n    },\n    {\n      \"name\": \"Green Lantern\",\n      \"secret_identity\": \"Hal Jordan\",\n      \"id\": 12\n    }\n  ],\n  \"meta\": {\n    \"offset\": 10,\n    \"total_items\": 12,\n    \"total_pages\": 2,\n    \"page_number\": 2\n  }\n}\n```\n\n[`FastAPI`]: https://fastapi.tiangolo.com/\n[`SQLAlchemy`]: http://sqlalchemy.org/\n",
    "bugtrack_url": null,
    "license": "MIT License",
    "summary": "SQLAlchemy extension for FastAPI that supports asynchronous sessions and includes built-in pagination.",
    "version": "0.2.4",
    "project_urls": {
        "Changelog": "https://github.com/hadrien/fastsqla/releases",
        "Documentation": "https://github.com/hadrien/fastsqla",
        "Homepage": "https://github.com/hadrien/fastsqla",
        "Issues": "https://github.com/hadrien/fastsqla/issues",
        "Repository": "https://github.com/hadrien/fastsqla"
    },
    "split_keywords": [
        "fastapi",
        " sqlalchemy",
        " asyncio"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f713039bde32ef2c49e122d3e8a114e304f4a46f5a6de340d74550965b0edddc",
                "md5": "d4bb6e27a61e893a7348a3738deddd17",
                "sha256": "c474edd62e20bb247d295edb10db7e0429c4fbe8c45ae935a1bea2c239517c2a"
            },
            "downloads": -1,
            "filename": "FastSQLA-0.2.4-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "d4bb6e27a61e893a7348a3738deddd17",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.12",
            "size": 6103,
            "upload_time": "2025-01-27T19:28:58",
            "upload_time_iso_8601": "2025-01-27T19:28:58.880767Z",
            "url": "https://files.pythonhosted.org/packages/f7/13/039bde32ef2c49e122d3e8a114e304f4a46f5a6de340d74550965b0edddc/FastSQLA-0.2.4-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "5f91ad1b298d7be88c30964c45490160de1f2bc1505ccbd21b144e1b3b73d20e",
                "md5": "126a9677e4b72c47db1e93d96d0ff4f8",
                "sha256": "7d1f93b0b9721eb70e04d2f5e7b696e5fe61ea45ee149828d2c06af7b5551b8a"
            },
            "downloads": -1,
            "filename": "fastsqla-0.2.4.tar.gz",
            "has_sig": false,
            "md5_digest": "126a9677e4b72c47db1e93d96d0ff4f8",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.12",
            "size": 6374,
            "upload_time": "2025-01-27T19:29:00",
            "upload_time_iso_8601": "2025-01-27T19:29:00.751072Z",
            "url": "https://files.pythonhosted.org/packages/5f/91/ad1b298d7be88c30964c45490160de1f2bc1505ccbd21b144e1b3b73d20e/fastsqla-0.2.4.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-01-27 19:29:00",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "hadrien",
    "github_project": "fastsqla",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "fastsqla"
}
        
Elapsed time: 0.37931s