diff --git a/docs/gcloud-auth.rst b/docs/gcloud-auth.rst index 802c404e2cce..28f724858908 100644 --- a/docs/gcloud-auth.rst +++ b/docs/gcloud-auth.rst @@ -219,10 +219,6 @@ you need a `Google Developers Service Account`_. * Google Cloud Pub/Sub - * **Search** - - * Google Cloud Search API - * **Storage** * Google Cloud Storage diff --git a/docs/index.rst b/docs/index.rst index 42066c74ba21..e66c09e8d1e8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -97,16 +97,6 @@ dns-resource-record-set dns-changes -.. toctree:: - :maxdepth: 0 - :hidden: - :caption: Cloud Search - - search-usage - Client - search-index - search-document - .. toctree:: :maxdepth: 0 :hidden: diff --git a/docs/search-client.rst b/docs/search-client.rst deleted file mode 100644 index 6d83696000d2..000000000000 --- a/docs/search-client.rst +++ /dev/null @@ -1,15 +0,0 @@ -Search Client -============= - -.. automodule:: gcloud.search.client - :members: - :undoc-members: - :show-inheritance: - -Connection -~~~~~~~~~~ - -.. automodule:: gcloud.search.connection - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/search-document.rst b/docs/search-document.rst deleted file mode 100644 index 25b32d0cf2b6..000000000000 --- a/docs/search-document.rst +++ /dev/null @@ -1,7 +0,0 @@ -Documents -~~~~~~~~~ - -.. automodule:: gcloud.search.document - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/search-index.rst b/docs/search-index.rst deleted file mode 100644 index 5cb17dc9f0db..000000000000 --- a/docs/search-index.rst +++ /dev/null @@ -1,7 +0,0 @@ -Indexes -~~~~~~~ - -.. automodule:: gcloud.search.index - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/search-usage.rst b/docs/search-usage.rst deleted file mode 100644 index 31d410ed557c..000000000000 --- a/docs/search-usage.rst +++ /dev/null @@ -1,204 +0,0 @@ -Using the API -============= - -Overview -~~~~~~~~ - -Cloud Search allows an application to quickly perform full-text and -geospatial searches without having to spin up instances -and without the hassle of managing and maintaining a search service. - -Cloud Search provides a model for indexing documents containing structured data, -with documents and indexes saved to a separate persistent store optimized -for search operations. - -The API supports full text matching on string fields and allows indexing -any number of documents in any number of indexes. - -.. note:: - - The Google Cloud Search API is an alpha API. To use it, accounts must - be whitelisted. - -Client ------- - -:class:`Client ` objects provide a means to -configure your Cloud Search applications. Eash instance holds both a -``project`` and an authenticated connection to the Cloud Search service. - -For an overview of authentication in ``gcloud-python``, see :doc:`gcloud-auth`. - -Assuming your environment is set up as described in that document, -create an instance of :class:`Client `. - -.. doctest:: - - >>> from gcloud import search - >>> client = search.Client() - -Indexes -~~~~~~~ - -Indexes are searchable collections of documents. - -List all indexes in the client's project: - -.. doctest:: - - >>> indexes = client.list_indexes() # API call - >>> for index in indexes: - ... print(index.name) - ... field_names = ', '.join([field.name for field in index.fields]) - ... print('- %s' % field_names) - index-name - - field-1, field-2 - another-index-name - - field-3 - -Create a new index: - -.. doctest:: - - >>> new_index = client.index('new-index-name') - -.. note:: - - Indexes cannot be created, updated, or deleted directly on the server: - they are derived from the documents which are created "within" them. - -Documents -~~~~~~~~~ - -Create a document instance, which is not yet added to its index on -the server: - -.. doctest:: - - >>> index = client.index('index-id') - >>> document = index.document('document-1') - >>> document.exists() # API call - False - >>> document.rank - None - -Add one or more fields to the document: - -.. doctest:: - - >>> field = document.Field('fieldname') - >>> field.add_value('string') - -Save the document into the index: - -.. doctest:: - - >>> document.create() # API call - >>> document.exists() # API call - True - >>> document.rank # set by the server - 1443648166 - -List all documents in an index: - -.. doctest:: - - >>> documents = index.list_documents() # API call - >>> [document.id for document in documents] - ['document-1'] - -Delete a document from its index: - -.. doctest:: - - >>> document = index.document('to-be-deleted') - >>> document.exists() # API call - True - >>> document.delete() # API call - >>> document.exists() # API clal - False - -.. note:: - - To update a document in place after manipulating its fields or rank, just - recreate it: E.g.: - - .. doctest:: - - >>> document = index.document('document-id') - >>> document.exists() # API call - True - >>> document.rank = 12345 - >>> field = document.field('field-name') - >>> field.add_value('christina aguilera') - >>> document.create() # API call - -Fields -~~~~~~ - -Fields belong to documents and are the data that actually gets searched. - -Each field can have multiple values, which can be of the following types: - -- String (Python2 :class:`unicode`, Python3 :class:`str`) -- Number (Python :class:`int` or :class:`float`) -- Timestamp (Python :class:`datetime.datetime`) -- Geovalue (Python tuple, (:class:`float`, :class:`float`)) - -String values can be tokenized using one of three different types of -tokenization, which can be passed when the value is added: - -- **Atom** (``atom``) means "don't tokenize this string", treat it as one - thing to compare against. - -- **Text** (``text``) means "treat this string as normal text" and split words - apart to be compared against. - -- **HTML** (``html``) means "treat this string as HTML", understanding the - tags, and treating the rest of the content like Text. - -.. doctest:: - - >>> from gcloud import search - >>> client = search.Client() - >>> index = client.index('index-id') - >>> document = index.document('document-id') - >>> field = document.field('field-name') - >>> field.add_value('britney spears', tokenization='atom') - >>> field.add_value(''

Britney Spears

