Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'Index.search' API wrapper. #1178

Merged
merged 3 commits into from
Oct 14, 2015
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
18 changes: 9 additions & 9 deletions gcloud/search/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class Client(JSONClient):

:type project: string
:param project: the project which the client acts on behalf of. Will be
passed when creating a zone. If not passed,
passed when creating a index. If not passed,
falls back to the default inferred from the environment.

:type credentials: :class:`oauth2client.client.OAuth2Credentials` or
Expand All @@ -45,19 +45,19 @@ class Client(JSONClient):

def list_indexes(self, max_results=None, page_token=None,
view=None, prefix=None):
"""List zones for the project associated with this client.
"""List indexes for the project associated with this client.

See:
https://cloud.google.com/search/reference/rest/v1/indexes/list

:type max_results: int
:param max_results: maximum number of zones to return, If not
:param max_results: maximum number of indexes to return, If not
passed, defaults to a value set by the API.

:type page_token: string
:param page_token: opaque marker for the next "page" of zones. If
:param page_token: opaque marker for the next "page" of indexes. If
not passed, the API will return the first page of
zones.
indexes.

:type view: string
:param view: One of 'ID_ONLY' (return only the index ID; the default)
Expand All @@ -69,7 +69,7 @@ def list_indexes(self, max_results=None, page_token=None,
:rtype: tuple, (list, str)
:returns: list of :class:`gcloud.dns.index.Index`, plus a
"next page token" string: if the token is not None,
indicates that more zones can be retrieved with another
indicates that more indexes can be retrieved with another
call (pass that value as ``page_token``).
"""
params = {}
Expand All @@ -89,9 +89,9 @@ def list_indexes(self, max_results=None, page_token=None,
path = '/projects/%s/indexes' % (self.project,)
resp = self.connection.api_request(method='GET', path=path,
query_params=params)
zones = [Index.from_api_repr(resource, self)
for resource in resp['indexes']]
return zones, resp.get('nextPageToken')
indexes = [Index.from_api_repr(resource, self)
for resource in resp['indexes']]
return indexes, resp.get('nextPageToken')

def index(self, name):
"""Construct an index bound to this client.
Expand Down
104 changes: 97 additions & 7 deletions gcloud/search/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,13 +159,13 @@ def list_documents(self, max_results=None, page_token=None,
https://cloud.google.com/search/reference/rest/v1/projects/indexes/documents/list

:type max_results: int
:param max_results: maximum number of zones to return, If not
:param max_results: maximum number of indexes to return, If not
passed, defaults to a value set by the API.

:type page_token: string
:param page_token: opaque marker for the next "page" of zones. If
:param page_token: opaque marker for the next "page" of indexes. If
not passed, the API will return the first page of
zones.
indexes.

:type view: string
:param view: One of 'ID_ONLY' (return only the document ID; the
Expand All @@ -176,7 +176,7 @@ def list_documents(self, max_results=None, page_token=None,
:rtype: tuple, (list, str)
:returns: list of :class:`gcloud.dns.document.Document`, plus a
"next page token" string: if the token is not None,
indicates that more zones can be retrieved with another
indicates that more indexes can be retrieved with another
call (pass that value as ``page_token``).
"""
params = {}
Expand All @@ -194,9 +194,9 @@ def list_documents(self, max_results=None, page_token=None,
connection = self._client.connection
resp = connection.api_request(method='GET', path=path,
query_params=params)
zones = [Document.from_api_repr(resource, self)
for resource in resp['documents']]
return zones, resp.get('nextPageToken')
indexes = [Document.from_api_repr(resource, self)
for resource in resp['documents']]
return indexes, resp.get('nextPageToken')

def document(self, name, rank=None):
"""Construct a document bound to this index.
Expand All @@ -212,3 +212,93 @@ def document(self, name, rank=None):
:returns: a new ``Document`` instance
"""
return Document(name, index=self, rank=rank)

def search(self,
query,
max_results=None,
page_token=None,
field_expressions=None,
order_by=None,
matched_count_accuracy=None,
scorer=None,
scorer_size=None,
return_fields=None):
"""Search documents created within this index.

See:
https://cloud.google.com/search/reference/rest/v1/projects/indexes/search

:type query: string
:param query: query string (see https://cloud.google.com/search/query).

:type max_results: int
:param max_results: maximum number of indexes to return, If not
passed, defaults to a value set by the API.

:type page_token: string
:param page_token: opaque marker for the next "page" of indexes. If
not passed, the API will return the first page of
indexes.

:type field_expressions: dict, or ``NoneType``
:param field_expressions: mapping of field name -> expression
for use in 'order_by' or 'return_fields'

:type order_by: sequence of string, or ``NoneType``
:param order_by: list of field names (plus optional ' desc' suffix)
specifying ordering of results.

:type matched_count_accuracy: integer or ``NoneType``
:param matched_count_accuracy: minimum accuracy for matched count
returned

:type return_fields: sequence of string, or ``NoneType``
:param return_fields: list of field names to be returned.

:type scorer: string or ``NoneType``
:param scorer: name of scorer function (e.g., "generic").

:type scorer_size: integer or ``NoneType``
:param scorer_size: max number of top results pass to scorer function.

:rtype: tuple, (list, str, int)
:returns: list of :class:`gcloud.dns.document.Document`, plus a
"next page token" string, and a "matched count". If the
token is not None, indicates that more indexes can be
retrieved with another call (pass that value as
``page_token``). The "matched count" indicates the total
number of documents matching the query string.
"""
params = {'query': query}

if max_results is not None:
params['pageSize'] = max_results

if page_token is not None:
params['pageToken'] = page_token

if field_expressions is not None:
params['fieldExpressions'] = field_expressions

if order_by is not None:
params['orderBy'] = order_by

if matched_count_accuracy is not None:
params['matchedCountAccuracy'] = matched_count_accuracy

if scorer is not None:
params['scorer'] = scorer

if scorer_size is not None:
params['scorerSize'] = scorer_size

if return_fields is not None:
params['returnFields'] = return_fields

path = '%s/search' % (self.path,)
connection = self._client.connection
resp = connection.api_request(method='GET', path=path,
query_params=params)
indexes = [Document.from_api_repr(resource, self)
for resource in resp['results']]
return indexes, resp.get('nextPageToken'), resp.get('matchedCount')
117 changes: 109 additions & 8 deletions gcloud/search/test_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ def _verifyResourceProperties(self, index, resource):
def _verifyDocumentResource(self, documents, resource):
from gcloud.search.document import Document
from gcloud.search.document import StringValue
self.assertEqual(len(documents), len(resource['documents']))
for found, expected in zip(documents, resource['documents']):
self.assertEqual(len(documents), len(resource))
for found, expected in zip(documents, resource):
self.assertTrue(isinstance(found, Document))
self.assertEqual(found.name, expected['docId'])
self.assertEqual(found.rank, expected.get('rank'))
Expand Down Expand Up @@ -149,17 +149,17 @@ def test_list_documents_defaults(self):
TOKEN = 'TOKEN'
DOC_1 = self._makeDocumentResource(DOCID_1)
DOC_2 = self._makeDocumentResource(DOCID_2)
DATA = {
RESPONSE = {
'nextPageToken': TOKEN,
'documents': [DOC_1, DOC_2],
}
client = _Client(self.PROJECT)
conn = client.connection = _Connection(DATA)
conn = client.connection = _Connection(RESPONSE)
index = self._makeOne(self.INDEX_ID, client)

documents, token = index.list_documents()

self._verifyDocumentResource(documents, DATA)
self._verifyDocumentResource(documents, RESPONSE['documents'])
self.assertEqual(token, TOKEN)

self.assertEqual(len(conn._requested), 1)
Expand All @@ -180,15 +180,15 @@ def test_list_documents_explicit(self):
TOKEN = 'TOKEN'
DOC_1 = self._makeDocumentResource(DOCID_1, RANK_1, TITLE_1)
DOC_2 = self._makeDocumentResource(DOCID_2, RANK_2, TITLE_2)
DATA = {'documents': [DOC_1, DOC_2]}
RESPONSE = {'documents': [DOC_1, DOC_2]}
client = _Client(self.PROJECT)
conn = client.connection = _Connection(DATA)
conn = client.connection = _Connection(RESPONSE)
index = self._makeOne(self.INDEX_ID, client)

documents, token = index.list_documents(
max_results=3, page_token=TOKEN, view='FULL')

self._verifyDocumentResource(documents, DATA)
self._verifyDocumentResource(documents, RESPONSE['documents'])
self.assertEqual(token, None)

self.assertEqual(len(conn._requested), 1)
Expand Down Expand Up @@ -227,6 +227,107 @@ def test_document_explicit(self):
self.assertEqual(document.rank, RANK)
self.assertTrue(document.index is index)

def test_search_defaults(self):
DOCID_1 = 'docid-one'
TITLE_1 = 'Title One'
DOCID_2 = 'docid-two'
TITLE_2 = 'Title Two'
PATH = 'projects/%s/indexes/%s/search' % (
self.PROJECT, self.INDEX_ID)
TOKEN = 'TOKEN'
DOC_1 = self._makeDocumentResource(DOCID_1, title=TITLE_1)
DOC_2 = self._makeDocumentResource(DOCID_2, title=TITLE_2)
QUERY = 'query string'
RESPONSE = {
'nextPageToken': TOKEN,
'matchedCount': 2,
'results': [DOC_1, DOC_2],
}
client = _Client(self.PROJECT)
conn = client.connection = _Connection(RESPONSE)
index = self._makeOne(self.INDEX_ID, client)

documents, token, matched_count = index.search(QUERY)

self._verifyDocumentResource(documents, RESPONSE['results'])
self.assertEqual(token, TOKEN)
self.assertEqual(matched_count, 2)

self.assertEqual(len(conn._requested), 1)
req = conn._requested[0]
self.assertEqual(req['method'], 'GET')
self.assertEqual(req['path'], '/%s' % PATH)
self.assertEqual(req['query_params'], {'query': QUERY})

def test_search_explicit(self):
DOCID_1 = 'docid-one'
TITLE_1 = 'Title One'
FUNKY_1 = 'this is a funky show'
RANK_1 = 2345
DOCID_2 = 'docid-two'
TITLE_2 = 'Title Two'
FUNKY_2 = 'delighfully funky ambiance'
RANK_2 = 1234
PATH = 'projects/%s/indexes/%s/search' % (
self.PROJECT, self.INDEX_ID)
TOKEN = 'TOKEN'

def _makeFunky(text):
return {
'values': [{
'stringValue': text,
'stringFormat': 'text',
'lang': 'en',
}]
}

DOC_1 = self._makeDocumentResource(DOCID_1, RANK_1, TITLE_1)
DOC_1['fields']['funky'] = _makeFunky(FUNKY_1)
DOC_2 = self._makeDocumentResource(DOCID_2, RANK_2, TITLE_2)
DOC_2['fields']['funky'] = _makeFunky(FUNKY_2)
EXPRESSIONS = {'funky': 'snippet("funky", content)'}
QUERY = 'query string'
RESPONSE = {
'matchedCount': 2,
'results': [DOC_1, DOC_2],
}
client = _Client(self.PROJECT)
conn = client.connection = _Connection(RESPONSE)
index = self._makeOne(self.INDEX_ID, client)

documents, token, matched_count = index.search(
query=QUERY,
max_results=3,
page_token=TOKEN,
field_expressions=EXPRESSIONS,
order_by=['title'],
matched_count_accuracy=100,
scorer='generic',
scorer_size=20,
return_fields=['_rank', 'title', 'funky'],
)

self._verifyDocumentResource(documents, RESPONSE['results'])
self.assertEqual(token, None)
self.assertEqual(matched_count, 2)

self.assertEqual(len(conn._requested), 1)
req = conn._requested[0]
self.assertEqual(req['method'], 'GET')
self.assertEqual(req['path'], '/%s' % PATH)
expected_params = {
'query': QUERY,
'pageSize': 3,
'pageToken': TOKEN,
'fieldExpressions': EXPRESSIONS,
'orderBy': ['title'],
'matchedCountAccuracy': 100,
'scorer': 'generic',
'scorerSize': 20,
'returnFields': ['_rank', 'title', 'funky'],
}
self.assertEqual(req['query_params'], expected_params)


class _Client(object):

Expand Down