Skip to content

Commit 69fadf2

Browse files
fix: async pagination evaluate fix for queryset object
1 parent ed41ef8 commit 69fadf2

File tree

4 files changed

+77
-30
lines changed

4 files changed

+77
-30
lines changed

ninja/conf.py

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class Settings(BaseModel):
1111
"ninja.pagination.LimitOffsetPagination", alias="NINJA_PAGINATION_CLASS"
1212
)
1313
PAGINATION_PER_PAGE: int = Field(100, alias="NINJA_PAGINATION_PER_PAGE")
14+
PAGINATION_MAX_PER_PAGE: int = Field(100, alias="NINJA_PAGINATION_MAX_PER_PAGE")
1415
PAGINATION_MAX_LIMIT: int = Field(inf, alias="NINJA_PAGINATION_MAX_LIMIT") # type: ignore
1516

1617
# Throttling

ninja/pagination.py

+17-12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from math import inf
55
from typing import Any, AsyncGenerator, Callable, List, Optional, Tuple, Type, Union
66

7+
from asgiref.sync import sync_to_async
78
from django.db.models import QuerySet
89
from django.http import HttpRequest
910
from django.utils.module_loading import import_string
@@ -80,9 +81,11 @@ class Input(Schema):
8081
limit: int = Field(
8182
settings.PAGINATION_PER_PAGE,
8283
ge=1,
83-
le=settings.PAGINATION_MAX_LIMIT
84-
if settings.PAGINATION_MAX_LIMIT != inf
85-
else None,
84+
le=(
85+
settings.PAGINATION_MAX_LIMIT
86+
if settings.PAGINATION_MAX_LIMIT != inf
87+
else None
88+
),
8689
)
8790
offset: int = Field(0, ge=0)
8891

@@ -108,19 +111,19 @@ async def apaginate_queryset(
108111
offset = pagination.offset
109112
limit: int = min(pagination.limit, settings.PAGINATION_MAX_LIMIT)
110113
return {
111-
"items": queryset[offset : offset + limit],
114+
"items": await sync_to_async(list)(queryset[offset : offset + limit]),
112115
"count": await self._aitems_count(queryset),
113116
} # noqa: E203
114117

115118

116119
class PageNumberPagination(AsyncPaginationBase):
117120
class Input(Schema):
118121
page: int = Field(1, ge=1)
122+
page_size: int = Field(
123+
settings.PAGINATION_PER_PAGE, ge=1, le=settings.PAGINATION_MAX_PER_PAGE
124+
)
119125

120-
def __init__(
121-
self, page_size: int = settings.PAGINATION_PER_PAGE, **kwargs: Any
122-
) -> None:
123-
self.page_size = page_size
126+
def __init__(self, **kwargs: Any) -> None:
124127
super().__init__(**kwargs)
125128

126129
def paginate_queryset(
@@ -129,9 +132,9 @@ def paginate_queryset(
129132
pagination: Input,
130133
**params: Any,
131134
) -> Any:
132-
offset = (pagination.page - 1) * self.page_size
135+
offset = (pagination.page - 1) * pagination.page_size
133136
return {
134-
"items": queryset[offset : offset + self.page_size],
137+
"items": queryset[offset : offset + pagination.page_size],
135138
"count": self._items_count(queryset),
136139
} # noqa: E203
137140

@@ -141,9 +144,11 @@ async def apaginate_queryset(
141144
pagination: Input,
142145
**params: Any,
143146
) -> Any:
144-
offset = (pagination.page - 1) * self.page_size
147+
offset = (pagination.page - 1) * pagination.page_size
145148
return {
146-
"items": queryset[offset : offset + self.page_size],
149+
"items": await sync_to_async(list)(
150+
queryset[offset : offset + pagination.page_size]
151+
),
147152
"count": await self._aitems_count(queryset),
148153
} # noqa: E203
149154

tests/test_pagination.py

+50-13
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@
44

55
import pytest
66
from django.test import override_settings
7-
from pydantic.errors import PydanticSchemaGenerationError
87

98
from ninja import NinjaAPI, Schema
109
from ninja.errors import ConfigError
1110
from ninja.operation import Operation
1211
from ninja.pagination import (
1312
LimitOffsetPagination,
14-
PageNumberPagination,
15-
PaginationBase,
1613
make_response_paginated,
14+
PageNumberPagination,
1715
paginate,
16+
PaginationBase,
1817
)
1918
from ninja.testing import TestClient
19+
from pydantic.errors import PydanticSchemaGenerationError
2020

2121
api = NinjaAPI()
2222

@@ -119,19 +119,19 @@ def items_3(request, **kwargs):
119119

120120

121121
@api.get("/items_4", response=List[int])
122-
@paginate(PageNumberPagination, page_size=10)
122+
@paginate(PageNumberPagination)
123123
def items_4(request, **kwargs):
124124
return ITEMS
125125

126126

127127
@api.get("/items_5", response=List[int])
128-
@paginate(PageNumberPagination, page_size=10)
128+
@paginate(PageNumberPagination)
129129
def items_5(request):
130130
return ITEMS
131131

132132

133133
@api.get("/items_6", response={101: int, 200: List[Any]})
134-
@paginate(PageNumberPagination, page_size=10, pass_parameter="page_info")
134+
@paginate(PageNumberPagination, pass_parameter="page_info")
135135
def items_6(request, **kwargs):
136136
return ITEMS + [kwargs["page_info"]]
137137

@@ -244,7 +244,7 @@ def test_case3():
244244

245245

246246
def test_case4():
247-
response = client.get("/items_4?page=2").json()
247+
response = client.get("/items_4?page=2&page_size=10").json()
248248
assert response == {"items": ITEMS[10:20], "count": 100}
249249

250250
schema = api.get_openapi_schema()["paths"]["/api/items_4"]["get"]
@@ -260,12 +260,24 @@ def test_case4():
260260
"type": "integer",
261261
},
262262
"required": False,
263-
}
263+
},
264+
{
265+
"in": "query",
266+
"name": "page_size",
267+
"schema": {
268+
"default": 100,
269+
"maximum": 100,
270+
"minimum": 1,
271+
"title": "Page Size",
272+
"type": "integer",
273+
},
274+
"required": False,
275+
},
264276
]
265277

