Pagination¶
Listings that can grow without bound come back a page at a time. Which page you get is decided by an opaque pagination token, and the SDK offers two ways to work with it: a paginator that follows the token for you, or a single-page call that hands you the token to drive yourself.
The shapes¶
Every paged listing in the SDK is one of these.
Shape |
Returns |
You do |
|---|---|---|
Auto-paginating |
|
Iterate. Pages are fetched lazily as you cross them. |
Single page |
A response object with a |
Read the page, then pass |
Unpaged |
A plain list-like model with no token |
Iterate once. There is nothing to follow. |
Several listings offer both of the first two, under names that differ by
namespace (iter_*, *_page, or *_paginated on the index):
Listing |
Auto-paginating |
Single page |
|---|---|---|
Indexes |
|
— |
Backups of one index |
|
— |
Backups, project-wide |
— |
|
Restore jobs |
— |
|
Backup schedules |
|
|
Backup schedule history |
|
|
Assistants |
|
|
Assistant files |
|
|
Assistant operations |
|
|
Documents |
|
— |
Vector IDs |
|
|
Namespaces |
|
|
Bulk imports |
|
|
Admin users, invites, service accounts, role bindings |
|
— |
Unpaged listings (pc.collections.list(), pc.inference.list_models(),
admin.api_keys.list(), admin.organizations.list(), admin.projects.list())
return every row in one response and carry no pagination attribute to check.
Reach for the single-page form when you need the token itself: to checkpoint a long walk to durable storage, to hand the next page to a different worker, or to resume after a crash. Otherwise iterate the paginator.
One paged surface is not a listing at all: idx.documents.fetch(filter=...) is
a read whose filter can match unboundedly many documents, so it pages too. It
takes a token and an optional limit page size; see
When the first call takes no token.
Paginator and AsyncPaginator¶
Paginator and
AsyncPaginator are lazy: no
request is made until you start iterating, and each page is fetched only when
iteration reaches it. They share one interface.
Member |
What it gives you |
|---|---|
|
Individual items, pages hidden |
|
|
|
Every item, in one list |
|
Where iteration got to; |
A paginator is not a list and not a snapshot. It has no len(), and iterating it
twice sends the requests twice.
Iterating items¶
The common case. The loop crosses page boundaries without you noticing.
from pinecone import Pinecone
pc = Pinecone()
for backup in pc.indexes.list_backups("product-search"):
print(backup.backup_id, backup.status)
import asyncio
from pinecone import AsyncPinecone
async def main() -> None:
async with AsyncPinecone() as pc:
async for backup in pc.indexes.list_backups("product-search"):
print(backup.backup_id, backup.status)
asyncio.run(main())
Iterating pages¶
Use .pages() when you want the token each page carries, or when a page is the
natural unit of work, such as one batch write or one checkpoint per page. The
generator stops on its own when a page arrives without a token; you do not need
to break on has_more.
from pinecone import Pinecone
pc = Pinecone()
for page in pc.backup_schedules.iter_history(schedule_id="sched-abc123").pages():
for run in page.items:
print(run.backup_id, run.status)
print("next page:", page.pagination_token) # None on the last page
from pinecone import AsyncPinecone
async with AsyncPinecone() as pc:
history = pc.backup_schedules.iter_history(schedule_id="sched-abc123")
async for page in history.pages():
for run in page.items:
print(run.backup_id, run.status)
print("next page:", page.pagination_token)
Each Page carries:
Attribute |
Type |
Meaning |
|---|---|---|
|
|
The rows on this page, in server order |
|
|
The page after this one; |
|
|
|
Collecting everything¶
to_list() walks every page and returns one list. Only reach for it when you
know the listing is small: it holds the whole result in memory and cannot start
work before the last page arrives.
from pinecone import Pinecone
pc = Pinecone()
backups = pc.indexes.list_backups("product-search").to_list()
print(len(backups))
from pinecone import AsyncPinecone
async with AsyncPinecone() as pc:
backups = await pc.indexes.list_backups("product-search").to_list()
print(len(backups))
Resuming¶
Page.pagination_token and Paginator.pagination_token are both accepted back
as the pagination_token argument of the same list method. Store one, and a
later process picks up where the earlier one stopped.
from pinecone import Pinecone
pc = Pinecone()
pages = pc.backup_schedules.iter_history(schedule_id="sched-abc123").pages()
first = next(pages)
for run in first.items:
print(run.backup_id)
token = first.pagination_token # save this somewhere durable
# Later, in another process:
for run in pc.backup_schedules.iter_history(
schedule_id="sched-abc123", pagination_token=token
):
print(run.backup_id)
from pinecone import AsyncPinecone
async with AsyncPinecone() as pc:
pages = pc.backup_schedules.iter_history(schedule_id="sched-abc123").pages()
first = await anext(pages)
for run in first.items:
print(run.backup_id)
token = first.pagination_token
async for run in pc.backup_schedules.iter_history(
schedule_id="sched-abc123", pagination_token=token
):
print(run.backup_id)
Tokens are opaque. They are not IDs, offsets, or timestamps; do not parse one, build one, or edit one.
Paging walks a live result set rather than a fixed snapshot, so rows created or deleted between requests can shift later pages. De-duplicate on the row’s own identifier rather than relying on page order.
What limit means¶
limit is the page size the SDK asks the server for. On some paginators it
also caps the total number of items iteration will yield:
Page size and total cap:
pc.indexes.list,pc.indexes.list_backups,pc.backup_schedules.iter_schedules,pc.backup_schedules.iter_history,pc.assistants.list,pc.assistants.list_files,pc.assistants.list_operations.Page size only (the paginator still walks every page):
idx.documents.list,admin.users.list,admin.invites.list,admin.service_accounts.list,admin.role_bindings.list, and theIndexlistings (idx.list,idx.list_namespaces,idx.list_imports).
Check the method’s own limit documentation before relying on it to stop
iteration. To stop early on any listing, break out of the loop or wrap it in
itertools.islice(), which works on every listing.
# Stops after 50 items on any listing, regardless of limit semantics.
import itertools
from pinecone import Pinecone
pc = Pinecone()
for backup in itertools.islice(pc.indexes.list_backups("product-search"), 50):
print(backup.backup_id)
When a limit truncates a page, that page reports pagination_token=None even
though the server had more. Paginator.pagination_token still holds the
server’s token for the following page, so resuming from it skips the rows the
limit cut from the truncated page.
Driving the token yourself¶
Single-page methods return the page plus a pagination envelope. pagination is None, or pagination.next is None, both mean the page you are holding is the
last one. That is the loop’s exit condition, not an error.
The loop below consumes each page before asking for the next one:
from pinecone import Pinecone
pc = Pinecone()
page = pc.backups.list(limit=100)
backups = list(page)
while page.pagination is not None and page.pagination.next is not None:
page = pc.backups.list(pagination_token=page.pagination.next)
backups.extend(page)
print(len(backups))
Pass limit on the first request only. The token already carries the page
size it was minted with, so these backup listings omit limit whenever you pass
pagination_token.
The async form is the same loop with await on each request:
from pinecone import AsyncPinecone
async with AsyncPinecone() as pc:
page = await pc.backups.list(limit=100)
backups = list(page)
while page.pagination is not None and page.pagination.next is not None:
page = await pc.backups.list(pagination_token=page.pagination.next)
backups.extend(page)
print(len(backups))
When the first call takes no token¶
Not every paged surface is a listing. idx.documents.fetch(filter=...) is a
paged read: the filter can match an unbounded number of documents, so the
response carries a pagination envelope, but there is no first-page call that
differs from the rest. Write it as a while True with the token starting at
None, and read each page’s documents before asking for the next one:
from pinecone import Pinecone
idx = Pinecone().index(name="articles-en")
pagination_token = None
while True:
response = idx.documents.fetch(
namespace="published",
filter={"category": {"$eq": "tech"}},
pagination_token=pagination_token,
)
for doc_id, doc in response.documents.items():
print(doc_id, doc.title)
if response.pagination is None:
break
pagination_token = response.pagination.next
Fetching by ids= instead of filter= is not paged: you get every requested
ID in one response. Only the filtered form pages, and pagination_token is
rejected without a filter.
Listings on the index¶
The data-plane listings on Index keep their own shape. The auto-following
form is a plain generator, not a paginator, so .pages(),
.to_list(), and .pagination_token are not available on it.
idx.list() and idx.list_namespaces() yield one response object per page,
not one row at a time, so the loop body iterates the page as well:
from pinecone import Pinecone
pc = Pinecone()
idx = pc.index(name="product-search")
for page in idx.list(prefix="product#", limit=100):
for item in page.vectors:
print(item.id)
for page in idx.list_namespaces(prefix="prod-"):
for ns in page.namespaces:
print(ns.name, ns.record_count, ns.size_bytes)
idx.list_imports() is the exception: it yields the import records themselves.
from pinecone import Pinecone
idx = Pinecone().index(name="product-search")
for imp in idx.list_imports():
print(imp.id, imp.status)
Each has a *_paginated twin that fetches exactly one page and leaves the token
to you:
from pinecone import Pinecone
idx = Pinecone().index(name="product-search")
page = idx.list_paginated(prefix="product#", limit=100)
ids = [item.id for item in page.vectors]
while page.pagination is not None and page.pagination.next is not None:
page = idx.list_paginated(
prefix="product#",
pagination_token=page.pagination.next,
)
ids.extend(item.id for item in page.vectors)
print(len(ids))
To inspect many namespaces, prefer idx.list_namespaces() over repeated
describe_namespace() calls: it describes every namespace in one request per
page.
See also¶
Error Handling: what a failed page request raises.
Sync vs Async Clients: choosing between
PineconeandAsyncPinecone.Retries and Resilience: a page request retries like any other request.