fastapi-sqla 2.10.1__tar.gz → 3.0.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.

Potentially problematic release.


This version of fastapi-sqla might be problematic. Click here for more details.

@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastapi-sqla
3
- Version: 2.10.1
3
+ Version: 3.0.0
4
4
  Summary: SQLAlchemy extension for FastAPI with support for pagination, asyncio, and pytest, ready for production.
5
5
  Home-page: https://github.com/dialoguemd/fastapi-sqla
6
6
  License: MIT
7
7
  Keywords: FastAPI,SQLAlchemy,asyncio,pytest,alembic
8
8
  Author: Hadrien David
9
9
  Author-email: hadrien.david@dialogue.co
10
- Requires-Python: >=3.7,<4.0
10
+ Requires-Python: >=3.9,<4.0
11
11
  Classifier: Development Status :: 5 - Production/Stable
12
12
  Classifier: Environment :: Web Environment
13
13
  Classifier: Framework :: AsyncIO
@@ -19,8 +19,6 @@ Classifier: License :: OSI Approved :: MIT License
19
19
  Classifier: Operating System :: OS Independent
20
20
  Classifier: Programming Language :: Python
21
21
  Classifier: Programming Language :: Python :: 3
22
- Classifier: Programming Language :: Python :: 3.7
23
- Classifier: Programming Language :: Python :: 3.8
24
22
  Classifier: Programming Language :: Python :: 3.9
25
23
  Classifier: Programming Language :: Python :: 3.10
26
24
  Classifier: Programming Language :: Python :: 3.11
@@ -43,7 +41,7 @@ Requires-Dist: asgi_lifespan (>=1.0.1,<2.0.0) ; extra == "tests"
43
41
  Requires-Dist: asyncpg (>=0.28.0,<0.29.0) ; extra == "asyncpg"
44
42
  Requires-Dist: black (>=22.8.0,<23.0.0) ; extra == "tests"
45
43
  Requires-Dist: boto3 (>=1.24.74,<2.0.0) ; extra == "aws-rds-iam"
46
- Requires-Dist: fastapi (>=0.61)
44
+ Requires-Dist: fastapi (>=0.95.1)
47
45
  Requires-Dist: greenlet (>=1.1.3,<2.0.0) ; extra == "tests"
48
46
  Requires-Dist: httpx (>=0.23.0,<0.24.0) ; extra == "tests"
49
47
  Requires-Dist: isort (>=5.5.3,<6.0.0) ; extra == "tests"
@@ -90,7 +88,7 @@ unique `email`:
90
88
 
91
89
  ```python
92
90
  # main.py
93
- from fastapi import Depends, FastAPI, HTTPException
91
+ from fastapi import FastAPI, HTTPException
94
92
  from fastapi_sqla import Base, Item, Page, Paginate, Session, setup
95
93
  from pydantic import BaseModel, EmailStr
96
94
  from sqlalchemy import select
@@ -118,12 +116,12 @@ class UserModel(UserIn):
118
116
 
119
117
 
120
118
  @app.get("/users", response_model=Page[UserModel])
121
- def list_users(paginate: Paginate = Depends()):
119
+ def list_users(paginate: Paginate):
122
120
  return paginate(select(User))
123
121
 
124
122
 
125
123
  @app.get("/users/{user_id}", response_model=Item[UserModel])
126
- def get_user(user_id: int, session: Session = Depends()):
124
+ def get_user(user_id: int, session: Session):
127
125
  user = session.get(User, user_id)
128
126
  if user is None:
129
127
  raise HTTPException(404)
@@ -131,7 +129,7 @@ def get_user(user_id: int, session: Session = Depends()):
131
129
 
132
130
 
133
131
  @app.post("/users", response_model=Item[UserModel])
134
- def create_user(new_user: UserIn, session: Session = Depends()):
132
+ def create_user(new_user: UserIn, session: Session):
135
133
  user = User(**new_user.model_dump())
136
134
  session.add(user)
137
135
  try:
@@ -173,6 +171,21 @@ The only required key is `sqlalchemy_url`, which provides the database URL, exam
173
171
  export sqlalchemy_url=postgresql://postgres@localhost
174
172
  ```
