ORM-agnostic pagination toolkit for Python
Project description
pagi
A minimal, ORM-agnostic pagination toolkit for Python.
pagi lets you define pagination logic once and reuse it across different ORMs (SQLAlchemy, Django, etc.), returning consistent, typed responses powered by Pydantic.
Features
- Offset/limit pagination with validation via Pydantic
- Unified response model (
PaginatedResponse) - SQLAlchemy support (sync and async)
- Django ORM support
- Tortoise ORM support
- Strategy-based internal design for easy extensibility
- ORM-agnostic public API
Installation
pip install pagi
Or with development dependencies:
pip install -e .[dev]
or if you are using uv
uv pip install -e .[dev]
Basic Usage
Importing
The installable package name is pagi, but the Python module is paginator.
Recommended import:
from paginator.paginator import paginate, paginate_sync
SQLAlchemy (Synchronous)
from sqlalchemy import select
from sqlalchemy.orm import Session
from paginator import paginate_sync
def get_users(session: Session):
return paginate_sync(
session,
lambda: select(User),
offset=10,
limit=5,
backend="sqlalchemy",
)
SQLAlchemy (Asynchronous)
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from paginator import paginate
async def get_users(session: AsyncSession):
return await paginate(
session,
lambda: select(User),
offset=10,
limit=5,
backend="sqlalchemy",
)
The correct strategy (sync vs async) is selected automatically based on the session type.
Django ORM
from paginator import paginate_sync
from myapp.models import User
result = paginate_sync(
connection=None,
query_func=lambda: User.objects.all(),
offset=20,
limit=10,
backend="django",
)
Notes:
query_funcmust return an unevaluated DjangoQuerySet- Django pagination is synchronous (async execution is not supported)
Tortoise ORM
from paginator import paginate
from myapp.models import User
result = await paginate(
connection=None,
query_func=lambda: User.all().order_by("id"),
offset=20,
limit=10,
backend="tortoise",
)
Notes:
- Tortoise ORM is async-first, so only
paginate()(async) is supported paginate_sync()will raise aRuntimeError- Make sure Tortoise is initialized before calling pagination functions
Design and Architecture
pagi is built around the Strategy pattern, allowing multiple ORMs to be supported while keeping a single, simple public API.
paginator.paginatorexposes the public functions (paginate,paginate_sync)- Each ORM implements its own pagination strategy
- A small factory selects the appropriate strategy at runtime based on the backend and connection type
- Pagination logic is decoupled from data access, making new backends easy to add
SQLAlchemy Strategy Selection
For SQLAlchemy, pagi uses a factory-based approach:
- Passing a
Sessionenables synchronous pagination - Passing an
AsyncSessionenables asynchronous pagination - The correct strategy is chosen automatically without extra configuration
Roadmap
- Cursor-based pagination (cursor tokens instead of offset/limit)
- Optional total count for performance-sensitive queries
Testing and Edge Cases
The following edge cases should be considered when testing pagination across all backends:
Common Edge Cases
- Empty result set - Query returns 0 records
- First page -
offset=0, limit=N - Last page (partial) - Requested limit exceeds remaining records
- Exact page boundary -
offset + limit == total - Offset beyond total -
offset > totalshould return empty items - Maximum limit - Test with
limit=100(the configured maximum) - Limit validation -
limit=0orlimit > 100should raise validation errors - Negative offset - Should raise validation errors
Backend-Specific Considerations
| Backend | Sync | Async | Notes |
|---|---|---|---|
| SQLAlchemy | yes | yes | Strategy auto-selected by Session type |
| Django | yes | no | Wrap with sync_to_async if needed |
| Tortoise | no | yes | Async-first ORM |
Development
Run tests with:
pytest
The test suite covers:
- SQLAlchemy (sync)
- Django ORM
- Tortoise ORM
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pagi-0.3.0.tar.gz.
File metadata
- Download URL: pagi-0.3.0.tar.gz
- Upload date:
- Size: 7.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7eb06b52c9d437c47346f67ee6c39cb10103baf2eb5579b0d12230da1ffa38c5
|
|
| MD5 |
985e89ff61da84bd7f83cdb3420b48e0
|
|
| BLAKE2b-256 |
03aa6f0f6f8b066b7ced5cd9d49934f181cc7e9e4b4aa6d1ff2a473679becf8e
|
File details
Details for the file pagi-0.3.0-py3-none-any.whl.
File metadata
- Download URL: pagi-0.3.0-py3-none-any.whl
- Upload date:
- Size: 8.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
643f7b018fa53296692b97af0baec66f76eb2023f66c32d9f2eef1910a9fe16d
|
|
| MD5 |
509f13d7ce43c91a0d01fcca2b38410e
|
|
| BLAKE2b-256 |
7c925f06423298abc549ffed5b9a8e11a84c07e03032fab52d3b53fd10a26f84
|