Skip to main content

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_func must return an unevaluated Django QuerySet
  • 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 a RuntimeError
  • 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.paginator exposes 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 Session enables synchronous pagination
  • Passing an AsyncSession enables 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 > total should return empty items
  • Maximum limit - Test with limit=100 (the configured maximum)
  • Limit validation - limit=0 or limit > 100 should 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

Metadata

Release files for pagi 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pagi 0.3.0
File Size Uploaded
pagi-0.3.0.tar.gz 7.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pagi 0.3.0
File Interpreter ABI Platform
pagi-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 16.8 kB

Release files / pagi-0.3.0.tar.gz

Download URL pagi-0.3.0.tar.gz
Size 7.9 kB
Tags Source
SHA-256 checksum
How to use checksums
7eb06b52c9d437c47346f67ee6c39cb10103baf2eb5579b0d12230da1ffa38c5
BLAKE2b-256 checksum
How to use checksums
03aa6f0f6f8b066b7ced5cd9d49934f181cc7e9e4b4aa6d1ff2a473679becf8e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.3

Release files / pagi-0.3.0-py3-none-any.whl

Download URL pagi-0.3.0-py3-none-any.whl
Size 8.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
643f7b018fa53296692b97af0baec66f76eb2023f66c32d9f2eef1910a9fe16d
BLAKE2b-256 checksum
How to use checksums
7c925f06423298abc549ffed5b9a8e11a84c07e03032fab52d3b53fd10a26f84
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.3

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page