diff --git a/gcloud/search/client.py b/gcloud/search/client.py index 757533fc1f21..451c1ee37a7c 100644 --- a/gcloud/search/client.py +++ b/gcloud/search/client.py @@ -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 @@ -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) @@ -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 = {} @@ -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. diff --git a/gcloud/search/index.py b/gcloud/search/index.py index 0dc979a740c1..c9014b24817e 100644 --- a/gcloud/search/index.py +++ b/gcloud/search/index.py @@ -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 @@ -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 = {} @@ -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. @@ -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') diff --git a/gcloud/search/test_index.py b/gcloud/search/test_index.py index 4627bc524e11..40225d1a358d 100644 --- a/gcloud/search/test_index.py +++ b/gcloud/search/test_index.py @@ -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')) @@ -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) @@ -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) @@ -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):