Skip to content

Commit 3e9c746

Browse files
committed
refactor!: Simplify pagination API
BREAKING CHANGE: * Renamed `Paginated` to `Page`, `PaginatedResult` to `Paginate`; * Removed `with_pagination`;
1 parent f639f20 commit 3e9c746

3 files changed

Lines changed: 38 additions & 51 deletions

File tree

README.md

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,7 @@ def example(session: Session = Depends()):
6565

6666
```python
6767
from fastapi import APIRouter, Depends
68-
from fastapi_sqla import (
69-
Base,
70-
Paginated,
71-
PaginatedResult,
72-
Session,
73-
with_pagination,
74-
)
68+
from fastapi_sqla import Base, Page, Paginate, Session
7569
from pydantic import BaseModel
7670

7771
router = APIRouter()
@@ -86,13 +80,10 @@ class User(BaseModel):
8680
name: str
8781

8882

89-
@router.get("/users", response_model=Paginated[User])
90-
def all_users(
91-
session: Session = Depends(),
92-
paginated_result: PaginatedResult = Depends(with_pagination),
93-
):
83+
@router.get("/users", response_model=Page[User])
84+
def all_users(session: Session = Depends(), paginate: Paginate = Depends()):
9485
query = session.query(UserEntity)
95-
return paginated_result(query)
86+
return paginate(query)
9687
```
9788

9889
By default:
@@ -109,13 +100,7 @@ To customize pagination, create a dependency using `fastapi_sqla.Pagination`
109100

110101
```python
111102
from fastapi import APIRouter, Depends
112-
from fastapi_sqla import (
113-
Base,
114-
Paginated,
115-
PaginatedResult,
116-
Pagination,
117-
Session,
118-
)
103+
from fastapi_sqla import Base, Page, Pagination, Session
119104
from pydantic import BaseModel
120105
from sqlalchemy import func
121106
from sqlalchemy.orm import Query
@@ -136,20 +121,20 @@ def query_count(session: Session, query: Query):
136121
return query.statement.with_only_columns([func.count()]).scalar()
137122

138123

139-
with_custom_pagination = Pagination(
124+
Paginate = Pagination(
140125
min_page_size=5,
141126
max_page_size=500,
142127
query_count=query_count,
143128
)
144129

145130

146-
@router.get("/users", response_model=Paginated[User])
131+
@router.get("/users", response_model=Page[User])
147132
def all_users(
148133
session: Session = Depends(),
149-
paginated_result: PaginatedResult = Depends(with_custom_pagination),
134+
paginate: Paginate = Depends(),
150135
):
151136
query = session.query(UserEntity)
152-
return paginated_result(query)
137+
return paginate(query)
153138
```
154139

155140
## Pytest fixtures

fastapi_sqla/__init__.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from sqlalchemy.orm import Query as DbQuery
1515
from sqlalchemy.orm.session import Session as SqlaSession, sessionmaker
1616

17-
__all__ = ["Base", "Session", "setup"]
17+
__all__ = ["Base", "Page", "Paginate", "Session", "setup"]
1818

1919
logger = structlog.get_logger(__name__)
2020

@@ -140,15 +140,12 @@ class Meta(BaseModel):
140140
page_number: int = Field(..., description="Current page number. Starts at 1.")
141141

142142

143-
class Paginated(Collection, Generic[T]):
144-
"""Paginated collection with information on current page and total items in meta."""
143+
class Page(Collection, Generic[T]):
144+
"""A page of the collection with info on current page and total items in meta."""
145145

146146
meta: Meta
147147

148148

