Skip to content

Commit

Permalink
Merge pull request #1165 from tseaver/search-index
Browse files Browse the repository at this point in the history
Add search indexes
  • Loading branch information
tseaver committed Oct 6, 2015
2 parents 8d36b33 + 3b8b31c commit eb30c53
Show file tree
Hide file tree
Showing 4 changed files with 437 additions and 2 deletions.
62 changes: 62 additions & 0 deletions gcloud/search/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

from gcloud.client import JSONClient
from gcloud.search.connection import Connection
from gcloud.search.index import Index


class Client(JSONClient):
Expand All @@ -41,3 +42,64 @@ class Client(JSONClient):
"""

_connection_class = Connection

def list_indexes(self, max_results=None, page_token=None,
view=None, prefix=None):
"""List zones 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
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
not passed, the API will return the first page of
zones.
:type view: string
:param view: One of 'ID_ONLY' (return only the index ID; the default)
or 'FULL' (return information on indexed fields).
:type prefix: string
:param prefix: return only indexes whose ID starts with ``prefix``.
: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
call (pass that value as ``page_token``).
"""
params = {}

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

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

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

if prefix is not None:
params['indexNamePrefix'] = prefix

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')

def index(self, name):
"""Construct an index bound to this client.
:type name: string
:param name: Name of the zone.
:rtype: :class:`gcloud.search.index.Index`
:returns: a new ``Index`` instance
"""
return Index(name, client=self)
150 changes: 150 additions & 0 deletions gcloud/search/index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Copyright 2015 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Define API Indexes."""


class Index(object):
"""Indexes are containers for documents.
See:
https://cloud.google.com/search/reference/rest/v1/indexes
:type name: string
:param name: the name of the index
:type client: :class:`gcloud.dns.client.Client`
:param client: A client which holds credentials and project configuration
for the index (which requires a project).
"""

def __init__(self, name, client):
self.name = name
self._client = client
self._properties = {}

@classmethod
def from_api_repr(cls, resource, client):
"""Factory: construct an index given its API representation
:type resource: dict
:param resource: index resource representation returned from the API
:type client: :class:`gcloud.dns.client.Client`
:param client: Client which holds credentials and project
configuration for the index.
:rtype: :class:`gcloud.dns.index.Index`
:returns: Index parsed from ``resource``.
"""
name = resource.get('indexId')
if name is None:
raise KeyError(
'Resource lacks required identity information: ["indexId"]')
index = cls(name, client=client)
index._set_properties(resource)
return index

@property
def project(self):
"""Project bound to the index.
:rtype: string
:returns: the project (derived from the client).
"""
return self._client.project

@property
def path(self):
"""URL path for the index's APIs.
:rtype: string
:returns: the path based on project and dataste name.
"""
return '/projects/%s/indexes/%s' % (self.project, self.name)

def _list_field_names(self, field_type):
"""Helper for 'text_fields', etc.
"""
fields = self._properties.get('indexedField', {})
return fields.get(field_type)

@property
def text_fields(self):
"""Names of text fields in the index.
:rtype: list of string, or None
:returns: names of text fields in the index, or None if no
resource information is available.
"""
return self._list_field_names('textFields')

@property
def atom_fields(self):
"""Names of atom fields in the index.
:rtype: list of string, or None
:returns: names of atom fields in the index, or None if no
resource information is available.
"""
return self._list_field_names('atomFields')

@property
def html_fields(self):
"""Names of html fields in the index.
:rtype: list of string, or None
:returns: names of html fields in the index, or None if no
resource information is available.
"""
return self._list_field_names('htmlFields')

@property
def date_fields(self):
"""Names of date fields in the index.
:rtype: list of string, or None
:returns: names of date fields in the index, or None if no
resource information is available.
"""
return self._list_field_names('dateFields')

@property
def number_fields(self):
"""Names of number fields in the index.
:rtype: list of string, or None
:returns: names of number fields in the index, or None if no
resource information is available.
"""
return self._list_field_names('numberFields')

@property
def geo_fields(self):
"""Names of geo fields in the index.
:rtype: list of string, or None
:returns: names of geo fields in the index, or None if no
resource information is available.
"""
return self._list_field_names('geoFields')

def _set_properties(self, api_response):
"""Update properties from resource in body of ``api_response``
:type api_response: httplib2.Response
:param api_response: response returned from an API call
"""
self._properties.clear()
self._properties.update(api_response)
114 changes: 112 additions & 2 deletions gcloud/search/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@