175
173
 
174
+ ### Multi-session support
175
+
176
+ In order to configure multiple sessions for the application,
177
+ set the environment variables with this prefix format: `fastapi_sqla__MY_KEY__`.
178
+
179
+ As with the default session, each matching key (after the prefix is stripped)
180
+ is treated as though it were the corresponding keyword argument to [`sqlalchemy.create_engine`]
181
+ call.
182
+
183
+ For example, to configure a session with the `read_only` key:
184
+
185
+ ```bash
186
+ export fastapi_sqla__read_only__sqlalchemy_url=postgresql://postgres@localhost
187
+ ```
188
+
176
189
  ### `asyncio` support using [`asyncpg`]
177
190
 
178
191
  SQLAlchemy `>= 1.4` supports `asyncio`.
@@ -182,7 +195,7 @@ To enable `asyncio` support against a Postgres DB, install `asyncpg`:
182
195
  pip install asyncpg
183
196
  ```
184
197
 
185
- And define environment variable `sqlalchemy_url` with `postgres+asyncpg` scheme:
198
+ And define the environment variable `sqlalchemy_url` with `postgres+asyncpg` scheme:
186
199
 
187
200
  ```bash
188
201
  export sqlalchemy_url=postgresql+asyncpg://postgres@localhost
@@ -216,23 +229,71 @@ class Entity(Base):
216
229
 
217
230
  Use [FastAPI dependency injection] to get a session as a parameter of a path operation
218
231
  function.
219
- SQLAlchemy session is committed before response is returned or rollbacked if any
232
+
233
+ The SQLAlchemy session is committed before the response is returned or rollbacked if any
220
234
  exception occurred:
221
235
 
222
236
  ```python
237
+ from fastapi import APIRouter
238
+ from fastapi_sqla import AsyncSession, Session
239
+
240
+ router = APIRouter()
241
+
242
+
243
+ @router.get("/example")
244
+ def example(session: Session):
245
+ return session.execute("SELECT now()").scalar()
246
+
247
+
248
+ @router.get("/async_example")
249
+ async def async_example(session: AsyncSession):
250
+ return await session.scalar("SELECT now()")
251
+ ```
252
+
253
+ In order to get a session configured with a custom key:
254
+
255
+ ```python
256
+ from typing import Annotated
257
+
223
258
  from fastapi import APIRouter, Depends
224
- from fastapi_sqla import Session, AsyncSession
259
+ from fastapi_sqla import (
260
+ AsyncSessionDependency,
261
+ SessionDependency,
262
+ SqlaAsyncSession,
263
+ SqlaSession,
264
+ )
225
265
 
226
266
  router = APIRouter()
227
267
 
228
268
 
269
+ # Preferred
270
+
271
+ ReadOnlySession = Annotated[SqlaSession, Depends(SessionDependency(key="read_only"))]
272
+ AsyncReadOnlySession = Annotated[
273
+ SqlaAsyncSession, Depends(AsyncSessionDependency(key="read_only"))
274
+ ]
275
+
229
276
  @router.get("/example")
230
- def example(session: Session = Depends()):
277
+ def example(session: ReadOnlySession):
231
278
  return session.execute("SELECT now()").scalar()
232
279
 
233
280
 
234
281
  @router.get("/async_example")
235
- async def async_example(session: AsyncSession = Depends()):
282
+ async def async_example(session: AsyncReadOnlySession):
283
+ return await session.scalar("SELECT now()")
284
+
285
+
286
+ # Alternative
287
+
288
+ @router.get("/example/alt")
289
+ def example_alt(session: SqlaSession = Depends(SessionDependency(key="read_only"))):
290
+ return session.execute("SELECT now()").scalar()
291
+
292
+
293
+ @router.get("/async_example/alt")
294
+ async def async_example_alt(
295
+ session: SqlaAsyncSession = Depends(AsyncSessionDependency(key="read_only")),
296
+ ):
236
297
  return await session.scalar("SELECT now()")
237
298
  ```
238
299
 
@@ -240,12 +301,12 @@ async def async_example(session: AsyncSession = Depends()):
240
301
 
241
302
  When needing a session outside of a path operation, like when using
