Skip to main content

pypaginate

Fast, typed pagination, filtering, sorting & search for Python — one Rust core, byte-for-byte parity with the TypeScript package.

CI PyPI version Python Versions License: MIT codecov Ruff uv

pypaginate paginates, filters, sorts, and searches your data — in memory or against a database — through a single paginate() entry point and a small set of ergonomic helpers. The algorithms live once in a native Rust core (paginate-core) that ships inside the wheel, so there is no Rust toolchain to install and the Python and TypeScript (@cyblow/paginate) packages return identical results and byte-identical cursors.

📖 Full documentation: cyblow.github.io/paginate

Highlights

  • One paginate() for in-memory lists; ORM-backed pages via the adapters.
  • Filtering — 20 operators (eq, gte, contains, between, in, regex, …) with flat AND/OR lists and nested And() / Or() groups.
  • Sorting — stable, multi-key, with per-key direction and null placement.
  • Search — ranked full-text with optional trigram fuzzy matching and per-field weights — all in the native engine, no extra dependency.
  • Cursor (keyset) pagination — opaque, URL-safe cursors that decode byte-for-byte across Python, TypeScript, and Rust.
  • Integrations — SQLAlchemy 2.0 (sync + async), Django, and FastAPI.
  • Zero runtime dependencies in the core; Pydantic is optional (only the FastAPI extra needs it).
  • Fully typed (PEP 561) — the spec/param shapes are generated from the core's JSON Schema, so Python and TypeScript can't drift.

Installation

pip install pypaginate                  # core: in-memory + native engine
pip install "pypaginate[sqlalchemy]"    # SQLAlchemy 2.0 offset + keyset adapter
pip install "pypaginate[fastapi]"       # FastAPI dependencies (+ Pydantic)
pip install "pypaginate[django]"        # Django Q-object builders
pip install "pypaginate[all]"           # everything

Or with uv: uv add pypaginate (Python 3.11+). The native engine is bundled in the wheel; fuzzy/ranked search needs no extra.

Quick start

Paginate an in-memory list in three lines:

from pypaginate import paginate, OffsetParams

page = paginate([1, 2, 3, 4, 5], OffsetParams(page=1, limit=2))

page.items       # [1, 2]
page.total       # 5
page.pages       # 3
page.has_next    # True
list(page)       # [1, 2]  — OffsetPage is iterable, indexable, sized

Filter, sort, and search

The one-shot helpers run a single operation over any list of dicts or objects:

from pypaginate import filter, sort, search, FilterSpec, SortSpec, SearchSpec, And

users = [...]  # list of dicts or objects

adults = filter(users, FilterSpec(field="age", operator="gte", value=18))
grouped = filter(users, And(
    FilterSpec(field="age", operator="gte", value=18),
    FilterSpec(field="role", operator="in", value=["admin", "owner"]),
))
newest = sort(users, SortSpec(field="created_at", direction="desc"))
hits   = search(users, SearchSpec(query="alice", fields=["name", "email"]))

Operators, directions, and search modes are plain strings ("gte", "desc", "contains") — defined once in the core, identical in every language.

Query the same data repeatedly — Dataset

Each one-shot call marshals the whole collection into the engine. When you query the same rows many times, build a Dataset once and reuse it — it runs the full filter → search → sort → paginate pipeline in a single native call:

from pypaginate import Dataset, OffsetParams, FilterSpec, SortSpec

ds = Dataset(users)                         # marshals once

page = ds.page(
    OffsetParams(page=1, limit=20),
    filters=[FilterSpec(field="age", operator="gte", value=18)],
    sorting=[SortSpec(field="created_at", direction="desc")],
)
page.total      # matches across all pages
list(page)      # the page's rows (your original objects, never copied)

SQLAlchemy

pip install "pypaginate[sqlalchemy]" — Pydantic-free pagination for SQLAlchemy 2.0, sync or async, offset or keyset.

from sqlalchemy import select
from pypaginate import OffsetParams, FilterSpec, SortSpec
from pypaginate.adapters.sqlalchemy import (
    SyncSQLAlchemyBackend, build_filter, build_order_by,
)

stmt = select(User)
where = build_filter(User, [FilterSpec(field="status", operator="eq", value="active")])
if where is not None:
    stmt = stmt.where(where)
stmt = stmt.order_by(*build_order_by(User, [SortSpec(field="created_at", direction="desc")]))

page = SyncSQLAlchemyBackend(session).paginate(stmt, OffsetParams(page=1, limit=20))
page.items       # list[User]
page.total       # int
page.has_next    # bool

The async backend is identical with await:

from pypaginate.adapters.sqlalchemy import SQLAlchemyBackend

page = await SQLAlchemyBackend(async_session).paginate(stmt, OffsetParams(page=1, limit=20))

Cursor (keyset) pagination

For large or frequently-changing datasets where OFFSET is slow or unstable:

from sqlalchemy import select
from pypaginate import CursorParams
from pypaginate.adapters.sqlalchemy import SyncSQLAlchemyCursorBackend