266278

267279
def test_case5_no_kwargs():
268-
response = client.get("/items_5?page=2").json()
280+
response = client.get("/items_5?page=2&page_size=10").json()
269281
assert response == {"items": ITEMS[10:20], "count": 100}
270282

271283
schema = api.get_openapi_schema()["paths"]["/api/items_5"]["get"]
@@ -281,14 +293,27 @@ def test_case5_no_kwargs():
281293
"type": "integer",
282294
},
283295
"required": False,
284-
}
296+
},
297+
{
298+
"in": "query",
299+
"name": "page_size",
300+
"schema": {
301+
"default": 100,
302+
"maximum": 100,
303+
"minimum": 1,
304+
"title": "Page Size",
305+
"type": "integer",
306+
},
307+
"required": False,
308+
},
285309
]
286310

287311

288312
def test_case6_pass_param_kwargs():
289313
page = 11
290-
response = client.get(f"/items_6?page={page}").json()
291-
assert response == {"items": [{"page": 11}], "count": 101}
314+
page_size = 10
315+
response = client.get(f"/items_6?page={page}&page_size={page_size}").json()
316+
assert response == {"items": [{"page": page, "page_size": page_size}], "count": 101}
292317

293318
schema = api.get_openapi_schema()["paths"]["/api/items_6"]["get"]
294319

@@ -303,7 +328,19 @@ def test_case6_pass_param_kwargs():
303328
"type": "integer",
304329
},
305330
"required": False,
306-
}
331+
},
332+
{
333+
"in": "query",
334+
"name": "page_size",
335+
"schema": {
336+
"default": 100,
337+
"maximum": 100,
338+
"minimum": 1,
339+
"title": "Page Size",
340+
"type": "integer",
341+
},
342+
"required": False,
343+
},
307344
]
308345

309346

tests/test_pagination_async.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
from ninja.pagination import (
1010
AsyncPaginationBase,
1111
PageNumberPagination,
12-
PaginationBase,
1312
paginate,
13+
PaginationBase,
1414
)
1515
from ninja.testing import TestAsyncClient
1616

@@ -113,11 +113,15 @@ async def test_async_page_number():
113113
api = NinjaAPI()
114114

115115
@api.get("/items_page_number", response=List[Any])
116-
@paginate(PageNumberPagination, page_size=10, pass_parameter="page_info")
116+
@paginate(PageNumberPagination, pass_parameter="page_info")
117117
async def items_page_number(request, **kwargs):
118118
return ITEMS + [kwargs["page_info"]]
119119

120120
client = TestAsyncClient(api)
121-
122-
response = await client.get("/items_page_number?page=11")
123-
assert response.json() == {"items": [{"page": 11}], "count": 101}
121+
page = 11
122+
page_size = 10
123+
response = await client.get(f"/items_page_number?page={page}&page_size={page_size}")
124+
assert response.json() == {
125+
"items": [{"page": page, "page_size": page_size}],
126+
"count": 101,
127+
}

0 commit comments

Comments
 (0)