242
303
  [FastAPI background tasks], use `fastapi_sqla.open_session` context manager.
243
- SQLAlchemy session is committed when exiting context or rollbacked if any exception
304
+ The SQLAlchemy session is committed when exiting context or rollbacked if any exception
244
305
  occurred:
245
306
 
246
307
  ```python
247
308
  from fastapi import APIRouter, BackgroundTasks
248
- from fastapi_sqla import open_session, open_async_session
309
+ from fastapi_sqla import open_async_session, open_session
249
310
 
250
311
  router = APIRouter()
251
312
 
@@ -260,16 +321,23 @@ def run_bg():
260
321
  with open_session() as session:
261
322
  session.execute("SELECT now()").scalar()
262
323
 
324
+ def run_bg_with_key():
325
+ with open_session(key="read_only") as session:
326
+ session.execute("SELECT now()").scalar()
263
327
 
264
328
  async def run_async_bg():
265
329
  async with open_async_session() as session:
266
330
  await session.scalar("SELECT now()")
331
+
332
+ async def run_async_bg_with_key():
333
+ async with open_async_session(key="read_only") as session:
334
+ await session.scalar("SELECT now()")
267
335
  ```
268
336
 
269
337
  ## Pagination
270
338
 
271
339
  ```python
272
- from fastapi import APIRouter, Depends
340
+ from fastapi import APIRouter
273
341
  from fastapi_sqla import Base, Page, Paginate
274
342
  from pydantic import BaseModel
275
343
  from sqlalchemy import select
@@ -290,7 +358,7 @@ class UserModel(BaseModel):
290
358
 
291
359
 
292
360
  @router.get("/users", response_model=Page[UserModel])
293
- def all_users(paginate: Paginate = Depends()):
361
+ def all_users(paginate: Paginate):
294
362
  return paginate(select(User))
295
363
  ```
296
364
 
@@ -327,7 +395,7 @@ To paginate a query which doesn't return [scalars], specify `scalars=False` when
327
395
  `paginate`:
328
396
 
329
397
  ```python
330
- from fastapi import APIRouter, Depends
398
+ from fastapi import APIRouter
331
399
  from fastapi_sqla import Base, Page, Paginate
332
400
  from pydantic import BaseModel
333
401
  from sqlalchemy import func, select
@@ -352,7 +420,7 @@ class UserModel(BaseModel):
352
420
 
353
421
 
354
422
  @router.get("/users", response_model=Page[UserModel])
355
- def all_users(paginate: Paginate = Depends()):
423
+ def all_users(paginate: Paginate):
356
424
  query = (
357
425
  select(User.id, User.name, func.count(Note.id).label("notes_count"))
358
426
  .join(Note)
@@ -388,15 +456,15 @@ class UserModel(BaseModel):
388
456
  name: str
389
457
 
390
458
 
391
- def query_count(session: Session = Depends()) -> int:
459
+ def query_count(session: Session) -> int:
392
460
  return session.execute(select(func.count()).select_from(User)).scalar()
393
461
 
394
462
 
395
- Paginate = Pagination(min_page_size=5, max_page_size=500, query_count=query_count)
463
+ CustomPaginate = Pagination(min_page_size=5, max_page_size=500, query_count=query_count)
396
464
 
397
465
 
398
466
  @router.get("/users", response_model=Page[UserModel])
399
- def all_users(paginate: Paginate = Depends()):
467
+ def all_users(paginate: CustomPaginate = Depends()):
400
468
  return paginate(select(User))
401
469
  ```
402
470
 
@@ -405,7 +473,7 @@ def all_users(paginate: Paginate = Depends()):
405
473
  When using the asyncio support, use the `AsyncPaginate` dependency:
406
474
 
407
475
  ```python
408
- from fastapi import APIRouter, Depends
476
+ from fastapi import APIRouter
409
477
  from fastapi_sqla import Base, Page, AsyncPaginate
410
478
  from pydantic import BaseModel
411
479
  from sqlalchemy import select
@@ -426,7 +494,7 @@ class UserModel(BaseModel):
426
494
 
427
495
 
428
496
  @router.get("/users", response_model=Page[UserModel])
429
- async def all_users(paginate: AsyncPaginate = Depends()):
497
+ async def all_users(paginate: AsyncPaginate):
430
498
  return await paginate(select(User))
431
499
  ```
