Skip to content

Commit c8367fc

Browse files
authored
docs: setup mkdocs-material + add documentation (#13)
1 parent 4e58835 commit c8367fc

File tree

14 files changed

+927
-95
lines changed

14 files changed

+927
-95
lines changed

.github/workflows/doc.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Build & Publish Doc
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- docs/**
8+
9+
jobs:
10+
11+
Build-Doc:
12+
runs-on: ubuntu-latest
13+
concurrency: release
14+
permissions:
15+
contents: write
16+
id-token: write
17+
steps:
18+
- name: 📥 checkout
19+
uses: actions/checkout@v4
20+
with:
21+
fetch-depth: 0
22+
- name: 🔧 setup uv
23+
uses: ./.github/uv
24+
- name: ⚙️ install deps
25+
run: uv sync --extra docs
26+
- name: 📙 mkdocs build
27+
run: uv run mkdocs build
28+
- name: 📦 Upload artifacts
29+
uses: actions/upload-pages-artifact@v3
30+
with:
31+
path: site
32+
33+
Publish-Doc:
34+
needs: Build-Doc
35+
runs-on: ubuntu-latest
36+
permissions:
37+
pages: write
38+
id-token: write
39+
steps:
40+
- name: Deploy to GitHub Pages
41+
id: deployment
42+
uses: actions/deploy-pages@v4
43+
environment:
44+
name: github-pages
45+
url: ${{ steps.deployment.outputs.page_url }}

README.md

Lines changed: 150 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,119 @@
1-
# 🚀 FastSQLA
1+
# FastSQLA
2+
3+
_Async SQLAlchemy 2 for FastAPI — boilerplate, pagination, and seamless session management._
24

35
[![PyPI - Version](https://img.shields.io/pypi/v/FastSQLA?color=brightgreen)](https://pypi.org/project/FastSQLA/)
6+
[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/hadrien/fastsqla/ci.yml?branch=main&logo=github&label=CI)](https://github.com/hadrien/FastSQLA/actions?query=branch%3Amain+event%3Apush)
7+
[![Codecov](https://img.shields.io/codecov/c/github/hadrien/fastsqla?token=XK3YT60MWK&logo=codecov)](https://codecov.io/gh/hadrien/FastSQLA)
48
[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-brightgreen.svg)](https://conventionalcommits.org)
5-
[![codecov](https://codecov.io/gh/hadrien/fastsqla/graph/badge.svg?token=XK3YT60MWK)](https://codecov.io/gh/hadrien/fastsqla)
9+
[![GitHub License](https://img.shields.io/github/license/hadrien/fastsqla)](https://github.com/hadrien/FastSQLA/blob/main/LICENSE)
10+
![🍁 With love from Canada](https://img.shields.io/badge/With%20love%20from%20Canada-ffffff?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MDAiIGhlaWdodD0iNjAwIiB2aWV3Qm94PSItMjAxNSAtMjAwMCA0MDMwIDQwMzAiPjxwYXRoIGZpbGw9IiNmMDAiIGQ9Im0tOTAgMjAzMCA0NS04NjNhOTUgOTUgMCAwIDAtMTExLTk4bC04NTkgMTUxIDExNi0zMjBhNjUgNjUgMCAwIDAtMjAtNzNsLTk0MS03NjIgMjEyLTk5YTY1IDY1IDAgMCAwIDM0LTc5bC0xODYtNTcyIDU0MiAxMTVhNjUgNjUgMCAwIDAgNzMtMzhsMTA1LTI0NyA0MjMgNDU0YTY1IDY1IDAgMCAwIDExMS01N2wtMjA0LTEwNTIgMzI3IDE4OWE2NSA2NSAwIDAgMCA5MS0yN2wzMzItNjUyIDMzMiA2NTJhNjUgNjUgMCAwIDAgOTEgMjdsMzI3LTE4OS0yMDQgMTA1MmE2NSA2NSAwIDAgMCAxMTEgNTdsNDIzLTQ1NCAxMDUgMjQ3YTY1IDY1IDAgMCAwIDczIDM4bDU0Mi0xMTUtMTg2IDU3MmE2NSA2NSAwIDAgMCAzNCA3OWwyMTIgOTktOTQxIDc2MmE2NSA2NSAwIDAgMC0yMCA3M2wxMTYgMzIwLTg1OS0xNTFhOTUgOTUgMCAwIDAtMTExIDk4bDQ1IDg2M3oiLz48L3N2Zz4K)
11+
12+
**Documentation**: [https://hadrien.github.io/FastSQLA/](https://hadrien.github.io/FastSQLA/)
13+
14+
**Github Repo:** [https://github.com/hadrien/fastsqla](https://github.com/hadrien/fastsqla)
15+
16+
-----------------------------------------------------------------------------------------
17+
18+
`FastSQLA` is an [`SQLAlchemy 2`](https://docs.sqlalchemy.org/en/20/) extension for
19+
[`FastAPI`](https://fastapi.tiangolo.com/).
20+
It streamlines the configuration and asynchronous connection to relational databases by
21+
providing boilerplate and intuitive helpers. Additionally, it offers built-in
22+
customizable pagination and automatically manages the `SQLAlchemy` session lifecycle
23+
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).
624

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

1126
## Features
1227

13-
<details>
14-
<summary>Automatic SQLAlchemy configuration at app startup.</summary>
28+
* Easy setup at app startup using
29+
[`FastAPI` Lifespan](https://fastapi.tiangolo.com/advanced/events/#lifespan):
1530

16-
Using [`FastAPI` Lifespan](https://fastapi.tiangolo.com/advanced/events/#lifespan):
17-
```python
18-
from fastapi import FastAPI
19-
from fastsqla import lifespan
31+
```python
32+
from fastapi import FastAPI
33+
from fastsqla import lifespan
2034

21-
app = FastAPI(lifespan=lifespan)
22-
```
23-
</details>
24-
<details>
25-
<summary>Async SQLAlchemy session as a FastAPI dependency.</summary>
35+
app = FastAPI(lifespan=lifespan)
36+
```
2637

27-
```python
28-
...
29-
from fastsqla import Session
30-
from sqlalchemy import select
31-
...
38+
* `SQLAlchemy` async session dependency:
3239

33-
@app.get("/heros")
34-
async def get_heros(session:Session):
35-
stmt = select(...)
36-
result = await session.execute(stmt)
40+
```python
41+
...
42+
from fastsqla import Session
43+
from sqlalchemy import select
3744
...
38-
```
39-
</details>
40-
<details>
41-
<summary>Built-in pagination.</summary>
4245

43-
```python
44-
...
45-
from fastsqla import Page, Paginate
46-
from sqlalchemy import select
47-
...
46+
@app.get("/heros")
47+
async def get_heros(session:Session):
48+
stmt = select(...)
49+
result = await session.execute(stmt)
50+
...
51+
```
4852

49-
@app.get("/heros", response_model=Page[HeroModel])
50-
async def get_heros(paginate:Paginate):
51-
return paginate(select(Hero))
52-
```
53-
</details>
54-
<details>
55-
<summary>Allows pagination customization.</summary>
53+
* `SQLAlchemy` async session with an async context manager:
5654

57-
```python
58-
...
59-
from fastapi import Page, new_pagination
60-
...
55+
```python
56+
from fastsqla import open_session
6157

62-
Paginate = new_pagination(min_page_size=5, max_page_size=500)
58+
async def background_job():
59+
async with open_session() as session:
60+
stmt = select(...)
61+
result = await session.execute(stmt)
62+
...
63+
```
6364

64-
@app.get("/heros", response_model=Page[HeroModel])
65-
async def get_heros(paginate:Paginate):
66-
return paginate(select(Hero))
67-
```
68-
</details>
65+
* Built-in pagination:
66+
67+
```python
68+
...
69+
from fastsqla import Page, Paginate
70+
from sqlalchemy import select
71+
...
72+
73+
@app.get("/heros", response_model=Page[HeroModel])
74+
async def get_heros(paginate:Paginate):
75+
return await paginate(select(Hero))
76+
```
77+
<center>👇👇👇</center>
78+
```json
79+
// /heros?offset=10&limit=10
80+
{
81+
"data": [
82+
{
83+
"name": "The Flash",
84+
"secret_identity": "Barry Allen",
85+
"id": 11
86+
},
87+
{
88+
"name": "Green Lantern",
89+
"secret_identity": "Hal Jordan",
90+
"id": 12
91+
}
92+
],
93+
"meta": {
94+
"offset": 10,
95+
"total_items": 12,
96+
"total_pages": 2,
97+
"page_number": 2
98+
}
99+
}
100+
```
101+
102+
* Pagination customization:
103+
```python
104+
...
105+
from fastapi import Page, new_pagination
106+
...
107+
108+
Paginate = new_pagination(min_page_size=5, max_page_size=500)
109+
110+
@app.get("/heros", response_model=Page[HeroModel])
111+
async def get_heros(paginate:Paginate):
112+
return paginate(select(Hero))
113+
```
114+
* Session lifecycle management: session is commited on request success or rollback on
115+
failure.
69116

70-
And more ...
71-
<!-- <details><summary></summary></details> -->
72117

73118
## Installing
74119

@@ -84,17 +129,21 @@ pip install fastsqla
84129

85130
## Quick Example
86131

132+
### `example.py`
133+
134+
Let's write some tiny app in `example.py`:
135+
87136
```python
88137
# example.py
89138
from http import HTTPStatus
90139

91140
from fastapi import FastAPI, HTTPException
141+
from fastsqla import Base, Item, Page, Paginate, Session, lifespan
92142
from pydantic import BaseModel, ConfigDict
93143
from sqlalchemy import select
94144
from sqlalchemy.exc import IntegrityError
95145
from sqlalchemy.orm import Mapped, mapped_column
96146

97-
from fastsqla import Base, Item, Page, Paginate, Session, lifespan
98147

99148
app = FastAPI(lifespan=lifespan)
100149

@@ -104,11 +153,13 @@ class Hero(Base):
104153
id: Mapped[int] = mapped_column(primary_key=True)
105154
name: Mapped[str] = mapped_column(unique=True)
106155
secret_identity: Mapped[str]
156+
age: Mapped[int]
107157

108158

109159
class HeroBase(BaseModel):
110160
name: str
111161
secret_identity: str
162+
age: int
112163

113164

114165
class HeroModel(HeroBase):
@@ -117,20 +168,21 @@ class HeroModel(HeroBase):
117168

118169

119170
@app.get("/heros", response_model=Page[HeroModel])
120-
async def list_users(paginate: Paginate):
121-
return await paginate(select(Hero))
171+
async def list_heros(paginate: Paginate):
172+
stmt = select(Hero)
173+
return await paginate(stmt)
122174

123175

124176
@app.get("/heros/{hero_id}", response_model=Item[HeroModel])
125-
async def get_user(hero_id: int, session: Session):
177+
async def get_hero(hero_id: int, session: Session):
126178
hero = await session.get(Hero, hero_id)
127179
if hero is None:
128180
raise HTTPException(HTTPStatus.NOT_FOUND, "Hero not found")
129181
return {"data": hero}
130182

131183

132184
@app.post("/heros", response_model=Item[HeroModel])
133-
async def create_user(new_hero: HeroBase, session: Session):
185+
async def create_hero(new_hero: HeroBase, session: Session):
134186
hero = Hero(**new_hero.model_dump())
135187
session.add(hero)
136188
try:
@@ -140,55 +192,56 @@ async def create_user(new_hero: HeroBase, session: Session):
140192
return {"data": hero}
141193
```
142194

143-
> [!NOTE]
144-
> Sqlite is used for the sake of the example.
145-
> FastSQLA is compatible with all async db drivers that SQLAlchemy is compatible with.
195+
### Database
196+
197+
💡 This example uses an `SQLite` database for simplicity: `FastSQLA` is compatible with
198+
all asynchronous db drivers that `SQLAlchemy` is compatible with.
146199

147-
<details>
148-
<summary>Create an <code>sqlite3</code> db:</summary>
200+
Let's create an `SQLite` database using `sqlite3` and insert 12 rows in the `hero` table:
149201

150202
```bash
151203
sqlite3 db.sqlite <<EOF
204+
-- Create Table hero
152205
CREATE TABLE hero (
153206
id INTEGER PRIMARY KEY AUTOINCREMENT,
154-
name TEXT NOT NULL UNIQUE, -- Hero name (e.g., Superman)
155-
secret_identity TEXT NOT NULL -- Secret identity (e.g., Clark Kent)
207+
name TEXT NOT NULL UNIQUE, -- Unique hero name (e.g., Superman)
208+
secret_identity TEXT NOT NULL, -- Secret identity (e.g., Clark Kent)
209+
age INTEGER NOT NULL -- Age of the hero (e.g., 30)
156210
);
157211
158-
-- Insert heroes with hero name and secret identity
159-
INSERT INTO hero (name, secret_identity) VALUES ('Superman', 'Clark Kent');
160-
INSERT INTO hero (name, secret_identity) VALUES ('Batman', 'Bruce Wayne');
161-
INSERT INTO hero (name, secret_identity) VALUES ('Wonder Woman', 'Diana Prince');
162-
INSERT INTO hero (name, secret_identity) VALUES ('Iron Man', 'Tony Stark');
163-
INSERT INTO hero (name, secret_identity) VALUES ('Spider-Man', 'Peter Parker');
164-
INSERT INTO hero (name, secret_identity) VALUES ('Captain America', 'Steve Rogers');
165-
INSERT INTO hero (name, secret_identity) VALUES ('Black Widow', 'Natasha Romanoff');
166-
INSERT INTO hero (name, secret_identity) VALUES ('Thor', 'Thor Odinson');
167-
INSERT INTO hero (name, secret_identity) VALUES ('Scarlet Witch', 'Wanda Maximoff');
168-
INSERT INTO hero (name, secret_identity) VALUES ('Doctor Strange', 'Stephen Strange');
169-
INSERT INTO hero (name, secret_identity) VALUES ('The Flash', 'Barry Allen');
170-
INSERT INTO hero (name, secret_identity) VALUES ('Green Lantern', 'Hal Jordan');
212+
-- Insert heroes with their name, secret identity, and age
213+
INSERT INTO hero (name, secret_identity, age) VALUES ('Superman', 'Clark Kent', 30);
214+
INSERT INTO hero (name, secret_identity, age) VALUES ('Batman', 'Bruce Wayne', 35);
215+
INSERT INTO hero (name, secret_identity, age) VALUES ('Wonder Woman', 'Diana Prince', 30);
216+
INSERT INTO hero (name, secret_identity, age) VALUES ('Iron Man', 'Tony Stark', 45);
217+
INSERT INTO hero (name, secret_identity, age) VALUES ('Spider-Man', 'Peter Parker', 25);
218+
INSERT INTO hero (name, secret_identity, age) VALUES ('Captain America', 'Steve Rogers', 100);
219+
INSERT INTO hero (name, secret_identity, age) VALUES ('Black Widow', 'Natasha Romanoff', 35);
220+
INSERT INTO hero (name, secret_identity, age) VALUES ('Thor', 'Thor Odinson', 1500);
221+
INSERT INTO hero (name, secret_identity, age) VALUES ('Scarlet Witch', 'Wanda Maximoff', 30);
222+
INSERT INTO hero (name, secret_identity, age) VALUES ('Doctor Strange', 'Stephen Strange', 40);
223+
INSERT INTO hero (name, secret_identity, age) VALUES ('The Flash', 'Barry Allen', 28);
224+
INSERT INTO hero (name, secret_identity, age) VALUES ('Green Lantern', 'Hal Jordan', 35);
171225
EOF
172226
```
173227

174-
</details>
175-
176-
<details>
177-
<summary>Install dependencies & run the app</summary>
228+
### Run the app
178229

230+
Let's install required dependencies:
179231
```bash
180232
pip install uvicorn aiosqlite fastsqla
181-
sqlalchemy_url=sqlite+aiosqlite:///db.sqlite?check_same_thread=false uvicorn example:app
233+
```
234+
Let's run the app:
235+
```
236+
sqlalchemy_url=sqlite+aiosqlite:///db.sqlite?check_same_thread=false \
237+
uvicorn example:app
182238
```
183239

184-
</details>
185-
186-
Execute `GET /heros?offset=10`:
240+
### Check the result
187241

242+
Execute `GET /heros?offset=10&limit=10` using `curl`:
188243
```bash
189-
curl -X 'GET' \
190-
'http://127.0.0.1:8000/heros?offset=10&limit=10' \
191-
-H 'accept: application/json'
244+
curl -X 'GET' -H 'accept: application/json' 'http://127.0.0.1:8000/heros?offset=10&limit=10'
192245
```
193246
Returns:
194247
```json
@@ -214,5 +267,11 @@ Returns:
214267
}
215268
```
216269

217-
[`FastAPI`]: https://fastapi.tiangolo.com/
218-
[`SQLAlchemy`]: http://sqlalchemy.org/
270+
You can also check the generated openapi doc by opening your browser to
271+
[http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs).
272+
273+
![OpenAPI generated documentation of the example API](images/example-openapi-generated-doc.png)
274+
275+
## License
276+
277+
This project is licensed under the terms of the [MIT license](https://github.com/hadrien/FastSQLA/blob/main/LICENSE).

CHANGELOG.md renamed to docs/changelog.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# CHANGELOG
1+
# Changelog
22

33
## v0.2.4 (2025-01-27)
44

448 KB
Loading

docs/images/favicon.png

12 KB
Loading

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../README.md

0 commit comments

Comments
 (0)