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 search indexes #1165

Merged
merged 2 commits into from
Oct 6, 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
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

This comment was marked as spam.



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

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

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

This comment was marked as spam.

This comment was marked as spam.

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