432
500
 
@@ -450,16 +518,85 @@ class UserModel(BaseModel):
450
518
  name: str
451
519
 
452
520
 
453
- async def query_count(session: AsyncSession = Depends()) -> int:
521
+ async def query_count(session: AsyncSession) -> int:
454
522
  result = await session.execute(select(func.count()).select_from(User))
455
523
  return result.scalar()
456
524
 
457
525
 
458
- Paginate = AsyncPagination(min_page_size=5, max_page_size=500, query_count=query_count)
526
+ CustomPaginate = AsyncPagination(min_page_size=5, max_page_size=500, query_count=query_count)
459
527
 
460
528
 
461
529
  @router.get("/users", response_model=Page[UserModel])
462
- def all_users(paginate: CustomPaginate = Depends()):
530
+ async def all_users(paginate: CustomPaginate = Depends()):
531
+ return await paginate(select(User))
532
+ ```
533
+
534
+ ### Multi-session support
535
+
536
+ Pagination supports multiple sessions as well. To paginate using a session
537
+ configured with a custom key:
538
+
539
+ ```python
540
+ from typing import Annotated
541
+
542
+ from fastapi import APIRouter, Depends
543
+ from fastapi_sqla import (
544
+ AsyncPaginateSignature,
545
+ AsyncPagination,
546
+ Base,
547
+ Page,
548
+ PaginateSignature,
549
+ Pagination,
550
+ )
551
+ from pydantic import BaseModel
552
+ from sqlalchemy import func, select
553
+
554
+ router = APIRouter()
555
+
556
+
557
+ class User(Base):
558
+ __tablename__ = "user"
559
+
560
+
561
+ class UserModel(BaseModel):
562
+ id: int
563
+ name: str
564
+
565
+
566
+ # Preferred
567
+
568
+ ReadOnlyPaginate = Annotated[
569
+ PaginateSignature, Depends(Pagination(session_key="read_only"))
570
+ ]
571
+ AsyncReadOnlyPaginate = Annotated[
572
+ AsyncPaginateSignature, Depends(AsyncPagination(session_key="read_only"))
573
+ ]
574
+
575
+ @router.get("/users", response_model=Page[UserModel])
576
+ def all_users(paginate: ReadOnlyPaginate):
577
+ return paginate(select(User))
578
+
579
+ @router.get("/async_users", response_model=Page[UserModel])
580
+ async def async_all_users(paginate: AsyncReadOnlyPaginate):
581
+ return await paginate(select(User))
582
+
583
+
584
+ # Alternative
585
+
586
+ @router.get("/users/alt", response_model=Page[UserModel])
587
+ def all_users_alt(
588
+ paginate: PaginateSignature = Depends(
589
+ Pagination(session_key="read_only")
590
+ ),
591
+ ):
592
+ return paginate(select(User))
593
+
594
+ @router.get("/async_users/alt", response_model=Page[UserModel])
595
+ async def async_all_users_alt(
596
+ paginate: AsyncPaginateSignature = Depends(
597
+ AsyncPagination(session_key="read_only")
598
+ ),
599
+ ):
463
600
  return await paginate(select(User))
464
601
  ```
465
602
 
@@ -27,7 +27,7 @@ unique `email`:
27
27
 
28
28
  ```python
29
29
  # main.py
30
- from fastapi import Depends, FastAPI, HTTPException
30
+ from fastapi import FastAPI, HTTPException
31
31
  from fastapi_sqla import Base, Item, Page, Paginate, Session, setup
32
32
  from pydantic import BaseModel, EmailStr
33
33
  from sqlalchemy import select
@@ -55,12 +55,12 @@ class UserModel(UserIn):
55
55
 
56
56
 
57
57
  @app.get("/users", response_model=Page[UserModel])
58
- def list_users(paginate: Paginate = Depends()):
58
+ def list_users(paginate: Paginate):
59
59
  return paginate(select(User))
60
60
 
61
61
 
62
62
  @app.get("/users/{user_id}", response_model=Item[UserModel])
