Model Service Pagination
Pagination controls how List responses are sliced and wrapped with metadata.
In django-grpc-extra pagination can be applied in two places:
ModelService(list_pagination_class)- regular service methods via
@grpc_pagination
Where It Applies
For ModelService:
List(unary) -> pagination supportedStreamList(server streaming) -> pagination is not applied
List pipeline order:
- filtering
- searching
- ordering
- pagination
Pagination always runs last, so pages are built from already filtered/ordered data.
Default Pagination: LimitOffsetPagination
Default class is LimitOffsetPagination.
It extends request schema with:
limit(default=100,ge=1,le=1000)offset(default=0,ge=0)
It wraps response schema as:
count: total matched rows/itemslimit: requested limitoffset: requested offsetresults: repeated item schema
Example request:
{
"limit": 20,
"offset": 40
}
Example response:
{
"count": 312,
"limit": 20,
"offset": 40,
"results": [
{"id": 41, "name": "..."},
{"id": 42, "name": "..."}
]
}
ModelService Usage
from grpc_extra import (
AllowedEndpoints,
LimitOffsetPagination,
ModelService,
ModelServiceConfig,
grpc_service,
)
@grpc_service(app_label="inventory", package="inventory")
class ProductService(ModelService):
config = ModelServiceConfig(
model=Product,
allowed_endpoints=[AllowedEndpoints.LIST],
list_schema=ProductOut,
list_pagination_class=LimitOffsetPagination,
)
If list_pagination_class is omitted, framework uses DEFAULT_PAGINATION_CLASS from settings.
Disable pagination for List
config = ModelServiceConfig(
...,
list_pagination_class=None,
)
Regular Service Usage: @grpc_pagination
from grpc_extra import grpc_method, grpc_pagination
@grpc_method(request_schema=None, response_schema=ProductOut)
@grpc_pagination
# also valid: @grpc_pagination()
def list_products(self, request, context):
return Product.objects.all()
Supported forms:
@grpc_pagination@grpc_pagination()@grpc_pagination(CustomPagination)
Constraints:
- unary methods only
- must be used with
@grpc_method response_schemamust be set
Proto Contract (What Clients See)
For limit/offset pagination, generated proto request contains limit/offset fields and response contains results + metadata fields.
This means client code should treat paginated endpoints as object responses, not plain repeated top-level arrays.
If you change pagination class later, proto contract changes. Regenerate stubs and update clients.
Global Default Pagination Class
GRPC_EXTRA = {
"DEFAULT_PAGINATION_CLASS": "grpc_extra.pagination.LimitOffsetPagination",
}
Used by:
ModelServicelist endpoints (if per-service class is not provided)@grpc_paginationwithout explicit class
Custom Pagination Class
Create a class inheriting BasePagination and implement three methods:
build_request_schema(request_schema)build_response_schema(response_schema)paginate(result, request)
Example:
from pydantic import BaseModel, Field, create_model
from grpc_extra.pagination import BasePagination
class PageNumberPagination(BasePagination):
@classmethod
def build_request_schema(cls, request_schema: type[BaseModel] | None) -> type[BaseModel]:
name = f"{request_schema.__name__}WithPage" if request_schema else "PageRequest"
if request_schema is None:
return create_model(
name,
page=(int, Field(default=1, ge=1)),
page_size=(int, Field(default=20, ge=1, le=200)),
)
return create_model(
name,
__base__=request_schema,
page=(int, Field(default=1, ge=1)),
page_size=(int, Field(default=20, ge=1, le=200)),
)
@classmethod
def build_response_schema(cls, response_schema: type[BaseModel]) -> type[BaseModel]:
return create_model(
f"{response_schema.__name__}Page",
total=(int, ...),
page=(int, ...),
page_size=(int, ...),
items=(list[response_schema], ...),
)
@classmethod
def paginate(cls, result, request: BaseModel) -> dict:
# Must return data matching build_response_schema.
...
Attach:
- per-service:
list_pagination_class=PageNumberPagination - per-method:
@grpc_pagination(PageNumberPagination)
QuerySet vs List Behavior
Pagination supports both:
QuerySet: efficientcount()and slicing- Python
list/ iterable: in-memory slicing
For large datasets, return QuerySet whenever possible.
Interaction with Searching and Ordering
Because pagination is final stage:
- search/order changes directly affect page boundaries
- unstable ordering leads to unstable pages
Recommendations:
- always configure explicit ordering fields
- sort by indexed and deterministic fields
- avoid non-deterministic ordering for paginated endpoints
Common Errors
INVALID_ARGUMENT for limit/offset
Triggered by request validation, for example:
limit <= 0offset < 0- wrong type
Pagination configured on streaming endpoint
Pagination wrappers are unary-oriented and not intended for streaming responses.
Client parse errors after schema changes
Typical root cause: server proto updated, client stubs stale.
Fix sequence:
- regenerate proto and stubs
- restart server
- refresh API client descriptor/cache
Stability Recommendations
- Keep one response envelope style per endpoint (do not switch often).
- Keep max page size conservative.
- Prefer additive changes over renaming/removing pagination fields.
- Document defaults (
limit,offset) for client teams. - Add tests for first page, middle page, empty tail page, invalid bounds.