Skip to content
This repository was archived by the owner on Sep 1, 2021. It is now read-only.

Commit 9ef4ce6

Browse files
authored
Merge pull request #18 from swiftype/backfill
Backfilled various endpoint support
2 parents da1fff2 + 118a292 commit 9ef4ce6

File tree

5 files changed

+231
-10
lines changed

5 files changed

+231
-10
lines changed

README.md

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,20 @@ The client can be configured to use a managed deploy by adjusting the `base_endp
9494
[{'id': 'INscMGmhmX4', 'errors': []}, {'id': 'JNDFojsd02', 'errors': []}]
9595
```
9696

97+
### Indexing: Updating documents (Partial Updates)
98+
99+
```python
100+
>>> engine_name = 'favorite-videos'
101+
>>> documents = [
102+
{
103+
'id': 'INscMGmhmX4',
104+
'title': 'Updated title'
105+
}
106+
]
107+
108+
>>> client.update_documents(engine_name, documents)
109+
```
110+
97111
### Get Documents
98112

99113
```python
@@ -102,6 +116,23 @@ The client can be configured to use a managed deploy by adjusting the `base_endp
102116
[{'id': 'INscMGmhmX4','url': 'https://www.youtube.com/watch?v=INscMGmhmX4','title': 'The Original Grumpy Cat','body': 'A wonderful video of a magnificent cat.'}]
103117
```
104118

119+
### List Documents
120+
```python
121+
>>> engine_name = 'favorite-videos'
122+
>>> client.list_documents(engine_name, current=1, size=20)
123+
{
124+
'meta': {
125+
'page': {
126+
'current': 1,
127+
'total_pages': 1,
128+
'total_results': 2,
129+
'size': 20
130+
}
131+
},
132+
'results': [{'id': 'INscMGmhmX4','url': 'https://www.youtube.com/watch?v=INscMGmhmX4','title': 'The Original Grumpy Cat','body': 'A wonderful video of a magnificent cat.'}]
133+
}
134+
```
135+
105136
### Destroy Documents
106137

107138
```python
@@ -137,8 +168,8 @@ The client can be configured to use a managed deploy by adjusting the `base_endp
137168
### Create an Engine
138169

139170
```python
140-
>>> client.create_engine('favorite-videos')
141-
{'name': 'favorite-videos'}
171+
>>> client.create_engine('favorite-videos', 'en')
172+
{'name': 'favorite-videos', 'type': 'default', 'language': 'en'}
142173
```
143174

144175
### Destroy an Engine
@@ -155,6 +186,40 @@ The client can be configured to use a managed deploy by adjusting the `base_endp
155186
{'meta': {'page': {'current': 1, 'total_pages': 1, 'total_results': 2, 'size': 10}, ...}, 'results': [...]}
156187
```
157188

189+
### Multi-Search
190+
191+
```python
192+
>>> client.multi_search('favorite-videos', [{
193+
'query': 'cat',
194+
'options': { 'search_fields': { 'title': {} }}
195+
},{
196+
'query': 'dog',
197+
'options': { 'search_fields': { 'body': {} }}
198+
}])
199+
[{'meta': {...}, 'results': [...]}, {'meta': {...}, 'results': [...]}]
200+
```
201+
202+
### Query Suggestion
203+
204+
```python
205+
>>> client.query_suggestion('favorite-videos', 'cat', {
206+
'size': 10,
207+
'types': {
208+
'documents': {
209+
'fields': ['title']
210+
}
211+
}
212+
})
213+
{'results': {'documents': [{'suggestion': 'cat'}]}, 'meta': {'request_id': '390be384ad5888353e1b32adcfaaf1c9'}}
214+
```
215+
216+
### Clickthrough Tracking
217+
218+
```python
219+
>>> client.click(engine_name, {'query': 'cat', 'document_id': 'INscMGmhmX4'})
220+
```
221+
222+
158223
### Create a Signed Search Key
159224

160225
Creating a search key that will only search over the body field.

swiftype_app_search/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
__title__ = 'swiftype_app_search'
22
__description__ = 'An API client for Swiftype App Search'
33
__url__ = 'https://github.com/swiftype/swiftype-app-search-python'
4-
__version__ = '0.4.0'
4+
__version__ = '0.5.0'
55
__author__ = 'Swiftype'
66
__author_email__ = 'eng@swiftype.com'

swiftype_app_search/client.py

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,17 @@ def get_documents(self, engine_name, document_ids):
3535
data = json.dumps(document_ids)
3636
return self.swiftype_session.request('get', endpoint, data=data)
3737

38+
def list_documents(self, engine_name, current=1, size=20):
39+
"""
40+
Lists all documents in engine.
41+
42+
:param current: Page of documents
43+
:param size: Number of documents to return per page
44+
:return: List of documemts.
45+
"""
46+
data = { 'page': { 'current': current, 'size': size } }
47+
return self.swiftype_session.request('get', "engines/{}/documents/list".format(engine_name), json=data)
48+
3849
def index_document(self, engine_name, document):
3950
"""
4051
Create or update a document for an engine. Raises
@@ -70,6 +81,20 @@ def index_documents(self, engine_name, documents):
7081

7182
return self.swiftype_session.request('post', endpoint, data=data)
7283

84+
def update_documents(self, engine_name, documents):
85+
"""
86+
Update a batch of documents for an engine.
87+
88+
:param engine_name: Name of engine to index documents into.
89+
:param documents: Hashes representing documents.
90+
:return: Array of document status dictionaries. Errors will be present
91+
in a document status with a key of `errors`.
92+
"""
93+
endpoint = "engines/{}/documents".format(engine_name)
94+
data = json.dumps(documents)
95+
96+
return self.swiftype_session.request('patch', endpoint, data=data)
97+
7398
def destroy_documents(self, engine_name, document_ids):
7499
"""
75100
Destroys documents by id for an engine.
@@ -102,13 +127,16 @@ def get_engine(self, engine_name):
102127
"""
103128
return self.swiftype_session.request('get', "engines/{}".format(engine_name))
104129

105-
def create_engine(self, engine_name):
130+
def create_engine(self, engine_name, language=None):
106131
"""
107132
Creates an engine with the specified name.
108133
:param engine_name: Name of the new engine.
109-
:return: A dictionary corresponding to the name of the engine.
134+
:param language: Language of the new engine.
135+
:return: A dictionary corresponding to the new engine.
110136
"""
111137
data = { 'name': engine_name }
138+
if language is not None:
139+
data['language'] = language
112140
return self.swiftype_session.request('post', 'engines', json=data)
113141

114142
def destroy_engine(self, engine_name):
@@ -134,6 +162,57 @@ def search(self, engine_name, query, options=None):
134162
options['query'] = query
135163
return self.swiftype_session.request('get', endpoint, json=options)
136164

165+
def multi_search(self, engine_name, searches=None):
166+
"""
167+
Run multiple searches for documents on a single request.
168+
See https://swiftype.com/documentation/app-search/ for more details
169+
on options and return values.
170+
171+
:param engine_name: Name of engine to search over.
172+
:param options: Array of search options. ex. {query: String, options: Dict}
173+
"""
174+
175+
def build_options_from_search(search):
176+
if 'options' in search:
177+
options = search['options']
178+
else:
179+
options = {}
180+
options['query'] = search['query']
181+
return options
182+
183+
endpoint = "engines/{}/multi_search".format(engine_name)
184+
options = {
185+
'queries': list(map(build_options_from_search, searches))
186+
}
187+
return self.swiftype_session.request('get', endpoint, json=options)
188+
189+
def query_suggestion(self, engine_name, query, options=None):
190+
"""
191+
Request Query Suggestions. See https://swiftype.com/documentation/app-search/ for more details
192+
on options and return values.
193+
194+
:param engine_name: Name of engine to search over.
195+
:param query: Query string to search for.
196+
:param options: Dict of search options.
197+
"""
198+
endpoint = "engines/{}/query_suggestion".format(engine_name)
199+
options = options or {}
200+
options['query'] = query
201+
return self.swiftype_session.request('get', endpoint, json=options)
202+
203+
def click(self, engine_name, options):
204+
"""
205+
Sends a click event to the Swiftype App Search Api, to track a click-through event.
206+
See https://swiftype.com/documentation/app-search/ for more details
207+
on options and return values.
208+
209+
:param engine_name: Name of engine to search over.
210+
:param options: Dict of search options.
211+
"""
212+
endpoint = "engines/{}/click".format(engine_name)
213+
return self.swiftype_session.request_ignore_response('post', endpoint, json=options)
214+
215+
137216
@staticmethod
138217
def create_signed_search_key(api_key, api_key_name, options):
139218
"""

swiftype_app_search/swiftype_request_session.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,11 @@ def raise_if_error(self, response):
3333
response.raise_for_status()
3434

3535
def request(self, http_method, endpoint, base_url=None, **kwargs):
36+
return self.request_ignore_response(http_method, endpoint, base_url, **kwargs).json()
37+
38+
def request_ignore_response(self, http_method, endpoint, base_url=None, **kwargs):
3639
base_url = base_url or self.base_url
3740
url = "{}/{}".format(base_url, endpoint)
3841
response = self.session.request(http_method, url, **kwargs)
3942
self.raise_if_error(response)
40-
return response.json()
43+
return response

tests/test_client.py

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,21 @@ def test_index_documents(self):
7171
response = self.client.index_documents(self.engine_name, [valid_document, other_document])
7272
self.assertEqual(response, expected_return)
7373

74+
def test_update_documents(self):
75+
id = 'INscMGmhmX4'
76+
valid_document = {'id': id}
77+
other_document = { 'body': 'some value' }
78+
79+
expected_return = [
80+
{'id': id, 'errors': []},
81+
{'id': 'some autogenerated id', 'errors': []}
82+
]
83+
84+
with requests_mock.Mocker() as m:
85+
m.register_uri('PATCH', self.document_index_url, json=expected_return, status_code=200)
86+
response = self.client.update_documents(self.engine_name, [valid_document, other_document])
87+
self.assertEqual(response, expected_return)
88+
7489
def test_get_documents(self):
7590
id = 'INscMGmhmX4'
7691
expected_return = [
@@ -87,6 +102,29 @@ def test_get_documents(self):
87102
response = self.client.get_documents(self.engine_name, [id])
88103
self.assertEqual(response, expected_return)
89104

105+
def test_list_documents(self):
106+
expected_return = {
107+
'meta': {
108+
'page': {'current': 1, 'total_results': 1, 'total_pages': 1, 'size': 20},
109+
'results': [
110+
{'body': 'this is a test', 'id': '1'},
111+
{'body': 'this is also a test', 'id': '2'}
112+
]
113+
}
114+
}
115+
116+
with requests_mock.Mocker() as m:
117+
url = "{}/engines/{}/documents/list".format(self.client.swiftype_session.base_url, self.engine_name)
118+
m.register_uri('GET',
119+
url,
120+
additional_matcher=lambda x: x.text == '{"page": {"current": 1, "size": 20}}',
121+
json=expected_return,
122+
status_code=200
123+
)
124+
125+
response = self.client.list_documents(self.engine_name)
126+
self.assertEqual(response, expected_return)
127+
90128
def test_destroy_documents(self):
91129
id = 'INscMGmhmX4'
92130
expected_return = [
@@ -147,12 +185,12 @@ def test_get_engine(self):
147185

148186
def test_create_engine(self):
149187
engine_name = 'myawesomeengine'
150-
expected_return = {'name': engine_name}
188+
expected_return = {'name': engine_name, 'language': 'en'}
151189

152190
with requests_mock.Mocker() as m:
153191
url = "{}/{}".format(self.client.swiftype_session.base_url, 'engines')
154192
m.register_uri('POST', url, json=expected_return, status_code=200)
155-
response = self.client.create_engine(engine_name)
193+
response = self.client.create_engine(engine_name, 'en')
156194
self.assertEqual(response, expected_return)
157195

158196
def test_destroy_engine(self):
@@ -169,11 +207,47 @@ def test_destroy_engine(self):
169207

170208
def test_search(self):
171209
query = 'query'
210+
expected_return = { 'meta': {}, 'results': []}
172211

173212
with requests_mock.Mocker() as m:
174213
url = "{}/{}".format(
175214
self.client.swiftype_session.base_url,
176215
"engines/{}/search".format(self.engine_name)
177216
)
178-
m.register_uri('GET', url, json={}, status_code=200)
179-
self.client.search(self.engine_name, query, {})
217+
m.register_uri('GET', url, json=expected_return, status_code=200)
218+
response = self.client.search(self.engine_name, query, {})
219+
self.assertEqual(response, expected_return)
220+
221+
def test_multi_search(self):
222+
expected_return = [{ 'meta': {}, 'results': []}, { 'meta': {}, 'results': []}]
223+
224+
with requests_mock.Mocker() as m:
225+
url = "{}/{}".format(
226+
self.client.swiftype_session.base_url,
227+
"engines/{}/multi_search".format(self.engine_name)
228+
)
229+
m.register_uri('GET', url, json=expected_return, status_code=200)
230+
response = self.client.multi_search(self.engine_name, {})
231+
self.assertEqual(response, expected_return)
232+
233+
def test_query_suggestion(self):
234+
query = 'query'
235+
expected_return = { 'meta': {}, 'results': {}}
236+
237+
with requests_mock.Mocker() as m:
238+
url = "{}/{}".format(
239+
self.client.swiftype_session.base_url,
240+
"engines/{}/query_suggestion".format(self.engine_name)
241+
)
242+
m.register_uri('GET', url, json=expected_return, status_code=200)
243+
response = self.client.query_suggestion(self.engine_name, query, {})
244+
self.assertEqual(response, expected_return)
245+
246+
def test_click(self):
247+
with requests_mock.Mocker() as m:
248+
url = "{}/{}".format(
249+
self.client.swiftype_session.base_url,
250+
"engines/{}/click".format(self.engine_name)
251+
)
252+
m.register_uri('POST', url, json={}, status_code=200)
253+
self.client.click(self.engine_name, {'query': 'cat', 'document_id': 'INscMGmhmX4'})

0 commit comments

Comments
 (0)