63
- def get_user(user_id: int, session: Session = Depends()):
63
+ def get_user(user_id: int, session: Session):
64
64
  user = session.get(User, user_id)
65
65
  if user is None:
66
66
  raise HTTPException(404)
@@ -68,7 +68,7 @@ def get_user(user_id: int, session: Session = Depends()):
68
68
 
69
69
 
70
70
  @app.post("/users", response_model=Item[UserModel])
71
- def create_user(new_user: UserIn, session: Session = Depends()):
71
+ def create_user(new_user: UserIn, session: Session):
72
72
  user = User(**new_user.model_dump())
73
73
  session.add(user)
74
74
  try:
@@ -110,6 +110,21 @@ The only required key is `sqlalchemy_url`, which provides the database URL, exam
110
110
  export sqlalchemy_url=postgresql://postgres@localhost
111
111
  ```
112
112
 
113
+ ### Multi-session support
114
+
115
+ In order to configure multiple sessions for the application,
116
+ set the environment variables with this prefix format: `fastapi_sqla__MY_KEY__`.
117
+
118
+ As with the default session, each matching key (after the prefix is stripped)
119
+ is treated as though it were the corresponding keyword argument to [`sqlalchemy.create_engine`]
120
+ call.
121
+
122
+ For example, to configure a session with the `read_only` key:
123
+
124
+ ```bash
125
+ export fastapi_sqla__read_only__sqlalchemy_url=postgresql://postgres@localhost
126
+ ```
127
+
113
128
  ### `asyncio` support using [`asyncpg`]
114
129
 
115
130
  SQLAlchemy `>= 1.4` supports `asyncio`.
@@ -119,7 +134,7 @@ To enable `asyncio` support against a Postgres DB, install `asyncpg`:
119
134
  pip install asyncpg
120
135
  ```
121
136
 
122
- And define environment variable `sqlalchemy_url` with `postgres+asyncpg` scheme:
137
+ And define the environment variable `sqlalchemy_url` with `postgres+asyncpg` scheme:
123
138
 
124
139
  ```bash
125
140
  export sqlalchemy_url=postgresql+asyncpg://postgres@localhost
@@ -153,23 +168,71 @@ class Entity(Base):
153
168
 
154
169
  Use [FastAPI dependency injection] to get a session as a parameter of a path operation
155
170
  function.
156
- SQLAlchemy session is committed before response is returned or rollbacked if any
171
+
172
+ The SQLAlchemy session is committed before the response is returned or rollbacked if any
157
173
  exception occurred:
158
174
 
159
175
  ```python
176
+ from fastapi import APIRouter
177
+ from fastapi_sqla import AsyncSession, Session
178
+
179
+ router = APIRouter()
180
+
181
+
182
+ @router.get("/example")
183
+ def example(session: Session):
184
+ return session.execute("SELECT now()").scalar()
185
+
186
+
187
+ @router.get("/async_example")
188
+ async def async_example(session: AsyncSession):
189
+ return await session.scalar("SELECT now()")
190
+ ```
191
+
192
+ In order to get a session configured with a custom key:
193
+
194
+ ```python
195
+ from typing import Annotated
196
+
160
197
  from fastapi import APIRouter, Depends
161
- from fastapi_sqla import Session, AsyncSession
198
+ from fastapi_sqla import (
199
+ AsyncSessionDependency,
200
+ SessionDependency,
201
+ SqlaAsyncSession,
202
+ SqlaSession,
203
+ )
162
204
 
163
205
  router = APIRouter()
164
206
 
165
207
 
208
+ # Preferred
209
+
210
+ ReadOnlySession = Annotated[SqlaSession, Depends(SessionDependency(key="read_only"))]
211
+ AsyncReadOnlySession = Annotated[
212
+ SqlaAsyncSession, Depends(AsyncSessionDependency(key="read_only"))
213
+ ]
214
+
166
215
  @router.get("/example")
167
- def example(session: Session = Depends()):
216
+ def example(session: ReadOnlySession):
168
217
  return session.execute("SELECT now()").scalar()
169
218
 
170
219
 
171
220
  @router.get("/async_example")
