Skip to content

Commit 00b36c8

Browse files
authored
Merge pull request #8 from pinecone-io/experimental/index-vector-transform-remove-type-check
pinecone.Index vector transform remove type check
2 parents 259deff + f817b7f commit 00b36c8

File tree

6 files changed

+126
-24
lines changed

6 files changed

+126
-24
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## Unreleased Changes
4+
5+
### Changed
6+
7+
- Some type validations were moved to the backend for performance reasons. In these cases a 4xx ApiException will be returned instead of an ApiTypeError.
8+
39
## [2.0.2] - 2021-10-21
410

511
### Changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@ For more information, see the docs at https://www.pinecone.io/docs/
55

66
## Installation
77

8+
Install a released version from pip:
9+
```shell
10+
pip install pinecone-client
11+
```
12+
13+
Or the latest development version:
14+
```shell
15+
pip install git+https://[email protected]/pinecone-io/pinecone-python-client.git
16+
```
17+
18+
Or a specific development version:
819
```shell
920
pip install git+https://[email protected]/pinecone-io/pinecone-python-client.git
21+
pip install git+https://[email protected]/pinecone-io/pinecone-python-client.git@example-branch-name
22+
pip install git+https://[email protected]/pinecone-io/pinecone-python-client.git@259deff
1023
```

pinecone/__version__

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.0.2
1+
2.0.2.a

pinecone/index.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,20 @@ def __init__(self, index_name: str, pool_threads=1):
4848
@sentry
4949
@validate_and_convert_errors
5050
def upsert(self, vectors, **kwargs):
51+
_check_type = kwargs.pop('_check_type', False)
52+
5153
def _vector_transform(item):
5254
if isinstance(item, Vector):
5355
return item
5456
if isinstance(item, tuple):
5557
id, values, metadata = fix_tuple_length(item, 3)
56-
return Vector(id=id, values=values, metadata=metadata or {})
58+
return Vector(id=id, values=values, metadata=metadata or {}, _check_type=_check_type)
5759
raise ValueError(f"Invalid vector value passed: cannot interpret type {type(item)}")
5860

