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