stmt = select(Post).order_by(Post.created_at.desc(), Post.id.desc())
backend = SyncSQLAlchemyCursorBackend(session)

first = backend.fetch_page(stmt, CursorParams(limit=20))
nxt   = backend.fetch_page(stmt, CursorParams(limit=20, after=first.next_cursor))

first.items            # list[Post]
first.next_cursor      # str | None — feed back in as `after`
first.previous_cursor  # str | None
first.has_next         # bool

The cursor codec is shared with the TS and Django adapters, so a cursor minted by any of them decodes byte-for-byte in the others.

FastAPI

pip install "pypaginate[fastapi]"Annotated dependencies that turn a request's query string into pypaginate specs (invalid params become HTTP 422):

from fastapi import FastAPI
from pypaginate import paginate, OffsetPage
from pypaginate.adapters.fastapi import OffsetDep, SortDep, SearchDep

app = FastAPI()

@app.get("/users")
def list_users(params: OffsetDep, sort: SortDep, search: SearchDep) -> OffsetPage[dict]:
    # params: OffsetParams from ?page=&limit=   (defaults page=1, limit=20)
    # sort:   list[SortSpec] from ?sort=name,-age   ('-' prefix = descending)
    # search: SearchSpec | None from ?q=&search_fields=name,email
    users = [{"name": "Alice"}, {"name": "Bob"}]
    return paginate(users, params)
Dependency Query params Produces
OffsetDep ?page=1&limit=20 OffsetParams
CursorDep ?limit=20&after=… CursorParams
SortDep ?sort=name,-age list[SortSpec]
SearchDep ?q=alice&search_fields=name,email SearchSpec | None
FilterDep (declarative, user-defined) list[FilterSpec]

Declarative filters map query params onto FilterSpec conditions:

from typing import Annotated
from fastapi import Query
from pypaginate.adapters.fastapi import FilterDep, FilterField

class UserFilters(FilterDep):
    name: str | None = FilterField(None, operator="contains")
    min_age: int | None = FilterField(None, field="age", operator="gte")
    status: str | None = FilterField(None, operator="eq")

@app.get("/users")
def list_users(filters: Annotated[UserFilters, Query()]):
    specs = filters.to_specs()   # list[FilterSpec] for the non-None fields
    ...

Django

