Upgrading from 10.0 to 10.1¶
10.1 removes nothing and deprecates nothing. Most of what changed is when a
mistake gets reported: calls that 10.0.0 sent to the server as given, or
quietly reshaped, now raise before a request goes out. One change can stop
working code without raising: a loop that polls documents.fetch to wait for
a write never exits. If you wait for writes to become visible, start with
Waiting for a write to become visible.
Coming from 9.x? Read Migrating to V10 first, then this
page.
The snippets run in order against one client. movies is a document index
whose schema declares title as a string field with full-text search:
from pinecone import Pinecone
pc = Pinecone(api_key="your-api-key")
index = pc.index("movies")
Contents¶
Waiting for a write to become visible¶
10.1.0 reads the log-position headers the Documents API sends. A documents
upsert, update or delete response now carries
response_info.lsn_committed, and a documents search response carries
response_info.lsn_reconciled. In 10.0.0 both were always None on the
Documents API.
That breaks the read-your-writes loop the 10.0.0 ResponseInfo reference
showed:
# 10.0.0 recipe: never exits on 10.1.0
import time
written = index.documents.upsert(
namespace="movies-en", documents=[{"_id": "movie-42", "title": "Arrival"}]
)
target = written.response_info.lsn_committed
while True:
fetched = index.documents.fetch(namespace="movies-en", ids=["movie-42"])
if target is None or fetched.response_info.is_reconciled(target):
break
time.sleep(0.5)
On 10.0.0, target was None, so the loop exited at once without waiting
for anything. On 10.1.0, target is a number, but a fetch response never
reports a reconciled position, so is_reconciled(target) stays False and
the loop runs forever. Nothing raises or warns.
Poll documents.search instead, and give the loop a deadline:
import time
from pinecone import TextQuery
written = index.documents.upsert(
namespace="movies-en", documents=[{"_id": "movie-42", "title": "Arrival"}]
)
target = written.response_info.lsn_committed
deadline = time.monotonic() + 30
while target is not None: # None: caught-up cannot be confirmed
found = index.documents.search(
namespace="movies-en",
top_k=1,
score_by=[TextQuery(query="arrival", fields=["title"])],
)
if found.response_info.is_reconciled(target):
break
if time.monotonic() > deadline:
raise TimeoutError(f"search had not reached LSN {target} after 30s")
time.sleep(0.5)
Fetch and search can see a new write at different times, so a fetch that
returns the document does not mean a search will find it. After
documents.batch_upsert, use result.response_info.lsn_committed as the
target; it is the highest position any of the batch’s requests reached.
Documents API arguments are checked before sending¶
A dict score_by clause with a key its type does not define now raises
PineconeValueError. 10.0.0 dropped the key and sent the rest, so an option
such as boost silently did nothing:
index.documents.search(
namespace="movies-en",
top_k=5,
score_by=[{"type": "text", "fields": ["title"], "query": "arrival", "boost": 2}],
)
# PineconeValueError: Object contains unknown field `boost`
A bare str where a list of names belongs now raises PineconeTypeError.
10.0.0 split ids, include_fields and remove_fields into one entry per
character, so ids="movie-42" deleted the IDs "m", "o", "v" and so on.
A clause’s fields given as one string, which 10.0.0 sent as is, is refused
the same way:
index.documents.delete(namespace="movies-en", ids="movie-42")
# PineconeTypeError: ids must be a sequence of strings, not a single str; pass ['movie-42'] to name one entry.
TextQuery(query="arrival", fields="title")
# PineconeTypeError: 'fields' must be a list of field names, not a single str; pass ['title'].
Three more wrong-type arguments now raise PineconeTypeError with a message
that names the fix:
Call |
10.0.0 |
10.1.0 |
|---|---|---|
|
|
|
|
sent to the server, or a misleading |
|
|
a bare |
|
PineconeTypeError is a TypeError, so an except TypeError around these
calls still catches them.
Timeouts must be positive¶
A timeout or total_timeout of zero, a negative number, or NaN now raises
PineconeValueError before anything is sent. This covers the client-level
timeout on Pinecone and AsyncPinecone, the per-call timeout on every
Documents API method, and total_timeout on documents.batch_upsert,
upsert(batch_size=...) and upsert_from_dataframe. 10.0.0 passed these
values through: timeout=0 reached the HTTP layer, and total_timeout=0
returned a result with every item failed and timed_out set.
index.documents.upsert(
namespace="movies-en", documents=[{"_id": "movie-42", "title": "Arrival"}], timeout=0
)
# PineconeValueError: timeout must be a positive number of seconds, got 0
None still means the default (no deadline, for total_timeout).
The wait timeout on pc.indexes.create(), create_for_model() and
delete() changed too. A negative value other than -1 now raises
PineconeValueError before the request. 10.0.0 sent the request, so the
index was created or deleted anyway, and then raised PineconeTimeoutError
at the first check that did not find the operation finished:
pc.indexes.create(
name="movies-v2",
schema={"fields": {"title": {"type": "string", "full_text_search": {}}}},
timeout=-5,
)
# PineconeValueError: timeout must be None (wait indefinitely), -1 (do not wait), or a non-negative number of seconds, got -5. Pass -1 to skip waiting.
Pass -1 to return without waiting, None to wait as long as it takes, or a
positive number of seconds.
Names and IDs must be strings¶
A name or ID the SDK puts into a URL (an index name, a backup ID, an assistant
name or file ID, an admin resource ID) must be a str. Anything else now
raises PineconeTypeError naming the parameter and the type it got, before
any request:
import uuid
pc.backups.list(index_name=1234)
# PineconeTypeError: index_name must be a string, got int
pc.assistants.describe(name=uuid.uuid4())
# PineconeTypeError: name must be a string, got UUID
pc.indexes.describe(None)
# PineconeTypeError: name must be a string, got NoneType
What each case did in 10.0.0:
backups.listand the assistant methods converted the value withstr()and sent it, asking forGET /indexes/1234/backups, a resource that cannot exist.Elsewhere a non-string escaped as a bare
AttributeError('int' object has no attribute 'strip') or aTypeErrorfrom the URL encoder.NoneraisedPineconeValueError: name must be a non-empty string. Anexcept PineconeValueError(orexcept ValueError) written for that no longer catches it; catchPineconeTypeErroras well. An empty string still raisesPineconeValueError.
The same check covers twelve arguments that never reach the URL, which move
from AttributeError in 10.0.0 to PineconeTypeError:
create_index_from_backup(name=), indexes.create_for_model(cloud=, region=),
collections.create(source=), backup_schedules.create(name=),
inference.rerank(query=), and, on the Admin client,
api_keys.create(name=), invites.create(email=), projects.create(name=),
projects.describe_by_name(name=), role_bindings.create(principal_id=) and
service_accounts.create(name=).
Convert at the call site, with str(uuid_value) or str(numeric_id). Two
cases keep their documented behavior: an optional path parameter still
accepts None, so pc.backups.list(index_name=None) lists backups across
every index, and describe_import() and cancel_import() still accept an
int ID and convert it.
Creating a client¶
An API key that cannot be sent as an HTTP header, such as one with a trailing
newline read from a file, now raises PineconeValueError when you create the
client. The message never repeats the key. 10.0.0 accepted the key, and the
first request failed inside the HTTP library with a message that could
include it:
api_key = "your-api-key\n" # as read from a file
Pinecone(api_key=api_key)
# PineconeValueError: The API key (api_key or PINECONE_API_KEY) is not a valid HTTP header value: it is blank or whitespace-only, has leading or trailing whitespace (such as a newline read from a file), or contains a control or non-ASCII character. Strip the key, for example api_key=key.strip().
pc = Pinecone(api_key=api_key.strip())
GrpcIndex still accepts leading and trailing spaces and tabs, which gRPC can
send, and refuses the rest.
An index host given with http:// now emits a RuntimeWarning, because the
API key and every request travel unencrypted. Loopback and private IP
addresses, localhost, and *.localhost are exempt, so a local emulator
stays quiet. Pass the host with https://, or with no scheme:
pc.index(host="http://movies-abc123.svc.aped-4627-b74a.pinecone.io")
# RuntimeWarning: The index host http://movies-abc123.svc.aped-4627-b74a.pinecone.io uses http, so the API key and every request and response body travel unencrypted.
additional_headers now replaces an SDK header whatever its capitalization.
In 10.0.0 only the exact spelling, such as "X-Pinecone-Api-Version",
replaced it; any other spelling was sent alongside the SDK’s own header, so
the server received two values. This client now sends one
X-Pinecone-Api-Version, 2025-10:
pinned = Pinecone(
api_key="your-api-key", additional_headers={"x-pinecone-api-version": "2025-10"}
)
Pinecone() and AsyncPinecone() called with a keyword they do not take
raise PineconeTypeError instead of a plain TypeError. It is a subclass of
TypeError, so existing handlers still catch it.
Bulk document upserts¶
When the server rejects a documents.batch_upsert batch as too large
(HTTP 413), 10.1.0 halves the batch and resends each half, down to single
documents. 10.0.0 recorded the whole batch as failed. Code that retried
result.failed_items with a smaller batch_size can drop that step; what
remains in failed_items after a 413 is a single document over the server’s
size cap, which no resend fixes. One batch can now land in part, so several
result.errors rows can share a batch_index.
A 413 from any call now raises PayloadTooLargeError rather than a plain
ApiError. It is a subclass of ApiError, so except ApiError still
catches it.
On an index with an embed or sparse_embed field, the server takes at most
96 documents per upsert or update request. The default batch_size of 50
fits; keep any value you pass at 96 or below:
index.documents.batch_upsert(
namespace="movies-en",
documents=[{"_id": f"movie-{i}", "title": f"Movie {i}"} for i in range(500)],
batch_size=96,
)
Summary¶
Change |
Before (10.0.0) |
Now (10.1.0) |
|---|---|---|
Polling |
exits at once ( |
never exits; poll |
Unknown key in a dict |
dropped |
|
Bare |
split into characters |
|
Bare |
sent as one string |
|
|
passed through |
|
Negative wait |
request sent, then |
|
Non- |
coerced, |
|
API key with whitespace or control characters |
first request fails |
|
Public index host with |
silent |
|
|
sent alongside the SDK’s header |
replaces it |
413 in |
whole batch failed |
halved and resent |
413 from any call |
|
|