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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pagi-0.3.0.tar.gz (7.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pagi-0.3.0-py3-none-any.whl (8.9 kB view details)

Uploaded Python 3

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

Hashes for pagi-0.3.0.tar.gz
Algorithm Hash digest
SHA256 7eb06b52c9d437c47346f67ee6c39cb10103baf2eb5579b0d12230da1ffa38c5
MD5 985e89ff61da84bd7f83cdb3420b48e0
BLAKE2b-256 03aa6f0f6f8b066b7ced5cd9d49934f181cc7e9e4b4aa6d1ff2a473679becf8e

See more details on using hashes here.

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

Hashes for pagi-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 643f7b018fa53296692b97af0baec66f76eb2023f66c32d9f2eef1910a9fe16d
MD5 509f13d7ce43c91a0d01fcca2b38410e
BLAKE2b-256 7c925f06423298abc549ffed5b9a8e11a84c07e03032fab52d3b53fd10a26f84

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 files

0.2.0

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page