5961
return self._vector_api.upsert(
6062
UpsertRequest(
6163
vectors=list(map(_vector_transform, vectors)),
64+
_check_type=_check_type,
6265
**{k: v for k, v in kwargs.items() if k not in _OPENAPI_ENDPOINT_PARAMS}
6366
),
6467
**{k: v for k, v in kwargs.items() if k in _OPENAPI_ENDPOINT_PARAMS}
@@ -77,19 +80,22 @@ def fetch(self, *args, **kwargs):
7780
@sentry
7881
@validate_and_convert_errors
7982
def query(self, queries, **kwargs):
83+
_check_type = kwargs.pop('_check_type', False)
84+
8085
def _query_transform(item):
8186
if isinstance(item, QueryVector):
8287
return item
8388
if isinstance(item, tuple):
8489
values, filter = fix_tuple_length(item, 2)
85-
return QueryVector(values=values, filter=filter)
90+
return QueryVector(values=values, filter=filter, _check_type=_check_type)
8691
if isinstance(item, Iterable):
87-
return QueryVector(values=item)
92+
return QueryVector(values=item, _check_type=_check_type)
8893
raise ValueError(f"Invalid query vector value passed: cannot interpret type {type(item)}")
8994

9095
return self._vector_api.query(
9196
QueryRequest(
9297
queries=list(map(_query_transform, queries)),
98+
_check_type=_check_type,
9399
**{k: v for k, v in kwargs.items() if k not in _OPENAPI_ENDPOINT_PARAMS}
94100
),
95101
**{k: v for k, v in kwargs.items() if k in _OPENAPI_ENDPOINT_PARAMS}

test-requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ pytest-cov==2.10.1
44
pytest-mock==3.6.1
55
tox==3.20.1
66
pytest-timeout==1.4.2
7-
urllib3_mock==0.3.3
7+
urllib3_mock==0.3.3
8+
responses==0.15.0

tests/integ/test_index.py

Lines changed: 95 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,113 @@
1+
import json
2+
13
import pytest
24
from urllib3_mock import Responses
5+
import responses as req_responses
36

47
import pinecone
5-
from pinecone import ApiTypeError
8+
from pinecone import ApiTypeError, ApiException
69

10+
responses_req = Responses()
711
responses = Responses('requests.packages.urllib3')
812

13+
@req_responses.activate
14+
@responses.activate
15+
def test_invalid_upsert_request_vector_value_type():
16+
environment = 'example-environment'
17+
project_name = 'example-project'
18+
req_responses.add(
19+
'GET', f'https://controller.{environment}.pinecone.io/actions/whoami',
20+
status=200, content_type='application/json',
21+
body=json.dumps(dict(project_name=project_name, user_label='example-label', user_name='test'))
22+
)
23+
responses.add(
24+
'POST', '/vectors/upsert',
25+
status=400, content_type='text/plain',
26+
adding_headers={
27+
'content-length': '62',
28+
'date': 'Thu, 28 Oct 2021 09:14:51 GMT',
29+
'server': 'envoy',
30+
'connection': 'close'
31+
},
32+
body='vectors[0].values[1]: invalid value "type" for type TYPE_FLOAT'
33+
)
34+
35+
pinecone.init('example-api-key', environment='example-environment')
36+
with pytest.raises(ApiException) as exc_info:
37+
index = pinecone.Index('example-index')
38+
resp = index.upsert(vectors=[('vec1', [0.1]*8), ('vec2', [0.2]*8)])
39+
40+
assert len(responses.calls) == 1
41+
assert responses.calls[0].request.scheme == 'https'
42+
assert responses.calls[0].request.host == 'example-index-example-project.svc.example-environment.pinecone.io'
43+
assert responses.calls[0].request.url == '/vectors/upsert'
944

45+
46+
@req_responses.activate
1047
@responses.activate
11-
def test_unrecognized_response_field():
12-
# unrecognized response fields are okay, shouldn't raise an exception
48+
def test_multiple_indexes():
49+
environment = 'example-environment'
50+
project_name = 'example-project'
51+
index1_name = 'index-1'
52+
index2_name = 'index-2'
53+
req_responses.add(
54+
'GET', f'https://controller.{environment}.pinecone.io/actions/whoami',
55+
status=200, content_type='application/json',
56+
body=json.dumps(dict(project_name=project_name, user_label='example-label', user_name='test'))
57+
)
58+
responses.add(
59+
'GET', f'/describe_index_stats',
60+
status=200, content_type='application/json',
61+
adding_headers={
62+
'date': 'Thu, 28 Oct 2021 09:14:51 GMT',
63+
'server': 'envoy'
64+
},
65+
body='{"namespaces":{"":{"vectorCount":50000},"example-namespace-2":{"vectorCount":30000}},"dimension":1024}'
66+
)
67+
1368
pinecone.init('example-api-key', environment='example-environment')
1469

15-
# responses.add('GET', '/actions/whoami', # fixme: requests-based, so mock fails?
16-
# body='{"project_name": "example-project", "user_label": "example-label", "user_name": "test"}',
17-
# status=200, content_type='application/json')
18-
responses.add('DELETE', '/vectors/delete',
19-
body='{"deleted_count": 2, "unexpected_key": "xyzzy"}',
20-
status=200, content_type='application/json')
70+
index1 = pinecone.Index(index1_name)
71+
resp1 = index1.describe_index_stats()
72+
assert resp1.dimension == 1024
73+
assert responses.calls[0].request.host == f'{index1_name}-{project_name}.svc.{environment}.pinecone.io'
74+
75+
index2 = pinecone.Index(index2_name)
76+
resp2 = index2.describe_index_stats()
77+
assert resp2.dimension == 1024
78+
assert responses.calls[1].request.host == f'{index2_name}-{project_name}.svc.{environment}.pinecone.io'
2179

80+
81+
@req_responses.activate
82+
@responses.activate
83+
def test_invalid_delete_response_unrecognized_field():
84+
# unrecognized response fields are okay, shouldn't raise an exception
85+
environment = 'example-environment'
86+
project_name = 'example-project'
87+
req_responses.add(
88+
'GET', f'https://controller.{environment}.pinecone.io/actions/whoami',
89+
status=200, content_type='application/json',
90+
body=json.dumps(dict(project_name=project_name, user_label='example-label', user_name='test'))
91+
)
92+
responses.add(
93+
'DELETE', '/vectors/delete',
94+
body='{"unexpected_key": "xyzzy"}',
95+
status=200, content_type='application/json'
96+
)
97+
98+
pinecone.init('example-api-key', environment=environment)
2299
index = pinecone.Index('example-index')
23100
resp = index.delete(ids=['vec1', 'vec2'])
24101

25-
# assert len(responses.calls) == 1
26-
# assert responses.calls[0].request.url == '/vectors/delete?ids=vec1&ids=vec2'
27-
# assert responses.calls[0].request.host == 'example-index-unknown.svc.example-environment.pinecone.io'
28-
# assert responses.calls[0].request.scheme == 'https'
29-
30-
assert resp.deleted_count == 2
102+
assert len(req_responses.calls) == 1
103+
assert responses.calls[0].request.scheme == 'https'
104+
assert responses.calls[0].request.host == f'example-index-{project_name}.svc.{environment}.pinecone.io'
105+
assert responses.calls[0].request.url == '/vectors/delete?ids=vec1&ids=vec2'
31106

32107

33108
@responses.activate
34-
def test_missing_response_field():
35-
# unrecognized response fields are okay, shouldn't raise an exception
109+
def test_delete_response_missing_field():
110+
# missing (optional) response fields are okay, shouldn't raise an exception
36111
pinecone.init('example-api-key', environment='example-environment')
37112
responses.add('DELETE', '/vectors/delete',
38113
body='{}',
@@ -44,8 +119,9 @@ def test_missing_response_field():
44119

45120

46121
@responses.activate
47-
def test_malformed_response_wrong_type():
48-
# unrecognized response fields are okay, shouldn't raise an exception
122+
def _test_invalid_delete_response_wrong_type():
123+
# FIXME: re-enable this test when accepted_count added back to response
124+
# wrong-typed response fields should raise an exception
49125
pinecone.init('example-api-key', environment='example-environment')
50126

51127
responses.add('DELETE', '/vectors/delete',

0 commit comments

Comments
 (0)