pip install "pypaginate[django]" — translate specs into Q objects and order_by arguments, then offset- or keyset-paginate a QuerySet (Django is imported lazily, so importing the adapter doesn't require Django):

from pypaginate import OffsetParams, FilterSpec, SortSpec
from pypaginate.adapters.django import apply_filters, apply_sorting, paginate_offset

qs = apply_filters(User.objects.all(), [FilterSpec(field="age", operator="gte", value=18)])
qs = apply_sorting(qs, [SortSpec(field="age", direction="desc")])

page = paginate_offset(qs, OffsetParams(page=1, limit=20))
page.total       # int
list(page)       # the page's model instances

paginate_keyset(qs, CursorParams(...)) provides cursor pagination over an explicitly ordered QuerySet.

Filter operators

Operator Meaning Operator Meaning
eq / ne Equal / not equal like / ilike SQL LIKE (case-(in)sensitive)
gt / gte Greater (or equal) between Inclusive range [lo, hi]
lt / lte Less (or equal) is_null / is_not_null Null / present
in / not_in Membership in a list empty / not_empty Empty / non-empty
contains Substring regex Regular-expression match
starts_with / ends_with Prefix / suffix exists Field path resolves

See the filtering guide for per-operator examples, and boolean groups for nested And() / Or() trees.

Errors

All failures derive from PaginateError (aliased PaginationError): ValidationError (bad page/limit) and its subclass InvalidCursorError (a malformed cursor), FilterError / FilterValidationError, SortError, SearchError / SearchQueryError, and ConfigurationError (unknown ORM field). Each carries a structured details mapping, mirrored by the TypeScript package.

Cross-language parity

Because the engine has a single implementation, pypaginate and @cyblow/paginate produce the same filtered/sorted/ranked order and byte-identical cursors. A frozen golden fixture is asserted by the Rust, Python, and TypeScript test suites in CI. See cross-language parity.

Development

git clone https://github.com/CybLow/paginate.git
cd paginate/py
uv sync                        # builds the native core (needs a Rust toolchain)

uv run pytest                  # tests
uv run pytest --cov            # coverage
uv run ruff format .           # format
uv run ruff check --fix .      # lint

See CONTRIBUTING.md.

Links

License

MIT — see LICENSE.

Release files for pypaginate 1.0.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 pypaginate 1.0.0
File Size Uploaded
pypaginate-1.0.0.tar.gz 106.6 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for pypaginate 1.0.0
File
pypaginate-1.0.0-cp311-abi3-win_arm64.whl CPython 3.11 abi3 Windows ARM64 Details
pypaginate-1.0.0-cp311-abi3-win_amd64.whl CPython 3.11 abi3 Windows x86-64 Details
pypaginate-1.0.0-cp311-abi3-musllinux_1_2_x86_64.whl CPython 3.11 abi3 Linux musl 1.2+ x86-64 Details
pypaginate-1.0.0-cp311-abi3-musllinux_1_2_aarch64.whl CPython 3.11 abi3 Linux musl 1.2+ ARM64 Details
pypaginate-1.0.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.11 abi3 Linux glibc 2.17+ x86-64 Details
pypaginate-1.0.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.11 abi3 Linux glibc 2.17+ ARM64 Details
pypaginate-1.0.0-cp311-abi3-macosx_10_12_x86_64.whl CPython 3.11 abi3 macOS 10.12+ x86-64 Details

Total release size: 8.1 MB

Release files / pypaginate-1.0.0.tar.gz

Download URL pypaginate-1.0.0.tar.gz
Size 106.6 kB
Tags Source
SHA-256 checksum
How to use checksums
b9a9613c9a367a95c7f191cd6461bdb1568d702db3189c69063321ef8b147961
BLAKE2b-256 checksum
How to use checksums
9cc1f7f5bbe474dcbd38b1b0d5cbba26716ad56c5b066d70c98254b0222b9ed2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 16, 2026.

Transparency log

Release files / pypaginate-1.0.0-cp311-abi3-win_arm64.whl

Download URL pypaginate-1.0.0-cp311-abi3-win_arm64.whl
Size 1.0 MB
Tags CPython 3.11 Windows ARM64 abi3
SHA-256 checksum
How to use checksums
a105ab42011fd3df2fbd32fcca831bd0aa887b02b840fbfab9b91003a1e421de
BLAKE2b-256 checksum
How to use checksums
c2c3af6e1409718a6290d7caa6aafcad16e60b29e95722447eb315ba8a94554b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 16, 2026.

Transparency log

Release files / pypaginate-1.0.0-cp311-abi3-win_amd64.whl

Download URL pypaginate-1.0.0-cp311-abi3-win_amd64.whl
Size 1.1 MB
Tags CPython 3.11 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
43bad14811d3620b0c7253ef9590beeca4613850d97a680ad5f0ab44e2406bb4
BLAKE2b-256 checksum
How to use checksums
8c4e23bacdcaaafafc2efaabfc0a81033a5d5c19fe67cb4c85f92d7e09742cb8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 16, 2026.

Transparency log

Release files / pypaginate-1.0.0-cp311-abi3-musllinux_1_2_x86_64.whl

Download URL pypaginate-1.0.0-cp311-abi3-musllinux_1_2_x86_64.whl
Size 1.4 MB
Tags CPython 3.11 Linux musl 1.2+ x86-64 abi3
SHA-256 checksum
How to use checksums
49046f5695c96a08974cdeecaf054b2d04b59c585b55dbeaa9f86db5559572e7
BLAKE2b-256 checksum
How to use checksums
97a8216bf9aa828582205b5e80bd3146e58e1ec369884b05360dc103a480ee10
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 16, 2026.

Transparency log

Release files / pypaginate-1.0.0-cp311-abi3-musllinux_1_2_aarch64.whl

Download URL pypaginate-1.0.0-cp311-abi3-musllinux_1_2_aarch64.whl
Size 1.3 MB
Tags CPython 3.11 Linux musl 1.2+ ARM64 abi3
SHA-256 checksum
How to use checksums
830c069e70c00605f777344717d9ddb241dbc45ab28d474638288e894540026f
BLAKE2b-256 checksum
How to use checksums
5b0ddad9b624214778eaaf9f33f527de9e4cd9f85d35ede701ea3aff749bd255
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 16, 2026.

Transparency log

Release files / pypaginate-1.0.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL pypaginate-1.0.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 1.1 MB
Tags CPython 3.11 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
a96e5afeab70a7058898c92475617c18f77949489fcf04e003da4a474b74554c
BLAKE2b-256 checksum
How to use checksums
c27ff538689c7c83e8c0fc6151ec6f425360fca7b2ec5f10688785d4701893e4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 16, 2026.

Transparency log

Release files / pypaginate-1.0.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL pypaginate-1.0.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 1.1 MB
Tags CPython 3.11 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
bf89295ff3f1c9d67106b38f1f0c5e943f575bae9ae8b255754407d561bd3752
BLAKE2b-256 checksum
How to use checksums
ee3a69149d919a9796113e5deff105c6a5635d814a42dd0e1b740a9b7323479b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 16, 2026.

Transparency log

Release files / pypaginate-1.0.0-cp311-abi3-macosx_10_12_x86_64.whl

Download URL pypaginate-1.0.0-cp311-abi3-macosx_10_12_x86_64.whl
Size 1.1 MB
Tags CPython 3.11 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
7b5437f8df976ed284744d108201b7f7c514b27e334f80f3693273426b51ffef
BLAKE2b-256 checksum
How to use checksums
51ebd53a3416384e04cb3ec5d04992a441f3ea4e3fd0ae054a4654c5c6debbd2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 16, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.0.0 This release

8 release files

0.2.1

8 release files

0.2.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