Skip to content

Commit 038b4d5

Browse files
authored
Added pagination for sqlalchemy results (#3)
* feat: Added count for queries * feat: finalized pagination * fix: pre commit correction
1 parent 5433db5 commit 038b4d5

File tree

3 files changed

+67
-9
lines changed

3 files changed

+67
-9
lines changed

app/songs/handlers.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,36 @@
1-
from fastapi import APIRouter, Depends
1+
from fastapi import APIRouter, Depends, Query
22
from fastapi.security import OAuth2PasswordBearer
33
from sqlalchemy import select
44
from sqlalchemy.ext.asyncio import AsyncSession
55
from sqlalchemy.orm import selectinload
66

77
from ..db import get_db_session
88
from ..redis import r
9+
from ..utils.pagination import paginate
910
from .models import City, Song, Tag
10-
from .schemas import CityCreate, CityRead, SongCreate, SongRead, TagCreate, TagRead
11+
from .schemas import (
12+
CityCreate,
13+
CityRead,
14+
SongCreate,
15+
TagCreate,
16+
TagRead,
17+
)
1118

1219
router = APIRouter()
1320

1421
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
1522

1623

17-
@router.get("/songs", response_model=list[SongRead])
18-
async def get_songs(session: AsyncSession = Depends(get_db_session)):
19-
result = await session.scalars(
20-
select(Song).options(selectinload(Song.tags), selectinload(Song.city))
21-
)
22-
songs = result.all()
23-
return songs
24+
@router.get("/songs")
25+
async def get_songs(
26+
page: int = Query(1, ge=1),
27+
per_page: int = Query(5, le=100),
28+
session: AsyncSession = Depends(get_db_session),
29+
):
30+
query = select(Song).options(selectinload(Song.tags), selectinload(Song.city))
31+
items, pagination = await paginate(session, query, page, per_page)
32+
33+
return {"meta": pagination, "data": items}
2434

2535

2636
@router.post("/songs")

app/songs/schemas.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,8 @@ class SongRead(BaseModel):
5050

5151
# class Config:
5252
# from_attributes = True
53+
54+
55+
class PaginatedSong(BaseModel):
56+
data: list[SongRead]
57+
meta: dict[str, int | None]

app/utils/pagination.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from fastapi import HTTPException
2+
from sqlalchemy import func, select
3+
from sqlalchemy.ext.asyncio import AsyncSession
4+
5+
6+
async def paginate(
7+
session: AsyncSession,
8+
query,
9+
page: int = 1,
10+
per_page: int = 10,
11+
):
12+
"""
13+
Paginate a SQLModel query.
14+
:param session: The SQLAlchemy session instance.
15+
:param query: The SQLModel query to paginate.
16+
:param page: The page number to retrieve.
17+
:param per_page: The number of items per page.
18+
:return: Tuple containing the paginated data and pagination information.
19+
"""
20+
total_items = await session.execute(select(func.count()).select_from(query))
21+
count = total_items.scalar()
22+
23+
if count == 0:
24+
return [], {"total_pages": 0, "current_page": 0, "next_page": None}
25+
26+
total_pages = (count - 1) // per_page + 1
27+
28+
if page > total_pages:
29+
raise HTTPException(status_code=404, detail="Page not found")
30+
31+
offset = (page - 1) * per_page
32+
items = await session.execute(query.offset(offset).limit(per_page))
33+
result = items.scalars().all()
34+
35+
next_page = page + 1 if page < total_pages else None
36+
37+
pagination = {
38+
"current_page": page,
39+
"total_pages": total_pages,
40+
"next_page": next_page,
41+
}
42+
43+
return result, pagination

0 commit comments

Comments
 (0)