FastSQLA 0.2.4__py3-none-any.whl → 0.4.4__py3-none-any.whl
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.
- fastsqla-0.4.4.dist-info/METADATA +357 -0
- fastsqla-0.4.4.dist-info/RECORD +5 -0
- {FastSQLA-0.2.4.dist-info → fastsqla-0.4.4.dist-info}/WHEEL +1 -1
- fastsqla.py +250 -19
- FastSQLA-0.2.4.dist-info/LICENSE +0 -21
- FastSQLA-0.2.4.dist-info/METADATA +0 -261
- FastSQLA-0.2.4.dist-info/RECORD +0 -6
- {FastSQLA-0.2.4.dist-info → fastsqla-0.4.4.dist-info}/top_level.txt +0 -0
|
@@ -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
|
+
[](https://pypi.org/project/FastSQLA/)
|
|
53
|
+
[](https://github.com/hadrien/FastSQLA/actions?query=branch%3Amain+event%3Apush)
|
|
54
|
+
[](https://codecov.io/gh/hadrien/FastSQLA)
|
|
55
|
+
[](https://conventionalcommits.org)
|
|
56
|
+
[](https://github.com/hadrien/FastSQLA/blob/main/LICENSE)
|
|
57
|
+
[](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
|
+

|
|
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,5 @@
|
|
|
1
|
+
fastsqla.py,sha256=wO4KpgkA3jVOCtpbwORlaUuOu4YsEQoRslzg9wjDNBk,12364
|
|
2
|
+
fastsqla-0.4.4.dist-info/METADATA,sha256=lyIlPmzzZ99nG1uKAmrpi1lH7Zpclod8LUTmNj0OOuo,12425
|
|
3
|
+
fastsqla-0.4.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
4
|
+
fastsqla-0.4.4.dist-info/top_level.txt,sha256=Uh-1ssTtuSS4_SYCBeDoDVOxqWTrRAPEBZkuih5isSE,9
|
|
5
|
+
fastsqla-0.4.4.dist-info/RECORD,,
|
fastsqla.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import math
|
|
2
2
|
import os
|
|
3
3
|
from collections.abc import AsyncGenerator, Awaitable, Callable, Iterable
|
|
4
|
-
from contextlib import asynccontextmanager
|
|
4
|
+
from contextlib import _AsyncGeneratorContextManager, asynccontextmanager
|
|
5
5
|
from typing import Annotated, Generic, TypeVar, TypedDict
|
|
6
6
|
|
|
7
|
-
from fastapi import Depends, Query
|
|
7
|
+
from fastapi import Depends, FastAPI, Query
|
|
8
8
|
from pydantic import BaseModel, Field
|
|
9
9
|
from sqlalchemy import Result, Select, func, select
|
|
10
10
|
from sqlalchemy.ext.asyncio import (
|
|
@@ -17,6 +17,15 @@ from sqlalchemy.ext.declarative import DeferredReflection
|
|
|
17
17
|
from sqlalchemy.orm import DeclarativeBase
|
|
18
18
|
from structlog import get_logger
|
|
19
19
|
|
|
20
|
+
logger = get_logger(__name__)
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from sqlmodel.ext.asyncio.session import AsyncSession
|
|
24
|
+
|
|
25
|
+
except ImportError:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
20
29
|
__all__ = [
|
|
21
30
|
"Base",
|
|
22
31
|
"Collection",
|
|
@@ -30,12 +39,38 @@ __all__ = [
|
|
|
30
39
|
"open_session",
|
|
31
40
|
]
|
|
32
41
|
|
|
33
|
-
SessionFactory = async_sessionmaker(expire_on_commit=False)
|
|
42
|
+
SessionFactory = async_sessionmaker(expire_on_commit=False, class_=AsyncSession)
|
|
34
43
|
|
|
35
44
|
logger = get_logger(__name__)
|
|
36
45
|
|
|
37
46
|
|
|
38
47
|
class Base(DeclarativeBase, DeferredReflection):
|
|
48
|
+
"""Inherit from `Base` to declare an `SQLAlchemy` model.
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
```py
|
|
52
|
+
from fastsqla import Base
|
|
53
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class Hero(Base):
|
|
57
|
+
__tablename__ = "hero"
|
|
58
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
59
|
+
name: Mapped[str] = mapped_column(unique=True)
|
|
60
|
+
secret_identity: Mapped[str]
|
|
61
|
+
age: Mapped[int]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
To learn more on `SQLAlchemy` ORM & Declarative mapping:
|
|
65
|
+
|
|
66
|
+
* [ORM Quick Start](https://docs.sqlalchemy.org/en/20/orm/quickstart.html)
|
|
67
|
+
* [Declarative Mapping](https://docs.sqlalchemy.org/en/20/orm/mapping_styles.html#declarative-mapping)
|
|
68
|
+
|
|
69
|
+
!!! note
|
|
70
|
+
|
|
71
|
+
You don't need this if you use [`SQLModel`](http://sqlmodel.tiangolo.com/).
|
|
72
|
+
"""
|
|
73
|
+
|
|
39
74
|
__abstract__ = True
|
|
40
75
|
|
|
41
76
|
|
|
@@ -43,33 +78,151 @@ class State(TypedDict):
|
|
|
43
78
|
fastsqla_engine: AsyncEngine
|
|
44
79
|
|
|
45
80
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
81
|
+
def new_lifespan(
|
|
82
|
+
url: str | None = None, **kw
|
|
83
|
+
) -> Callable[[FastAPI], _AsyncGeneratorContextManager[State, None]]:
|
|
84
|
+
"""Create a new lifespan async context manager.
|
|
85
|
+
|
|
86
|
+
It expects the exact same parameters as
|
|
87
|
+
[`sqlalchemy.ext.asyncio.create_async_engine`][sqlalchemy.ext.asyncio.create_async_engine]
|
|
88
|
+
|
|
89
|
+
Example:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from fastapi import FastAPI
|
|
93
|
+
from fastsqla import new_lifespan
|
|
52
94
|
|
|
53
|
-
|
|
54
|
-
|
|
95
|
+
lifespan = new_lifespan(
|
|
96
|
+
"sqlite+aiosqlite:///app/db.sqlite", connect_args={"autocommit": False}
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
app = FastAPI(lifespan=lifespan)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
url (str): Database url.
|
|
104
|
+
kw (dict): Configuration parameters as expected by [`sqlalchemy.ext.asyncio.create_async_engine`][sqlalchemy.ext.asyncio.create_async_engine]
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
has_config = url is not None
|
|
108
|
+
|
|
109
|
+
@asynccontextmanager
|
|
110
|
+
async def lifespan(app: FastAPI) -> AsyncGenerator[State, None]:
|
|
111
|
+
if has_config:
|
|
112
|
+
prefix = ""
|
|
113
|
+
sqla_config = {**kw, **{"url": url}}
|
|
114
|
+
|
|
115
|
+
else:
|
|
116
|
+
prefix = "sqlalchemy_"
|
|
117
|
+
sqla_config = {k.lower(): v for k, v in os.environ.items()}
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
engine = async_engine_from_config(sqla_config, prefix=prefix)
|
|
121
|
+
|
|
122
|
+
except KeyError as exc:
|
|
123
|
+
raise Exception(f"Missing {prefix}{exc.args[0]} in environ.") from exc
|
|
124
|
+
|
|
125
|
+
async with engine.begin() as conn:
|
|
126
|
+
await conn.run_sync(Base.prepare)
|
|
55
127
|
|
|
56
|
-
|
|
57
|
-
await conn.run_sync(Base.prepare)
|
|
128
|
+
SessionFactory.configure(bind=engine)
|
|
58
129
|
|
|
59
|
-
|
|
130
|
+
await logger.ainfo("Configured SQLAlchemy.")
|
|
60
131
|
|
|
61
|
-
|
|
132
|
+
yield {"fastsqla_engine": engine}
|
|
62
133
|
|
|
63
|
-
|
|
134
|
+
SessionFactory.configure(bind=None)
|
|
135
|
+
await engine.dispose()
|
|
64
136
|
|
|
65
|
-
|
|
66
|
-
await engine.dispose()
|
|
137
|
+
await logger.ainfo("Cleared SQLAlchemy config.")
|
|
67
138
|
|
|
68
|
-
|
|
139
|
+
return lifespan
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
lifespan = new_lifespan()
|
|
143
|
+
"""Use `fastsqla.lifespan` to set up SQLAlchemy directly from environment variables.
|
|
144
|
+
|
|
145
|
+
In an ASGI application, [lifespan events](https://asgi.readthedocs.io/en/latest/specs/lifespan.html)
|
|
146
|
+
are used to communicate startup & shutdown events.
|
|
147
|
+
|
|
148
|
+
The [`lifespan`](https://fastapi.tiangolo.com/advanced/events/#lifespan) parameter of
|
|
149
|
+
the `FastAPI` app can be assigned to a context manager, which is opened when the app
|
|
150
|
+
starts and closed when the app stops.
|
|
151
|
+
|
|
152
|
+
In order for `FastSQLA` to setup `SQLAlchemy` before the app is started, set
|
|
153
|
+
`lifespan` parameter to `fastsqla.lifespan`:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from fastapi import FastAPI
|
|
157
|
+
from fastsqla import lifespan
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
app = FastAPI(lifespan=lifespan)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
If multiple lifespan contexts are required, create an async context manager function
|
|
164
|
+
to handle them and set it as the app's lifespan:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from collections.abc import AsyncGenerator
|
|
168
|
+
from contextlib import asynccontextmanager
|
|
169
|
+
|
|
170
|
+
from fastapi import FastAPI
|
|
171
|
+
from fastsqla import lifespan as fastsqla_lifespan
|
|
172
|
+
from this_other_library import another_lifespan
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@asynccontextmanager
|
|
176
|
+
async def lifespan(app:FastAPI) -> AsyncGenerator[dict, None]:
|
|
177
|
+
async with AsyncExitStack() as stack:
|
|
178
|
+
yield {
|
|
179
|
+
**stack.enter_async_context(lifespan(app)),
|
|
180
|
+
**stack.enter_async_context(another_lifespan(app)),
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
app = FastAPI(lifespan=lifespan)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
To learn more about lifespan protocol:
|
|
188
|
+
|
|
189
|
+
* [Lifespan Protocol](https://asgi.readthedocs.io/en/latest/specs/lifespan.html)
|
|
190
|
+
* [Use Lifespan State instead of `app.state`](https://github.com/Kludex/fastapi-tips?tab=readme-ov-file#6-use-lifespan-state-instead-of-appstate)
|
|
191
|
+
* [FastAPI lifespan documentation](https://fastapi.tiangolo.com/advanced/events/)
|
|
192
|
+
"""
|
|
69
193
|
|
|
70
194
|
|
|
71
195
|
@asynccontextmanager
|
|
72
196
|
async def open_session() -> AsyncGenerator[AsyncSession, None]:
|
|
197
|
+
"""Async context manager that opens a new `SQLAlchemy` or `SQLModel` async session.
|
|
198
|
+
|
|
199
|
+
To the contrary of the [`Session`][fastsqla.Session] dependency which can only be
|
|
200
|
+
used in endpoints, `open_session` can be used anywhere such as in background tasks.
|
|
201
|
+
|
|
202
|
+
On exit, it automatically commits the session if no errors occur inside the context,
|
|
203
|
+
or rolls back when an exception is raised.
|
|
204
|
+
In all cases, it closes the session and returns the associated connection to the
|
|
205
|
+
connection pool.
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
When `SQLModel` is not installed, an async generator that yields an
|
|
210
|
+
[`SQLAlchemy AsyncSession`][sqlalchemy.ext.asyncio.AsyncSession].
|
|
211
|
+
|
|
212
|
+
When `SQLModel` is installed, an async generator that yields an
|
|
213
|
+
[`SQLModel AsyncSession`](https://github.com/fastapi/sqlmodel/blob/main/sqlmodel/ext/asyncio/session.py#L32)
|
|
214
|
+
which inherits from [`SQLAlchemy AsyncSession`][sqlalchemy.ext.asyncio.AsyncSession].
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
from fastsqla import open_session
|
|
219
|
+
|
|
220
|
+
async def example():
|
|
221
|
+
async with open_session() as session:
|
|
222
|
+
await session.execute(...)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
"""
|
|
73
226
|
session = SessionFactory()
|
|
74
227
|
try:
|
|
75
228
|
yield session
|
|
@@ -100,6 +253,61 @@ async def new_session() -> AsyncGenerator[AsyncSession, None]:
|
|
|
100
253
|
|
|
101
254
|
|
|
102
255
|
Session = Annotated[AsyncSession, Depends(new_session)]
|
|
256
|
+
"""Dependency used exclusively in endpoints to get an `SQLAlchemy` or `SQLModel` session.
|
|
257
|
+
|
|
258
|
+
`Session` is a [`FastAPI` dependency](https://fastapi.tiangolo.com/tutorial/dependencies/)
|
|
259
|
+
that provides an asynchronous `SQLAlchemy` session or `SQLModel` one if it's installed.
|
|
260
|
+
By defining an argument with type `Session` in an endpoint, `FastAPI` will automatically
|
|
261
|
+
inject an async session into the endpoint.
|
|
262
|
+
|
|
263
|
+
At the end of request handling:
|
|
264
|
+
|
|
265
|
+
* If no exceptions are raised, the session is automatically committed.
|
|
266
|
+
* If an exception is raised, the session is automatically rolled back.
|
|
267
|
+
* In all cases, the session is closed and the associated connection is returned to the
|
|
268
|
+
connection pool.
|
|
269
|
+
|
|
270
|
+
Example:
|
|
271
|
+
|
|
272
|
+
``` py title="example.py" hl_lines="6"
|
|
273
|
+
from fastsqla import Item, Session
|
|
274
|
+
...
|
|
275
|
+
|
|
276
|
+
@app.get("/heros/{hero_id}", response_model=Item[HeroItem])
|
|
277
|
+
async def get_items(
|
|
278
|
+
session: Session, # (1)!
|
|
279
|
+
item_id: int,
|
|
280
|
+
):
|
|
281
|
+
hero = await session.get(Hero, hero_id)
|
|
282
|
+
return {"data": hero}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
1. Just define an argument with type `Session` to get an async session injected
|
|
286
|
+
in your endpoint.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
**Recommendation**: Unless there is a good reason to do so, avoid committing the session
|
|
291
|
+
manually, as `FastSQLA` handles it automatically.
|
|
292
|
+
|
|
293
|
+
If you need data generated by the database server, such as auto-incremented IDs, flush
|
|
294
|
+
the session instead:
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
from fastsqla import Item, Session
|
|
298
|
+
...
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@app.post("/heros", response_model=Item[HeroItem])
|
|
302
|
+
async def create_item(session: Session, new_hero: HeroBase):
|
|
303
|
+
hero = Hero(**new_hero.model_dump())
|
|
304
|
+
session.add(hero)
|
|
305
|
+
await session.flush()
|
|
306
|
+
return {"data": hero}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Or use the [session context manager][fastsqla.open_session] instead.
|
|
310
|
+
"""
|
|
103
311
|
|
|
104
312
|
|
|
105
313
|
class Meta(BaseModel):
|
|
@@ -121,6 +329,23 @@ class Collection(BaseModel, Generic[T]):
|
|
|
121
329
|
|
|
122
330
|
|
|
123
331
|
class Page(Collection[T]):
|
|
332
|
+
"""Generic container that contains collection data and page metadata.
|
|
333
|
+
|
|
334
|
+
The `Page` model is used to return paginated data in paginated endpoints:
|
|
335
|
+
|
|
336
|
+
```json
|
|
337
|
+
{
|
|
338
|
+
"data": list[T],
|
|
339
|
+
"meta": {
|
|
340
|
+
"offset": int,
|
|
341
|
+
"total_items": int,
|
|
342
|
+
"total_pages": int,
|
|
343
|
+
"page_number": int,
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
"""
|
|
348
|
+
|
|
124
349
|
meta: Meta
|
|
125
350
|
|
|
126
351
|
|
|
@@ -193,4 +418,10 @@ def new_pagination(
|
|
|
193
418
|
|
|
194
419
|
|
|
195
420
|
type PaginateType[T] = Callable[[Select], Awaitable[Page[T]]]
|
|
421
|
+
|
|
196
422
|
Paginate = Annotated[PaginateType[T], Depends(new_pagination())]
|
|
423
|
+
"""A dependency used in endpoints to paginate `SQLAlchemy` select queries.
|
|
424
|
+
|
|
425
|
+
It adds **`offset`** and **`limit`** query parameters to the endpoint, which are used to
|
|
426
|
+
paginate. The model returned by the endpoint is a [`Page`][fastsqla.Page] model.
|
|
427
|
+
"""
|
FastSQLA-0.2.4.dist-info/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Hadrien David
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.2
|
|
2
|
-
Name: FastSQLA
|
|
3
|
-
Version: 0.2.4
|
|
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
|
-
|
|
44
|
-
# 🚀 FastSQLA
|
|
45
|
-
|
|
46
|
-
[](https://pypi.org/project/FastSQLA/)
|
|
47
|
-
[](https://conventionalcommits.org)
|
|
48
|
-
[](https://codecov.io/gh/hadrien/fastsqla)
|
|
49
|
-
|
|
50
|
-
`FastSQLA` is an [`SQLAlchemy`] extension for [`FastAPI`].
|
|
51
|
-
It supports asynchronous `SQLAlchemy` sessions and includes built-in custimizable
|
|
52
|
-
pagination.
|
|
53
|
-
|
|
54
|
-
## Features
|
|
55
|
-
|
|
56
|
-
<details>
|
|
57
|
-
<summary>Automatic SQLAlchemy configuration at app startup.</summary>
|
|
58
|
-
|
|
59
|
-
Using [`FastAPI` Lifespan](https://fastapi.tiangolo.com/advanced/events/#lifespan):
|
|
60
|
-
```python
|
|
61
|
-
from fastapi import FastAPI
|
|
62
|
-
from fastsqla import lifespan
|
|
63
|
-
|
|
64
|
-
app = FastAPI(lifespan=lifespan)
|
|
65
|
-
```
|
|
66
|
-
</details>
|
|
67
|
-
<details>
|
|
68
|
-
<summary>Async SQLAlchemy session as a FastAPI dependency.</summary>
|
|
69
|
-
|
|
70
|
-
```python
|
|
71
|
-
...
|
|
72
|
-
from fastsqla import Session
|
|
73
|
-
from sqlalchemy import select
|
|
74
|
-
...
|
|
75
|
-
|
|
76
|
-
@app.get("/heros")
|
|
77
|
-
async def get_heros(session:Session):
|
|
78
|
-
stmt = select(...)
|
|
79
|
-
result = await session.execute(stmt)
|
|
80
|
-
...
|
|
81
|
-
```
|
|
82
|
-
</details>
|
|
83
|
-
<details>
|
|
84
|
-
<summary>Built-in pagination.</summary>
|
|
85
|
-
|
|
86
|
-
```python
|
|
87
|
-
...
|
|
88
|
-
from fastsqla import Page, Paginate
|
|
89
|
-
from sqlalchemy import select
|
|
90
|
-
...
|
|
91
|
-
|
|
92
|
-
@app.get("/heros", response_model=Page[HeroModel])
|
|
93
|
-
async def get_heros(paginate:Paginate):
|
|
94
|
-
return paginate(select(Hero))
|
|
95
|
-
```
|
|
96
|
-
</details>
|
|
97
|
-
<details>
|
|
98
|
-
<summary>Allows pagination customization.</summary>
|
|
99
|
-
|
|
100
|
-
```python
|
|
101
|
-
...
|
|
102
|
-
from fastapi import new_pagination
|
|
103
|
-
...
|
|
104
|
-
|
|
105
|
-
Paginate = new_pagination(min_page_size=5, max_page_size=500)
|
|
106
|
-
|
|
107
|
-
@app.get("/heros", response_model=Page[HeroModel])
|
|
108
|
-
async def get_heros(paginate:Paginate):
|
|
109
|
-
return paginate(select(Hero))
|
|
110
|
-
```
|
|
111
|
-
</details>
|
|
112
|
-
|
|
113
|
-
And more ...
|
|
114
|
-
<!-- <details><summary></summary></details> -->
|
|
115
|
-
|
|
116
|
-
## Installing
|
|
117
|
-
|
|
118
|
-
Using [uv](https://docs.astral.sh/uv/):
|
|
119
|
-
```bash
|
|
120
|
-
uv add fastsqla
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
Using [pip](https://pip.pypa.io/):
|
|
124
|
-
```
|
|
125
|
-
pip install fastsqla
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## Quick Example
|
|
129
|
-
|
|
130
|
-
```python
|
|
131
|
-
# example.py
|
|
132
|
-
from http import HTTPStatus
|
|
133
|
-
|
|
134
|
-
from fastapi import FastAPI, HTTPException
|
|
135
|
-
from pydantic import BaseModel, ConfigDict
|
|
136
|
-
from sqlalchemy import select
|
|
137
|
-
from sqlalchemy.exc import IntegrityError
|
|
138
|
-
from sqlalchemy.orm import Mapped, mapped_column
|
|
139
|
-
|
|
140
|
-
from fastsqla import Base, Item, Page, Paginate, Session, lifespan
|
|
141
|
-
|
|
142
|
-
app = FastAPI(lifespan=lifespan)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
class Hero(Base):
|
|
146
|
-
__tablename__ = "hero"
|
|
147
|
-
id: Mapped[int] = mapped_column(primary_key=True)
|
|
148
|
-
name: Mapped[str] = mapped_column(unique=True)
|
|
149
|
-
secret_identity: Mapped[str]
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
class HeroBase(BaseModel):
|
|
153
|
-
name: str
|
|
154
|
-
secret_identity: str
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
class HeroModel(HeroBase):
|
|
158
|
-
model_config = ConfigDict(from_attributes=True)
|
|
159
|
-
id: int
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
@app.get("/heros", response_model=Page[HeroModel])
|
|
163
|
-
async def list_users(paginate: Paginate):
|
|
164
|
-
return await paginate(select(Hero))
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
@app.get("/heros/{hero_id}", response_model=Item[HeroModel])
|
|
168
|
-
async def get_user(hero_id: int, session: Session):
|
|
169
|
-
hero = await session.get(Hero, hero_id)
|
|
170
|
-
if hero is None:
|
|
171
|
-
raise HTTPException(HTTPStatus.NOT_FOUND, "Hero not found")
|
|
172
|
-
return {"data": hero}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
@app.post("/heros", response_model=Item[HeroModel])
|
|
176
|
-
async def create_user(new_hero: HeroBase, session: Session):
|
|
177
|
-
hero = Hero(**new_hero.model_dump())
|
|
178
|
-
session.add(hero)
|
|
179
|
-
try:
|
|
180
|
-
await session.flush()
|
|
181
|
-
except IntegrityError:
|
|
182
|
-
raise HTTPException(HTTPStatus.CONFLICT, "Duplicate hero name")
|
|
183
|
-
return {"data": hero}
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
> [!NOTE]
|
|
187
|
-
> Sqlite is used for the sake of the example.
|
|
188
|
-
> FastSQLA is compatible with all async db drivers that SQLAlchemy is compatible with.
|
|
189
|
-
|
|
190
|
-
<details>
|
|
191
|
-
<summary>Create an <code>sqlite3</code> db:</summary>
|
|
192
|
-
|
|
193
|
-
```bash
|
|
194
|
-
sqlite3 db.sqlite <<EOF
|
|
195
|
-
CREATE TABLE hero (
|
|
196
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
197
|
-
name TEXT NOT NULL UNIQUE, -- Hero name (e.g., Superman)
|
|
198
|
-
secret_identity TEXT NOT NULL -- Secret identity (e.g., Clark Kent)
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
-- Insert heroes with hero name and secret identity
|
|
202
|
-
INSERT INTO hero (name, secret_identity) VALUES ('Superman', 'Clark Kent');
|
|
203
|
-
INSERT INTO hero (name, secret_identity) VALUES ('Batman', 'Bruce Wayne');
|
|
204
|
-
INSERT INTO hero (name, secret_identity) VALUES ('Wonder Woman', 'Diana Prince');
|
|
205
|
-
INSERT INTO hero (name, secret_identity) VALUES ('Iron Man', 'Tony Stark');
|
|
206
|
-
INSERT INTO hero (name, secret_identity) VALUES ('Spider-Man', 'Peter Parker');
|
|
207
|
-
INSERT INTO hero (name, secret_identity) VALUES ('Captain America', 'Steve Rogers');
|
|
208
|
-
INSERT INTO hero (name, secret_identity) VALUES ('Black Widow', 'Natasha Romanoff');
|
|
209
|
-
INSERT INTO hero (name, secret_identity) VALUES ('Thor', 'Thor Odinson');
|
|
210
|
-
INSERT INTO hero (name, secret_identity) VALUES ('Scarlet Witch', 'Wanda Maximoff');
|
|
211
|
-
INSERT INTO hero (name, secret_identity) VALUES ('Doctor Strange', 'Stephen Strange');
|
|
212
|
-
INSERT INTO hero (name, secret_identity) VALUES ('The Flash', 'Barry Allen');
|
|
213
|
-
INSERT INTO hero (name, secret_identity) VALUES ('Green Lantern', 'Hal Jordan');
|
|
214
|
-
EOF
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
</details>
|
|
218
|
-
|
|
219
|
-
<details>
|
|
220
|
-
<summary>Install dependencies & run the app</summary>
|
|
221
|
-
|
|
222
|
-
```bash
|
|
223
|
-
pip install uvicorn aiosqlite fastsqla
|
|
224
|
-
sqlalchemy_url=sqlite+aiosqlite:///db.sqlite?check_same_thread=false uvicorn example:app
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
</details>
|
|
228
|
-
|
|
229
|
-
Execute `GET /heros?offset=10`:
|
|
230
|
-
|
|
231
|
-
```bash
|
|
232
|
-
curl -X 'GET' \
|
|
233
|
-
'http://127.0.0.1:8000/heros?offset=10&limit=10' \
|
|
234
|
-
-H 'accept: application/json'
|
|
235
|
-
```
|
|
236
|
-
Returns:
|
|
237
|
-
```json
|
|
238
|
-
{
|
|
239
|
-
"data": [
|
|
240
|
-
{
|
|
241
|
-
"name": "The Flash",
|
|
242
|
-
"secret_identity": "Barry Allen",
|
|
243
|
-
"id": 11
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
"name": "Green Lantern",
|
|
247
|
-
"secret_identity": "Hal Jordan",
|
|
248
|
-
"id": 12
|
|
249
|
-
}
|
|
250
|
-
],
|
|
251
|
-
"meta": {
|
|
252
|
-
"offset": 10,
|
|
253
|
-
"total_items": 12,
|
|
254
|
-
"total_pages": 2,
|
|
255
|
-
"page_number": 2
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
[`FastAPI`]: https://fastapi.tiangolo.com/
|
|
261
|
-
[`SQLAlchemy`]: http://sqlalchemy.org/
|
FastSQLA-0.2.4.dist-info/RECORD
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
fastsqla.py,sha256=FRN1RhVQPlD7hMNZG7wvSWZ7qdfXaqjmB3pXVa3z6Yw,5188
|
|
2
|
-
FastSQLA-0.2.4.dist-info/LICENSE,sha256=uNKcyfhTq0YUZxgSDiDGBHoJfflKjGWecSfWxpYe_O4,1070
|
|
3
|
-
FastSQLA-0.2.4.dist-info/METADATA,sha256=b0Lb3jCdUsQEEOLdW881z5nlLtfXNwaH3ir8PQRcSuw,7658
|
|
4
|
-
FastSQLA-0.2.4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
5
|
-
FastSQLA-0.2.4.dist-info/top_level.txt,sha256=Uh-1ssTtuSS4_SYCBeDoDVOxqWTrRAPEBZkuih5isSE,9
|
|
6
|
-
FastSQLA-0.2.4.dist-info/RECORD,,
|
|
File without changes
|