Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 6 additions & 9 deletions pinecone/openapi_support/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
)
from .auth_util import AuthUtil
from .serializer import Serializer
from pinecone.utils.response_info import extract_response_info


class ApiClient(object):
Expand Down Expand Up @@ -208,16 +209,12 @@ def __call_api(
if return_data is not None:
headers = response_data.getheaders()
if headers:
from pinecone.utils.response_info import extract_response_info

response_info = extract_response_info(headers)
# Attach if response_info exists (may contain raw_headers even without LSN values)
if response_info:
if isinstance(return_data, dict):
return_data["_response_info"] = response_info
else:
# Dynamic attribute assignment on OpenAPI models
setattr(return_data, "_response_info", response_info)
if isinstance(return_data, dict):
return_data["_response_info"] = response_info
else:
# Dynamic attribute assignment on OpenAPI models
setattr(return_data, "_response_info", response_info)

if _return_http_data_only:
return return_data
Expand Down
15 changes: 6 additions & 9 deletions pinecone/openapi_support/asyncio_api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from .serializer import Serializer
from .deserializer import Deserializer
from .auth_util import AuthUtil
from pinecone.utils.response_info import extract_response_info

logger = logging.getLogger(__name__)
""" :meta private: """
Expand Down Expand Up @@ -173,16 +174,12 @@ async def __call_api(
if return_data is not None:
headers = response_data.getheaders()
if headers:
from pinecone.utils.response_info import extract_response_info

response_info = extract_response_info(headers)
# Attach if response_info exists (may contain raw_headers even without LSN values)
if response_info:
if isinstance(return_data, dict):
return_data["_response_info"] = response_info
else:
# Dynamic attribute assignment on OpenAPI models
setattr(return_data, "_response_info", response_info)
if isinstance(return_data, dict):
return_data["_response_info"] = response_info
else:
# Dynamic attribute assignment on OpenAPI models
setattr(return_data, "_response_info", response_info)

if _return_http_data_only:
return return_data
Expand Down
20 changes: 16 additions & 4 deletions pinecone/openapi_support/model_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,9 +498,15 @@ def __eq__(self, other):
if not isinstance(other, self.__class__):
return False

if not set(self._data_store.keys()) == set(other._data_store.keys()):
# Exclude _response_info from equality comparison since it contains
# timing-dependent headers that may differ between requests
self_keys = {k for k in self._data_store.keys() if k != "_response_info"}
other_keys = {k for k in other._data_store.keys() if k != "_response_info"}

if not self_keys == other_keys:
return False
for _var_name, this_val in self._data_store.items():
for _var_name in self_keys:
this_val = self._data_store[_var_name]
that_val = other._data_store[_var_name]
types = set()
types.add(this_val.__class__)
Expand Down Expand Up @@ -653,9 +659,15 @@ def __eq__(self, other):
if not isinstance(other, self.__class__):
return False

if not set(self._data_store.keys()) == set(other._data_store.keys()):
# Exclude _response_info from equality comparison since it contains
# timing-dependent headers that may differ between requests
self_keys = {k for k in self._data_store.keys() if k != "_response_info"}
other_keys = {k for k in other._data_store.keys() if k != "_response_info"}

if not self_keys == other_keys:
return False
for _var_name, this_val in self._data_store.items():
for _var_name in self_keys:
this_val = self._data_store[_var_name]
that_val = other._data_store[_var_name]
types = set()
types.add(this_val.__class__)
Expand Down
41 changes: 15 additions & 26 deletions pinecone/utils/response_info.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
"""Response information utilities for extracting LSN headers from API responses."""
"""Response information utilities for extracting headers from API responses."""

from typing import Any, TypedDict

# Exclude timing-dependent headers that cause test flakiness
# Defined at module level to avoid recreation on every function call
_TIMING_HEADERS = frozenset(
(
"x-envoy-upstream-service-time",
"date",
"x-request-id", # Request IDs are unique per request
)
)


class ResponseInfo(TypedDict):
"""Response metadata including raw headers.
Expand All @@ -26,8 +16,9 @@ class ResponseInfo(TypedDict):
def extract_response_info(headers: dict[str, Any] | None) -> ResponseInfo:
"""Extract raw headers from response headers.

Extracts and normalizes response headers from API responses.
Header names are normalized to lowercase keys.
Extracts and normalizes all response headers from API responses.
Header names are normalized to lowercase keys. All headers are included
without filtering.

Args:
headers: Dictionary of response headers, or None.
Expand All @@ -47,21 +38,19 @@ def extract_response_info(headers: dict[str, Any] | None) -> ResponseInfo:
if not headers:
return {"raw_headers": {}}

# Optimized: use dictionary comprehension for better performance
# Pre-compute lowercase keys and filter in one pass
# Optimized: normalize keys to lowercase and convert values to strings
# Check string type first (most common case) for better performance
raw_headers = {}
for key, value in headers.items():
key_lower = key.lower()
if key_lower not in _TIMING_HEADERS:
# Optimize value conversion: check most common types first
if isinstance(value, list) and value:
raw_headers[key_lower] = str(value[0])
elif isinstance(value, tuple) and value:
raw_headers[key_lower] = str(value[0])
elif isinstance(value, str):
# Already a string, no conversion needed
raw_headers[key_lower] = value
else:
raw_headers[key_lower] = str(value)
if isinstance(value, str):
# Already a string, no conversion needed
raw_headers[key_lower] = value
elif isinstance(value, list) and value:
raw_headers[key_lower] = str(value[0])
elif isinstance(value, tuple) and value:
raw_headers[key_lower] = str(value[0])
else:
raw_headers[key_lower] = str(value)

return {"raw_headers": raw_headers}