FastSQLA 0.2.4__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,359 @@
1
+ Metadata-Version: 2.2
2
+ Name: FastSQLA
3
+ Version: 0.3.0
4
+ Summary: SQLAlchemy extension for FastAPI that supports asynchronous sessions and includes built-in pagination.
5
+ Author-email: Hadrien David <bonjour@hadriendavid.com>
6
+ License: MIT License
7
+ Project-URL: Homepage, https://github.com/hadrien/fastsqla
8
+ Project-URL: Documentation, https://github.com/hadrien/fastsqla
9
+ Project-URL: Repository, https://github.com/hadrien/fastsqla
10
+ Project-URL: Issues, https://github.com/hadrien/fastsqla/issues
11
+ Project-URL: Changelog, https://github.com/hadrien/fastsqla/releases
12
+ Keywords: FastAPI,SQLAlchemy,AsyncIO
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Web Environment
15
+ Classifier: Framework :: AsyncIO
16
+ Classifier: Framework :: FastAPI
17
+ Classifier: Framework :: Pydantic :: 2
18
+ Classifier: Framework :: Pydantic
19
+ Classifier: Intended Audience :: Developers
20
+ Classifier: Intended Audience :: Information Technology
21
+ Classifier: Intended Audience :: System Administrators
22
+ Classifier: License :: OSI Approved :: MIT License
23
+ Classifier: Operating System :: OS Independent
24
+ Classifier: Programming Language :: Python :: 3 :: Only
25
+ Classifier: Programming Language :: Python :: 3.12
26
+ Classifier: Programming Language :: Python :: 3
27
+ Classifier: Programming Language :: Python
28
+ Classifier: Programming Language :: SQL
29
+ Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
30
+ Classifier: Topic :: Internet :: WWW/HTTP
31
+ Classifier: Topic :: Internet
32
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
33
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
34
+ Classifier: Topic :: Software Development :: Libraries
35
+ Classifier: Topic :: Software Development
36
+ Classifier: Typing :: Typed
37
+ Requires-Python: >=3.12
38
+ Description-Content-Type: text/markdown
39
+ License-File: LICENSE
40
+ Requires-Dist: fastapi>=0.115.6
41
+ Requires-Dist: sqlalchemy[asyncio]>=2.0.37
42
+ Requires-Dist: structlog>=24.4.0
43
+ Provides-Extra: docs
44
+ Requires-Dist: mkdocs-glightbox>=0.4.0; extra == "docs"
45
+ Requires-Dist: mkdocs-material>=9.5.50; extra == "docs"
46
+ Requires-Dist: mkdocstrings[python]>=0.27.0; extra == "docs"
47
+ Provides-Extra: sqlmodel
48
+ Requires-Dist: sqlmodel>=0.0.22; extra == "sqlmodel"
49
+
50
+ # FastSQLA
51
+
52
+ _Async SQLAlchemy 2.0+ for FastAPI — boilerplate, pagination, and seamless session management._
53
+
54
+ [![PyPI - Version](https://img.shields.io/pypi/v/FastSQLA?color=brightgreen)](https://pypi.org/project/FastSQLA/)
55
+ [![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)
56
+ [![Codecov](https://img.shields.io/codecov/c/github/hadrien/fastsqla?token=XK3YT60MWK&logo=codecov)](https://codecov.io/gh/hadrien/FastSQLA)
57
+ [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-brightgreen.svg)](https://conventionalcommits.org)
58
+ [![GitHub License](https://img.shields.io/github/license/hadrien/fastsqla)](https://github.com/hadrien/FastSQLA/blob/main/LICENSE)
59
+ [![🍁 With love from Canada](https://img.shields.io/badge/With%20love%20from%20Canada-ffffff?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MDAiIGhlaWdodD0iNjAwIiB2aWV3Qm94PSItMjAxNSAtMjAwMCA0MDMwIDQwMzAiPjxwYXRoIGZpbGw9IiNmMDAiIGQ9Im0tOTAgMjAzMCA0NS04NjNhOTUgOTUgMCAwIDAtMTExLTk4bC04NTkgMTUxIDExNi0zMjBhNjUgNjUgMCAwIDAtMjAtNzNsLTk0MS03NjIgMjEyLTk5YTY1IDY1IDAgMCAwIDM0LTc5bC0xODYtNTcyIDU0MiAxMTVhNjUgNjUgMCAwIDAgNzMtMzhsMTA1LTI0NyA0MjMgNDU0YTY1IDY1IDAgMCAwIDExMS01N2wtMjA0LTEwNTIgMzI3IDE4OWE2NSA2NSAwIDAgMCA5MS0yN2wzMzItNjUyIDMzMiA2NTJhNjUgNjUgMCAwIDAgOTEgMjdsMzI3LTE4OS0yMDQgMTA1MmE2NSA2NSAwIDAgMCAxMTEgNTdsNDIzLTQ1NCAxMDUgMjQ3YTY1IDY1IDAgMCAwIDczIDM4bDU0Mi0xMTUtMTg2IDU3MmE2NSA2NSAwIDAgMCAzNCA3OWwyMTIgOTktOTQxIDc2MmE2NSA2NSAwIDAgMC0yMCA3M2wxMTYgMzIwLTg1OS0xNTFhOTUgOTUgMCAwIDAtMTExIDk4bDQ1IDg2M3oiLz48L3N2Zz4K)](https://montrealpython.org)
60
+
61
+ **Documentation**: [https://hadrien.github.io/FastSQLA/](https://hadrien.github.io/FastSQLA/)
62
+
63
+ **Github Repo:** [https://github.com/hadrien/fastsqla](https://github.com/hadrien/fastsqla)
64
+
65
+ -----------------------------------------------------------------------------------------
66
+
67
+ `FastSQLA` is an async [`SQLAlchemy 2.0+`](https://docs.sqlalchemy.org/en/20/)
68
+ extension for [`FastAPI`](https://fastapi.tiangolo.com/) with built-in pagination,
69
+ [`SQLModel`](http://sqlmodel.tiangolo.com/) support and more.
70
+
71
+ It streamlines the configuration and asynchronous connection to relational databases by
72
+ providing boilerplate and intuitive helpers. Additionally, it offers built-in
73
+ customizable pagination and automatically manages the `SQLAlchemy` session lifecycle
74
+ 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).
75
+
76
+
77
+ ## Features
78
+
79
+ * Easy setup at app startup using
80
+ [`FastAPI` Lifespan](https://fastapi.tiangolo.com/advanced/events/#lifespan):
81
+
82
+ ```python
83
+ from fastapi import FastAPI
84
+ from fastsqla import lifespan
85
+
86
+ app = FastAPI(lifespan=lifespan)
87
+ ```
88
+
89
+ * `SQLAlchemy` async session dependency:
90
+
91
+ ```python
92
+ ...
93
+ from fastsqla import Session
94
+ from sqlalchemy import select
95
+ ...
96
+
97
+ @app.get("/heros")
98
+ async def get_heros(session:Session):
99
+ stmt = select(...)
100
+ result = await session.execute(stmt)
101
+ ...
102
+ ```
103
+
104
+ * `SQLAlchemy` async session with an async context manager:
105
+
106
+ ```python
107
+ from fastsqla import open_session
108
+
109
+ async def background_job():
110
+ async with open_session() as session:
111
+ stmt = select(...)
112
+ result = await session.execute(stmt)
113
+ ...
114
+ ```
115
+
116
+ * Built-in pagination:
117
+
118
+ ```python
119
+ ...
120
+ from fastsqla import Page, Paginate
121
+ from sqlalchemy import select
122
+ ...
123
+
124
+ @app.get("/heros", response_model=Page[HeroModel])
125
+ async def get_heros(paginate:Paginate):
126
+ return await paginate(select(Hero))
127
+ ```
128
+
129
+ <center>
130
+
131
+ 👇 `/heros?offset=10&limit=10` 👇
132
+
133
+ </center>
134
+
135
+ ```json
136
+ {
137
+ "data": [
138
+ {
139
+ "name": "The Flash",
140
+ "secret_identity": "Barry Allen",
141
+ "id": 11
142
+ },
143
+ {
144
+ "name": "Green Lantern",
145
+ "secret_identity": "Hal Jordan",
146
+ "id": 12
147
+ }
148
+ ],
149
+ "meta": {
150
+ "offset": 10,
151
+ "total_items": 12,
152
+ "total_pages": 2,
153
+ "page_number": 2
154
+ }
155
+ }
156
+ ```
157
+
158
+ * Pagination customization:
159
+ ```python
160
+ ...
161
+ from fastapi import Page, new_pagination
162
+ ...
163
+
164
+ Paginate = new_pagination(min_page_size=5, max_page_size=500)
165
+
166
+ @app.get("/heros", response_model=Page[HeroModel])
167
+ async def get_heros(paginate:Paginate):
168
+ return paginate(select(Hero))
169
+ ```
170
+ * Session lifecycle management: session is commited on request success or rollback on
171
+ failure.
172
+
173
+ * [`SQLModel`](http://sqlmodel.tiangolo.com/) support:
174
+ ```python
175
+ ...
176
+ from fastsqla import Item, Page, Paginate, Session
177
+ from sqlmodel import Field, SQLModel
178
+ ...
179
+
180
+ class Hero(SQLModel, table=True):
181
+ id: int | None = Field(default=None, primary_key=True)
182
+ name: str
183
+ secret_identity: str
184
+ age: int
185
+
186
+
187
+ @app.get("/heroes", response_model=Page[Hero])
188
+ async def get_heroes(paginate: Paginate):
189
+ return await paginate(select(Hero))
190
+
191
+
192
+ @app.get("/heroes/{hero_id}", response_model=Item[Hero])
193
+ async def get_hero(session: Session, hero_id: int):
194
+ hero = await session.get(Hero, hero_id)
195
+ if hero is None:
196
+ raise HTTPException(status_code=HTTPStatus.NOT_FOUND)
197
+ return {"data": hero}
198
+ ```
199
+
200
+ ## Installing
201
+
202
+ Using [uv](https://docs.astral.sh/uv/):
203
+ ```bash
204
+ uv add fastsqla
205
+ ```
206
+
207
+ Using [pip](https://pip.pypa.io/):
208
+ ```
209
+ pip install fastsqla
210
+ ```
211
+
212
+ ## Quick Example
213
+
214
+ ### `example.py`
215
+
216
+ Let's write some tiny app in `example.py`:
217
+
218
+ ```python
219
+ # example.py
220
+ from http import HTTPStatus
221
+
222
+ from fastapi import FastAPI, HTTPException
223
+ from fastsqla import Base, Item, Page, Paginate, Session, lifespan
224
+ from pydantic import BaseModel, ConfigDict
225
+ from sqlalchemy import select
226
+ from sqlalchemy.exc import IntegrityError
227
+ from sqlalchemy.orm import Mapped, mapped_column
228
+
229
+
230
+ app = FastAPI(lifespan=lifespan)
231
+
232
+
233
+ class Hero(Base):
234
+ __tablename__ = "hero"
235
+ id: Mapped[int] = mapped_column(primary_key=True)
236
+ name: Mapped[str] = mapped_column(unique=True)
237
+ secret_identity: Mapped[str]
238
+ age: Mapped[int]
239
+
240
+
241
+ class HeroBase(BaseModel):
242
+ name: str
243
+ secret_identity: str
244
+ age: int
245
+
246
+
247
+ class HeroModel(HeroBase):
248
+ model_config = ConfigDict(from_attributes=True)
249
+ id: int
250
+
251
+
252
+ @app.get("/heros", response_model=Page[HeroModel])
253
+ async def list_heros(paginate: Paginate):
254
+ stmt = select(Hero)
255
+ return await paginate(stmt)
256
+
257
+
258
+ @app.get("/heros/{hero_id}", response_model=Item[HeroModel])
259
+ async def get_hero(hero_id: int, session: Session):
260
+ hero = await session.get(Hero, hero_id)
261
+ if hero is None:
262
+ raise HTTPException(HTTPStatus.NOT_FOUND, "Hero not found")
263
+ return {"data": hero}
264
+
265
+
266
+ @app.post("/heros", response_model=Item[HeroModel])
267
+ async def create_hero(new_hero: HeroBase, session: Session):
268
+ hero = Hero(**new_hero.model_dump())
269
+ session.add(hero)
270
+ try:
271
+ await session.flush()
272
+ except IntegrityError:
273
+ raise HTTPException(HTTPStatus.CONFLICT, "Duplicate hero name")
274
+ return {"data": hero}
275
+ ```
276
+
277
+ ### Database
278
+
279
+ 💡 This example uses an `SQLite` database for simplicity: `FastSQLA` is compatible with
280
+ all asynchronous db drivers that `SQLAlchemy` is compatible with.
281
+
282
+ Let's create an `SQLite` database using `sqlite3` and insert 12 rows in the `hero` table:
283
+
284
+ ```bash
285
+ sqlite3 db.sqlite <<EOF
286
+ -- Create Table hero
287
+ CREATE TABLE hero (
288
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
289
+ name TEXT NOT NULL UNIQUE, -- Unique hero name (e.g., Superman)
290
+ secret_identity TEXT NOT NULL, -- Secret identity (e.g., Clark Kent)
291
+ age INTEGER NOT NULL -- Age of the hero (e.g., 30)
292
+ );
293
+
294
+ -- Insert heroes with their name, secret identity, and age
295
+ INSERT INTO hero (name, secret_identity, age) VALUES ('Superman', 'Clark Kent', 30);
296
+ INSERT INTO hero (name, secret_identity, age) VALUES ('Batman', 'Bruce Wayne', 35);
297
+ INSERT INTO hero (name, secret_identity, age) VALUES ('Wonder Woman', 'Diana Prince', 30);
298
+ INSERT INTO hero (name, secret_identity, age) VALUES ('Iron Man', 'Tony Stark', 45);
299
+ INSERT INTO hero (name, secret_identity, age) VALUES ('Spider-Man', 'Peter Parker', 25);
300
+ INSERT INTO hero (name, secret_identity, age) VALUES ('Captain America', 'Steve Rogers', 100);
301
+ INSERT INTO hero (name, secret_identity, age) VALUES ('Black Widow', 'Natasha Romanoff', 35);
302
+ INSERT INTO hero (name, secret_identity, age) VALUES ('Thor', 'Thor Odinson', 1500);
303
+ INSERT INTO hero (name, secret_identity, age) VALUES ('Scarlet Witch', 'Wanda Maximoff', 30);
304
+ INSERT INTO hero (name, secret_identity, age) VALUES ('Doctor Strange', 'Stephen Strange', 40);
305
+ INSERT INTO hero (name, secret_identity, age) VALUES ('The Flash', 'Barry Allen', 28);
306
+ INSERT INTO hero (name, secret_identity, age) VALUES ('Green Lantern', 'Hal Jordan', 35);
307
+ EOF
308
+ ```
309
+
310
+ ### Run the app
311
+
312
+ Let's install required dependencies:
313
+ ```bash
314
+ pip install uvicorn aiosqlite fastsqla
315
+ ```
316
+ Let's run the app:
317
+ ```
318
+ sqlalchemy_url=sqlite+aiosqlite:///db.sqlite?check_same_thread=false \
319
+ uvicorn example:app
320
+ ```
321
+
322
+ ### Check the result
323
+
324
+ Execute `GET /heros?offset=10&limit=10` using `curl`:
325
+ ```bash
326
+ curl -X 'GET' -H 'accept: application/json' 'http://127.0.0.1:8000/heros?offset=10&limit=10'
327
+ ```
328
+ Returns:
329
+ ```json
330
+ {
331
+ "data": [
332
+ {
333
+ "name": "The Flash",
334
+ "secret_identity": "Barry Allen",
335
+ "id": 11
336
+ },
337
+ {
338
+ "name": "Green Lantern",
339
+ "secret_identity": "Hal Jordan",
340
+ "id": 12
341
+ }
342
+ ],
343
+ "meta": {
344
+ "offset": 10,
345
+ "total_items": 12,
346
+ "total_pages": 2,
347
+ "page_number": 2
348
+ }
349
+ }
350
+ ```
351
+
352
+ You can also check the generated openapi doc by opening your browser to
353
+ [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs).
354
+
355
+ ![OpenAPI generated documentation of the example API](https://raw.githubusercontent.com/hadrien/FastSQLA/refs/heads/main/docs/images/example-openapi-generated-doc.png)
356
+
357
+ ## License
358
+
359
+ This project is licensed under the terms of the [MIT license](https://github.com/hadrien/FastSQLA/blob/main/LICENSE).
@@ -0,0 +1,310 @@
1
+ # FastSQLA
2
+
3
+ _Async SQLAlchemy 2.0+ for FastAPI — boilerplate, pagination, and seamless session management._
4
+
5
+ [![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)
8
+ [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-brightgreen.svg)](https://conventionalcommits.org)
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)](https://montrealpython.org)
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 async [`SQLAlchemy 2.0+`](https://docs.sqlalchemy.org/en/20/)
19
+ extension for [`FastAPI`](https://fastapi.tiangolo.com/) with built-in pagination,
20
+ [`SQLModel`](http://sqlmodel.tiangolo.com/) support and more.
21
+
22
+ It streamlines the configuration and asynchronous connection to relational databases by
23
+ providing boilerplate and intuitive helpers. Additionally, it offers built-in
24
+ customizable pagination and automatically manages the `SQLAlchemy` session lifecycle
25
+ 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).
26
+
27
+
28
+ ## Features
29
+
30
+ * Easy setup at app startup using
31
+ [`FastAPI` Lifespan](https://fastapi.tiangolo.com/advanced/events/#lifespan):
32
+
33
+ ```python
34
+ from fastapi import FastAPI
35
+ from fastsqla import lifespan
36
+
37
+ app = FastAPI(lifespan=lifespan)
38
+ ```
39
+
40
+ * `SQLAlchemy` async session dependency:
41
+
42
+ ```python
43
+ ...
44
+ from fastsqla import Session
45
+ from sqlalchemy import select
46
+ ...
47
+
48
+ @app.get("/heros")
49
+ async def get_heros(session:Session):
50
+ stmt = select(...)
51
+ result = await session.execute(stmt)
52
+ ...
53
+ ```
54
+
55
+ * `SQLAlchemy` async session with an async context manager:
56
+
57
+ ```python
58
+ from fastsqla import open_session
59
+
60
+ async def background_job():
61
+ async with open_session() as session:
62
+ stmt = select(...)
63
+ result = await session.execute(stmt)
64
+ ...
65
+ ```
66
+
67
+ * Built-in pagination:
68
+
69
+ ```python
70
+ ...
71
+ from fastsqla import Page, Paginate
72
+ from sqlalchemy import select
73
+ ...
74
+
75
+ @app.get("/heros", response_model=Page[HeroModel])
76
+ async def get_heros(paginate:Paginate):
77
+ return await paginate(select(Hero))
78
+ ```
79
+
80
+ <center>
81
+
82
+ 👇 `/heros?offset=10&limit=10` 👇
83
+
84
+ </center>
85
+
86
+ ```json
87
+ {
88
+ "data": [
89
+ {
90
+ "name": "The Flash",
91
+ "secret_identity": "Barry Allen",
92
+ "id": 11
93
+ },
94
+ {
95
+ "name": "Green Lantern",
96
+ "secret_identity": "Hal Jordan",
97
+ "id": 12
98
+ }
99
+ ],
100
+ "meta": {
101
+ "offset": 10,
102
+ "total_items": 12,
103
+ "total_pages": 2,
104
+ "page_number": 2
105
+ }
106
+ }
107
+ ```
108
+
109
+ * Pagination customization:
110
+ ```python
111
+ ...
112
+ from fastapi import Page, new_pagination
113
+ ...
114
+
115
+ Paginate = new_pagination(min_page_size=5, max_page_size=500)
116
+
117
+ @app.get("/heros", response_model=Page[HeroModel])
118
+ async def get_heros(paginate:Paginate):
119
+ return paginate(select(Hero))
120
+ ```
121
+ * Session lifecycle management: session is commited on request success or rollback on
122
+ failure.
123
+
124
+ * [`SQLModel`](http://sqlmodel.tiangolo.com/) support:
125
+ ```python
126
+ ...
127
+ from fastsqla import Item, Page, Paginate, Session
128
+ from sqlmodel import Field, SQLModel
129
+ ...
130
+
131
+ class Hero(SQLModel, table=True):
132
+ id: int | None = Field(default=None, primary_key=True)
133
+ name: str
134
+ secret_identity: str
135
+ age: int
136
+
137
+
138
+ @app.get("/heroes", response_model=Page[Hero])
139
+ async def get_heroes(paginate: Paginate):
140
+ return await paginate(select(Hero))
141
+
142
+
143
+ @app.get("/heroes/{hero_id}", response_model=Item[Hero])
144
+ async def get_hero(session: Session, hero_id: int):
145
+ hero = await session.get(Hero, hero_id)
146
+ if hero is None:
147
+ raise HTTPException(status_code=HTTPStatus.NOT_FOUND)
148
+ return {"data": hero}
149
+ ```
150
+
151
+ ## Installing
152
+
153
+ Using [uv](https://docs.astral.sh/uv/):
154
+ ```bash
155
+ uv add fastsqla
156
+ ```
157
+
158
+ Using [pip](https://pip.pypa.io/):
159
+ ```
160
+ pip install fastsqla
161
+ ```
162
+
163
+ ## Quick Example
164
+
165
+ ### `example.py`
166
+
167
+ Let's write some tiny app in `example.py`:
168
+
169
+ ```python
170
+ # example.py
171
+ from http import HTTPStatus
172
+
173
+ from fastapi import FastAPI, HTTPException
174
+ from fastsqla import Base, Item, Page, Paginate, Session, lifespan
175
+ from pydantic import BaseModel, ConfigDict
176
+ from sqlalchemy import select
177
+ from sqlalchemy.exc import IntegrityError
178
+ from sqlalchemy.orm import Mapped, mapped_column
179
+
180
+
181
+ app = FastAPI(lifespan=lifespan)
182
+
183
+
184
+ class Hero(Base):
185
+ __tablename__ = "hero"
186
+ id: Mapped[int] = mapped_column(primary_key=True)
187
+ name: Mapped[str] = mapped_column(unique=True)
188
+ secret_identity: Mapped[str]
189
+ age: Mapped[int]
190
+
191
+
192
+ class HeroBase(BaseModel):
193
+ name: str
194
+ secret_identity: str
195
+ age: int
196
+
197
+
198
+ class HeroModel(HeroBase):
199
+ model_config = ConfigDict(from_attributes=True)
200
+ id: int
201
+
202
+
203
+ @app.get("/heros", response_model=Page[HeroModel])
204
+ async def list_heros(paginate: Paginate):
205
+ stmt = select(Hero)
206
+ return await paginate(stmt)
207
+
208
+
209
+ @app.get("/heros/{hero_id}", response_model=Item[HeroModel])
210
+ async def get_hero(hero_id: int, session: Session):
211
+ hero = await session.get(Hero, hero_id)
212
+ if hero is None:
213
+ raise HTTPException(HTTPStatus.NOT_FOUND, "Hero not found")
214
+ return {"data": hero}
215
+
216
+
217
+ @app.post("/heros", response_model=Item[HeroModel])
218
+ async def create_hero(new_hero: HeroBase, session: Session):
219
+ hero = Hero(**new_hero.model_dump())
220
+ session.add(hero)
221
+ try:
222
+ await session.flush()
223
+ except IntegrityError:
224
+ raise HTTPException(HTTPStatus.CONFLICT, "Duplicate hero name")
225
+ return {"data": hero}
226
+ ```
227
+
228
+ ### Database
229
+
230
+ 💡 This example uses an `SQLite` database for simplicity: `FastSQLA` is compatible with
231
+ all asynchronous db drivers that `SQLAlchemy` is compatible with.
232
+
233
+ Let's create an `SQLite` database using `sqlite3` and insert 12 rows in the `hero` table:
234
+
235
+ ```bash
236
+ sqlite3 db.sqlite <<EOF
237
+ -- Create Table hero
238
+ CREATE TABLE hero (
239
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
240
+ name TEXT NOT NULL UNIQUE, -- Unique hero name (e.g., Superman)
241
+ secret_identity TEXT NOT NULL, -- Secret identity (e.g., Clark Kent)
242
+ age INTEGER NOT NULL -- Age of the hero (e.g., 30)
243
+ );
244
+
245
+ -- Insert heroes with their name, secret identity, and age
246
+ INSERT INTO hero (name, secret_identity, age) VALUES ('Superman', 'Clark Kent', 30);
247
+ INSERT INTO hero (name, secret_identity, age) VALUES ('Batman', 'Bruce Wayne', 35);
248
+ INSERT INTO hero (name, secret_identity, age) VALUES ('Wonder Woman', 'Diana Prince', 30);
249
+ INSERT INTO hero (name, secret_identity, age) VALUES ('Iron Man', 'Tony Stark', 45);
250
+ INSERT INTO hero (name, secret_identity, age) VALUES ('Spider-Man', 'Peter Parker', 25);
251
+ INSERT INTO hero (name, secret_identity, age) VALUES ('Captain America', 'Steve Rogers', 100);
252
+ INSERT INTO hero (name, secret_identity, age) VALUES ('Black Widow', 'Natasha Romanoff', 35);
253
+ INSERT INTO hero (name, secret_identity, age) VALUES ('Thor', 'Thor Odinson', 1500);
254
+ INSERT INTO hero (name, secret_identity, age) VALUES ('Scarlet Witch', 'Wanda Maximoff', 30);
255
+ INSERT INTO hero (name, secret_identity, age) VALUES ('Doctor Strange', 'Stephen Strange', 40);
256
+ INSERT INTO hero (name, secret_identity, age) VALUES ('The Flash', 'Barry Allen', 28);
257
+ INSERT INTO hero (name, secret_identity, age) VALUES ('Green Lantern', 'Hal Jordan', 35);
258
+ EOF
259
+ ```
260
+
261
+ ### Run the app
262
+
263
+ Let's install required dependencies:
264
+ ```bash
265
+ pip install uvicorn aiosqlite fastsqla
266
+ ```
267
+ Let's run the app:
268
+ ```
269
+ sqlalchemy_url=sqlite+aiosqlite:///db.sqlite?check_same_thread=false \
270
+ uvicorn example:app
271
+ ```
272
+
273
+ ### Check the result
274
+
275
+ Execute `GET /heros?offset=10&limit=10` using `curl`:
276
+ ```bash
277
+ curl -X 'GET' -H 'accept: application/json' 'http://127.0.0.1:8000/heros?offset=10&limit=10'
278
+ ```
279
+ Returns:
280
+ ```json
281
+ {
282
+ "data": [
283
+ {
284
+ "name": "The Flash",
285
+ "secret_identity": "Barry Allen",
286
+ "id": 11
287
+ },
288
+ {
289
+ "name": "Green Lantern",
290
+ "secret_identity": "Hal Jordan",
291
+ "id": 12
292
+ }
293
+ ],
294
+ "meta": {
295
+ "offset": 10,
296
+ "total_items": 12,
297
+ "total_pages": 2,
298
+ "page_number": 2
299
+ }
300
+ }
301
+ ```
302
+
303
+ You can also check the generated openapi doc by opening your browser to
304
+ [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs).
305
+
306
+ ![OpenAPI generated documentation of the example API](https://raw.githubusercontent.com/hadrien/FastSQLA/refs/heads/main/docs/images/example-openapi-generated-doc.png)
307
+
308
+ ## License
309
+
310
+ This project is licensed under the terms of the [MIT license](https://github.com/hadrien/FastSQLA/blob/main/LICENSE).