', tokenization='html') - -Searching -~~~~~~~~~ - -After populating an index with documents, search through them by -issuing a search query: - -.. doctest:: - - >>> from gcloud import search - >>> client = search.Client() - >>> index = client.index('index-id') - >>> query = client.query('britney spears') - >>> matching_documents = index.search(query) # API call - >>> for document in matching_documents: - ... print(document.id) - ['document-id'] - -By default, all queries are sorted by the ``rank`` value set when the -document was created. See: -https://cloud.google.com/search/reference/rest/v1/projects/indexes/documents#resource_representation.google.cloudsearch.v1.Document.rank - -To sort differently, use the ``order_by`` parameter: - -.. doctest:: - - >>> ordered = client.query('britney spears', order_by=['field1', '-field2']) - -Note that the ``-`` character before ``field2`` means that this query will -be sorted ascending by ``field1`` and then descending by ``field2``. - -To limit the fields to be returned in the match, use the ``fields`` paramater: - -.. doctest:: - - >>> projected = client.query('britney spears', fields=['field1', 'field2']) diff --git a/gcloud/search/__init__.py b/gcloud/search/__init__.py deleted file mode 100644 index 97ffb55ad822..000000000000 --- a/gcloud/search/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# 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. - -"""Google Cloud Search API wrapper.""" - -from gcloud.search.client import Client -from gcloud.search.connection import Connection - - -SCOPE = Connection.SCOPE diff --git a/gcloud/search/client.py b/gcloud/search/client.py deleted file mode 100644 index 451c1ee37a7c..000000000000 --- a/gcloud/search/client.py +++ /dev/null @@ -1,105 +0,0 @@ -# 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. - -"""Client for interacting with the Google Cloud search API.""" - - -from gcloud.client import JSONClient -from gcloud.search.connection import Connection -from gcloud.search.index import Index - - -class Client(JSONClient): - """Client to bundle configuration needed for API requests. - - :type project: string - :param project: the project which the client acts on behalf of. Will be - passed when creating a index. If not passed, - falls back to the default inferred from the environment. - - :type credentials: :class:`oauth2client.client.OAuth2Credentials` or - :class:`NoneType` - :param credentials: The OAuth2 Credentials to use for the connection - owned by this client. If not passed (and if no ``http`` - object is passed), falls back to the default inferred - from the environment. - - :type http: :class:`httplib2.Http` or class that defines ``request()``. - :param http: An optional HTTP object to make requests. If not passed, an - ``http`` object is created that is bound to the - ``credentials`` for the current object. - """ - - _connection_class = Connection - - def list_indexes(self, max_results=None, page_token=None, - view=None, prefix=None): - """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 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 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 indexes 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) - 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. - - :type name: string - :param name: Name of the index. - - :rtype: :class:`gcloud.search.index.Index` - :returns: a new ``Index`` instance - """ - return Index(name, client=self) diff --git a/gcloud/search/connection.py b/gcloud/search/connection.py deleted file mode 100644 index f14627c6916d..000000000000 --- a/gcloud/search/connection.py +++ /dev/null @@ -1,33 +0,0 @@ -# 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. - -"""Create / interact with gcloud search connections.""" - -from gcloud import connection as base_connection - - -class Connection(base_connection.JSONConnection): - """A connection to Google Cloud Search via the JSON REST API.""" - - API_BASE_URL = 'https://cloudsearch.googleapis.com' - """The base of the API call URL.""" - - API_VERSION = 'v1' - """The version of the API, used in building the API call's URL.""" - - API_URL_TEMPLATE = '{api_base_url}/{api_version}{path}' - """A template for the URL of a particular API call.""" - - SCOPE = ('https://www.googleapis.com/auth/cloudsearch',) - """The scopes required for authenticating as a Cloud Search consumer.""" diff --git a/gcloud/search/document.py b/gcloud/search/document.py deleted file mode 100644 index 1fa645e953dc..000000000000 --- a/gcloud/search/document.py +++ /dev/null @@ -1,355 +0,0 @@ -# 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 Document.""" - -import datetime - -import six - -from gcloud._helpers import _datetime_to_rfc3339 -from gcloud._helpers import _rfc3339_to_datetime -from gcloud.exceptions import NotFound - - -class StringValue(object): - """StringValues hold individual text values for a given field - - See: - https://cloud.google.com/search/reference/rest/google/cloudsearch/v1/FieldValue - - :type string_value: string - :param string_value: the actual value. - - :type string_format: string - :param string_format: how the value should be indexed: one of - 'ATOM', 'TEXT', 'HTML' (leave as ``None`` to - use the server-supplied default). - - :type language: string - :param language: Human language of the text. Should be an ISO 639-1 - language code. - """ - - value_type = 'string' - - def __init__(self, string_value, string_format=None, language=None): - self.string_value = string_value - self.string_format = string_format - self.language = language - - -class NumberValue(object): - """NumberValues hold individual numeric values for a given field - - See: - https://cloud.google.com/search/reference/rest/google/cloudsearch/v1/FieldValue - - :type number_value: integer, float (long on Python2) - :param number_value: the actual value. - """ - - value_type = 'number' - - def __init__(self, number_value): - self.number_value = number_value - - -class TimestampValue(object): - """TimestampValues hold individual datetime values for a given field - See: - https://cloud.google.com/search/reference/rest/google/cloudsearch/v1/FieldValue - - :type timestamp_value: class:``datetime.datetime`` - :param timestamp_value: the actual value. - """ - - value_type = 'timestamp' - - def __init__(self, timestamp_value): - self.timestamp_value = timestamp_value - - -class GeoValue(object): - """GeoValues hold individual latitude/longitude values for a given field - See: - https://cloud.google.com/search/reference/rest/google/cloudsearch/v1/FieldValue - - :type geo_value: tuple, (float, float) - :param geo_value: latitude, longitude - """ - - value_type = 'geo' - - def __init__(self, geo_value): - self.geo_value = geo_value - - -class Field(object): - """Fields hold values for a given document - - See: - https://cloud.google.com/search/reference/rest/google/cloudsearch/v1/FieldValueList - - :type name: string - :param name: field name - """ - - def __init__(self, name): - self.name = name - self.values = [] - - def add_value(self, value, **kw): - """Add a value to the field. - - Selects type of value instance based on type of ``value``. - - :type value: string, integer, float, datetime, or tuple (float, float) - :param value: the field value to add. - - :param kw: extra keyword arguments to be passed to the value instance - constructor. Currently, only :class:`StringValue` - expects / honors additional parameters. - - :raises: ValueError if unable to match the type of ``value``. - """ - if isinstance(value, six.string_types): - self.values.append(StringValue(value, **kw)) - elif isinstance(value, (six.integer_types, float)): - self.values.append(NumberValue(value, **kw)) - elif isinstance(value, datetime.datetime): - self.values.append(TimestampValue(value, **kw)) - elif isinstance(value, tuple): - self.values.append(GeoValue(value, **kw)) - else: - raise ValueError("Couldn't determine value type: %s" % (value,)) - - -class Document(object): - """Documents hold values for search within indexes. - - See: - https://cloud.google.com/search/reference/rest/v1/projects/indexes/documents - - :type name: string - :param name: the name of the document - - :type index: :class:`gcloud.search.index.Index` - :param index: the index to which the document belongs. - - :type rank: positive integer - :param rank: override the server-generated rank for ordering the document - within in queries. If not passed, the server generates a - timestamp-based value. See the ``rank`` entry on the - page above for details. - """ - def __init__(self, name, index, rank=None): - self.name = name - self.index = index - self.rank = rank - self.fields = {} - - @classmethod - def from_api_repr(cls, resource, index): - """Factory: construct a document given its API representation - - :type resource: dict - :param resource: document resource representation returned from the API - - :type index: :class:`gcloud.search.index.Index` - :param index: Index holding the document. - - :rtype: :class:`gcloud.search.document.Document` - :returns: Document parsed from ``resource``. - """ - name = resource.get('docId') - if name is None: - raise KeyError( - 'Resource lacks required identity information: ["docId"]') - rank = resource.get('rank') - document = cls(name, index, rank) - document._parse_fields_resource(resource) - return document - - @staticmethod - def _parse_value_resource(resource): - """Helper for _parse_fields_resource""" - if 'stringValue' in resource: - string_format = resource.get('stringFormat') - language = resource.get('lang') - value = resource['stringValue'] - return StringValue(value, string_format, language) - if 'numberValue' in resource: - value = resource['numberValue'] - if isinstance(value, six.string_types): - if '.' in value: - value = float(value) - else: - value = int(value) - return NumberValue(value) - if 'timestampValue' in resource: - stamp = resource['timestampValue'] - value = _rfc3339_to_datetime(stamp) - return TimestampValue(value) - if 'geoValue' in resource: - lat_long = resource['geoValue'] - latitude, longitude = [float(coord.strip()) - for coord in lat_long.split(',')] - return GeoValue((latitude, longitude)) - raise ValueError("Unknown value type") - - def _parse_fields_resource(self, resource): - """Helper for from_api_repr, create, reload""" - self.fields.clear() - for field_name, val_obj in resource.get('fields', {}).items(): - field = self.field(field_name) - for value in val_obj['values']: - field.values.append(self._parse_value_resource(value)) - - @property - def path(self): - """URL path for the document's APIs""" - return '%s/documents/%s' % (self.index.path, self.name) - - def field(self, name): - """Construct a Field instance. - - :type name: string - :param name: field's name - """ - field = self.fields[name] = Field(name) - return field - - def _require_client(self, client): - """Check client or verify over-ride. - - :type client: :class:`gcloud.search.client.Client` or ``NoneType`` - :param client: the client to use. If not passed, falls back to the - ``client`` stored on the index of the - current document. - - :rtype: :class:`gcloud.search.client.Client` - :returns: The client passed in or the currently bound client. - """ - if client is None: - client = self.index._client - return client - - @staticmethod - def _build_value_resource(value): - """Helper for _build_fields_resource""" - result = {} - if value.value_type == 'string': - result['stringValue'] = value.string_value - if value.string_format is not None: - result['stringFormat'] = value.string_format - if value.language is not None: - result['lang'] = value.language - elif value.value_type == 'number': - result['numberValue'] = value.number_value - elif value.value_type == 'timestamp': - stamp = _datetime_to_rfc3339(value.timestamp_value) - result['timestampValue'] = stamp - elif value.value_type == 'geo': - result['geoValue'] = '%s, %s' % value.geo_value - else: - raise ValueError('Unknown value_type: %s' % value.value_type) - return result - - def _build_fields_resource(self): - """Helper for create""" - fields = {} - for field_name, field in self.fields.items(): - if field.values: - values = [] - fields[field_name] = {'values': values} - for value in field.values: - values.append(self._build_value_resource(value)) - return fields - - def _set_properties(self, api_response): - """Helper for create, reload""" - self.rank = api_response.get('rank') - self._parse_fields_resource(api_response) - - def create(self, client=None): - """API call: create the document via a PUT request - - See: - https://cloud.google.com/search/reference/rest/v1/projects/indexes/documents/create - - :type client: :class:`gcloud.search.client.Client` or ``NoneType`` - :param client: the client to use. If not passed, falls back to the - ``client`` stored on the current document's index. - """ - data = {'docId': self.name} - - if self.rank is not None: - data['rank'] = self.rank - - fields = self._build_fields_resource() - if fields: - data['fields'] = fields - - client = self._require_client(client) - api_response = client.connection.api_request( - method='PUT', path=self.path, data=data) - - self._set_properties(api_response) - - def exists(self, client=None): - """API call: test existence of the document via a GET request - - See - https://cloud.google.com/search/reference/rest/v1/projects/indexes/documents/get - - :type client: :class:`gcloud.search.client.Client` or ``NoneType`` - :param client: the client to use. If not passed, falls back to the - ``client`` stored on the current document's index. - """ - client = self._require_client(client) - try: - client.connection.api_request(method='GET', path=self.path) - except NotFound: - return False - else: - return True - - def reload(self, client=None): - """API call: sync local document configuration via a GET request - - See - https://cloud.google.com/search/reference/rest/v1/projects/indexes/documents/get - - :type client: :class:`gcloud.search.client.Client` or ``NoneType`` - :param client: the client to use. If not passed, falls back to the - ``client`` stored on the current document's index. - """ - client = self._require_client(client) - api_response = client.connection.api_request( - method='GET', path=self.path) - self._set_properties(api_response) - - def delete(self, client=None): - """API call: delete the document via a DELETE request. - - See: - https://cloud.google.com/search/reference/rest/v1/projects/indexes/documents/delete - - :type client: :class:`gcloud.search.client.Client` or ``NoneType`` - :param client: the client to use. If not passed, falls back to the - ``client`` stored on the current document's index. - """ - client = self._require_client(client) - client.connection.api_request(method='DELETE', path=self.path) diff --git a/gcloud/search/index.py b/gcloud/search/index.py deleted file mode 100644 index c9014b24817e..000000000000 --- a/gcloud/search/index.py +++ /dev/null @@ -1,304 +0,0 @@ -# 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.""" - -from gcloud.search.document import Document - - -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) - - def list_documents(self, max_results=None, page_token=None, - view=None): - """List documents created within this index. - - See: - https://cloud.google.com/search/reference/rest/v1/projects/indexes/documents/list - - :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 view: string - :param view: One of 'ID_ONLY' (return only the document ID; the - default) or 'FULL' (return the full resource - representation for the document, including field - values) - - :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 indexes 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 - - path = '%s/documents' % (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['documents']] - return indexes, resp.get('nextPageToken') - - def document(self, name, rank=None): - """Construct a document bound to this index. - - :type name: string - :param name: Name of the document. - - :type rank: integer - :param rank: Rank of the document (defaults to a server-assigned - value based on timestamp). - - :rtype: :class:`gcloud.search.document.Document` - :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_client.py b/gcloud/search/test_client.py deleted file mode 100644 index 9ba7c65e4d52..000000000000 --- a/gcloud/search/test_client.py +++ /dev/null @@ -1,159 +0,0 @@ -# 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. - -import unittest2 - - -class TestClient(unittest2.TestCase): - PROJECT = 'PROJECT' - - def _getTargetClass(self): - from gcloud.search.client import Client - return Client - - def _makeOne(self, *args, **kw): - return self._getTargetClass()(*args, **kw) - - def test_ctor(self): - from gcloud.search.connection import Connection - creds = _Credentials() - http = object() - 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) - - indexes, token = client.list_indexes() - - self.assertEqual(len(indexes), len(DATA['indexes'])) - for found, expected in zip(indexes, 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) - self.assertEqual(req['query_params'], {}) - - 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) - - indexes, token = client.list_indexes( - max_results=3, page_token=TOKEN, prefix='index', view='FULL') - - self.assertEqual(len(indexes), len(DATA['indexes'])) - for found, expected in zip(indexes, 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): - - _scopes = None - - @staticmethod - def create_scoped_required(): - return True - - 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 diff --git a/gcloud/search/test_connection.py b/gcloud/search/test_connection.py deleted file mode 100644 index 5b41154e7073..000000000000 --- a/gcloud/search/test_connection.py +++ /dev/null @@ -1,46 +0,0 @@ -# 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. - -import unittest2 - - -class TestConnection(unittest2.TestCase): - - def _getTargetClass(self): - from gcloud.search.connection import Connection - return Connection - - def _makeOne(self, *args, **kw): - return self._getTargetClass()(*args, **kw) - - def test_build_api_url_no_extra_query_params(self): - conn = self._makeOne() - URI = '/'.join([ - conn.API_BASE_URL, - conn.API_VERSION, - 'foo', - ]) - self.assertEqual(conn.build_api_url('/foo'), URI) - - def test_build_api_url_w_extra_query_params(self): - from six.moves.urllib.parse import parse_qsl - from six.moves.urllib.parse import urlsplit - conn = self._makeOne() - uri = conn.build_api_url('/foo', {'bar': 'baz'}) - scheme, netloc, path, qs, _ = urlsplit(uri) - self.assertEqual('%s://%s' % (scheme, netloc), conn.API_BASE_URL) - self.assertEqual(path, - '/'.join(['', conn.API_VERSION, 'foo'])) - parms = dict(parse_qsl(qs)) - self.assertEqual(parms['bar'], 'baz') diff --git a/gcloud/search/test_document.py b/gcloud/search/test_document.py deleted file mode 100644 index 688fefd4520f..000000000000 --- a/gcloud/search/test_document.py +++ /dev/null @@ -1,609 +0,0 @@ -# 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. - -import unittest2 - - -class TestStringValue(unittest2.TestCase): - - def _getTargetClass(self): - from gcloud.search.document import StringValue - return StringValue - - def _makeOne(self, *args, **kw): - return self._getTargetClass()(*args, **kw) - - def test_ctor_defaults(self): - sv = self._makeOne('abcde') - self.assertEqual(sv.string_value, 'abcde') - self.assertEqual(sv.string_format, None) - self.assertEqual(sv.language, None) - - def test_ctor_explicit(self): - sv = self._makeOne('abcde', 'text', 'en') - self.assertEqual(sv.string_value, 'abcde') - self.assertEqual(sv.string_format, 'text') - self.assertEqual(sv.language, 'en') - - -class TestNumberValue(unittest2.TestCase): - - def _getTargetClass(self): - from gcloud.search.document import NumberValue - return NumberValue - - def _makeOne(self, *args, **kw): - return self._getTargetClass()(*args, **kw) - - def test_ctor(self): - nv = self._makeOne(42) - self.assertEqual(nv.number_value, 42) - - -class TestTimestampValue(unittest2.TestCase): - - def _getTargetClass(self): - from gcloud.search.document import TimestampValue - return TimestampValue - - def _makeOne(self, *args, **kw): - return self._getTargetClass()(*args, **kw) - - def test_ctor(self): - import datetime - from gcloud._helpers import UTC - NOW = datetime.datetime.utcnow().replace(tzinfo=UTC) - tv = self._makeOne(NOW) - self.assertEqual(tv.timestamp_value, NOW) - - -class TestGeoValue(unittest2.TestCase): - - LATITUDE, LONGITUDE = 38.301931, -77.458722 - - def _getTargetClass(self): - from gcloud.search.document import GeoValue - return GeoValue - - def _makeOne(self, *args, **kw): - return self._getTargetClass()(*args, **kw) - - def test_ctor(self): - gv = self._makeOne((self.LATITUDE, self.LONGITUDE)) - self.assertEqual(gv.geo_value, (self.LATITUDE, self.LONGITUDE)) - - -class TestField(unittest2.TestCase): - - def _getTargetClass(self): - from gcloud.search.document import Field - return Field - - def _makeOne(self, *args, **kw): - return self._getTargetClass()(*args, **kw) - - def test_ctor(self): - field = self._makeOne('field_name') - self.assertEqual(field.name, 'field_name') - self.assertEqual(len(field.values), 0) - - def test_add_value_unknown(self): - field = self._makeOne('field_name') - with self.assertRaises(ValueError): - field.add_value(object()) - - def test_add_value_string_defaults(self): - from gcloud.search.document import StringValue - field = self._makeOne('field_name') - field.add_value('this is a string') - self.assertEqual(len(field.values), 1) - value = field.values[0] - self.assertTrue(isinstance(value, StringValue)) - self.assertEqual(value.string_value, 'this is a string') - self.assertEqual(value.string_format, None) - self.assertEqual(value.language, None) - - def test_add_value_string_explicit(self): - from gcloud.search.document import StringValue - field = self._makeOne('field_name') - field.add_value('this is a string', - string_format='text', language='en') - self.assertEqual(len(field.values), 1) - value = field.values[0] - self.assertTrue(isinstance(value, StringValue)) - self.assertEqual(value.string_value, 'this is a string') - self.assertEqual(value.string_format, 'text') - self.assertEqual(value.language, 'en') - - def test_add_value_integer(self): - from gcloud.search.document import NumberValue - field = self._makeOne('field_name') - field.add_value(42) - self.assertEqual(len(field.values), 1) - value = field.values[0] - self.assertTrue(isinstance(value, NumberValue)) - self.assertEqual(value.number_value, 42) - - def test_add_value_datetime(self): - import datetime - from gcloud._helpers import UTC - from gcloud.search.document import TimestampValue - NOW = datetime.datetime.utcnow().replace(tzinfo=UTC) - field = self._makeOne('field_name') - field.add_value(NOW) - self.assertEqual(len(field.values), 1) - value = field.values[0] - self.assertTrue(isinstance(value, TimestampValue)) - self.assertEqual(value.timestamp_value, NOW) - - def test_add_value_geo(self): - from gcloud.search.document import GeoValue - LATITUDE, LONGITUDE = 38.301931, -77.458722 - field = self._makeOne('field_name') - field.add_value((LATITUDE, LONGITUDE)) - self.assertEqual(len(field.values), 1) - value = field.values[0] - self.assertTrue(isinstance(value, GeoValue)) - self.assertEqual(value.geo_value, (LATITUDE, LONGITUDE)) - - -class TestDocument(unittest2.TestCase): - - PROJECT = 'PROJECT' - DOC_NAME = 'doc_name' - INDEX_NAME = 'index_name' - DOC_PATH = 'projects/%s/indexes/%s/documents/%s' % ( - PROJECT, INDEX_NAME, DOC_NAME) - RANK = 42 - - def _getTargetClass(self): - from gcloud.search.document import Document - return Document - - def _makeOne(self, *args, **kw): - return self._getTargetClass()(*args, **kw) - - def test_ctor_defaults(self): - index = object() - document = self._makeOne(self.DOC_NAME, index) - self.assertEqual(document.name, self.DOC_NAME) - self.assertTrue(document.index is index) - self.assertEqual(document.rank, None) - self.assertEqual(document.fields, {}) - - def test_ctor_explicit(self): - index = object() - document = self._makeOne(self.DOC_NAME, index, self.RANK) - self.assertEqual(document.name, self.DOC_NAME) - self.assertTrue(document.index is index) - self.assertEqual(document.rank, self.RANK) - self.assertEqual(document.fields, {}) - - def test_from_api_repr_invalid(self): - klass = self._getTargetClass() - index = object() - with self.assertRaises(KeyError): - klass.from_api_repr({}, index) - - def test_from_api_repr(self): - import datetime - from gcloud._helpers import UTC, _RFC3339_MICROS - VALUE = 'The quick brown fox' - HTML_VALUE = 'jumped over the lazy fence.' - NOW = datetime.datetime.utcnow().replace(tzinfo=UTC) - NOW_STR = NOW.strftime(_RFC3339_MICROS) - LATITUDE, LONGITUDE = 38.301931, -77.458722 - resource = { - 'docId': self.DOC_NAME, - 'rank': self.RANK, - 'fields': { - 'title': { - 'values': [ - {'stringFormat': 'text', - 'lang': 'en', - 'stringValue': VALUE}, - {'stringFormat': 'html', - 'lang': 'en', - 'stringValue': HTML_VALUE}, - {'numberValue': 42}, - {'numberValue': '42'}, - {'numberValue': '3.1415926'}, - {'timestampValue': NOW_STR}, - {'geoValue': '%s, %s' % (LATITUDE, LONGITUDE)}, - ], - } - } - } - klass = self._getTargetClass() - index = object() - - document = klass.from_api_repr(resource, index) - - self.assertEqual(document.name, self.DOC_NAME) - self.assertTrue(document.index is index) - self.assertEqual(document.rank, self.RANK) - - self.assertEqual(list(document.fields), ['title']) - field = document.fields['title'] - self.assertEqual(field.name, 'title') - self.assertEqual(len(field.values), 7) - - value = field.values[0] - self.assertEqual(value.value_type, 'string') - self.assertEqual(value.language, 'en') - self.assertEqual(value.string_format, 'text') - self.assertEqual(value.string_value, VALUE) - - value = field.values[1] - self.assertEqual(value.value_type, 'string') - self.assertEqual(value.language, 'en') - self.assertEqual(value.string_format, 'html') - self.assertEqual(value.string_value, - 'jumped over the lazy fence.') - - value = field.values[2] - self.assertEqual(value.value_type, 'number') - self.assertEqual(value.number_value, 42) - - value = field.values[3] - self.assertEqual(value.value_type, 'number') - self.assertEqual(value.number_value, 42) - - value = field.values[4] - self.assertEqual(value.value_type, 'number') - self.assertEqual(value.number_value, 3.1415926) - - value = field.values[5] - self.assertEqual(value.value_type, 'timestamp') - self.assertEqual(value.timestamp_value, NOW) - - value = field.values[6] - self.assertEqual(value.value_type, 'geo') - self.assertEqual(value.geo_value, (LATITUDE, LONGITUDE)) - - def test__parse_value_resource_invalid(self): - conn = _Connection() - client = _Client(project=self.PROJECT, connection=conn) - index = _Index(self.INDEX_NAME, client=client) - document = self._makeOne(self.DOC_NAME, index) - with self.assertRaises(ValueError): - document._parse_value_resource({}) - - def test__build_value_resource_invalid(self): - class _UnknownValue(object): - value_type = 'nonesuch' - conn = _Connection() - client = _Client(project=self.PROJECT, connection=conn) - index = _Index(self.INDEX_NAME, client=client) - document = self._makeOne(self.DOC_NAME, index) - with self.assertRaises(ValueError): - document._build_value_resource(_UnknownValue()) - - def test__build_field_resources_field_wo_values(self): - conn = _Connection() - client = _Client(project=self.PROJECT, connection=conn) - index = _Index(self.INDEX_NAME, client=client) - document = self._makeOne(self.DOC_NAME, index) - _ = document.field('testing') # no values - self.assertEqual(document._build_fields_resource(), {}) - - def test_create_wo_fields(self): - import copy - BODY = {'docId': self.DOC_NAME} - RESPONSE = copy.deepcopy(BODY) - RESPONSE['rank'] = self.RANK - conn = _Connection(RESPONSE) - client = _Client(project=self.PROJECT, connection=conn) - index = _Index(self.INDEX_NAME, client=client) - document = self._makeOne(self.DOC_NAME, index) - - document.create() - - self.assertEqual(list(document.fields), []) - - self.assertEqual(len(conn._requested), 1) - req = conn._requested[0] - self.assertEqual(req['method'], 'PUT') - self.assertEqual(req['path'], '/%s' % self.DOC_PATH) - self.assertEqual(req['data'], BODY) - - def test_create_wo_rank_w_bound_client(self): - import copy - VALUE = 'The quick brown fox' - BODY = { - 'docId': self.DOC_NAME, - 'fields': { - 'testing': { - 'values': [ - {'stringValue': VALUE}, - ], - } - } - } - RESPONSE = copy.deepcopy(BODY) - RESPONSE['rank'] = self.RANK - response_value = RESPONSE['fields']['testing']['values'][0] - response_value['stringFormat'] = 'auto' - conn = _Connection(RESPONSE) - client = _Client(project=self.PROJECT, connection=conn) - index = _Index(self.INDEX_NAME, client=client) - document = self._makeOne(self.DOC_NAME, index) - field = document.field('testing') - field.add_value(VALUE) - - document.create() - - self.assertEqual(list(document.fields), ['testing']) - field = document.fields['testing'] - self.assertEqual(len(field.values), 1) - - value = field.values[0] - self.assertEqual(value.value_type, 'string') - self.assertEqual(value.string_format, 'auto') - self.assertEqual(value.string_value, VALUE) - self.assertEqual(value.language, None) - - self.assertEqual(len(conn._requested), 1) - req = conn._requested[0] - self.assertEqual(req['method'], 'PUT') - self.assertEqual(req['path'], '/%s' % self.DOC_PATH) - self.assertEqual(req['data'], BODY) - - def test_create_w_rank_w_alternate_client(self): - import datetime - from gcloud._helpers import UTC, _RFC3339_MICROS - VALUE = 'The quick brown fox' - NOW = datetime.datetime.utcnow().replace(tzinfo=UTC) - NOW_STR = NOW.strftime(_RFC3339_MICROS) - LATITUDE, LONGITUDE = 38.301931, -77.458722 - BODY = { - 'docId': self.DOC_NAME, - 'rank': self.RANK, - 'fields': { - 'title': { - 'values': [ - {'stringValue': VALUE, - 'stringFormat': 'text', - 'lang': 'en'}, - {'numberValue': 17.5}, - {'timestampValue': NOW_STR}, - {'geoValue': '%s, %s' % (LATITUDE, LONGITUDE)}, - ], - } - } - } - RESPONSE = BODY.copy() - RESPONSE['rank'] = self.RANK - conn1 = _Connection() - client1 = _Client(project=self.PROJECT, connection=conn1) - conn2 = _Connection(BODY) - client2 = _Client(project=self.PROJECT, connection=conn2) - index = _Index(self.INDEX_NAME, client=client1) - document = self._makeOne(self.DOC_NAME, index, rank=self.RANK) - field = document.field('title') - field.add_value(VALUE, string_format='text', language='en') - field.add_value(17.5) - field.add_value(NOW) - field.add_value((LATITUDE, LONGITUDE)) - - document.create(client=client2) - - self.assertEqual(list(document.fields), ['title']) - field = document.fields['title'] - self.assertEqual(len(field.values), 4) - - value = field.values[0] - self.assertEqual(value.value_type, 'string') - self.assertEqual(value.string_format, 'text') - self.assertEqual(value.string_value, VALUE) - self.assertEqual(value.language, 'en') - - value = field.values[1] - self.assertEqual(value.value_type, 'number') - self.assertEqual(value.number_value, 17.5) - - value = field.values[2] - self.assertEqual(value.value_type, 'timestamp') - self.assertEqual(value.timestamp_value, NOW) - - value = field.values[3] - self.assertEqual(value.value_type, 'geo') - self.assertEqual(value.geo_value, (LATITUDE, LONGITUDE)) - - self.assertEqual(len(conn1._requested), 0) - self.assertEqual(len(conn2._requested), 1) - - req = conn2._requested[0] - self.assertEqual(req['method'], 'PUT') - self.assertEqual(req['path'], '/%s' % self.DOC_PATH) - self.assertEqual(req['data'], BODY) - - def test_exists_miss_w_bound_client(self): - conn = _Connection() - client = _Client(project=self.PROJECT, connection=conn) - index = _Index(self.INDEX_NAME, client=client) - document = self._makeOne(self.DOC_NAME, index) - - self.assertFalse(document.exists()) - - self.assertEqual(len(conn._requested), 1) - req = conn._requested[0] - self.assertEqual(req['method'], 'GET') - self.assertEqual(req['path'], '/%s' % self.DOC_PATH) - self.assertEqual(req.get('query_params'), None) - - def test_exists_hit_w_alternate_client(self): - BODY = {'docId': self.DOC_NAME, 'rank': self.RANK} - conn1 = _Connection() - client1 = _Client(project=self.PROJECT, connection=conn1) - conn2 = _Connection(BODY) - client2 = _Client(project=self.PROJECT, connection=conn2) - index = _Index(self.INDEX_NAME, client=client1) - document = self._makeOne(self.DOC_NAME, index) - - self.assertTrue(document.exists(client=client2)) - - self.assertEqual(len(conn1._requested), 0) - self.assertEqual(len(conn2._requested), 1) - req = conn2._requested[0] - self.assertEqual(req['method'], 'GET') - self.assertEqual(req['path'], '/%s' % self.DOC_PATH) - self.assertEqual(req.get('query_params'), None) - - def test_reload_w_bound_client(self): - VALUE = 'The quick brown fox' - BODY = { - 'docId': self.DOC_NAME, - 'rank': self.RANK, - 'fields': { - 'title': { - 'values': [ - {'stringFormat': 'text', - 'lang': 'en', - 'stringValue': VALUE}, - ], - } - } - } - conn = _Connection(BODY) - client = _Client(project=self.PROJECT, connection=conn) - index = _Index(self.INDEX_NAME, client=client) - document = self._makeOne(self.DOC_NAME, index) - - document.reload() - - self.assertEqual(document.rank, self.RANK) - - self.assertEqual(list(document.fields), ['title']) - field = document.fields['title'] - self.assertEqual(len(field.values), 1) - self.assertEqual(field.name, 'title') - self.assertEqual(len(field.values), 1) - - value = field.values[0] - self.assertEqual(value.value_type, 'string') - self.assertEqual(value.language, 'en') - self.assertEqual(value.string_format, 'text') - self.assertEqual(value.string_value, VALUE) - - self.assertEqual(len(conn._requested), 1) - req = conn._requested[0] - self.assertEqual(req['method'], 'GET') - self.assertEqual(req['path'], '/%s' % self.DOC_PATH) - - def test_reload_w_alternate_client(self): - VALUE = 'The quick brown fox' - BODY = { - 'docId': self.DOC_NAME, - 'rank': self.RANK, - 'fields': { - 'title': { - 'values': [ - {'stringFormat': 'text', - 'lang': 'en', - 'stringValue': VALUE}, - ], - } - } - } - conn1 = _Connection() - client1 = _Client(project=self.PROJECT, connection=conn1) - conn2 = _Connection(BODY) - client2 = _Client(project=self.PROJECT, connection=conn2) - index = _Index(self.INDEX_NAME, client=client1) - document = self._makeOne(self.DOC_NAME, index) - - document.reload(client=client2) - - self.assertEqual(document.rank, self.RANK) - - self.assertEqual(list(document.fields), ['title']) - field = document.fields['title'] - self.assertEqual(field.name, 'title') - self.assertEqual(len(field.values), 1) - - value = field.values[0] - self.assertEqual(value.value_type, 'string') - self.assertEqual(value.language, 'en') - self.assertEqual(value.string_format, 'text') - self.assertEqual(value.string_value, VALUE) - - self.assertEqual(len(conn1._requested), 0) - self.assertEqual(len(conn2._requested), 1) - req = conn2._requested[0] - self.assertEqual(req['method'], 'GET') - self.assertEqual(req['path'], '/%s' % self.DOC_PATH) - - def test_delete_w_bound_client(self): - conn = _Connection({}) - client = _Client(project=self.PROJECT, connection=conn) - index = _Index(self.INDEX_NAME, client=client) - document = self._makeOne(self.DOC_NAME, index) - - document.delete() - - self.assertEqual(len(conn._requested), 1) - req = conn._requested[0] - self.assertEqual(req['method'], 'DELETE') - self.assertEqual(req['path'], '/%s' % self.DOC_PATH) - - def test_delete_w_alternate_client(self): - conn1 = _Connection({}) - client1 = _Client(project=self.PROJECT, connection=conn1) - conn2 = _Connection({}) - client2 = _Client(project=self.PROJECT, connection=conn2) - index = _Index(self.INDEX_NAME, client=client1) - document = self._makeOne(self.DOC_NAME, index) - - document.delete(client=client2) - - self.assertEqual(len(conn1._requested), 0) - self.assertEqual(len(conn2._requested), 1) - req = conn2._requested[0] - self.assertEqual(req['method'], 'DELETE') - self.assertEqual(req['path'], '/%s' % self.DOC_PATH) - - -class _Connection(object): - - def __init__(self, *responses): - self._responses = responses - self._requested = [] - - def api_request(self, **kw): - from gcloud.exceptions import NotFound - self._requested.append(kw) - - try: - response, self._responses = self._responses[0], self._responses[1:] - except: - raise NotFound('miss') - else: - return response - - -class _Index(object): - - def __init__(self, name, client): - self.name = name - self._client = client - self.project = client.project - self.path = '/projects/%s/indexes/%s' % (client.project, name) - - -class _Client(object): - - def __init__(self, project, connection=None): - self.project = project - self.connection = connection diff --git a/gcloud/search/test_index.py b/gcloud/search/test_index.py deleted file mode 100644 index 898cadb9ab86..000000000000 --- a/gcloud/search/test_index.py +++ /dev/null @@ -1,350 +0,0 @@ -# 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. - -import unittest2 - - -class TestIndex(unittest2.TestCase): - PROJECT = 'project' - INDEX_ID = 'index-id' - - def _getTargetClass(self): - from gcloud.search.index import Index - return Index - - def _makeOne(self, *args, **kw): - return self._getTargetClass()(*args, **kw) - - def _setUpConstants(self): - import datetime - from gcloud._helpers import UTC - - self.WHEN_TS = 1437767599.006 - self.WHEN = datetime.datetime.utcfromtimestamp(self.WHEN_TS).replace( - tzinfo=UTC) - self.ZONE_ID = 12345 - - def _makeResource(self): - self._setUpConstants() - return { - 'projectId': self.PROJECT, - 'indexId': self.INDEX_ID, - 'indexedField': { - 'textFields': ['text-1', 'text-2'], - 'htmlFields': ['html-1', 'html-2'], - 'atomFields': ['atom-1', 'atom-2'], - 'dateFields': ['date-1', 'date-2'], - 'numberFields': ['number-1', 'number-2'], - 'geoFields': ['geo-1', 'geo-2'], - } - } - - def _makeDocumentResource(self, doc_id, rank=None, title=None): - resource = {'docId': doc_id} - if rank is not None: - resource['rank'] = rank - if title is not None: - resource['fields'] = { - 'title': { - 'values': [{ - 'stringValue': title, - 'stringFormat': 'text', - 'lang': 'en'}] - } - } - return resource - - def _verifyResourceProperties(self, index, resource): - - self.assertEqual(index.name, resource.get('indexId')) - field_info = resource.get('indexedField', {}) - self.assertEqual(index.text_fields, field_info.get('textFields')) - self.assertEqual(index.html_fields, field_info.get('htmlFields')) - self.assertEqual(index.atom_fields, field_info.get('atomFields')) - self.assertEqual(index.date_fields, field_info.get('dateFields')) - self.assertEqual(index.number_fields, field_info.get('numberFields')) - self.assertEqual(index.geo_fields, field_info.get('geoFields')) - - def _verifyDocumentResource(self, documents, resource): - from gcloud.search.document import Document - from gcloud.search.document import StringValue - 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')) - e_fields = expected.get('fields', ()) - self.assertEqual(sorted(found.fields), sorted(e_fields)) - for field, f_field in found.fields.items(): - e_field = e_fields[field] - for f_value, e_value in zip(f_field.values, e_field['values']): - self.assertTrue(isinstance(f_value, StringValue)) - self.assertEqual(f_value.string_value, - e_value['stringValue']) - self.assertEqual(f_value.string_format, - e_value['stringFormat']) - self.assertEqual(f_value.language, - e_value['lang']) - - def test_ctor(self): - client = _Client(self.PROJECT) - index = self._makeOne(self.INDEX_ID, client) - self.assertEqual(index.name, self.INDEX_ID) - self.assertTrue(index._client is client) - self.assertEqual(index.project, client.project) - self.assertEqual( - index.path, - '/projects/%s/indexes/%s' % (self.PROJECT, self.INDEX_ID)) - self.assertEqual(index.text_fields, None) - self.assertEqual(index.html_fields, None) - self.assertEqual(index.atom_fields, None) - self.assertEqual(index.date_fields, None) - self.assertEqual(index.number_fields, None) - self.assertEqual(index.geo_fields, None) - - def test_from_api_repr_missing_identity(self): - self._setUpConstants() - client = _Client(self.PROJECT) - RESOURCE = {} - klass = self._getTargetClass() - with self.assertRaises(KeyError): - klass.from_api_repr(RESOURCE, client=client) - - def test_from_api_repr_bare(self): - self._setUpConstants() - client = _Client(self.PROJECT) - RESOURCE = { - 'indexId': self.INDEX_ID, - } - klass = self._getTargetClass() - index = klass.from_api_repr(RESOURCE, client=client) - self.assertTrue(index._client is client) - self._verifyResourceProperties(index, RESOURCE) - - def test_from_api_repr_w_properties(self): - self._setUpConstants() - client = _Client(self.PROJECT) - RESOURCE = self._makeResource() - klass = self._getTargetClass() - index = klass.from_api_repr(RESOURCE, client=client) - self.assertTrue(index._client is client) - self._verifyResourceProperties(index, RESOURCE) - - def test_list_documents_defaults(self): - DOCID_1 = 'docid-one' - DOCID_2 = 'docid-two' - PATH = 'projects/%s/indexes/%s/documents' % ( - self.PROJECT, self.INDEX_ID) - TOKEN = 'TOKEN' - DOC_1 = self._makeDocumentResource(DOCID_1) - DOC_2 = self._makeDocumentResource(DOCID_2) - RESPONSE = { - 'nextPageToken': TOKEN, - 'documents': [DOC_1, DOC_2], - } - client = _Client(self.PROJECT) - conn = client.connection = _Connection(RESPONSE) - index = self._makeOne(self.INDEX_ID, client) - - documents, token = index.list_documents() - - self._verifyDocumentResource(documents, RESPONSE['documents']) - 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) - self.assertEqual(req['query_params'], {}) - - def test_list_documents_explicit(self): - DOCID_1 = 'docid-one' - RANK_1 = 2345 - TITLE_1 = 'Title One' - DOCID_2 = 'docid-two' - RANK_2 = 1234 - TITLE_2 = 'Title Two' - PATH = 'projects/%s/indexes/%s/documents' % ( - self.PROJECT, self.INDEX_ID) - TOKEN = 'TOKEN' - DOC_1 = self._makeDocumentResource(DOCID_1, RANK_1, TITLE_1) - DOC_2 = self._makeDocumentResource(DOCID_2, RANK_2, TITLE_2) - RESPONSE = {'documents': [DOC_1, DOC_2]} - client = _Client(self.PROJECT) - 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, RESPONSE['documents']) - 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'], - {'pageSize': 3, - 'pageToken': TOKEN, - 'view': 'FULL'}) - - def test_document_defaults(self): - from gcloud.search.document import Document - DOCUMENT_ID = 'document-id' - client = _Client(self.PROJECT) - index = self._makeOne(self.INDEX_ID, client) - - document = index.document(DOCUMENT_ID) - - self.assertTrue(isinstance(document, Document)) - self.assertEqual(document.name, DOCUMENT_ID) - self.assertEqual(document.rank, None) - self.assertTrue(document.index is index) - - def test_document_explicit(self): - from gcloud.search.document import Document - DOCUMENT_ID = 'document-id' - RANK = 1234 - client = _Client(self.PROJECT) - index = self._makeOne(self.INDEX_ID, client) - - document = index.document(DOCUMENT_ID, rank=RANK) - - self.assertTrue(isinstance(document, Document)) - self.assertEqual(document.name, DOCUMENT_ID) - 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): - - def __init__(self, project='project', connection=None): - self.project = project - self.connection = connection - - -class _Connection(object): - - def __init__(self, *responses): - self._responses = responses - self._requested = [] - - def api_request(self, **kw): - from gcloud.exceptions import NotFound - self._requested.append(kw) - - response, self._responses = self._responses[0], self._responses[1:] - return response diff --git a/scripts/verify_included_modules.py b/scripts/verify_included_modules.py index 551c81d975e7..57bcfde21855 100644 --- a/scripts/verify_included_modules.py +++ b/scripts/verify_included_modules.py @@ -40,7 +40,6 @@ 'gcloud.monitoring.__init__', 'gcloud.pubsub.__init__', 'gcloud.resource_manager.__init__', - 'gcloud.search.__init__', 'gcloud.storage.__init__', 'gcloud.streaming.__init__', 'gcloud.streaming.buffered_stream',