class TestClient(unittest2.TestCase):
PROJECT = 'PROJECT'

def _getTargetClass(self):
from gcloud.search.client import Client
Expand All @@ -26,14 +27,111 @@ def _makeOne(self, *args, **kw):

def test_ctor(self):
from gcloud.search.connection import Connection
PROJECT = 'PROJECT'
creds = _Credentials()
http = object()
client = self._makeOne(project=PROJECT, credentials=creds, http=http)
client = self._makeOne(
project=self.PROJECT, credentials=creds, http=http)
self.assertTrue(isinstance(client.connection, Connection))
self.assertTrue(client.connection.credentials is creds)
self.assertTrue(client.connection.http is http)

def test_list_indexes_defaults(self):
from gcloud.search.index import Index
INDEX_1 = 'index-one'
INDEX_2 = 'index-two'
PATH = 'projects/%s/indexes' % self.PROJECT
TOKEN = 'TOKEN'
DATA = {
'nextPageToken': TOKEN,
'indexes': [
{'project': self.PROJECT,
'indexId': INDEX_1},
{'project': self.PROJECT,
'indexId': INDEX_2},
]
}
creds = _Credentials()
client = self._makeOne(self.PROJECT, creds)
conn = client.connection = _Connection(DATA)

zones, token = client.list_indexes()

self.assertEqual(len(zones), len(DATA['indexes']))
for found, expected in zip(zones, DATA['indexes']):
self.assertTrue(isinstance(found, Index))
self.assertEqual(found.name, expected['indexId'])
self.assertEqual(found.text_fields, None)
self.assertEqual(found.atom_fields, None)
self.assertEqual(found.html_fields, None)
self.assertEqual(found.date_fields, None)
self.assertEqual(found.number_fields, None)
self.assertEqual(found.geo_fields, None)
self.assertEqual(token, TOKEN)

self.assertEqual(len(conn._requested), 1)
req = conn._requested[0]
self.assertEqual(req['method'], 'GET')
self.assertEqual(req['path'], '/%s' % PATH)

def test_list_indexes_explicit(self):
from gcloud.search.index import Index
INDEX_1 = 'index-one'
INDEX_2 = 'index-two'
PATH = 'projects/%s/indexes' % self.PROJECT
TOKEN = 'TOKEN'
DATA = {
'indexes': [
{'project': self.PROJECT,
'indexId': INDEX_1,
'indexedField': {'textFields': ['text-1']}},
{'project': self.PROJECT,
'indexId': INDEX_2,
'indexedField': {'htmlFields': ['html-1']}},
]
}
creds = _Credentials()
client = self._makeOne(self.PROJECT, creds)
conn = client.connection = _Connection(DATA)

zones, token = client.list_indexes(
max_results=3, page_token=TOKEN, prefix='index', view='FULL')

self.assertEqual(len(zones), len(DATA['indexes']))
for found, expected in zip(zones, DATA['indexes']):
self.assertTrue(isinstance(found, Index))
self.assertEqual(found.name, expected['indexId'])
field_info = expected['indexedField']
self.assertEqual(found.text_fields, field_info.get('textFields'))
self.assertEqual(found.atom_fields, field_info.get('atomFields'))
self.assertEqual(found.html_fields, field_info.get('htmlFields'))
self.assertEqual(found.date_fields, field_info.get('dateFields'))
self.assertEqual(found.number_fields,
field_info.get('numberFields'))
self.assertEqual(found.geo_fields, field_info.get('geoFields'))
self.assertEqual(token, None)

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'],
{'indexNamePrefix': 'index',
'pageSize': 3,
'pageToken': TOKEN,
'view': 'FULL'})

def test_index(self):
from gcloud.search.index import Index
INDEX_ID = 'index-id'
creds = _Credentials()
http = object()
client = self._makeOne(
project=self.PROJECT, credentials=creds, http=http)
index = client.index(INDEX_ID)
self.assertTrue(isinstance(index, Index))
self.assertEqual(index.name, INDEX_ID)
self.assertTrue(index._client is client)


class _Credentials(object):

Expand All @@ -46,3 +144,15 @@ def create_scoped_required():
def create_scoped(self, scope):
self._scopes = scope
return self


class _Connection(object):

def __init__(self, *responses):
self._responses = responses
self._requested = []

def api_request(self, **kw):
self._requested.append(kw)
response, self._responses = self._responses[0], self._responses[1:]
return response
Loading

0 comments on commit eb30c53

Please sign in to comment.