Skip to content

Commit 45c18e9

Browse files
authored
Optional configs to pass additional_headers/additional_metadata to indexes (#297)
## Problem There are some debugging scenarios where, in order to debug or track a specific request, we would like the ability to pass additional headers with each request. ## Solution - Add `additional_headers` kwarg to `Index()` for REST calls - Add `additional_metadata` field within existing `grpc_config` object for configuring equivalent for GRPC - Add unit tests for both ways of doing it. Metadata is a [similar concept](https://grpc.io/docs/guides/metadata/) to http request headers in GRPC, not to be confused with vector metadata. ## Type of Change - [x] None of the above: New feature, but really for Pinecone developer/support use only. ### Usage (REST) ```python from pinecone import Pinecone pc = Pinecone(api_key='xxx') index = pc.Index( host='hosturl', additional_headers={ 'header-1': 'header-1-value' } ) # Now do things index.upsert(...) ``` ### Usage (GRPC) ```python from pinecone.grpc import PineconeGRPC, GRPCClientConfig pc = PineconeGRPC(api_key='YOUR_API_KEY') grpc_config = GRPCClientConfig(additional_metadata={'extra-header': 'value123'}) index = pc.Index( name='my-index', host='host', grpc_config=grpc_config ) # do stuff index.upsert(...) ``` ## Test Plan Besides unit tests, I will try running some test commands with `PINECONE_DEBUG_CURL='true'` enabled to see what request is being sent.
1 parent 995c0a1 commit 45c18e9

File tree

7 files changed

+115
-7
lines changed

7 files changed

+115
-7
lines changed

pinecone/control/pinecone.py

+1
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,7 @@ def _get_status(self, name: str):
446446
response = api_instance.describe_index(name)
447447
return response["status"]
448448

449+
449450
def Index(self, name: str = '', host: str = '', **kwargs):
450451
"""
451452
Target an index for data operations.

pinecone/data/index.py

+16-6
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,25 @@ class Index():
6969
For improved performance, use the Pinecone GRPC index client.
7070
"""
7171

72-
def __init__(self, api_key: str, host: str, pool_threads=1, **kwargs):
73-
api_key = api_key or kwargs.get("api_key", None)
74-
host = host or kwargs.get('host', None)
75-
pool_threads = pool_threads or kwargs.get("pool_threads")
76-
72+
def __init__(
73+
self,
74+
api_key: str,
75+
host: str,
76+
pool_threads: Optional[int] = 1,
77+
additional_headers: Optional[Dict[str, str]] = {},
78+
**kwargs
79+
):
7780
self._config = ConfigBuilder.build(api_key=api_key, host=host, **kwargs)
7881

79-
api_client = ApiClient(configuration=self._config.openapi_config, pool_threads=pool_threads)
82+
api_client = ApiClient(configuration=self._config.openapi_config,
83+
pool_threads=pool_threads)
84+
85+
# Configure request headers
8086
api_client.user_agent = get_user_agent()
87+
extra_headers = additional_headers or {}
88+
for key, value in extra_headers.items():
89+
api_client.set_default_header(key, value)
90+
8191
self._api_client = api_client
8292
self._vector_api = VectorOperationsApi(api_client=api_client)
8393

pinecone/grpc/base.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,15 @@ def __init__(
3939
self.config = config
4040
self.grpc_client_config = grpc_config or GRPCClientConfig()
4141
self.retry_config = self.grpc_client_config.retry_config or RetryConfig()
42-
self.fixed_metadata = {"api-key": config.api_key, "service-name": index_name, "client-version": CLIENT_VERSION}
42+
43+
self.fixed_metadata = {
44+
"api-key": config.api_key,
45+
"service-name": index_name,
46+
"client-version": CLIENT_VERSION
47+
}
48+
if self.grpc_client_config.additional_metadata:
49+
self.fixed_metadata.update(self.grpc_client_config.additional_metadata)
50+
4351
self._endpoint_override = _endpoint_override
4452

4553
self.method_config = json.dumps(

pinecone/grpc/config.py

+5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ class GRPCClientConfig(NamedTuple):
1818
:type retry_config: RetryConfig, optional
1919
:param grpc_channel_options: A dict of gRPC channel arguments
2020
:type grpc_channel_options: Dict[str, str]
21+
:param additional_metadata: Additional metadata to be sent to the server with each request. Note that this
22+
metadata refers to [gRPC metadata](https://grpc.io/docs/guides/metadata/) which is a concept similar
23+
to HTTP headers. This is unrelated to the metadata can be stored with a vector in the index.
24+
:type additional_metadata: Dict[str, str]
2125
"""
2226

2327
secure: bool = True
@@ -26,6 +30,7 @@ class GRPCClientConfig(NamedTuple):
2630
reuse_channel: bool = True
2731
retry_config: Optional[RetryConfig] = None
2832
grpc_channel_options: Optional[Dict[str, str]] = None
33+
additional_metadata: Optional[Dict[str, str]] = None
2934

3035
@classmethod
3136
def _from_dict(cls, kwargs: dict):

tests/unit/test_control.py

+10
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ def test_passing_additional_headers(self):
2828

2929
for key, value in extras.items():
3030
assert p.index_api.api_client.default_headers[key] == value
31+
assert 'User-Agent' in p.index_api.api_client.default_headers
32+
assert len(p.index_api.api_client.default_headers) == 3
33+
34+
def test_overwrite_useragent(self):
35+
# This doesn't seem like a common use case, but we may want to allow this
36+
# when embedding the client in other pinecone tools such as canopy.
37+
extras = {"User-Agent": "test-user-agent"}
38+
p = Pinecone(api_key="123-456-789", additional_headers=extras)
39+
assert p.index_api.api_client.default_headers['User-Agent'] == 'test-user-agent'
40+
assert len(p.index_api.api_client.default_headers) == 1
3141

3242
@pytest.mark.parametrize("timeout_value, describe_index_responses, expected_describe_index_calls, expected_sleep_calls", [
3343
# When timeout=None, describe_index is called until ready
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import pytest
2+
from pinecone import Pinecone
3+
4+
class TestIndexClientInitialization():
5+
@pytest.mark.parametrize(
6+
'additional_headers',
7+
[
8+
None,
9+
{}
10+
]
11+
)
12+
def test_no_additional_headers_leaves_useragent_only(self, additional_headers):
13+
pc = Pinecone(api_key='YOUR_API_KEY')
14+
index = pc.Index(host='myhost', additional_headers=additional_headers)
15+
assert len(index._api_client.default_headers) == 1
16+
assert 'User-Agent' in index._api_client.default_headers
17+
assert 'python-client-' in index._api_client.default_headers['User-Agent']
18+
19+
def test_additional_headers_one_additional(self):
20+
pc = Pinecone(api_key='YOUR_API_KEY')
21+
index = pc.Index(
22+
host='myhost',
23+
additional_headers={'test-header': 'test-header-value'}
24+
)
25+
assert 'test-header' in index._api_client.default_headers
26+
assert len(index._api_client.default_headers) == 2
27+
28+
def test_multiple_additional_headers(self):
29+
pc = Pinecone(api_key='YOUR_API_KEY')
30+
index = pc.Index(
31+
host='myhost',
32+
additional_headers={
33+
'test-header': 'test-header-value',
34+
'test-header2': 'test-header-value2'
35+
}
36+
)
37+
assert 'test-header' in index._api_client.default_headers
38+
assert 'test-header2' in index._api_client.default_headers
39+
assert len(index._api_client.default_headers) == 3
40+
41+
def test_overwrite_useragent(self):
42+
# This doesn't seem like a common use case, but we may want to allow this
43+
# when embedding the client in other pinecone tools such as canopy.
44+
pc = Pinecone(api_key='YOUR_API_KEY')
45+
index = pc.Index(
46+
host='myhost',
47+
additional_headers={
48+
'User-Agent': 'test-user-agent'
49+
}
50+
)
51+
assert len(index._api_client.default_headers) == 1
52+
assert 'User-Agent' in index._api_client.default_headers
53+
assert index._api_client.default_headers['User-Agent'] == 'test-user-agent'

tests/unit_grpc/test_grpc_index_initialization.py

+21
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,27 @@ def test_init_with_default_config(self):
1111
assert index.grpc_client_config.reuse_channel == True
1212
assert index.grpc_client_config.retry_config == None
1313
assert index.grpc_client_config.grpc_channel_options == None
14+
assert index.grpc_client_config.additional_metadata == None
15+
16+
# Default metadata, grpc equivalent to http request headers
17+
assert len(index.fixed_metadata) == 3
18+
assert index.fixed_metadata['api-key'] == 'YOUR_API_KEY'
19+
assert index.fixed_metadata['service-name'] == 'my-index'
20+
assert index.fixed_metadata['client-version'] != None
21+
22+
def test_init_with_additional_metadata(self):
23+
pc = PineconeGRPC(api_key='YOUR_API_KEY')
24+
config = GRPCClientConfig(additional_metadata={
25+
'debug-header': 'value123',
26+
'debug-header2': 'value456'
27+
})
28+
index = pc.Index(name='my-index', host='host', grpc_config=config)
29+
assert len(index.fixed_metadata) == 5
30+
assert index.fixed_metadata['api-key'] == 'YOUR_API_KEY'
31+
assert index.fixed_metadata['service-name'] == 'my-index'
32+
assert index.fixed_metadata['client-version'] != None
33+
assert index.fixed_metadata['debug-header'] == 'value123'
34+
assert index.fixed_metadata['debug-header2'] == 'value456'
1435

1536
def test_init_with_grpc_config_from_dict(self):
1637
pc = PineconeGRPC(api_key='YOUR_API_KEY')

0 commit comments

Comments
 (0)