149-
PaginatedResult = Callable[[DbQuery], Paginated[T]]
150-
151-
152149
def _query_count(session: Session, query: DbQuery) -> int:
153150
"""Default function used to count items returned by a query.
154151
@@ -160,21 +157,24 @@ def _query_count(session: Session, query: DbQuery) -> int:
160157
return query.count()
161158

162159

160+
PaginateSignature = Callable[[DbQuery], Page[T]]
161+
162+
163163
def Pagination(
164164
min_page_size: int = 10,
165165
max_page_size: int = 100,
166166
query_count: Callable[[Session, DbQuery], int] = _query_count,
167-
) -> Callable[[Session, int, int], PaginatedResult]:
167+
) -> Callable[[Session, int, int], PaginateSignature]:
168168
def dependency(
169169
session: Session = Depends(),
170170
offset: int = Query(0, ge=0),
171171
limit: int = Query(min_page_size, ge=1, le=max_page_size),
172-
) -> PaginatedResult:
173-
def paginated_result(query: DbQuery) -> Paginated[T]:
172+
) -> PaginateSignature:
173+
def paginate(query: DbQuery) -> Page[T]:
174174
total_items = query_count(session, query)
175175
total_pages = math.ceil(total_items / limit)
176176
page_number = offset / limit + 1
177-
return Paginated[T](
177+
return Page[T](
178178
data=query.offset(offset).limit(limit).all(),
179179
meta={
180180
"offset": offset,
@@ -184,9 +184,9 @@ def paginated_result(query: DbQuery) -> Paginated[T]:
184184
},
185185
)
186186

187-
return paginated_result
187+
return paginate
188188

189189
return dependency
190190

191191

192-
with_pagination = Pagination()
192+
Paginate: PaginateSignature = Pagination()

tests/test_pagination.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ def setup_tear_down(engine):
3333
note = Table("note", metadata, autoload=True, autoload_with=engine)
3434
user_params = [{"id": i, "name": faker.name()} for i in range(1, 43)]
3535
note_params = [
36-
{"user_id": i % 42 + 1, "id": math.ceil(i / 42)} for i in range(0, 84)
36+
{"user_id": i % 42 + 1, "id": math.ceil(i / 42), "content": faker.text()}
37+
for i in range(0, 84)
3738
]
3839
engine.execute(user.insert(), *user_params)
3940
engine.execute(note.insert(), *note_params)
@@ -74,10 +75,10 @@ class Note(Base):
7475
[(0, 5, 9, 1), (10, 10, 5, 2), (40, 10, 5, 5)],
7576
)
7677
def test_pagination(session, user_cls, offset, limit, total_pages, page_number):
77-
from fastapi_sqla import with_pagination
78+
from fastapi_sqla import Paginate
7879

7980
query = session.query(user_cls).options(joinedload("notes"))
80-
result = with_pagination(session, offset, limit)(query)
81+
result = Paginate(session, offset, limit)(query)
8182

8283
assert result.meta.total_items == 42
8384
assert result.meta.offset == offset
@@ -99,9 +100,9 @@ def test_Pagination_with_custom_count(
99100
.statement.with_only_columns([func.count()])
100101
.scalar()
101102
)
102-
with_pagination = Pagination(query_count=query_count)
103+
pagination = Pagination(query_count=query_count)
103104
query = session.query(user_cls).options(joinedload("notes"))
104-
result = with_pagination(session, offset, limit)(query)
105+
result = pagination(session, offset, limit)(query)
105106

106107
assert result.meta.total_items == 42
107108
assert result.meta.offset == offset
@@ -112,8 +113,7 @@ def test_Pagination_with_custom_count(
112113
@fixture
113114
def app(user_cls, note_cls):
114115
from sqlalchemy.orm import joinedload
115-
116-
from fastapi_sqla import Paginated, Session, setup, with_pagination
116+
from fastapi_sqla import Page, Paginate, Session, setup
117117

118118
app = FastAPI()
119119
setup(app)
@@ -122,19 +122,21 @@ class Note(BaseModel):
122122
id: int
123123
content: str
124124

125+
class Config:
126+
orm_mode = True
127+
125128
class User(BaseModel):
126129
id: int
127130
name: str
128131
notes: List[Note]
129132

130-
@app.get("/users")
131-
def all_users(
132-
session: Session = Depends(),
133-
paginated_result=Depends(with_pagination),
134-
reponse_model=Paginated[User],
135-
):
133+
class Config:
134+
orm_mode = True
135+
136+
@app.get("/users", response_model=Page[User])
137+
def all_users(session: Session = Depends(), paginate: Paginate = Depends()):
136138
query = session.query(user_cls).options(joinedload("notes"))
137-
return paginated_result(query)
139+
return paginate(query)
138140

139141
return app
140142

@@ -156,7 +158,7 @@ async def client(app):
156158
async def test_functional(client, offset, items_number):
157159
result = await client.get("/users", params={"offset": offset})
158160

159-
assert result.status_code == 200
161+
assert result.status_code == 200, result.json()
160162
users = result.json()["data"]
161163
assert len(users) == items_number
162164
user_ids = [u["id"] for u in users]

0 commit comments

Comments
 (0)