fix: handle empty response body in deserializer (#564)#623
Merged
Conversation
When the server returns an empty response body (e.g. from pinecone-local), the deserializer falls back to a raw empty string which then crashes downstream. Return None early from the deserializer when response data is empty, which is already handled correctly by both sync and async clients.
…tests - Handle whitespace-only response bodies (e.g. "\n", " ") in deserializer - Update delete() return type to dict[str, Any] | None in both clients - Add sync client tests and parametrize over whitespace variants
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Stale
castcontradicts new nullable return type- Updated cast statements in both delete methods from cast(dict[str, Any], result) to cast(dict[str, Any] | None, result) to match the nullable return type annotation.
Or push these changes by commenting:
@cursor push abb4041c83
Preview (abb4041c83)
diff --git a/pinecone/db_data/index.py b/pinecone/db_data/index.py
--- a/pinecone/db_data/index.py
+++ b/pinecone/db_data/index.py
@@ -821,7 +821,7 @@
),
**self._openapi_kwargs(kwargs),
)
- return cast(dict[str, Any], result)
+ return cast(dict[str, Any] | None, result)
@validate_and_convert_errors
def fetch(self, ids: list[str], namespace: str | None = None, **kwargs) -> FetchResponse:
diff --git a/pinecone/db_data/index_asyncio.py b/pinecone/db_data/index_asyncio.py
--- a/pinecone/db_data/index_asyncio.py
+++ b/pinecone/db_data/index_asyncio.py
@@ -595,7 +595,7 @@
),
**{k: v for k, v in kwargs.items() if k in _OPENAPI_ENDPOINT_PARAMS},
)
- return cast(dict[str, Any], result)
+ return cast(dict[str, Any] | None, result)
@validate_and_convert_errors
async def fetch(self, ids: list[str], namespace: str | None = None, **kwargs) -> FetchResponse:This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.
The version bump regex in publish-to-pypi.yaml matches all `version = "..."` occurrences, including ruff's `target-version`. Anchor the pattern to start-of-line with `^` and `re.MULTILINE` so only the project version on line 3 is replaced. Also restores target-version to "py310" after it was overwritten by the v8.1.0 release. This is the same fix as 0f60567 which was never merged to main.
The test_docs_links test was calling the versioned URL template with
__version__ (SDK version, e.g. "8.1.0") instead of API_VERSION
("2025-10"), producing URLs like .../api/8.1.0/... that 404.
The production code already uses API_VERSION correctly — only the
test was wrong.
The cast() statements still asserted dict[str, Any] while the return type was widened to dict[str, Any] | None. This misleads the type checker and suppresses None-safety warnings.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Same issue as delete() — delete_namespace() goes through the same deserializer path and can return None for empty response bodies.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Problem
Deserializer crash (fixes #564): When using
IndexAsyncio.delete()against pinecone-local, the server returns an empty response body (b""). The deserializer triesorjson.loads(""), which fails, and theexcept ValueErrorfallback assigns the raw empty string asreturn_data. Downstream code then crashes attemptingsetattr("", "_response_info", ...).Release process clobbers ruff config: The version bump regex in
publish-to-pypi.yamlmatches allversion = "..."occurrences, including ruff'starget-version = "py310". Every release overwrites it with the SDK version, breaking the lint CI check. This was previously fixed in 0f60567 but never merged to main.Docs link test uses wrong version:
test_docs_links.pycalls the versioned URL template with__version__(SDK version, e.g."8.1.0") instead ofAPI_VERSION("2025-10"), producing URLs that 404.Solution
Deserializer: Return
Noneearly fromDeserializer.deserialize()whenresponse.datais empty or whitespace-only. This is the single shared deserialization codepath for both sync and async clients, so the fix covers all endpoints. Bothapi_client.pyandasyncio_api_client.pyalready guard_response_infoattachment withif return_data is not None.Release process: Anchor the version bump regex with
^andre.MULTILINEso only the project version at the start of a line is matched. Restoretarget-versionto"py310".Docs link test: Use
API_VERSIONfrompinecone.core.openapi.db_controlinstead of__version__, matching what the production code does.Key decisions
Deserializer.deserialize()rather than patching bothapi_client.pyandasyncio_api_client.pyseparately."\n"," ") in addition to truly empty strings, since these are equally unparseable and could come from non-standard server implementations.delete()signatures on bothIndexand_IndexAsyncioto-> dict[str, Any] | Noneto reflect that an empty server response now returnsNoneinstead of crashing.Test plan
b"",b" ",b"\n",b" \n ") — both async and sync clientsb"{}"still deserializes todicttarget-version = "py310"API_VERSIONinstead of__version__Note
Medium Risk
Touches the shared OpenAPI deserialization path, changing empty/whitespace HTTP bodies to deserialize as
None, which could affect any endpoint that previously surfaced an empty string. Other changes are limited to type hints/tests and CI/release tooling.Overview
Fixes crashes when delete endpoints return an empty/whitespace body by making
Deserializer.deserialize()returnNoneearly, and updatesIndex.delete()/delete_namespace()(sync + asyncio) return types todict | Noneto reflect this.Adds unit coverage for sync/async
call_apibehavior with empty bodies, fixes the docs-link test to useAPI_VERSION(not the SDK package version), and hardens the PyPI publish workflow’s version-bump regex so it only updates the projectversionline (preventing clobbering Ruff’starget-version, which is also restored topy310).Written by Cursor Bugbot for commit 84e0dc8. This will update automatically on new commits. Configure here.