Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

sqlalchemy-relay-pagination

Python package that make setting up relay-style pagination with SQLAlchemy easy.

Quickstart

Installation

uv add sqlalchemy-relay-pagination

Schema

Define your connection types in SDL:

type Query {
  users(first: Int, last: Int, after: String, before: String): UserConnection!
}

type UserConnection {
  edges: [UserEdge!]!
  pageInfo: PageInfo!
}

type UserEdge {
  node: User!
  cursor: String!
}

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
  totalCount: Int
}

type User {
  id: Int!
  name: String!
  posts(first: Int, last: Int, after: String, before: String): PostConnection!
}

type PostConnection {
  edges: [PostEdge!]!
  pageInfo: PageInfo!
}

type PostEdge {
  node: Post!
  cursor: String!
}

type Post {
  id: Int!
  title: String!
}

Resolver

from ariadne import QueryType
from sqlalchemy import select
from sqlalchemy_relay_pagination import paginate
from sqlalchemy_relay_pagination.graphql import extract_requested_fields

query_type = QueryType()

@query_type.field("users")
async def resolve_users(_, info, first=None, last=None, after=None, before=None):
    session = info.context["session"]
    return await paginate(
        session,
        select(User),
        order_by=User.id,
        first=first,
        last=last,
        after=after,
        before=before,
        requested_fields=extract_requested_fields(info),
    )

extract_requested_fields inspects the GraphQL selection set and tells paginate which fields the client actually requested — skipping the COUNT(*) query when totalCount is absent, and skipping the extra-row fetch when neither hasNextPage nor hasPreviousPage is selected.

Batch-loading nested connections (avoiding N+1)

For nested paginated fields (e.g. User.posts), use paginate_many inside a dataloader to fetch all parents' children in a single query:

from aiodataloader import DataLoader
from ariadne import ObjectType
from sqlalchemy_relay_pagination import paginate_many

user_type = ObjectType("User")

class UserPostsLoader(DataLoader):
    def __init__(self, session, first, last, fields):
        super().__init__()
        self._session = session
        self._first = first
        self._last = last
        self._fields = fields

    async def batch_load_fn(self, user_ids):
        results = await paginate_many(
            self._session,
            select(Post),
            model=Post,
            partition_by=Post.user_id,
            order_by=Post.id,
            keys=list(user_ids),
            first=self._first,
            last=self._last,
            requested_fields=self._fields,
        )
        return [results[uid] for uid in user_ids]

@user_type.field("posts")
async def resolve_user_posts(user, info, first=None, last=None):
    session = info.context["session"]
    loaders = info.context["loaders"]

    loader_key = (first, last)
    if loader_key not in loaders:
        loaders[loader_key] = UserPostsLoader(
            session, first, last, extract_requested_fields(info)
        )

    return await loaders[loader_key].load(user.id)

Note: when after or before cursors are provided, paginate_many falls back to one query per parent key instead of a single batched query, because each parent may be at a different cursor position. Results are still correct; only the single-query optimisation is lost.

Global configuration

Call configure() once at application startup:

from sqlalchemy_relay_pagination import configure, PaginationConfig

configure(PaginationConfig(default_page_size=20, max_page_size=100))

Settings can also be provided via environment variables:

SQLALCHEMY_RELAY_PAGINATION_DEFAULT_PAGE_SIZE=20
SQLALCHEMY_RELAY_PAGINATION_MAX_PAGE_SIZE=100

Resources

Metadata

Release files for sqlalchemy-relay-pagination 0.1.0rc2

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

Source distribution (sdist)

Source distribution for sqlalchemy-relay-pagination 0.1.0rc2
File Size Uploaded
sqlalchemy_relay_pagination-0.1.0rc2.tar.gz 99.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for sqlalchemy-relay-pagination 0.1.0rc2
File Interpreter ABI Platform
sqlalchemy_relay_pagination-0.1.0rc2-py3-none-any.whl Python 3 none any Details

Total release size: 118.9 kB

Release files / sqlalchemy_relay_pagination-0.1.0rc2.tar.gz

Download URL sqlalchemy_relay_pagination-0.1.0rc2.tar.gz
Size 99.5 kB
Tags Source
SHA-256 checksum
How to use checksums
1a68dc51f833c5d3f39be84e89a6dbbb3911ad9bc539d77f869c7044fb77b980
BLAKE2b-256 checksum
How to use checksums
410cb64fb3558ab0c1b76018a0cfb636a31478898c0205bd86d2768262819f70
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Mar 21, 2026.

Transparency log

Release files / sqlalchemy_relay_pagination-0.1.0rc2-py3-none-any.whl

Download URL sqlalchemy_relay_pagination-0.1.0rc2-py3-none-any.whl
Size 19.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3f92677aff68d63b785a97e6d8df0e47c02dd774baf72e921f683774a0af1b79
BLAKE2b-256 checksum
How to use checksums
0bbc0eafef5ab1a8c49031c9c3598ccad01953d9b3593f1b7ee85e268babda6d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Mar 21, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0rc2 This release

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