172
- async def async_example(session: AsyncSession = Depends()):
221
+ async def async_example(session: AsyncReadOnlySession):
222
+ return await session.scalar("SELECT now()")
223
+
224
+
225
+ # Alternative
226
+
227
+ @router.get("/example/alt")
228
+ def example_alt(session: SqlaSession = Depends(SessionDependency(key="read_only"))):
229
+ return session.execute("SELECT now()").scalar()
230
+
231
+
232
+ @router.get("/async_example/alt")
233
+ async def async_example_alt(
234
+ session: SqlaAsyncSession = Depends(AsyncSessionDependency(key="read_only")),
235
+ ):
173
236
  return await session.scalar("SELECT now()")
174
237
  ```
175
238
 
@@ -177,12 +240,12 @@ async def async_example(session: AsyncSession = Depends()):
177
240
 
178
241
  When needing a session outside of a path operation, like when using
179
242
  [FastAPI background tasks], use `fastapi_sqla.open_session` context manager.
180
- SQLAlchemy session is committed when exiting context or rollbacked if any exception
243
+ The SQLAlchemy session is committed when exiting context or rollbacked if any exception
181
244
  occurred:
182
245
 
183
246
  ```python
184
247
  from fastapi import APIRouter, BackgroundTasks
185
- from fastapi_sqla import open_session, open_async_session
248
+ from fastapi_sqla import open_async_session, open_session
186
249
 
187
250
  router = APIRouter()
188
251
 
@@ -197,16 +260,23 @@ def run_bg():
197
260
  with open_session() as session:
198
261
  session.execute("SELECT now()").scalar()
199
262
 
263
+ def run_bg_with_key():
264
+ with open_session(key="read_only") as session:
265
+ session.execute("SELECT now()").scalar()
200
266
 
201
267
  async def run_async_bg():
202
268
  async with open_async_session() as session:
203
269
  await session.scalar("SELECT now()")
270
+
271
+ async def run_async_bg_with_key():
272
+ async with open_async_session(key="read_only") as session:
273
+ await session.scalar("SELECT now()")
204
274
  ```
205
275
 
206
276
  ## Pagination
207
277
 
208
278
  ```python
209
- from fastapi import APIRouter, Depends
279
+ from fastapi import APIRouter
210
280
  from fastapi_sqla import Base, Page, Paginate
211
281
  from pydantic import BaseModel
212
282
  from sqlalchemy import select
@@ -227,7 +297,7 @@ class UserModel(BaseModel):
227
297
 
228
298
 
229
299
  @router.get("/users", response_model=Page[UserModel])
230
- def all_users(paginate: Paginate = Depends()):
300
+ def all_users(paginate: Paginate):
231
301
  return paginate(select(User))
232
302
  ```
233
303
 
@@ -264,7 +334,7 @@ To paginate a query which doesn't return [scalars], specify `scalars=False` when
264
334
  `paginate`:
265
335
 
266
336
  ```python
267
- from fastapi import APIRouter, Depends
337
+ from fastapi import APIRouter
268
338
  from fastapi_sqla import Base, Page, Paginate
269
339
  from pydantic import BaseModel
270
340
  from sqlalchemy import func, select
@@ -289,7 +359,7 @@ class UserModel(BaseModel):
289
359
 
290
360
 
291
361
  @router.get("/users", response_model=Page[UserModel])
