Skip to content

Commit 9b4a2a1

Browse files
authored
Adjust config kwargs to Pinecone and PineconeAsyncio (#447)
## Problem We want to deprecate a few kwargs and give more helpful error messages. ## Solution - Deprecate `config` and `openapi_config` kwargs; as far as I know these were only used for tests. They have not appeared in documentation. It adds quite a bit of complexity trying to merge configuration from all these sources so going forward we would prefer to just expect configuration from a single source, which are named kwargs. - Add `NotImplementedError` messages that clearly explain some features that are not implemented yet for PineconeAsyncio. This is preferrable to allowing users to pass kwargs that have no effect, then getting frustrated when they seem not to work. - Give more informative error when it looks like the user has passed an invalid value as the host ## Type of Change - [x] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [x] This change requires a documentation update
1 parent a7119a8 commit 9b4a2a1

15 files changed

+133
-88
lines changed

pinecone/control/pinecone.py

+34-19
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from .index_host_store import IndexHostStore
77
from .pinecone_interface import PineconeDBControlInterface
88

9-
from pinecone.config import PineconeConfig, Config, ConfigBuilder
9+
from pinecone.config import PineconeConfig, ConfigBuilder
1010

1111
from pinecone.core.openapi.db_control.api.manage_indexes_api import ManageIndexesApi
1212
from pinecone.openapi_support.api_client import ApiClient
@@ -59,33 +59,30 @@ def __init__(
5959
proxy_headers: Optional[Dict[str, str]] = None,
6060
ssl_ca_certs: Optional[str] = None,
6161
ssl_verify: Optional[bool] = None,
62-
config: Optional[Config] = None,
6362
additional_headers: Optional[Dict[str, str]] = {},
6463
pool_threads: Optional[int] = None,
6564
**kwargs,
6665
):
67-
if config:
68-
if not isinstance(config, Config):
69-
raise TypeError("config must be of type pinecone.config.Config")
70-
else:
71-
self.config = config
72-
else:
73-
self.config = PineconeConfig.build(
74-
api_key=api_key,
75-
host=host,
76-
additional_headers=additional_headers,
77-
proxy_url=proxy_url,
78-
proxy_headers=proxy_headers,
79-
ssl_ca_certs=ssl_ca_certs,
80-
ssl_verify=ssl_verify,
81-
**kwargs,
66+
if kwargs.get("config", None):
67+
raise Exception(
68+
"Passing config is no longer supported. Please pass individual settings such as proxy_url, proxy_headers, ssl_ca_certs, and ssl_verify directly to the Pinecone constructor as keyword arguments. See the README at https://github.com/pinecone-io/pinecone-python-client for examples."
8269
)
83-
8470
if kwargs.get("openapi_config", None):
8571
raise Exception(
86-
"Passing openapi_config is no longer supported. Please pass settings such as proxy_url, proxy_headers, ssl_ca_certs, and ssl_verify directly to the Pinecone constructor as keyword arguments. See the README at https://github.com/pinecone-io/pinecone-python-client for examples."
72+
"Passing openapi_config is no longer supported. Please pass individual settings such as proxy_url, proxy_headers, ssl_ca_certs, and ssl_verify directly to the Pinecone constructor as keyword arguments. See the README at https://github.com/pinecone-io/pinecone-python-client for examples."
8773
)
8874

75+
self.config = PineconeConfig.build(
76+
api_key=api_key,
77+
host=host,
78+
additional_headers=additional_headers,
79+
proxy_url=proxy_url,
80+
proxy_headers=proxy_headers,
81+
ssl_ca_certs=ssl_ca_certs,
82+
ssl_verify=ssl_verify,
83+
**kwargs,
84+
)
85+
8986
self.openapi_config = ConfigBuilder.build_openapi_config(self.config, **kwargs)
9087

9188
if pool_threads is None:
@@ -300,6 +297,8 @@ def Index(self, name: str = "", host: str = "", **kwargs):
300297
openapi_config = self.openapi_config
301298

302299
if host != "":
300+
check_realistic_host(host)
301+
303302
# Use host url if it is provided
304303
index_host = normalize_host(host)
305304
else:
@@ -314,3 +313,19 @@ def Index(self, name: str = "", host: str = "", **kwargs):
314313
source_tag=self.config.source_tag,
315314
**kwargs,
316315
)
316+
317+
318+
def check_realistic_host(host: str) -> None:
319+
"""@private
320+
321+
Checks whether a user-provided host string seems plausible.
322+
Someone could erroneously pass an index name as the host by
323+
mistake, and if they have done that we'd like to give them a
324+
simple error message as feedback rather than attempting to
325+
call the url and getting a more cryptic DNS resolution error.
326+
"""
327+
328+
if "." not in host and "localhost" not in host:
329+
raise ValueError(
330+
f"You passed '{host}' as the host but this does not appear to be valid. Call describe_index() to confirm the host of the index."
331+
)

pinecone/control/pinecone_asyncio.py

+27-12
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from .types import CreateIndexForModelEmbedTypedDict
3434
from .request_factory import PineconeDBControlRequestFactory
3535
from .pinecone_interface_asyncio import PineconeAsyncioDBControlInterface
36+
from .pinecone import check_realistic_host
3637

3738
logger = logging.getLogger(__name__)
3839
""" @private """
@@ -50,21 +51,33 @@ def __init__(
5051
self,
5152
api_key: Optional[str] = None,
5253
host: Optional[str] = None,
53-
proxy_url: Optional[str] = None,
54-
proxy_headers: Optional[Dict[str, str]] = None,
55-
ssl_ca_certs: Optional[str] = None,
56-
ssl_verify: Optional[bool] = None,
57-
additional_headers: Optional[Dict[str, str]] = {},
54+
# proxy_url: Optional[str] = None,
55+
# proxy_headers: Optional[Dict[str, str]] = None,
56+
# ssl_ca_certs: Optional[str] = None,
57+
# ssl_verify: Optional[bool] = None,
58+
# additional_headers: Optional[Dict[str, str]] = {},
5859
**kwargs,
5960
):
61+
for kwarg in {
62+
"additional_headers",
63+
"proxy_url",
64+
"proxy_headers",
65+
"ssl_ca_certs",
66+
"ssl_verify",
67+
}:
68+
if kwarg in kwargs:
69+
raise NotImplementedError(
70+
f"You have passed {kwarg} but this configuration has not been implemented yet for PineconeAsyncio."
71+
)
72+
6073
self.config = PineconeConfig.build(
6174
api_key=api_key,
6275
host=host,
63-
additional_headers=additional_headers,
64-
proxy_url=proxy_url,
65-
proxy_headers=proxy_headers,
66-
ssl_ca_certs=ssl_ca_certs,
67-
ssl_verify=ssl_verify,
76+
additional_headers=None,
77+
proxy_url=None,
78+
proxy_headers=None,
79+
ssl_ca_certs=None,
80+
ssl_verify=None,
6881
**kwargs,
6982
)
7083
self.openapi_config = ConfigBuilder.build_openapi_config(self.config, **kwargs)
@@ -257,9 +270,11 @@ def Index(self, host: str, **kwargs):
257270
api_key = self.config.api_key
258271
openapi_config = self.openapi_config
259272

273+
if host is None or host == "":
274+
raise ValueError("A host must be specified")
275+
276+
check_realistic_host(host)
260277
index_host = normalize_host(host)
261-
if index_host == "":
262-
raise ValueError("host must be specified")
263278

264279
return _AsyncioIndex(
265280
host=index_host,

pinecone/control/pinecone_interface_asyncio.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ async def describe_collection(self, name: str):
529529
pass
530530

531531
@abstractmethod
532-
async def Index(self, host: str, **kwargs):
532+
def Index(self, host: str, **kwargs):
533533
"""
534534
Target an index for data operations.
535535

tests/integration/control/serverless/test_index_instantiation_ux.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
class TestIndexInstantiationUX:
66
def test_index_instantiation_ux(self):
77
with pytest.raises(Exception) as e:
8-
pinecone.Index(name="my-index", host="host")
8+
pinecone.Index(name="my-index", host="test-bt8x3su.svc.apw5-4e34-81fa.pinecone.io")
99

1010
assert (
1111
"You are attempting to access the Index client directly from the pinecone module."

tests/unit/data/test_instantiation.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import pytest
2+
3+
4+
class TestIndexInstantiation:
5+
def test_invalid_host(self):
6+
from pinecone import Pinecone
7+
8+
pc = Pinecone(api_key="key")
9+
10+
with pytest.raises(ValueError) as e:
11+
pc.Index(host="invalid")
12+
assert "You passed 'invalid' as the host but this does not appear to be valid" in str(
13+
e.value
14+
)
15+
16+
with pytest.raises(ValueError) as e:
17+
pc.Index(host="my-index")
18+
assert "You passed 'my-index' as the host but this does not appear to be valid" in str(
19+
e.value
20+
)
21+
22+
# Can instantiate with realistic host
23+
pc.Index(host="test-bt8x3su.svc.apw5-4e34-81fa.pinecone.io")
24+
25+
# Can instantiate with localhost address
26+
pc.Index(host="localhost:8080")

tests/unit/test_config.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,11 @@ def test_errors_when_no_api_key_is_present(self):
100100
PineconeConfig.build()
101101

102102
def test_config_pool_threads(self):
103-
pc = Pinecone(api_key="test-api-key", host="test-controller-host", pool_threads=10)
103+
pc = Pinecone(
104+
api_key="test-api-key", host="test-controller-host.pinecone.io", pool_threads=10
105+
)
104106
assert pc.index_api.api_client.pool_threads == 10
105-
idx = pc.Index(host="my-index-host", name="my-index-name")
107+
idx = pc.Index(host="my-index-host.pinecone.io", name="my-index-name")
106108
assert idx._vector_api.api_client.pool_threads == 10
107109

108110
def test_ssl_config_passed_to_index_client(self):
@@ -112,7 +114,7 @@ def test_ssl_config_passed_to_index_client(self):
112114
assert pc.openapi_config.ssl_ca_cert == "path/to/cert"
113115
assert pc.openapi_config.proxy_headers == proxy_headers
114116

115-
idx = pc.Index(host="host")
117+
idx = pc.Index(host="host.pinecone.io")
116118
assert idx._vector_api.api_client.configuration.ssl_ca_cert == "path/to/cert"
117119
assert idx._vector_api.api_client.configuration.proxy_headers == proxy_headers
118120

@@ -124,10 +126,10 @@ def test_host_config_not_clobbered_by_index(self):
124126
assert pc.openapi_config.proxy_headers == proxy_headers
125127
assert pc.openapi_config.host == "https://api.pinecone.io"
126128

127-
idx = pc.Index(host="host")
129+
idx = pc.Index(host="host.pinecone.io")
128130
assert idx._vector_api.api_client.configuration.ssl_ca_cert == "path/to/cert"
129131
assert idx._vector_api.api_client.configuration.proxy_headers == proxy_headers
130-
assert idx._vector_api.api_client.configuration.host == "https://host"
132+
assert idx._vector_api.api_client.configuration.host == "https://host.pinecone.io"
131133

132134
assert pc.openapi_config.host == "https://api.pinecone.io"
133135

tests/unit/test_config_builder.py

+12-12
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,27 @@
77

88
class TestConfigBuilder:
99
def test_build_simple(self):
10-
config = ConfigBuilder.build(api_key="my-api-key", host="https://my-controller-host")
10+
config = ConfigBuilder.build(api_key="my-api-key", host="https://my-controller-host.com")
1111
assert config.api_key == "my-api-key"
12-
assert config.host == "https://my-controller-host"
12+
assert config.host == "https://my-controller-host.com"
1313
assert config.additional_headers == {}
1414

1515
def test_build_merges_key_and_host_when_openapi_config_provided(self):
1616
config = ConfigBuilder.build(
1717
api_key="my-api-key",
18-
host="https://my-controller-host",
18+
host="https://my-controller-host.com",
1919
openapi_config=OpenApiConfiguration(),
2020
)
2121
assert config.api_key == "my-api-key"
22-
assert config.host == "https://my-controller-host"
22+
assert config.host == "https://my-controller-host.com"
2323
assert config.additional_headers == {}
2424

2525
def test_build_with_source_tag(self):
2626
config = ConfigBuilder.build(
27-
api_key="my-api-key", host="https://my-controller-host", source_tag="my-source-tag"
27+
api_key="my-api-key", host="https://my-controller-host.com", source_tag="my-source-tag"
2828
)
2929
assert config.api_key == "my-api-key"
30-
assert config.host == "https://my-controller-host"
30+
assert config.host == "https://my-controller-host.com"
3131
assert config.additional_headers == {}
3232
assert config.source_tag == "my-source-tag"
3333

@@ -42,35 +42,35 @@ def test_build_errors_when_no_host_is_present(self):
4242
assert str(e.value) == "You haven't specified a host."
4343

4444
def test_build_openapi_config(self):
45-
config = ConfigBuilder.build(api_key="my-api-key", host="https://my-controller-host")
45+
config = ConfigBuilder.build(api_key="my-api-key", host="https://my-controller-host.com")
4646
openapi_config = ConfigBuilder.build_openapi_config(config)
47-
assert openapi_config.host == "https://my-controller-host"
47+
assert openapi_config.host == "https://my-controller-host.com"
4848
assert openapi_config.api_key == {"ApiKeyAuth": "my-api-key"}
4949

5050
def test_build_openapi_config_merges_with_existing_config(self):
51-
config = ConfigBuilder.build(api_key="my-api-key", host="https://my-controller-host")
51+
config = ConfigBuilder.build(api_key="my-api-key", host="https://my-controller-host.com")
5252
openapi_config = OpenApiConfiguration()
5353
openapi_config.ssl_ca_cert = "path/to/bundle"
5454
openapi_config.proxy = "http://my-proxy:8080"
5555

5656
openapi_config = ConfigBuilder.build_openapi_config(config, openapi_config)
5757

5858
assert openapi_config.api_key == {"ApiKeyAuth": "my-api-key"}
59-
assert openapi_config.host == "https://my-controller-host"
59+
assert openapi_config.host == "https://my-controller-host.com"
6060
assert openapi_config.ssl_ca_cert == "path/to/bundle"
6161
assert openapi_config.proxy == "http://my-proxy:8080"
6262

6363
def test_build_openapi_config_does_not_mutate_input(self):
6464
config = ConfigBuilder.build(
65-
api_key="my-api-key", host="foo", ssl_ca_certs="path/to/bundle.foo"
65+
api_key="my-api-key", host="foo.pinecone.io", ssl_ca_certs="path/to/bundle.foo"
6666
)
6767

6868
input_openapi_config = OpenApiConfiguration()
6969
input_openapi_config.host = "bar"
7070
input_openapi_config.ssl_ca_cert = "asdfasdf"
7171

7272
openapi_config = ConfigBuilder.build_openapi_config(config, input_openapi_config)
73-
assert openapi_config.host == "https://foo"
73+
assert openapi_config.host == "https://foo.pinecone.io"
7474
assert openapi_config.ssl_ca_cert == "path/to/bundle.foo"
7575

7676
assert input_openapi_config.host == "bar"

tests/unit/test_control.py

+6-14
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import re
33
from unittest.mock import patch, MagicMock
44
from pinecone import (
5-
ConfigBuilder,
65
Pinecone,
76
PodSpec,
87
ServerlessSpec,
@@ -34,7 +33,7 @@ def description_with_status(status: bool):
3433
status=IndexModelStatus(ready=status, state=state),
3534
dimension=10,
3635
deletion_protection=DeletionProtection(value="enabled"),
37-
host="https://foo",
36+
host="https://foo.pinecone.io",
3837
metric="euclidean",
3938
spec=IndexModelSpec(serverless=ServerlessSpecOpenApi(cloud="aws", region="us-west1")),
4039
)
@@ -48,7 +47,7 @@ def index_list_response():
4847
name="index1",
4948
dimension=10,
5049
metric="euclidean",
51-
host="asdf",
50+
host="asdf.pinecone.io",
5251
status={"ready": True},
5352
spec={},
5453
deletion_protection=DeletionProtection("enabled"),
@@ -58,7 +57,7 @@ def index_list_response():
5857
name="index2",
5958
dimension=10,
6059
metric="euclidean",
61-
host="asdf",
60+
host="asdf.pinecone.io",
6261
status={"ready": True},
6362
spec={},
6463
deletion_protection=DeletionProtection("enabled"),
@@ -68,7 +67,7 @@ def index_list_response():
6867
name="index3",
6968
dimension=10,
7069
metric="euclidean",
71-
host="asdf",
70+
host="asdf.pinecone.io",
7271
status={"ready": True},
7372
spec={},
7473
deletion_protection=DeletionProtection("disabled"),
@@ -89,8 +88,8 @@ def test_default_host(self):
8988
assert p.index_api.api_client.configuration.host == "https://api.pinecone.io"
9089

9190
def test_passing_host(self):
92-
p = Pinecone(api_key="123-456-789", host="my-host")
93-
assert p.index_api.api_client.configuration.host == "https://my-host"
91+
p = Pinecone(api_key="123-456-789", host="my-host.pinecone.io")
92+
assert p.index_api.api_client.configuration.host == "https://my-host.pinecone.io"
9493

9594
def test_passing_additional_headers(self):
9695
extras = {"header1": "my-value", "header2": "my-value2"}
@@ -119,13 +118,6 @@ def test_set_source_tag_in_useragent(self):
119118
re.search(r"source_tag=test_source_tag", p.index_api.api_client.user_agent) is not None
120119
)
121120

122-
def test_set_source_tag_in_useragent_via_config(self):
123-
config = ConfigBuilder.build(
124-
api_key="YOUR_API_KEY", host="https://my-host", source_tag="my_source_tag"
125-
)
126-
p = Pinecone(config=config)
127-
assert re.search(r"source_tag=my_source_tag", p.index_api.api_client.user_agent) is not None
128-
129121
@pytest.mark.parametrize(
130122
"timeout_value, describe_index_responses, expected_describe_index_calls, expected_sleep_calls",
131123
[

0 commit comments

Comments
 (0)