292
- def all_users(paginate: Paginate = Depends()):
362
+ def all_users(paginate: Paginate):
293
363
  query = (
294
364
  select(User.id, User.name, func.count(Note.id).label("notes_count"))
295
365
  .join(Note)
@@ -325,15 +395,15 @@ class UserModel(BaseModel):
325
395
  name: str
326
396
 
327
397
 
328
- def query_count(session: Session = Depends()) -> int:
398
+ def query_count(session: Session) -> int:
329
399
  return session.execute(select(func.count()).select_from(User)).scalar()
330
400
 
331
401
 
332
- Paginate = Pagination(min_page_size=5, max_page_size=500, query_count=query_count)
402
+ CustomPaginate = Pagination(min_page_size=5, max_page_size=500, query_count=query_count)
333
403
 
334
404
 
335
405
  @router.get("/users", response_model=Page[UserModel])
336
- def all_users(paginate: Paginate = Depends()):
406
+ def all_users(paginate: CustomPaginate = Depends()):
337
407
  return paginate(select(User))
338
408
  ```
339
409
 
@@ -342,7 +412,7 @@ def all_users(paginate: Paginate = Depends()):
342
412
  When using the asyncio support, use the `AsyncPaginate` dependency:
343
413
 
344
414
  ```python
345
- from fastapi import APIRouter, Depends
415
+ from fastapi import APIRouter
346
416
  from fastapi_sqla import Base, Page, AsyncPaginate
347
417
  from pydantic import BaseModel
348
418
  from sqlalchemy import select
@@ -363,7 +433,7 @@ class UserModel(BaseModel):
363
433
 
364
434
 
365
435
  @router.get("/users", response_model=Page[UserModel])
366
- async def all_users(paginate: AsyncPaginate = Depends()):
436
+ async def all_users(paginate: AsyncPaginate):
367
437
  return await paginate(select(User))
368
438
  ```
369
439
 
@@ -387,16 +457,85 @@ class UserModel(BaseModel):
387
457
  name: str
388
458
 
389
459
 
390
- async def query_count(session: AsyncSession = Depends()) -> int:
460
+ async def query_count(session: AsyncSession) -> int:
391
461
  result = await session.execute(select(func.count()).select_from(User))
392
462
  return result.scalar()
393
463
 
394
464
 
395
- Paginate = AsyncPagination(min_page_size=5, max_page_size=500, query_count=query_count)
465
+ CustomPaginate = AsyncPagination(min_page_size=5, max_page_size=500, query_count=query_count)
396
466
 
397
467
 
398
468
  @router.get("/users", response_model=Page[UserModel])
399
- def all_users(paginate: CustomPaginate = Depends()):
469
+ async def all_users(paginate: CustomPaginate = Depends()):
470
+ return await paginate(select(User))
471
+ ```
472
+
473
+ ### Multi-session support
474
+
475
+ Pagination supports multiple sessions as well. To paginate using a session
476
+ configured with a custom key:
477
+
478
+ ```python
479
+ from typing import Annotated
480
+
481
+ from fastapi import APIRouter, Depends
482
+ from fastapi_sqla import (
483
+ AsyncPaginateSignature,
484
+ AsyncPagination,
485
+ Base,
486
+ Page,
487
+ PaginateSignature,
488
+ Pagination,
489
+ )
490
+ from pydantic import BaseModel
491
+ from sqlalchemy import func, select
492
+
493
+ router = APIRouter()
494
+
495
+
496
+ class User(Base):
497
+ __tablename__ = "user"
498
+
499
+
500
+ class UserModel(BaseModel):
501
+ id: int
502
+ name: str
503
+
504
+
505
+ # Preferred
506
+
507
+ ReadOnlyPaginate = Annotated[
508
+ PaginateSignature, Depends(Pagination(session_key="read_only"))
509
+ ]
510
+ AsyncReadOnlyPaginate = Annotated[
511
+ AsyncPaginateSignature, Depends(AsyncPagination(session_key="read_only"))
512
+ ]
513
+
514
+ @router.get("/users", response_model=Page[UserModel])
515
+ def all_users(paginate: ReadOnlyPaginate):
516
+ return paginate(select(User))
517
+
518
+ @router.get("/async_users", response_model=Page[UserModel])
519
+ async def async_all_users(paginate: AsyncReadOnlyPaginate):
520
+ return await paginate(select(User))
521
+
522
+
523
+ # Alternative
524
+
525
+ @router.get("/users/alt", response_model=Page[UserModel])
526
+ def all_users_alt(
527
+ paginate: PaginateSignature = Depends(
528
+ Pagination(session_key="read_only")
529
+ ),
530
+ ):
531
+ return paginate(select(User))
532
+
533
+ @router.get("/async_users/alt", response_model=Page[UserModel])
534
+ async def async_all_users_alt(
535
+ paginate: AsyncPaginateSignature = Depends(
536
+ AsyncPagination(session_key="read_only")
537
+ ),
538
+ ):
400
539
  return await paginate(select(User))
401
540
  ```
402
541