diff options
author | Daniel G. Taylor <danielgtaylor@gmail.com> | 2014-04-17 16:35:08 -0700 |
---|---|---|
committer | Daniel G. Taylor <danielgtaylor@gmail.com> | 2014-04-17 16:35:08 -0700 |
commit | 0f93bbad88c0ed66eb945e8bf6b03008ffb2a87b (patch) | |
tree | 68956df2f6c5ffe67199c833d7888e1bb2e097ff | |
parent | 5ec8c7cb1bd778be3dbd01d2f005775ea910217a (diff) | |
download | boto-0f93bbad88c0ed66eb945e8bf6b03008ffb2a87b.tar.gz |
CloudSearch2 updates
Changes the following:
* Generated layer1.py from service model
* Layer1 -> CloudSearchConnection
* Layer1 now uses JSON instead of XML responses
* Updated layer2 objects to use JSON and generated layer1
* Updated doc service to remove attributes which are no longer valid
* Updated search service to remove attributes which are no longer valid and
modify existing attributes which have changed
* Updated unit tests and integration tests for all of the above
-rw-r--r-- | boto/cloudsearch2/__init__.py | 9 | ||||
-rw-r--r-- | boto/cloudsearch2/document.py | 57 | ||||
-rw-r--r-- | boto/cloudsearch2/domain.py | 238 | ||||
-rw-r--r-- | boto/cloudsearch2/exceptions.py | 46 | ||||
-rw-r--r-- | boto/cloudsearch2/layer1.py | 1440 | ||||
-rw-r--r-- | boto/cloudsearch2/layer2.py | 21 | ||||
-rw-r--r-- | boto/cloudsearch2/optionstatus.py | 50 | ||||
-rw-r--r-- | boto/cloudsearch2/search.py | 8 | ||||
-rw-r--r-- | docs/source/ref/cloudsearch.rst | 49 | ||||
-rw-r--r-- | docs/source/ref/cloudsearch2.rst | 61 | ||||
-rw-r--r-- | setup.py | 3 | ||||
-rw-r--r-- | tests/integration/cloudsearch2/test_layers.py | 11 | ||||
-rw-r--r-- | tests/unit/cloudsearch2/test_connection.py | 240 | ||||
-rw-r--r-- | tests/unit/cloudsearch2/test_document.py | 71 | ||||
-rw-r--r-- | tests/unit/cloudsearch2/test_search.py | 161 |
15 files changed, 1261 insertions, 1204 deletions
diff --git a/boto/cloudsearch2/__init__.py b/boto/cloudsearch2/__init__.py index bffa50d9..d14c9179 100644 --- a/boto/cloudsearch2/__init__.py +++ b/boto/cloudsearch2/__init__.py @@ -1,6 +1,4 @@ -# Copyright (c) 2012 Mitch Garnaat http://garnaat.org/ -# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. -# All Rights Reserved +# Copyright (c) 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the @@ -20,8 +18,7 @@ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. -# -from boto.regioninfo import RegionInfo, get_regions +from boto.regioninfo import get_regions def regions(): @@ -34,7 +31,7 @@ def regions(): import boto.cloudsearch2.layer1 return get_regions( 'cloudsearch', - connection_cls=boto.cloudsearch2.layer1.Layer1 + connection_cls=boto.cloudsearch2.layer1.CloudSearchConnection ) diff --git a/boto/cloudsearch2/document.py b/boto/cloudsearch2/document.py index 7ce4b3fb..ed0f6c3f 100644 --- a/boto/cloudsearch2/document.py +++ b/boto/cloudsearch2/document.py @@ -1,6 +1,5 @@ # Copyright (c) 2012 Mitch Garnaat http://garnaat.org/ -# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. -# All Rights Reserved +# Copyright (c) 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the @@ -62,13 +61,14 @@ class DocumentServiceConnection(object): A CloudSearch document service. The DocumentServiceConection is used to add, remove and update documents in - CloudSearch. Commands are uploaded to CloudSearch in SDF (Search Document Format). + CloudSearch. Commands are uploaded to CloudSearch in SDF (Search Document + Format). To generate an appropriate SDF, use :func:`add` to add or update documents, as well as :func:`delete` to remove documents. - Once the set of documents is ready to be index, use :func:`commit` to send the - commands to CloudSearch. + Once the set of documents is ready to be index, use :func:`commit` to send + the commands to CloudSearch. If there are a lot of documents to index, it may be preferable to split the generation of SDF data and the actual uploading into CloudSearch. Retrieve @@ -91,7 +91,7 @@ class DocumentServiceConnection(object): self.documents_batch = [] self._sdf = None - def add(self, _id, version, fields, lang='en'): + def add(self, _id, fields): """ Add a document to be processed by the DocumentService @@ -100,39 +100,25 @@ class DocumentServiceConnection(object): :type _id: string :param _id: A unique ID used to refer to this document. - :type version: int - :param version: Version of the document being indexed. If a file is - being reindexed, the version should be higher than the existing one - in CloudSearch. - :type fields: dict :param fields: A dictionary of key-value pairs to be uploaded . - - :type lang: string - :param lang: The language code the data is in. Only 'en' is currently - supported """ - d = {'type': 'add', 'id': _id, 'version': version, 'lang': lang, - 'fields': fields} + d = {'type': 'add', 'id': _id, 'fields': fields} self.documents_batch.append(d) - def delete(self, _id, version): + def delete(self, _id): """ Schedule a document to be removed from the CloudSearch service - The document will not actually be scheduled for removal until :func:`commit` is called + The document will not actually be scheduled for removal until + :func:`commit` is called :type _id: string :param _id: The unique ID of this document. - - :type version: int - :param version: Version of the document to remove. The delete will only - occur if this version number is higher than the version currently - in the index. """ - d = {'type': 'delete', 'id': _id, 'version': version} + d = {'type': 'delete', 'id': _id} self.documents_batch.append(d) def get_sdf(self): @@ -149,8 +135,8 @@ class DocumentServiceConnection(object): """ Clear the working documents from this DocumentServiceConnection - This should be used after :func:`commit` if the connection will be reused - for another set of documents. + This should be used after :func:`commit` if the connection will be + reused for another set of documents. """ self._sdf = None @@ -184,8 +170,8 @@ class DocumentServiceConnection(object): sdf = self.get_sdf() if ': null' in sdf: - boto.log.error('null value in sdf detected. This will probably raise ' - '500 error.') + boto.log.error('null value in sdf detected. This will probably ' + 'raise 500 error.') index = sdf.index(': null') boto.log.error(sdf[index - 100:index + 100]) @@ -203,7 +189,8 @@ class DocumentServiceConnection(object): ) session.mount('http://', adapter) session.mount('https://', adapter) - r = session.post(url, data=sdf, headers={'Content-Type': 'application/json'}) + r = session.post(url, data=sdf, + headers={'Content-Type': 'application/json'}) return CommitResponse(r, self, sdf) @@ -231,14 +218,15 @@ class CommitResponse(object): try: self.content = json.loads(response.content) except: - boto.log.error('Error indexing documents.\nResponse Content:\n{0}\n\n' - 'SDF:\n{1}'.format(response.content, self.sdf)) + boto.log.error('Error indexing documents.\nResponse Content:\n{0}' + '\n\nSDF:\n{1}'.format(response.content, self.sdf)) raise boto.exception.BotoServerError(self.response.status_code, '', body=response.content) self.status = self.content['status'] if self.status == 'error': - self.errors = [e.get('message') for e in self.content.get('errors', [])] + self.errors = [e.get('message') for e in self.content.get('errors', + [])] for e in self.errors: if "Illegal Unicode character" in e: raise EncodingError("Illegal Unicode character in document") @@ -267,6 +255,7 @@ class CommitResponse(object): if d['type'] == type_]) if response_num != commit_num: + boto.log.debug(self.response.content) raise CommitMismatchError( - 'Incorrect number of {0}s returned. Commit: {1} Response: {2}'\ + 'Incorrect number of {0}s returned. Commit: {1} Response: {2}' .format(type_, commit_num, response_num)) diff --git a/boto/cloudsearch2/domain.py b/boto/cloudsearch2/domain.py index cca13c19..0643eaf8 100644 --- a/boto/cloudsearch2/domain.py +++ b/boto/cloudsearch2/domain.py @@ -1,6 +1,4 @@ -# Copyright (c) 2012 Mitch Garnaat http://garnaat.org/ -# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. -# All Rights Reserved +# Copyright (c) 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the @@ -22,7 +20,6 @@ # IN THE SOFTWARE. # -import boto from .optionstatus import IndexFieldStatus from .optionstatus import ServicePoliciesStatus from .optionstatus import ExpressionStatus @@ -90,18 +87,18 @@ class Domain(object): self.update_from_data(data) def update_from_data(self, data): - self.created = data['created'] - self.deleted = data['deleted'] - self.processing = data['processing'] - self.requires_index_documents = data['requires_index_documents'] - self.domain_id = data['domain_id'] - self.domain_name = data['domain_name'] - self.search_instance_count = data['search_instance_count'] - self.search_instance_type = data.get('search_instance_type', None) - self.search_partition_count = data['search_partition_count'] - self._doc_service = data['doc_service'] - self._service_arn = data['arn'] - self._search_service = data['search_service'] + self.created = data['Created'] + self.deleted = data['Deleted'] + self.processing = data['Processing'] + self.requires_index_documents = data['RequiresIndexDocuments'] + self.domain_id = data['DomainId'] + self.domain_name = data['DomainName'] + self.search_instance_count = data['SearchInstanceCount'] + self.search_instance_type = data.get('SearchInstanceType', None) + self.search_partition_count = data['SearchPartitionCount'] + self._doc_service = data['DocService'] + self._service_arn = data['ARN'] + self._search_service = data['SearchService'] @property def service_arn(self): @@ -109,11 +106,11 @@ class Domain(object): @property def doc_service_endpoint(self): - return self._doc_service['endpoint'] + return self._doc_service['Endpoint'] @property def search_service_endpoint(self): - return self._search_service['endpoint'] + return self._search_service['Endpoint'] @property def created(self): @@ -189,11 +186,15 @@ class Domain(object): object representing the currently defined availability options for the domain. :return: OptionsStatus object - :rtype: :class:`boto.cloudsearch2.option.AvailabilityOptionsStatus` object + :rtype: :class:`boto.cloudsearch2.option.AvailabilityOptionsStatus` + object """ return AvailabilityOptionsStatus( - self, None, self.layer1.describe_availability_options, - self.layer1.update_availability_options) + self, refresh_fn=self.layer1.describe_availability_options, + refresh_key=['DescribeAvailabilityOptionsResponse', + 'DescribeAvailabilityOptionsResult', + 'AvailabilityOptions'], + save_fn=self.layer1.update_availability_options) def get_scaling_options(self): """ @@ -201,12 +202,15 @@ class Domain(object): object representing the currently defined scaling options for the domain. :return: ScalingParametersStatus object - :rtype: :class:`boto.cloudsearch2.option.ScalingParametersStatus` object + :rtype: :class:`boto.cloudsearch2.option.ScalingParametersStatus` + object """ return ScalingParametersStatus( - self, None, - self.layer1.describe_scaling_parameters, - self.layer1.update_scaling_parameters) + self, refresh_fn=self.layer1.describe_scaling_parameters, + refresh_key=['DescribeScalingParametersResponse', + 'DescribeScalingParametersResult', + 'ScalingParameters'], + save_fn=self.layer1.update_scaling_parameters) def get_access_policies(self): """ @@ -216,9 +220,12 @@ class Domain(object): :return: ServicePoliciesStatus object :rtype: :class:`boto.cloudsearch2.option.ServicePoliciesStatus` object """ - return ServicePoliciesStatus(self, None, - self.layer1.describe_service_access_policies, - self.layer1.update_service_access_policies) + return ServicePoliciesStatus( + self, refresh_fn=self.layer1.describe_service_access_policies, + refresh_key=['DescribeServiceAccessPoliciesResponse', + 'DescribeServiceAccessPoliciesResult', + 'AccessPolicies'], + save_fn=self.layer1.update_service_access_policies) def index_documents(self): """ @@ -234,9 +241,15 @@ class Domain(object): """ Return a list of index fields defined for this domain. :return: list of IndexFieldStatus objects - :rtype: list of :class:`boto.cloudsearch2.option.IndexFieldStatus` object + :rtype: list of :class:`boto.cloudsearch2.option.IndexFieldStatus` + object """ data = self.layer1.describe_index_fields(self.name, field_names) + + data = (data['DescribeIndexFieldsResponse'] + ['DescribeIndexFieldsResult'] + ['IndexFields']) + return [IndexFieldStatus(self, d) for d in data] def create_index_field(self, field_name, field_type, @@ -308,14 +321,141 @@ class Domain(object): :raises: BaseException, InternalException, LimitExceededException, InvalidTypeException, ResourceNotFoundException """ - data = self.layer1.define_index_field(self.name, field_name, field_type, - default=default, facet=facet, - returnable=returnable, - searchable=searchable, - sortable=sortable, - highlight=highlight, - source_field=source_field, - analysis_scheme=analysis_scheme) + index = { + 'IndexFieldName': field_name, + 'IndexFieldType': field_type + } + if field_type == 'literal': + index['LiteralOptions'] = { + 'FacetEnabled': facet, + 'ReturnEnabled': returnable, + 'SearchEnabled': searchable, + 'SortEnabled': sortable + } + if default: + index['LiteralOptions']['DefaultValue'] = default + if source_field: + index['LiteralOptions']['SourceField'] = source_field + elif field_type == 'literal-array': + index['LiteralArrayOptions'] = { + 'FacetEnabled': facet, + 'ReturnEnabled': returnable, + 'SearchEnabled': searchable + } + if default: + index['LiteralArrayOptions']['DefaultValue'] = default + if source_field: + index['LiteralArrayOptions']['SourceFields'] = \ + ','.join(source_field) + elif field_type == 'int': + index['IntOptions'] = { + 'DefaultValue': default, + 'FacetEnabled': facet, + 'ReturnEnabled': returnable, + 'SearchEnabled': searchable, + 'SortEnabled': sortable + } + if default: + index['IntOptions']['DefaultValue'] = default + if source_field: + index['IntOptions']['SourceField'] = source_field + elif field_type == 'int-array': + index['IntArrayOptions'] = { + 'FacetEnabled': facet, + 'ReturnEnabled': returnable, + 'SearchEnabled': searchable + } + if default: + index['IntArrayOptions']['DefaultValue'] = default + if source_field: + index['IntArrayOptions']['SourceFields'] = \ + ','.join(source_field) + elif field_type == 'date': + index['DateOptions'] = { + 'FacetEnabled': facet, + 'ReturnEnabled': returnable, + 'SearchEnabled': searchable, + 'SortEnabled': sortable + } + if default: + index['DateOptions']['DefaultValue'] = default + if source_field: + index['DateOptions']['SourceField'] = source_field + elif field_type == 'date-array': + index['DateArrayOptions'] = { + 'FacetEnabled': facet, + 'ReturnEnabled': returnable, + 'SearchEnabled': searchable + } + if default: + index['DateArrayOptions']['DefaultValue'] = default + if source_field: + index['DateArrayOptions']['SourceFields'] = \ + ','.join(source_field) + elif field_type == 'double': + index['DoubleOptions'] = { + 'FacetEnabled': facet, + 'ReturnEnabled': returnable, + 'SearchEnabled': searchable, + 'SortEnabled': sortable + } + if default: + index['DoubleOptions']['DefaultValue'] = default + if source_field: + index['DoubleOptions']['SourceField'] = source_field + elif field_type == 'double-array': + index['DoubleArrayOptions'] = { + 'FacetEnabled': facet, + 'ReturnEnabled': returnable, + 'SearchEnabled': searchable + } + if default: + index['DoubleArrayOptions']['DefaultValue'] = default + if source_field: + index['DoubleArrayOptions']['SourceFields'] = \ + ','.join(source_field) + elif field_type == 'text': + index['TextOptions'] = { + 'ReturnEnabled': returnable, + 'HighlightEnabled': highlight, + 'SortEnabled': sortable + } + if default: + index['TextOptions']['DefaultValue'] = default + if source_field: + index['TextOptions']['SourceField'] = source_field + if analysis_scheme: + index['TextOptions']['AnalysisScheme'] = analysis_scheme + elif field_type == 'text-array': + index['TextArrayOptions'] = { + 'ReturnEnabled': returnable, + 'HighlightEnabled': highlight + } + if default: + index['TextArrayOptions']['DefaultValue'] = default + if source_field: + index['TextArrayOptions']['SourceFields'] = \ + ','.join(source_field) + if analysis_scheme: + index['TextArrayOptions']['AnalysisScheme'] = analysis_scheme + elif field_type == 'latlon': + index['LatLonOptions'] = { + 'FacetEnabled': facet, + 'ReturnEnabled': returnable, + 'SearchEnabled': searchable, + 'SortEnabled': sortable + } + if default: + index['LatLonOptions']['DefaultValue'] = default + if source_field: + index['LatLonOptions']['SourceField'] = source_field + + data = self.layer1.define_index_field(self.name, index) + + data = (data['DefineIndexFieldResponse'] + ['DefineIndexFieldResult'] + ['IndexField']) + return IndexFieldStatus(self, data, self.layer1.describe_index_fields) @@ -323,10 +463,16 @@ class Domain(object): """ Return a list of rank expressions defined for this domain. :return: list of ExpressionStatus objects - :rtype: list of :class:`boto.cloudsearch2.option.ExpressionStatus` object + :rtype: list of :class:`boto.cloudsearch2.option.ExpressionStatus` + object """ fn = self.layer1.describe_expressions data = fn(self.name, names) + + data = (data['DescribeExpressionsResponse'] + ['DescribeExpressionsResult'] + ['Expressions']) + return [ExpressionStatus(self, d, fn) for d in data] def create_expression(self, name, value): @@ -345,7 +491,8 @@ class Domain(object): * Single value, sort enabled numeric fields (int, double, date) * Other expressions - * The _score variable, which references a document's relevance score + * The _score variable, which references a document's relevance + score * The _time variable, which references the current epoch time * Integer, floating point, hex, and octal literals * Arithmetic operators: + - * / % @@ -361,10 +508,10 @@ class Domain(object): Expressions always return an integer value from 0 to the maximum 64-bit signed integer value (2^63 - 1). Intermediate results are calculated as double-precision floating point values and the return - value is rounded to the nearest integer. If the expression is invalid - or evaluates to a negative value, it returns 0. If the expression - evaluates to a value greater than the maximum, it returns the maximum - value. + value is rounded to the nearest integer. If the expression is + invalid or evaluates to a negative value, it returns 0. If the + expression evaluates to a value greater than the maximum, it + returns the maximum value. The source data for an Expression can be the name of an IndexField of type int or double, another Expression or the @@ -386,6 +533,11 @@ class Domain(object): InvalidTypeException, ResourceNotFoundException """ data = self.layer1.define_expression(self.name, name, value) + + data = (data['DefineExpressionResponse'] + ['DefineExpressionResult'] + ['Expression']) + return ExpressionStatus(self, data, self.layer1.describe_expressions) diff --git a/boto/cloudsearch2/exceptions.py b/boto/cloudsearch2/exceptions.py new file mode 100644 index 00000000..c1141139 --- /dev/null +++ b/boto/cloudsearch2/exceptions.py @@ -0,0 +1,46 @@ +""" +Exceptions that are specific to the cloudsearch2 module. +""" +from boto.exception import BotoServerError + + +class InvalidTypeException(BotoServerError): + """ + Raised when an invalid record type is passed to CloudSearch. + """ + pass + + +class LimitExceededException(BotoServerError): + """ + Raised when a limit has been exceeded. + """ + pass + + +class InternalException(BotoServerError): + """ + A generic server-side error. + """ + pass + + +class DisabledOperationException(BotoServerError): + """ + Raised when an operation has been disabled. + """ + pass + + +class ResourceNotFoundException(BotoServerError): + """ + Raised when a requested resource does not exist. + """ + pass + + +class BaseException(BotoServerError): + """ + A generic server-side error. + """ + pass diff --git a/boto/cloudsearch2/layer1.py b/boto/cloudsearch2/layer1.py index 08c52e85..2604ee97 100644 --- a/boto/cloudsearch2/layer1.py +++ b/boto/cloudsearch2/layer1.py @@ -1,6 +1,4 @@ -# Copyright (c) 2012 Mitch Garnaat http://garnaat.org/ -# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. -# All Rights Reserved +# Copyright (c) 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the @@ -20,897 +18,767 @@ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. +# + +try: + import json +except ImportError: + import simplejson as json import boto -import boto.jsonresponse -from boto.compat import json from boto.connection import AWSQueryConnection from boto.regioninfo import RegionInfo - -#boto.set_stream_logger('cloudsearch') - - -def do_bool(val): - return 'true' if val in [True, 1, '1', 'true'] else 'false' - - -class Layer1(AWSQueryConnection): - - APIVersion = '2013-01-01' - #AuthServiceName = 'sqs' - DefaultRegionName = boto.config.get('Boto', 'cs_region_name', 'us-east-1') - DefaultRegionEndpoint = boto.config.get('Boto', 'cs_region_endpoint', - 'cloudsearch.us-east-1.amazonaws.com') - - def __init__(self, aws_access_key_id=None, aws_secret_access_key=None, - is_secure=True, host=None, port=None, - proxy=None, proxy_port=None, - proxy_user=None, proxy_pass=None, debug=0, - https_connection_factory=None, region=None, path='/', - api_version=None, security_token=None, - validate_certs=True, profile_name=None): +from boto.exception import JSONResponseError +from boto.cloudsearch2 import exceptions + + +class CloudSearchConnection(AWSQueryConnection): + """ + Amazon CloudSearch Configuration Service + You use the Amazon CloudSearch configuration service to create, + configure, and manage search domains. Configuration service + requests are submitted using the AWS Query protocol. AWS Query + requests are HTTP or HTTPS requests submitted via HTTP GET or POST + with a query parameter named Action. + + The endpoint for configuration service requests is region- + specific: cloudsearch. region .amazonaws.com. For example, + cloudsearch.us-east-1.amazonaws.com. For a current list of + supported regions and endpoints, see `Regions and Endpoints`_. + """ + APIVersion = "2013-01-01" + DefaultRegionName = "us-east-1" + DefaultRegionEndpoint = "cloudsearch.us-east-1.amazonaws.com" + ResponseError = JSONResponseError + + _faults = { + "InvalidTypeException": exceptions.InvalidTypeException, + "LimitExceededException": exceptions.LimitExceededException, + "InternalException": exceptions.InternalException, + "DisabledOperationException": exceptions.DisabledOperationException, + "ResourceNotFoundException": exceptions.ResourceNotFoundException, + "BaseException": exceptions.BaseException, + } + + + def __init__(self, **kwargs): + region = kwargs.pop('region', None) if not region: region = RegionInfo(self, self.DefaultRegionName, self.DefaultRegionEndpoint) + + if 'host' not in kwargs or kwargs['host'] is None: + kwargs['host'] = region.endpoint + + super(CloudSearchConnection, self).__init__(**kwargs) self.region = region - AWSQueryConnection.__init__( - self, - host=self.region.endpoint, - aws_access_key_id=aws_access_key_id, - aws_secret_access_key=aws_secret_access_key, - is_secure=is_secure, - port=port, - proxy=proxy, - proxy_port=proxy_port, - proxy_user=proxy_user, - proxy_pass=proxy_pass, - debug=debug, - https_connection_factory=https_connection_factory, - path=path, - security_token=security_token, - validate_certs=validate_certs, - profile_name=profile_name) def _required_auth_capability(self): return ['hmac-v4'] - def get_response(self, doc_path, action, params, path='/', - parent=None, verb='GET', list_marker=None): - if not parent: - parent = self - response = self.make_request(action, params, path, verb) - body = response.read() - boto.log.debug(body) - if response.status == 200: - e = boto.jsonresponse.Element( - list_marker=list_marker if list_marker else 'Set', - pythonize_name=True) - h = boto.jsonresponse.XmlHandler(e, parent) - h.parse(body) - inner = e - for p in doc_path: - inner = inner.get(p) - if not inner: - return None if list_marker is None else [] - if isinstance(inner, list): - return inner - else: - return dict(**inner) - else: - raise self.ResponseError(response.status, response.reason, body) + def build_suggesters(self, domain_name): + """ + Indexes the search suggestions. + + :type domain_name: string + :param domain_name: A string that represents the name of a domain. + Domain names are unique across the domains owned by an account + within an AWS region. Domain names start with a letter or number + and can contain the following characters: a-z (lowercase), 0-9, and + - (hyphen). + + """ + params = {'DomainName': domain_name, } + return self._make_request( + action='BuildSuggesters', + verb='POST', + path='/', params=params) def create_domain(self, domain_name): """ - Create a new search domain. + Creates a new search domain. For more information, see + `Creating a Search Domain`_ in the Amazon CloudSearch + Developer Guide . :type domain_name: string - :param domain_name: A string that represents the name of a - domain. Domain names must be unique across the domains - owned by an account within an AWS region. Domain names - must start with a letter or number and can contain the - following characters: a-z (lowercase), 0-9, and - - (hyphen). Uppercase letters and underscores are not - allowed. + :param domain_name: A name for the domain you are creating. Allowed + characters are a-z (lower-case letters), 0-9, and hyphen (-). + Domain names must start with a letter or number and be at least 3 + and no more than 28 characters long. - :raises: BaseException, InternalException, LimitExceededException """ - doc_path = ('create_domain_response', - 'create_domain_result', - 'domain_status') - params = {'DomainName': domain_name} - return self.get_response(doc_path, 'CreateDomain', - params, verb='POST') + params = {'DomainName': domain_name, } + return self._make_request( + action='CreateDomain', + verb='POST', + path='/', params=params) - def define_analysis_scheme(self, domain_name, name, language, - algorithmic_stemming="none", stemming_dictionary=None, - stopwords=None, synonyms=None): + def define_analysis_scheme(self, domain_name, analysis_scheme): """ - Updates stemming options used by indexing for the search domain. + Configures an analysis scheme for a domain. An analysis scheme + defines language-specific text processing options for a `text` + field. For more information, see `Configuring Analysis + Schemes`_ in the Amazon CloudSearch Developer Guide . :type domain_name: string - :param domain_name: A string that represents the name of a - domain. Domain names must be unique across the domains - owned by an account within an AWS region. Domain names - must start with a letter or number and can contain the - following characters: a-z (lowercase), 0-9, and - - (hyphen). Uppercase letters and underscores are not - allowed. - - :type name: str - :param name: Name of the analysis scheme - - :type language: str - :param language: IETF RFC 4646 lang code or 'mul' for multiple - languages. - - :type algorithmic_stemming: str - :param algorithmic_stemming: Which type of stemming to use. - one of ``none | minimal | light | full`` - - :type stemming_dictionary: dict - :param stemming_dictionary: dict of stemming words - ``{"running": "run", "jumping": "jump"}`` - - :type stopwords: list of strings - :param stopwords: list of stopwords - - :type synonyms: dict - :param synonyms: dict of Array of words to use as synonyms - ``{"aliases": {"running": ["run", "ran"], "jumping": ["jump", "jumped"]}, - "groups": [["sit", "sitting", "sat"], ["hit", "hitting"]]}`` - - :raises: BaseException, InternalException, InvalidTypeException, - LimitExceededException, ResourceNotFoundException - """ - doc_path = ('define_analysis_scheme_response', - 'define_analysis_scheme_result', - 'analysis_scheme') - params = {'DomainName': domain_name, 'AnalysisScheme.AnalysisSchemeName': name, - 'AnalysisScheme.AnalysisSchemeLanguage': language, - 'AnalysisScheme.AnalysisOptions.AlgorithmicStemming': algorithmic_stemming, - 'AnalysisScheme.AnalysisOptions.StemmingDictionary': - json.dumps(stemming_dictionary) if stemming_dictionary else dict(), - 'AnalysisScheme.AnalysisOptions.Stopwords': - json.dumps(stopwords) if stopwords else list(), - 'AnalysisScheme.AnalysisOptions.Synonyms': - json.dumps(synonyms) if synonyms else dict(), - } - - return self.get_response(doc_path, 'DefineAnalysisScheme', - params, verb='POST') - - def define_expression(self, domain_name, name, value): - """ - Defines an Expression, either replacing an existing - definition or creating a new one. + :param domain_name: A string that represents the name of a domain. + Domain names are unique across the domains owned by an account + within an AWS region. Domain names start with a letter or number + and can contain the following characters: a-z (lowercase), 0-9, and + - (hyphen). + + :type analysis_scheme: dict + :param analysis_scheme: Configuration information for an analysis + scheme. Each analysis scheme has a unique name and specifies the + language of the text to be processed. The following options can be + configured for an analysis scheme: `Synonyms`, `Stopwords`, + `StemmingDictionary`, and `AlgorithmicStemming`. + + """ + params = {'DomainName': domain_name, } + self.build_complex_param(params, 'AnalysisScheme', + analysis_scheme) + return self._make_request( + action='DefineAnalysisScheme', + verb='POST', + path='/', params=params) + + def define_expression(self, domain_name, expression): + """ + Configures an `Expression` for the search domain. Used to + create new expressions and modify existing ones. If the + expression exists, the new configuration replaces the old one. + For more information, see `Configuring Expressions`_ in the + Amazon CloudSearch Developer Guide . :type domain_name: string - :param domain_name: A string that represents the name of a - domain. Domain names must be unique across the domains - owned by an account within an AWS region. Domain names - must start with a letter or number and can contain the - following characters: a-z (lowercase), 0-9, and - - (hyphen). Uppercase letters and underscores are not - allowed. - - :type name: string - :param name: The name of an expression. - - :type value: string - :param value: The expression to evaluate for ranking or - thresholding while processing a search request. The - Expression syntax is based on JavaScript and supports: - - * Single value, sort enabled numeric fields (int, double, date) - * Other expressions - * The _score variable, which references a document's relevance score - * The _time variable, which references the current epoch time - * Integer, floating point, hex, and octal literals - * Arithmetic operators: + - * / % - * Bitwise operators: | & ^ ~ << >> >>> - * Boolean operators (including the ternary operator): && || ! ?: - * Comparison operators: < <= == >= > - * Mathematical functions: abs ceil exp floor ln log2 log10 logn - max min pow sqrt pow - * Trigonometric functions: acos acosh asin asinh atan atan2 atanh - cos cosh sin sinh tanh tan - * The haversin distance function - - Expressions always return an integer value from 0 to the maximum - 64-bit signed integer value (2^63 - 1). Intermediate results are - calculated as double-precision floating point values and the return - value is rounded to the nearest integer. If the expression is invalid - or evaluates to a negative value, it returns 0. If the expression - evaluates to a value greater than the maximum, it returns the maximum - value. - - The source data for an Expression can be the name of an - IndexField of type int or double, another Expression or the - reserved name _score, or the functions above. The _score source is - defined to return as a double with a floor of 0 to - indicate how relevant a document is to the search request, - taking into account repetition of search terms in the - document and proximity of search terms to each other in - each matching IndexField in the document. - - For more information about using expressions to customize results, - see the Amazon CloudSearch Developer Guide. - - :raises: BaseException, InternalException, LimitExceededException, - InvalidTypeException, ResourceNotFoundException - """ - doc_path = ('define_expression_response', - 'define_expression_result', - 'expression') - params = {'DomainName': domain_name, - 'Expression.ExpressionValue': value, - 'Expression.ExpressionName': name} - return self.get_response(doc_path, 'DefineExpression', - params, verb='POST') - - def define_index_field(self, domain_name, field_name, field_type, - default=None, facet=False, returnable=False, - searchable=False, sortable=False, - highlight=False, source_field=None, - analysis_scheme=None): - """ - Defines an ``IndexField``, either replacing an existing - definition or creating a new one. + :param domain_name: A string that represents the name of a domain. + Domain names are unique across the domains owned by an account + within an AWS region. Domain names start with a letter or number + and can contain the following characters: a-z (lowercase), 0-9, and + - (hyphen). + + :type expression: dict + :param expression: A named expression that can be evaluated at search + time. Can be used for sorting and filtering search results and + constructing other expressions. + + """ + params = {'DomainName': domain_name, } + self.build_complex_param(params, 'Expression', + expression) + return self._make_request( + action='DefineExpression', + verb='POST', + path='/', params=params) + + def define_index_field(self, domain_name, index_field): + """ + Configures an `IndexField` for the search domain. Used to + create new fields and modify existing ones. You must specify + the name of the domain you are configuring and an index field + configuration. The index field configuration specifies a + unique name, the index field type, and the options you want to + configure for the field. The options you can specify depend on + the `IndexFieldType`. If the field exists, the new + configuration replaces the old one. For more information, see + `Configuring Index Fields`_ in the Amazon CloudSearch + Developer Guide . :type domain_name: string - :param domain_name: A string that represents the name of a - domain. Domain names must be unique across the domains - owned by an account within an AWS region. Domain names - must start with a letter or number and can contain the - following characters: a-z (lowercase), 0-9, and - - (hyphen). Uppercase letters and underscores are not - allowed. - - :type field_name: string - :param field_name: The name of a field in the search index. - - :type field_type: string - :param field_type: The type of field. Valid values are - int | double | literal | text | date | latlon | - int-array | double-array | literal-array | text-array | date-array - - :type default: string or int - :param default: The default value for the field. If the - field is of type ``int`` this should be an integer value. - Otherwise, it's a string. - - :type facet: bool - :param facet: A boolean to indicate whether facets - are enabled for this field or not. Does not apply to - fields of type ``int, int-array, text, text-array``. - - :type returnable: bool - :param returnable: A boolean to indicate whether values - of this field can be returned in search results or - used in ranking. - - :type searchable: bool - :param searchable: A boolean to indicate whether search - is enabled for this field or not. - - :type sortable: bool - :param sortable: A boolean to indicate whether sorting - is enabled for this field or not. Does not apply to - fields of array types. - - :type highlight: bool - :param highlight: A boolean to indicate whether highlighting - is enabled for this field or not. Does not apply to - fields of type ``double, int, date, latlon`` - - :type source_field: list of strings or string - :param source_field: For array types, this is the list of fields - to treat as the source. For singular types, pass a string only. - - :type analysis_scheme: string - :param analysis_scheme: The analysis scheme to use for this field. - Only applies to ``text | text-array`` field types - - :raises: BaseException, InternalException, LimitExceededException, - InvalidTypeException, ResourceNotFoundException - """ - doc_path = ('define_index_field_response', - 'define_index_field_result', - 'index_field') - params = {'DomainName': domain_name, - 'IndexField.IndexFieldName': field_name, - 'IndexField.IndexFieldType': field_type} - if field_type == 'literal': - if default: - params['IndexField.LiteralOptions.DefaultValue'] = default - params['IndexField.LiteralOptions.FacetEnabled'] = do_bool(facet) - params['IndexField.LiteralOptions.ReturnEnabled'] = do_bool(returnable) - params['IndexField.LiteralOptions.SearchEnabled'] = do_bool(searchable) - params['IndexField.LiteralOptions.SortEnabled'] = do_bool(sortable) - if source_field: - params['IndexField.LiteralOptions.SourceField'] = source_field - elif field_type == 'literal-array': - if default: - params['IndexField.LiteralArrayOptions.DefaultValue'] = default - params['IndexField.LiteralArrayOptions.FacetEnabled'] = do_bool(facet) - params['IndexField.LiteralArrayOptions.ReturnEnabled'] = do_bool(returnable) - params['IndexField.LiteralArrayOptions.SearchEnabled'] = do_bool(searchable) - if source_field: - params['IndexField.LiteralArrayOptions.SourceFields'] = ','.join(source_field) - elif field_type == 'int': - if default: - params['IndexField.IntOptions.DefaultValue'] = default - params['IndexField.IntOptions.FacetEnabled'] = do_bool(facet) - params['IndexField.IntOptions.ReturnEnabled'] = do_bool(returnable) - params['IndexField.IntOptions.SearchEnabled'] = do_bool(searchable) - params['IndexField.IntOptions.SortEnabled'] = do_bool(sortable) - if source_field: - params['IndexField.IntOptions.SourceField'] = source_field - elif field_type == 'int-array': - if default: - params['IndexField.IntArrayOptions.DefaultValue'] = default - params['IndexField.IntArrayOptions.FacetEnabled'] = do_bool(facet) - params['IndexField.IntArrayOptions.ReturnEnabled'] = do_bool(returnable) - params['IndexField.IntArrayOptions.SearchEnabled'] = do_bool(searchable) - if source_field: - params['IndexField.IntArrayOptions.SourceFields'] = ','.join(source_field) - elif field_type == 'date': - if default: - params['IndexField.DateOptions.DefaultValue'] = default - params['IndexField.DateOptions.FacetEnabled'] = do_bool(facet) - params['IndexField.DateOptions.ReturnEnabled'] = do_bool(returnable) - params['IndexField.DateOptions.SearchEnabled'] = do_bool(searchable) - params['IndexField.DateOptions.SortEnabled'] = do_bool(sortable) - if source_field: - params['IndexField.DateOptions.SourceField'] = source_field - elif field_type == 'date-array': - if default: - params['IndexField.DateArrayOptions.DefaultValue'] = default - params['IndexField.DateArrayOptions.FacetEnabled'] = do_bool(facet) - params['IndexField.DateArrayOptions.ReturnEnabled'] = do_bool(returnable) - params['IndexField.DateArrayOptions.SearchEnabled'] = do_bool(searchable) - if source_field: - params['IndexField.DateArrayOptions.SourceFields'] = ','.join(source_field) - elif field_type == 'double': - if default: - params['IndexField.DoubleOptions.DefaultValue'] = default - params['IndexField.DoubleOptions.FacetEnabled'] = do_bool(facet) - params['IndexField.DoubleOptions.ReturnEnabled'] = do_bool(returnable) - params['IndexField.DoubleOptions.SearchEnabled'] = do_bool(searchable) - params['IndexField.DoubleOptions.SortEnabled'] = do_bool(sortable) - if source_field: - params['IndexField.DoubleOptions.SourceField'] = source_field - elif field_type == 'double-array': - if default: - params['IndexField.DoubleArrayOptions.DefaultValue'] = default - params['IndexField.DoubleArrayOptions.FacetEnabled'] = do_bool(facet) - params['IndexField.DoubleArrayOptions.ReturnEnabled'] = do_bool(returnable) - params['IndexField.DoubleArrayOptions.SearchEnabled'] = do_bool(searchable) - if source_field: - params['IndexField.DoubleArrayOptions.SourceFields'] = ','.join(source_field) - elif field_type == 'text': - if default: - params['IndexField.TextOptions.DefaultValue'] = default - params['IndexField.TextOptions.ReturnEnabled'] = do_bool(returnable) - params['IndexField.TextOptions.HighlightEnabled'] = do_bool(highlight) - params['IndexField.TextOptions.SortEnabled'] = do_bool(sortable) - if source_field: - params['IndexField.TextOptions.SourceField'] = source_field - if analysis_scheme: - params['IndexField.TextOptions.AnalysisScheme'] = analysis_scheme - elif field_type == 'text-array': - if default: - params['IndexField.TextArrayOptions.DefaultValue'] = default - params['IndexField.TextArrayOptions.ReturnEnabled'] = do_bool(returnable) - params['IndexField.TextArrayOptions.HighlightEnabled'] = do_bool(highlight) - if source_field: - params['IndexField.TextArrayOptions.SourceFields'] = ','.join(source_field) - if analysis_scheme: - params['IndexField.TextArrayOptions.AnalysisScheme'] = analysis_scheme - elif field_type == 'latlon': - if default: - params['IndexField.LatLonOptions.DefaultValue'] = default - params['IndexField.LatLonOptions.FacetEnabled'] = do_bool(facet) - params['IndexField.LatLonOptions.ReturnEnabled'] = do_bool(returnable) - params['IndexField.LatLonOptions.SearchEnabled'] = do_bool(searchable) - params['IndexField.LatLonOptions.SortEnabled'] = do_bool(sortable) - if source_field: - params['IndexField.LatLonOptions.SourceField'] = source_field - - return self.get_response(doc_path, 'DefineIndexField', - params, verb='POST') - - def define_suggester(self, domain_name, name, source_field, - fuzzy_matching=None, sort_expression=None): - """ - Defines an Expression, either replacing an existing - definition or creating a new one. + :param domain_name: A string that represents the name of a domain. + Domain names are unique across the domains owned by an account + within an AWS region. Domain names start with a letter or number + and can contain the following characters: a-z (lowercase), 0-9, and + - (hyphen). + + :type index_field: dict + :param index_field: The index field and field options you want to + configure. + + """ + params = {'DomainName': domain_name, } + self.build_complex_param(params, 'IndexField', + index_field) + return self._make_request( + action='DefineIndexField', + verb='POST', + path='/', params=params) + + def define_suggester(self, domain_name, suggester): + """ + Configures a suggester for a domain. A suggester enables you + to display possible matches before users finish typing their + queries. When you configure a suggester, you must specify the + name of the text field you want to search for possible matches + and a unique name for the suggester. For more information, see + `Getting Search Suggestions`_ in the Amazon CloudSearch + Developer Guide . :type domain_name: string - :param domain_name: A string that represents the name of a - domain. Domain names must be unique across the domains - owned by an account within an AWS region. Domain names - must start with a letter or number and can contain the - following characters: a-z (lowercase), 0-9, and - - (hyphen). Uppercase letters and underscores are not - allowed. - - :type name: string - :param name: The name of an suggester to use. + :param domain_name: A string that represents the name of a domain. + Domain names are unique across the domains owned by an account + within an AWS region. Domain names start with a letter or number + and can contain the following characters: a-z (lowercase), 0-9, and + - (hyphen). - :type source_field: string - :param source_field: The source field name to use for the ``Suggester`` + :type suggester: dict + :param suggester: Configuration information for a search suggester. + Each suggester has a unique name and specifies the text field you + want to use for suggestions. The following options can be + configured for a suggester: `FuzzyMatching`, `SortExpression`. - :type fuzzy_matching: string or None - :param fuzzy_matching: The optional type of fuzzy matching to use. One of - none | low | high - - :type sort_expression: string or None - :param sort_expression: The optional sort expression to use - - :raises: BaseException, InternalException, LimitExceededException, - InvalidTypeException, ResourceNotFoundException """ - doc_path = ('define_expression_response', - 'define_expression_result', - 'expression') - params = {'DomainName': domain_name, - 'Suggester.SuggesterName': name, - 'Suggester.DocumentSuggesterOptions.SourceField': source_field} - if fuzzy_matching is not None: - params['Suggester.DocumentSuggesterOptions.FuzzyMatching'] = fuzzy_matching - if sort_expression is not None: - params['Suggester.DocumentSuggesterOptions.SortExpression'] = sort_expression + params = {'DomainName': domain_name, } + self.build_complex_param(params, 'Suggester', + suggester) + return self._make_request( + action='DefineSuggester', + verb='POST', + path='/', params=params) - return self.get_response(doc_path, 'DefineExpression', params, - verb='POST') - - def delete_analysis_scheme(self, domain_name, scheme_name): + def delete_analysis_scheme(self, domain_name, analysis_scheme_name): """ - Deletes an existing ``AnalysisScheme`` from the search domain. + Deletes an analysis scheme. For more information, see + `Configuring Analysis Schemes`_ in the Amazon CloudSearch + Developer Guide . :type domain_name: string - :param domain_name: A string that represents the name of a - domain. Domain names must be unique across the domains - owned by an account within an AWS region. Domain names - must start with a letter or number and can contain the - following characters: a-z (lowercase), 0-9, and - - (hyphen). Uppercase letters and underscores are not - allowed. - - :type scheme_name: string - :param scheme_name: The analysis scheme name to delete - - :raises: BaseException, InternalException, ResourceNotFoundException - """ - doc_path = ('delete_analysis_scheme_response', - 'delete_analysis_scheme_result', - 'analysis_scheme') - params = {'DomainName': domain_name, - 'AnalysisSchemeName': scheme_name} - return self.get_response(doc_path, 'DeleteAnalysisScheme', - params, verb='POST') + :param domain_name: A string that represents the name of a domain. + Domain names are unique across the domains owned by an account + within an AWS region. Domain names start with a letter or number + and can contain the following characters: a-z (lowercase), 0-9, and + - (hyphen). + + :type analysis_scheme_name: string + :param analysis_scheme_name: The name of the analysis scheme you want + to delete. + + """ + params = { + 'DomainName': domain_name, + 'AnalysisSchemeName': analysis_scheme_name, + } + return self._make_request( + action='DeleteAnalysisScheme', + verb='POST', + path='/', params=params) def delete_domain(self, domain_name): """ - Delete a search domain. + Permanently deletes a search domain and all of its data. Once + a domain has been deleted, it cannot be recovered. For more + information, see `Deleting a Search Domain`_ in the Amazon + CloudSearch Developer Guide . :type domain_name: string - :param domain_name: A string that represents the name of a - domain. Domain names must be unique across the domains - owned by an account within an AWS region. Domain names - must start with a letter or number and can contain the - following characters: a-z (lowercase), 0-9, and - - (hyphen). Uppercase letters and underscores are not - allowed. + :param domain_name: The name of the domain you want to permanently + delete. - :raises: BaseException, InternalException """ - doc_path = ('delete_domain_response', - 'delete_domain_result', - 'domain_status') - params = {'DomainName': domain_name} - return self.get_response(doc_path, 'DeleteDomain', - params, verb='POST') + params = {'DomainName': domain_name, } + return self._make_request( + action='DeleteDomain', + verb='POST', + path='/', params=params) - def delete_index_field(self, domain_name, field_name): + def delete_expression(self, domain_name, expression_name): """ - Deletes an existing ``IndexField`` from the search domain. + Removes an `Expression` from the search domain. For more + information, see `Configuring Expressions`_ in the Amazon + CloudSearch Developer Guide . :type domain_name: string - :param domain_name: A string that represents the name of a - domain. Domain names must be unique across the domains - owned by an account within an AWS region. Domain names - must start with a letter or number and can contain the - following characters: a-z (lowercase), 0-9, and - - (hyphen). Uppercase letters and underscores are not - allowed. - - :type field_name: string - :param field_name: A string that represents the name of - an index field. Field names must begin with a letter and - can contain the following characters: a-z (lowercase), - 0-9, and _ (underscore). Uppercase letters and hyphens are - not allowed. The names "body", "docid", and - "text_relevance" are reserved and cannot be specified as - field or rank expression names. - - :raises: BaseException, InternalException, ResourceNotFoundException - """ - doc_path = ('delete_index_field_response', - 'delete_index_field_result', - 'index_field') - params = {'DomainName': domain_name, - 'IndexFieldName': field_name} - return self.get_response(doc_path, 'DeleteIndexField', - params, verb='POST') - - def delete_expression(self, domain_name, name): - """ - Deletes an existing ``Expression`` from the search domain. + :param domain_name: A string that represents the name of a domain. + Domain names are unique across the domains owned by an account + within an AWS region. Domain names start with a letter or number + and can contain the following characters: a-z (lowercase), 0-9, and + - (hyphen). - :type domain_name: string - :param domain_name: A string that represents the name of a - domain. Domain names must be unique across the domains - owned by an account within an AWS region. Domain names - must start with a letter or number and can contain the - following characters: a-z (lowercase), 0-9, and - - (hyphen). Uppercase letters and underscores are not - allowed. + :type expression_name: string + :param expression_name: The name of the `Expression` to delete. - :type name: string - :param name: Name of the ``Expression`` to delete. - - :raises: BaseException, InternalException, ResourceNotFoundException """ - doc_path = ('delete_expression_response', - 'delete_expression_result', - 'expression') - params = {'DomainName': domain_name, 'ExpressionName': name} - return self.get_response(doc_path, 'DeleteExpression', - params, verb='POST') + params = { + 'DomainName': domain_name, + 'ExpressionName': expression_name, + } + return self._make_request( + action='DeleteExpression', + verb='POST', + path='/', params=params) - def delete_suggester(self, domain_name, name): + def delete_index_field(self, domain_name, index_field_name): """ - Deletes an existing ``Suggester`` from the search domain. + Removes an `IndexField` from the search domain. For more + information, see `Configuring Index Fields`_ in the Amazon + CloudSearch Developer Guide . :type domain_name: string - :param domain_name: A string that represents the name of a - domain. Domain names must be unique across the domains - owned by an account within an AWS region. Domain names - must start with a letter or number and can contain the - following characters: a-z (lowercase), 0-9, and - - (hyphen). Uppercase letters and underscores are not - allowed. + :param domain_name: A string that represents the name of a domain. + Domain names are unique across the domains owned by an account + within an AWS region. Domain names start with a letter or number + and can contain the following characters: a-z (lowercase), 0-9, and + - (hyphen). - :type name: string - :param name: Name of the ``Suggester`` to delete. + :type index_field_name: string + :param index_field_name: The name of the index field your want to + remove from the domain's indexing options. - :raises: BaseException, InternalException, ResourceNotFoundException """ - doc_path = ('delete_suggester_response', - 'delete_suggester_result', - 'suggester') - params = {'DomainName': domain_name, 'SuggesterName': name} - return self.get_response(doc_path, 'DeleteSuggester', - params, verb='POST') + params = { + 'DomainName': domain_name, + 'IndexFieldName': index_field_name, + } + return self._make_request( + action='DeleteIndexField', + verb='POST', + path='/', params=params) - def describe_analysis_schemes(self, domain_name): + def delete_suggester(self, domain_name, suggester_name): """ - Describes analysis schemes used by indexing for the search domain. + Deletes a suggester. For more information, see `Getting Search + Suggestions`_ in the Amazon CloudSearch Developer Guide . :type domain_name: string - :param domain_name: A string that represents the name of a - domain. Domain names must be unique across the domains - owned by an account within an AWS region. Domain names - must start with a letter or number and can contain the - following characters: a-z (lowercase), 0-9, and - - (hyphen). Uppercase letters and underscores are not - allowed. - - :raises: BaseException, InternalException, ResourceNotFoundException - """ - doc_path = ('describe_analysis_schemes_response', - 'describe_analysis_schemes_result', - 'analysis_schemes') - params = {'DomainName': domain_name} - return self.get_response(doc_path, 'DescribeAnalysisSchemes', - params, verb='POST') + :param domain_name: A string that represents the name of a domain. + Domain names are unique across the domains owned by an account + within an AWS region. Domain names start with a letter or number + and can contain the following characters: a-z (lowercase), 0-9, and + - (hyphen). + + :type suggester_name: string + :param suggester_name: Specifies the name of the suggester you want to + delete. + + """ + params = { + 'DomainName': domain_name, + 'SuggesterName': suggester_name, + } + return self._make_request( + action='DeleteSuggester', + verb='POST', + path='/', params=params) + + def describe_analysis_schemes(self, domain_name, + analysis_scheme_names=None, deployed=None): + """ + Gets the analysis schemes configured for a domain. An analysis + scheme defines language-specific text processing options for a + `text` field. Can be limited to specific analysis schemes by + name. By default, shows all analysis schemes and includes any + pending changes to the configuration. Set the `Deployed` + option to `True` to show the active configuration and exclude + pending changes. For more information, see `Configuring + Analysis Schemes`_ in the Amazon CloudSearch Developer Guide . - def describe_availability_options(self, domain_name): - """ - Describes the availability options for the search domain. + :type domain_name: string + :param domain_name: The name of the domain you want to describe. + + :type analysis_scheme_names: list + :param analysis_scheme_names: The analysis schemes you want to + describe. + + :type deployed: boolean + :param deployed: Whether to display the deployed configuration ( + `True`) or include any pending changes ( `False`). Defaults to + `False`. + + """ + params = {'DomainName': domain_name, } + if analysis_scheme_names is not None: + self.build_list_params(params, + analysis_scheme_names, + 'AnalysisSchemeNames.member') + if deployed is not None: + params['Deployed'] = str( + deployed).lower() + return self._make_request( + action='DescribeAnalysisSchemes', + verb='POST', + path='/', params=params) + + def describe_availability_options(self, domain_name, deployed=None): + """ + Gets the availability options configured for a domain. By + default, shows the configuration with any pending changes. Set + the `Deployed` option to `True` to show the active + configuration and exclude pending changes. For more + information, see `Configuring Availability Options`_ in the + Amazon CloudSearch Developer Guide . :type domain_name: string - :param domain_name: A string that represents the name of a - domain. Domain names must be unique across the domains - owned by an account within an AWS region. Domain names - must start with a letter or number and can contain the - following characters: a-z (lowercase), 0-9, and - - (hyphen). Uppercase letters and underscores are not - allowed. - - :raises: BaseException, InternalException, ResourceNotFoundException - """ - doc_path = ('describe_availability_options_response', - 'describe_availability_options_result', - 'availability_options') - params = {'DomainName': domain_name} - return self.get_response(doc_path, 'DescribeAvailabilityOptions', - params, verb='POST') + :param domain_name: The name of the domain you want to describe. + + :type deployed: boolean + :param deployed: Whether to display the deployed configuration ( + `True`) or include any pending changes ( `False`). Defaults to + `False`. + + """ + params = {'DomainName': domain_name, } + if deployed is not None: + params['Deployed'] = str( + deployed).lower() + return self._make_request( + action='DescribeAvailabilityOptions', + verb='POST', + path='/', params=params) def describe_domains(self, domain_names=None): """ - Describes the domains (optionally limited to one or more - domains by name) owned by this account. + Gets information about the search domains owned by this + account. Can be limited to specific domains. Shows all domains + by default. For more information, see `Getting Information + about a Search Domain`_ in the Amazon CloudSearch Developer + Guide . :type domain_names: list - :param domain_names: Limits the response to the specified domains. + :param domain_names: The names of the domains you want to include in + the response. - :raises: BaseException, InternalException """ - doc_path = ('describe_domains_response', - 'describe_domains_result', - 'domain_status_list') params = {} - if domain_names: - for i, domain_name in enumerate(domain_names, 1): - params['DomainNames.member.%d' % i] = domain_name - return self.get_response(doc_path, 'DescribeDomains', - params, verb='POST', - list_marker='DomainStatusList') - - def describe_expressions(self, domain_name, names=None): - """ - Describes RankExpressions in the search domain, optionally - limited to a single expression. + if domain_names is not None: + self.build_list_params(params, + domain_names, + 'DomainNames.member') + return self._make_request( + action='DescribeDomains', + verb='POST', + path='/', params=params) + + def describe_expressions(self, domain_name, expression_names=None, + deployed=None): + """ + Gets the expressions configured for the search domain. Can be + limited to specific expressions by name. By default, shows all + expressions and includes any pending changes to the + configuration. Set the `Deployed` option to `True` to show the + active configuration and exclude pending changes. For more + information, see `Configuring Expressions`_ in the Amazon + CloudSearch Developer Guide . :type domain_name: string - :param domain_name: A string that represents the name of a - domain. Domain names must be unique across the domains - owned by an account within an AWS region. Domain names - must start with a letter or number and can contain the - following characters: a-z (lowercase), 0-9, and - - (hyphen). Uppercase letters and underscores are not - allowed. - - :type names: list - :param names: Limit response to the specified names. - - :raises: BaseException, InternalException, ResourceNotFoundException - """ - doc_path = ('describe_expressions_response', - 'describe_expressions_result', - 'expressions') - params = {'DomainName': domain_name} - if names: - for i, expr_name in enumerate(names, 1): - params['ExpressionNames.member.%d' % i] = expr_name - return self.get_response(doc_path, 'DescribeExpressions', - params, verb='POST', - list_marker='Expressions') - - def describe_index_fields(self, domain_name, field_names=None): - """ - Describes index fields in the search domain, optionally - limited to a single ``IndexField``. + :param domain_name: The name of the domain you want to describe. + + :type expression_names: list + :param expression_names: Limits the `DescribeExpressions` response to + the specified expressions. If not specified, all expressions are + shown. + + :type deployed: boolean + :param deployed: Whether to display the deployed configuration ( + `True`) or include any pending changes ( `False`). Defaults to + `False`. + + """ + params = {'DomainName': domain_name, } + if expression_names is not None: + self.build_list_params(params, + expression_names, + 'ExpressionNames.member') + if deployed is not None: + params['Deployed'] = str( + deployed).lower() + return self._make_request( + action='DescribeExpressions', + verb='POST', + path='/', params=params) + + def describe_index_fields(self, domain_name, field_names=None, + deployed=None): + """ + Gets information about the index fields configured for the + search domain. Can be limited to specific fields by name. By + default, shows all fields and includes any pending changes to + the configuration. Set the `Deployed` option to `True` to show + the active configuration and exclude pending changes. For more + information, see `Getting Domain Information`_ in the Amazon + CloudSearch Developer Guide . :type domain_name: string - :param domain_name: A string that represents the name of a - domain. Domain names must be unique across the domains - owned by an account within an AWS region. Domain names - must start with a letter or number and can contain the - following characters: a-z (lowercase), 0-9, and - - (hyphen). Uppercase letters and underscores are not - allowed. + :param domain_name: The name of the domain you want to describe. :type field_names: list - :param field_names: Limits the response to the specified fields. - - :raises: BaseException, InternalException, ResourceNotFoundException - """ - doc_path = ('describe_index_fields_response', - 'describe_index_fields_result', - 'index_fields') - params = {'DomainName': domain_name} - if field_names: - for i, field_name in enumerate(field_names, 1): - params['FieldNames.member.%d' % i] = field_name - return self.get_response(doc_path, 'DescribeIndexFields', - params, verb='POST', - list_marker='IndexFields') + :param field_names: A list of the index fields you want to describe. If + not specified, information is returned for all configured index + fields. + + :type deployed: boolean + :param deployed: Whether to display the deployed configuration ( + `True`) or include any pending changes ( `False`). Defaults to + `False`. + + """ + params = {'DomainName': domain_name, } + if field_names is not None: + self.build_list_params(params, + field_names, + 'FieldNames.member') + if deployed is not None: + params['Deployed'] = str( + deployed).lower() + return self._make_request( + action='DescribeIndexFields', + verb='POST', + path='/', params=params) def describe_scaling_parameters(self, domain_name): """ - Describes the scaling parameters for the search domain. + Gets the scaling parameters configured for a domain. A + domain's scaling parameters specify the desired search + instance type and replication count. For more information, see + `Configuring Scaling Options`_ in the Amazon CloudSearch + Developer Guide . :type domain_name: string - :param domain_name: A string that represents the name of a - domain. Domain names must be unique across the domains - owned by an account within an AWS region. Domain names - must start with a letter or number and can contain the - following characters: a-z (lowercase), 0-9, and - - (hyphen). Uppercase letters and underscores are not - allowed. + :param domain_name: A string that represents the name of a domain. + Domain names are unique across the domains owned by an account + within an AWS region. Domain names start with a letter or number + and can contain the following characters: a-z (lowercase), 0-9, and + - (hyphen). - :raises: BaseException, InternalException, ResourceNotFoundException """ - doc_path = ('describe_scaling_parameters_response', - 'describe_scaling_parameters_result', - 'scaling_parameters') - params = {'DomainName': domain_name} - return self.get_response(doc_path, 'DescribeScalingParameters', - params, verb='POST') + params = {'DomainName': domain_name, } + return self._make_request( + action='DescribeScalingParameters', + verb='POST', + path='/', params=params) - def describe_service_access_policies(self, domain_name): + def describe_service_access_policies(self, domain_name, deployed=None): """ - Describes the resource-based policies controlling access to - the services in this search domain. + Gets information about the access policies that control access + to the domain's document and search endpoints. By default, + shows the configuration with any pending changes. Set the + `Deployed` option to `True` to show the active configuration + and exclude pending changes. For more information, see + `Configuring Access for a Search Domain`_ in the Amazon + CloudSearch Developer Guide . :type domain_name: string - :param domain_name: A string that represents the name of a - domain. Domain names must be unique across the domains - owned by an account within an AWS region. Domain names - must start with a letter or number and can contain the - following characters: a-z (lowercase), 0-9, and - - (hyphen). Uppercase letters and underscores are not - allowed. + :param domain_name: The name of the domain you want to describe. + + :type deployed: boolean + :param deployed: Whether to display the deployed configuration ( + `True`) or include any pending changes ( `False`). Defaults to + `False`. + + """ + params = {'DomainName': domain_name, } + if deployed is not None: + params['Deployed'] = str( + deployed).lower() + return self._make_request( + action='DescribeServiceAccessPolicies', + verb='POST', + path='/', params=params) + + def describe_suggesters(self, domain_name, suggester_names=None, + deployed=None): + """ + Gets the suggesters configured for a domain. A suggester + enables you to display possible matches before users finish + typing their queries. Can be limited to specific suggesters by + name. By default, shows all suggesters and includes any + pending changes to the configuration. Set the `Deployed` + option to `True` to show the active configuration and exclude + pending changes. For more information, see `Getting Search + Suggestions`_ in the Amazon CloudSearch Developer Guide . - :raises: BaseException, InternalException, ResourceNotFoundException - """ - doc_path = ('describe_service_access_policies_response', - 'describe_service_access_policies_result', - 'access_policies') - params = {'DomainName': domain_name} - return self.get_response(doc_path, 'DescribeServiceAccessPolicies', - params, verb='POST') + :type domain_name: string + :param domain_name: The name of the domain you want to describe. + + :type suggester_names: list + :param suggester_names: The suggesters you want to describe. + + :type deployed: boolean + :param deployed: Whether to display the deployed configuration ( + `True`) or include any pending changes ( `False`). Defaults to + `False`. + + """ + params = {'DomainName': domain_name, } + if suggester_names is not None: + self.build_list_params(params, + suggester_names, + 'SuggesterNames.member') + if deployed is not None: + params['Deployed'] = str( + deployed).lower() + return self._make_request( + action='DescribeSuggesters', + verb='POST', + path='/', params=params) - def describe_suggesters(self, domain_name, names=None): + def index_documents(self, domain_name): """ - Describes the suggesters for the search domain. + Tells the search domain to start indexing its documents using + the latest indexing options. This operation must be invoked to + activate options whose OptionStatus is + `RequiresIndexDocuments`. :type domain_name: string - :param domain_name: A string that represents the name of a - domain. Domain names must be unique across the domains - owned by an account within an AWS region. Domain names - must start with a letter or number and can contain the - following characters: a-z (lowercase), 0-9, and - - (hyphen). Uppercase letters and underscores are not - allowed. - - :type names: list - :param names: Limit response to the specified names. - - :raises: BaseException, InternalException, ResourceNotFoundException - """ - doc_path = ('describe_suggesters_response', - 'describe_suggesters_result', - 'suggesters') - params = {'DomainName': domain_name} - if names: - for i, suggester_name in enumerate(names, 1): - params['SuggesterNames.member.%d' % i] = suggester_name - - return self.get_response(doc_path, 'DescribeSuggesters', - params, verb='POST', list_marker="Suggesters") + :param domain_name: A string that represents the name of a domain. + Domain names are unique across the domains owned by an account + within an AWS region. Domain names start with a letter or number + and can contain the following characters: a-z (lowercase), 0-9, and + - (hyphen). - def index_documents(self, domain_name): """ - Tells the search domain to start scanning its documents using - the latest text processing options and ``IndexFields``. This - operation must be invoked to make visible in searches any - options whose <a>OptionStatus</a> has ``OptionState`` of - ``RequiresIndexDocuments``. + params = {'DomainName': domain_name, } + return self._make_request( + action='IndexDocuments', + verb='POST', + path='/', params=params) - :type domain_name: string - :param domain_name: A string that represents the name of a - domain. Domain names must be unique across the domains - owned by an account within an AWS region. Domain names - must start with a letter or number and can contain the - following characters: a-z (lowercase), 0-9, and - - (hyphen). Uppercase letters and underscores are not - allowed. - - :raises: BaseException, InternalException, ResourceNotFoundException - """ - doc_path = ('index_documents_response', - 'index_documents_result', - 'field_names') - params = {'DomainName': domain_name} - return self.get_response(doc_path, 'IndexDocuments', params, - verb='POST', list_marker='FieldNames') + def list_domain_names(self): + """ + Lists all search domains owned by an account. + + + """ + params = {} + return self._make_request( + action='ListDomainNames', + verb='POST', + path='/', params=params) def update_availability_options(self, domain_name, multi_az): """ - Updates availability options for the search domain. + Configures the availability options for a domain. Enabling the + Multi-AZ option expands an Amazon CloudSearch domain to an + additional Availability Zone in the same Region to increase + fault tolerance in the event of a service disruption. Changes + to the Multi-AZ option can take about half an hour to become + active. For more information, see `Configuring Availability + Options`_ in the Amazon CloudSearch Developer Guide . :type domain_name: string - :param domain_name: A string that represents the name of a - domain. Domain names must be unique across the domains - owned by an account within an AWS region. Domain names - must start with a letter or number and can contain the - following characters: a-z (lowercase), 0-9, and - - (hyphen). Uppercase letters and underscores are not - allowed. - - :type multi_az: bool - :param multi_az: Should the domain be setup in multiple - Availability Zones - - :raises: BaseException, InternalException, InvalidTypeException, - LimitExceededException, ResourceNotFoundException - """ - doc_path = ('update_availability_options_response', - 'update_availability_options_result', - 'availability_options') - params = {'DomainName': domain_name, - 'MultiAZ': do_bool(multi_az)} - return self.get_response(doc_path, 'UpdateAvailabilityOptions', - params, verb='POST') - - def update_scaling_parameters(self, domain_name, instance_type=None, - replication_count=0): - """ - Updates scaling parameters for the search domain. + :param domain_name: A string that represents the name of a domain. + Domain names are unique across the domains owned by an account + within an AWS region. Domain names start with a letter or number + and can contain the following characters: a-z (lowercase), 0-9, and + - (hyphen). + + :type multi_az: boolean + :param multi_az: You expand an existing search domain to a second + Availability Zone by setting the Multi-AZ option to true. + Similarly, you can turn off the Multi-AZ option to downgrade the + domain to a single Availability Zone by setting the Multi-AZ option + to `False`. + + """ + params = {'DomainName': domain_name, 'MultiAZ': multi_az, } + return self._make_request( + action='UpdateAvailabilityOptions', + verb='POST', + path='/', params=params) + + def update_scaling_parameters(self, domain_name, scaling_parameters): + """ + Configures scaling parameters for a domain. A domain's scaling + parameters specify the desired search instance type and + replication count. Amazon CloudSearch will still automatically + scale your domain based on the volume of data and traffic, but + not below the desired instance type and replication count. If + the Multi-AZ option is enabled, these values control the + resources used per Availability Zone. For more information, + see `Configuring Scaling Options`_ in the Amazon CloudSearch + Developer Guide . :type domain_name: string - :param domain_name: A string that represents the name of a - domain. Domain names must be unique across the domains - owned by an account within an AWS region. Domain names - must start with a letter or number and can contain the - following characters: a-z (lowercase), 0-9, and - - (hyphen). Uppercase letters and underscores are not - allowed. - - :type instance_type: str or None - :param instance_type: The type of instance to use. One of - None | search.m1.small | search.m1.large | search.m2.xlarge | search.m2.2xlarge - - :type replication_count: int - :param replication_count: The desired number of replicas. A - value of 0 will reset to the default. - - :raises: BaseException, InternalException, InvalidTypeException, - LimitExceededException, ResourceNotFoundException - """ - doc_path = ('update_scaling_parameters_response', - 'update_scaling_parameters_result', - 'scaling_parameters') - params = {'DomainName': domain_name} - if instance_type is not None: - params["ScalingParameters.DesiredInstanceType"] = instance_type - if replication_count is not None: - params["ScalingParameters.DesiredReplicationCount"] = replication_count - return self.get_response(doc_path, 'UpdateScalingParameters', - params, verb='POST') + :param domain_name: A string that represents the name of a domain. + Domain names are unique across the domains owned by an account + within an AWS region. Domain names start with a letter or number + and can contain the following characters: a-z (lowercase), 0-9, and + - (hyphen). + + :type scaling_parameters: dict + :param scaling_parameters: The desired instance type and desired number + of replicas of each index partition. + + """ + params = {'DomainName': domain_name, } + self.build_complex_param(params, 'ScalingParameters', + scaling_parameters) + return self._make_request( + action='UpdateScalingParameters', + verb='POST', + path='/', params=params) def update_service_access_policies(self, domain_name, access_policies): """ - Updates the policies controlling access to the services in - this search domain. + Configures the access rules that control access to the + domain's document and search endpoints. For more information, + see ` Configuring Access for an Amazon CloudSearch Domain`_. :type domain_name: string - :param domain_name: A string that represents the name of a - domain. Domain names must be unique across the domains - owned by an account within an AWS region. Domain names - must start with a letter or number and can contain the - following characters: a-z (lowercase), 0-9, and - - (hyphen). Uppercase letters and underscores are not - allowed. + :param domain_name: A string that represents the name of a domain. + Domain names are unique across the domains owned by an account + within an AWS region. Domain names start with a letter or number + and can contain the following characters: a-z (lowercase), 0-9, and + - (hyphen). :type access_policies: string - :param access_policies: An IAM access policy as described in - The Access Policy Language in Using AWS Identity and - Access Management. The maximum size of an access policy - document is 100KB. - - :raises: BaseException, InternalException, LimitExceededException, - ResourceNotFoundException, InvalidTypeException - """ - doc_path = ('update_service_access_policies_response', - 'update_service_access_policies_result', - 'access_policies') - params = {'AccessPolicies': access_policies, - 'DomainName': domain_name} - return self.get_response(doc_path, 'UpdateServiceAccessPolicies', - params, verb='POST') + :param access_policies: The access rules you want to configure. These + rules replace any existing rules. + + """ + params = { + 'DomainName': domain_name, + 'AccessPolicies': access_policies, + } + return self._make_request( + action='UpdateServiceAccessPolicies', + verb='POST', + path='/', params=params) + + def build_complex_param(self, params, label, value): + """Serialize a structure. + + For example:: + + param_type = 'structure' + label = 'IndexField' + value = {'IndexFieldName': 'a', 'IntOptions': {'DefaultValue': 5}} + + would result in the params dict being updated with these params:: + + IndexField.IndexFieldName = a + IndexField.IntOptions.DefaultValue = 5 + + :type params: dict + :param params: The params dict. The complex list params + will be added to this dict. + + :type label: str + :param label: String label for param key + + :type value: any + :param value: The value to serialize + """ + for k, v in value.items(): + if type(v) in [dict]: + for k2, v2 in v.items(): + self.build_complex_param(params, label + '.' + k, v) + elif type(v) in [bool]: + params['%s.%s' % (label, k)] = v and 'true' or 'false' + else: + params['%s.%s' % (label, k)] = v + + def _make_request(self, action, verb, path, params): + params['ContentType'] = 'JSON' + response = self.make_request(action=action, verb='POST', + path='/', params=params) + body = response.read() + boto.log.debug(body) + if response.status == 200: + return json.loads(body) + else: + json_body = json.loads(body) + fault_name = json_body.get('Error', {}).get('Code', None) + exception_class = self._faults.get(fault_name, self.ResponseError) + raise exception_class(response.status, response.reason, + body=json_body) diff --git a/boto/cloudsearch2/layer2.py b/boto/cloudsearch2/layer2.py index bd73e523..d76c25e8 100644 --- a/boto/cloudsearch2/layer2.py +++ b/boto/cloudsearch2/layer2.py @@ -22,7 +22,7 @@ # IN THE SOFTWARE. # -from .layer1 import Layer1 +from .layer1 import CloudSearchConnection from .domain import Domain @@ -32,7 +32,15 @@ class Layer2(object): is_secure=True, port=None, proxy=None, proxy_port=None, host=None, debug=0, session_token=None, region=None, validate_certs=True): - self.layer1 = Layer1( + + if type(region) in [str, unicode]: + import boto.cloudsearch2 + for region_info in boto.cloudsearch2.regions(): + if region_info.name == region: + region = region_info + break + + self.layer1 = CloudSearchConnection( aws_access_key_id=aws_access_key_id, aws_secret_access_key=aws_secret_access_key, is_secure=is_secure, @@ -52,6 +60,11 @@ class Layer2(object): :rtype: list of :class:`boto.cloudsearch2.domain.Domain` """ domain_data = self.layer1.describe_domains(domain_names) + + domain_data = (domain_data['DescribeDomainsResponse'] + ['DescribeDomainsResult'] + ['DomainStatusList']) + return [Domain(self.layer1, data) for data in domain_data] def create_domain(self, domain_name): @@ -61,7 +74,9 @@ class Layer2(object): :rtype: :class:`boto.cloudsearch2.domain.Domain` """ data = self.layer1.create_domain(domain_name) - return Domain(self.layer1, data) + return Domain(self.layer1, data['CreateDomainResponse'] + ['CreateDomainResult'] + ['DomainStatus']) def lookup(self, domain_name): """ diff --git a/boto/cloudsearch2/optionstatus.py b/boto/cloudsearch2/optionstatus.py index a633eba0..9531ca85 100644 --- a/boto/cloudsearch2/optionstatus.py +++ b/boto/cloudsearch2/optionstatus.py @@ -51,21 +51,23 @@ class OptionStatus(dict): option was last updated. """ - def __init__(self, domain, data=None, refresh_fn=None, save_fn=None): + def __init__(self, domain, data=None, refresh_fn=None, refresh_key=None, + save_fn=None): self.domain = domain self.refresh_fn = refresh_fn + self.refresh_key = refresh_key self.save_fn = save_fn self.refresh(data) def _update_status(self, status): - self.creation_date = status['creation_date'] - self.status = status['state'] - self.update_date = status['update_date'] - self.update_version = int(status['update_version']) + self.creation_date = status['CreationDate'] + self.status = status['State'] + self.update_date = status['UpdateDate'] + self.update_version = int(status['UpdateVersion']) def _update_options(self, options): if options: - self.update(json.loads(options)) + self.update(options) def refresh(self, data=None): """ @@ -76,9 +78,14 @@ class OptionStatus(dict): if not data: if self.refresh_fn: data = self.refresh_fn(self.domain.name) + + if data and self.refresh_key: + # Attempt to pull out the right nested bag of data + for key in self.refresh_key: + data = data[key] if data: - self._update_status(data['status']) - self._update_options(data['options']) + self._update_status(data['Status']) + self._update_options(data['Options']) def to_json(self): """ @@ -86,23 +93,6 @@ class OptionStatus(dict): """ return json.dumps(self) - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == 'CreationDate': - self.created = value - elif name == 'State': - self.state = value - elif name == 'UpdateDate': - self.updated = value - elif name == 'UpdateVersion': - self.update_version = int(value) - elif name == 'Options': - self.update_from_json_doc(value) - else: - setattr(self, name, value) - def save(self): """ Write the current state of the local object back to the @@ -123,30 +113,20 @@ class OptionStatus(dict): class IndexFieldStatus(OptionStatus): - - def _update_options(self, options): - self.update(options) - def save(self): pass class AvailabilityOptionsStatus(OptionStatus): - - def _update_options(self, options): - self.update(MultiAZ=json.loads(options)) - def save(self): pass class ScalingParametersStatus(IndexFieldStatus): - pass class ExpressionStatus(IndexFieldStatus): - pass diff --git a/boto/cloudsearch2/search.py b/boto/cloudsearch2/search.py index 03155816..bfeca5a9 100644 --- a/boto/cloudsearch2/search.py +++ b/boto/cloudsearch2/search.py @@ -21,6 +21,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. # +import json from math import ceil import boto from boto.compat import json @@ -43,13 +44,10 @@ class CommitMismatchError(Exception): class SearchResults(object): def __init__(self, **attrs): self.rid = attrs['status']['rid'] - # self.doc_coverage_pct = attrs['info']['doc-coverage-pct'] self.time_ms = attrs['status']['time-ms'] self.hits = attrs['hits']['found'] self.docs = attrs['hits']['hit'] self.start = attrs['hits']['start'] - #self.rank = attrs['rank'] - #self.match_expression = attrs['match-expr'] self.query = attrs['query'] self.search_service = attrs['search_service'] @@ -57,7 +55,7 @@ class SearchResults(object): if 'facets' in attrs: for (facet, values) in attrs['facets'].iteritems(): if 'buckets' in values: - self.facets[facet] = dict((k, v) for (k, v) in map(lambda x: (x['value'], x['count']), values['buckets'])) + self.facets[facet] = dict((k, v) for (k, v) in map(lambda x: (x['value'], x['count']), values.get('buckets', []))) self.num_pages_needed = ceil(self.hits / self.query.real_size) @@ -131,6 +129,8 @@ class Query(object): if self.facet: for k, v in self.facet.iteritems(): + if type(v) not in [str, unicode]: + v = json.dumps(v) params['facet.%s' % k] = v if self.highlight: diff --git a/docs/source/ref/cloudsearch.rst b/docs/source/ref/cloudsearch.rst index 1610200a..98d4a224 100644 --- a/docs/source/ref/cloudsearch.rst +++ b/docs/source/ref/cloudsearch.rst @@ -4,56 +4,51 @@ Cloudsearch =========== -boto.cloudsearch ----------------- +boto.cloudsearch2 +----------------- -.. automodule:: boto.cloudsearch - :members: +.. automodule:: boto.cloudsearch2 + :members: :undoc-members: -boto.cloudsearch.domain ------------------------ +boto.cloudsearch2.domain +------------------------ -.. automodule:: boto.cloudsearch.domain +.. automodule:: boto.cloudsearch2.domain :members: :undoc-members: -boto.cloudsearch.layer1 ------------------------ +boto.cloudsearch2.layer1 +------------------------ -.. automodule:: boto.cloudsearch.layer1 +.. automodule:: boto.cloudsearch2.layer1 :members: :undoc-members: -boto.cloudsearch.layer2 ------------------------ +boto.cloudsearch2.layer2 +------------------------ -.. automodule:: boto.cloudsearch.layer2 +.. automodule:: boto.cloudsearch2.layer2 :members: :undoc-members: -boto.cloudsearch.optionstatus ------------------------------ +boto.cloudsearch2.optionstatus +------------------------------ -.. automodule:: boto.cloudsearch.optionstatus +.. automodule:: boto.cloudsearch2.optionstatus :members: :undoc-members: -boto.cloudsearch.search ------------------------ +boto.cloudsearch2.search +------------------------ -.. automodule:: boto.cloudsearch.search +.. automodule:: boto.cloudsearch2.search :members: :undoc-members: -boto.cloudsearch.document -------------------------- +boto.cloudsearch2.document +-------------------------- -.. automodule:: boto.cloudsearch.document +.. automodule:: boto.cloudsearch2.document :members: :undoc-members: - - - - - diff --git a/docs/source/ref/cloudsearch2.rst b/docs/source/ref/cloudsearch2.rst new file mode 100644 index 00000000..bac2d866 --- /dev/null +++ b/docs/source/ref/cloudsearch2.rst @@ -0,0 +1,61 @@ +.. ref-cloudsearch + +=========== +Cloudsearch +=========== + +boto.cloudsearch +---------------- + +.. automodule:: boto.cloudsearch + :members: + :undoc-members: + +boto.cloudsearch.domain +----------------------- + +.. automodule:: boto.cloudsearch.domain + :members: + :undoc-members: + +boto.cloudsearch.exceptions +----------------------- + +.. automodule:: boto.cloudsearch.exceptions + :members: + :undoc-members: + +boto.cloudsearch.layer1 +----------------------- + +.. automodule:: boto.cloudsearch.layer1 + :members: + :undoc-members: + +boto.cloudsearch.layer2 +----------------------- + +.. automodule:: boto.cloudsearch.layer2 + :members: + :undoc-members: + +boto.cloudsearch.optionstatus +----------------------------- + +.. automodule:: boto.cloudsearch.optionstatus + :members: + :undoc-members: + +boto.cloudsearch.search +----------------------- + +.. automodule:: boto.cloudsearch.search + :members: + :undoc-members: + +boto.cloudsearch.document +------------------------- + +.. automodule:: boto.cloudsearch.document + :members: + :undoc-members: @@ -75,7 +75,8 @@ setup(name = "boto", "boto.beanstalk", "boto.datapipeline", "boto.elasticache", "boto.elastictranscoder", "boto.opsworks", "boto.redshift", "boto.dynamodb2", "boto.support", "boto.cloudtrail", - "boto.directconnect", "boto.kinesis", "boto.rds2"], + "boto.directconnect", "boto.kinesis", "boto.rds2", + "boto.cloudsearch2"], package_data = { "boto.cacerts": ["cacerts.txt"], "boto": ["endpoints.json"], diff --git a/tests/integration/cloudsearch2/test_layers.py b/tests/integration/cloudsearch2/test_layers.py index 90ceda46..d2b13756 100644 --- a/tests/integration/cloudsearch2/test_layers.py +++ b/tests/integration/cloudsearch2/test_layers.py @@ -26,7 +26,7 @@ Tests for Layer1 of Cloudsearch import time from tests.unit import unittest -from boto.cloudsearch2.layer1 import Layer1 +from boto.cloudsearch2.layer1 import CloudSearchConnection from boto.cloudsearch2.layer2 import Layer2 from boto.regioninfo import RegionInfo @@ -36,13 +36,18 @@ class CloudSearchLayer1Test(unittest.TestCase): def setUp(self): super(CloudSearchLayer1Test, self).setUp() - self.layer1 = Layer1() + self.layer1 = CloudSearchConnection() self.domain_name = 'test-%d' % int(time.time()) def test_create_domain(self): resp = self.layer1.create_domain(self.domain_name) + + resp = (resp['CreateDomainResponse'] + ['CreateDomainResult'] + ['DomainStatus']) + self.addCleanup(self.layer1.delete_domain, self.domain_name) - self.assertTrue(resp.get('created', False)) + self.assertTrue(resp.get('Created', False)) class CloudSearchLayer2Test(unittest.TestCase): diff --git a/tests/unit/cloudsearch2/test_connection.py b/tests/unit/cloudsearch2/test_connection.py index 6a2a0200..e31d7190 100644 --- a/tests/unit/cloudsearch2/test_connection.py +++ b/tests/unit/cloudsearch2/test_connection.py @@ -3,48 +3,50 @@ from tests.unit import AWSMockServiceTestCase from boto.cloudsearch2.domain import Domain -from boto.cloudsearch2.layer1 import Layer1 +from boto.cloudsearch2.layer1 import CloudSearchConnection -import json class TestCloudSearchCreateDomain(AWSMockServiceTestCase): - connection_class = Layer1 + connection_class = CloudSearchConnection def default_body(self): return """ -<CreateDomainResponse xmlns="http://cloudsearch.amazonaws.com/doc/2013-01-01"> - <CreateDomainResult> - <DomainStatus> - <SearchPartitionCount>0</SearchPartitionCount> - <SearchService> - <Arn>arn:aws:cs:us-east-1:1234567890:domain/demo</Arn> - <Endpoint>search-demo-userdomain.us-east-1.cloudsearch.amazonaws.com</Endpoint> - </SearchService> - <Created>true</Created> - <DomainId>1234567890/demo</DomainId> - <Processing>false</Processing> - <SearchInstanceCount>0</SearchInstanceCount> - <DomainName>demo</DomainName> - <RequiresIndexDocuments>false</RequiresIndexDocuments> - <Deleted>false</Deleted> - <DocService> - <Arn>arn:aws:cs:us-east-1:1234567890:domain/demo</Arn> - <Endpoint>doc-demo-userdomain.us-east-1.cloudsearch.amazonaws.com</Endpoint> - </DocService> - </DomainStatus> - </CreateDomainResult> - <ResponseMetadata> - <RequestId>00000000-0000-0000-0000-000000000000</RequestId> - </ResponseMetadata> -</CreateDomainResponse> +{ + "CreateDomainResponse": { + "CreateDomainResult": { + "DomainStatus": { + "SearchInstanceType": null, + "DomainId": "1234567890/demo", + "DomainName": "demo", + "Deleted": false, + "SearchInstanceCount": 0, + "Created": true, + "SearchService": { + "Endpoint": "search-demo.us-east-1.cloudsearch.amazonaws.com" + }, + "RequiresIndexDocuments": false, + "Processing": false, + "DocService": { + "Endpoint": "doc-demo.us-east-1.cloudsearch.amazonaws.com" + }, + "ARN": "arn:aws:cs:us-east-1:1234567890:domain/demo", + "SearchPartitionCount": 0 + } + }, + "ResponseMetadata": { + "RequestId": "00000000-0000-0000-0000-000000000000" + } + } +} """ def test_create_domain(self): self.set_http_response(status_code=200) - api_response = self.service_connection.create_domain('demo') + self.service_connection.create_domain('demo') self.assert_request_parameters({ 'Action': 'CreateDomain', + 'ContentType': 'JSON', 'DomainName': 'demo', 'Version': '2013-01-01', }) @@ -54,22 +56,26 @@ class TestCloudSearchCreateDomain(AWSMockServiceTestCase): self.set_http_response(status_code=200) api_response = self.service_connection.create_domain('demo') - domain = Domain(self, api_response) + domain = Domain(self, api_response['CreateDomainResponse'] + ['CreateDomainResult'] + ['DomainStatus']) self.assertEqual( domain.doc_service_endpoint, - "doc-demo-userdomain.us-east-1.cloudsearch.amazonaws.com") + "doc-demo.us-east-1.cloudsearch.amazonaws.com") self.assertEqual(domain.service_arn, "arn:aws:cs:us-east-1:1234567890:domain/demo") self.assertEqual( domain.search_service_endpoint, - "search-demo-userdomain.us-east-1.cloudsearch.amazonaws.com") + "search-demo.us-east-1.cloudsearch.amazonaws.com") def test_cloudsearch_connect_result_statuses(self): """Check that domain statuses are correctly returned from AWS""" self.set_http_response(status_code=200) api_response = self.service_connection.create_domain('demo') - domain = Domain(self, api_response) + domain = Domain(self, api_response['CreateDomainResponse'] + ['CreateDomainResult'] + ['DomainStatus']) self.assertEqual(domain.created, True) self.assertEqual(domain.processing, False) @@ -80,7 +86,9 @@ class TestCloudSearchCreateDomain(AWSMockServiceTestCase): """Check that the domain information is correctly returned from AWS""" self.set_http_response(status_code=200) api_response = self.service_connection.create_domain('demo') - domain = Domain(self, api_response) + domain = Domain(self, api_response['CreateDomainResponse'] + ['CreateDomainResult'] + ['DomainStatus']) self.assertEqual(domain.id, "1234567890/demo") self.assertEqual(domain.name, "demo") @@ -88,56 +96,62 @@ class TestCloudSearchCreateDomain(AWSMockServiceTestCase): def test_cloudsearch_documentservice_creation(self): self.set_http_response(status_code=200) api_response = self.service_connection.create_domain('demo') - domain = Domain(self, api_response) + domain = Domain(self, api_response['CreateDomainResponse'] + ['CreateDomainResult'] + ['DomainStatus']) document = domain.get_document_service() self.assertEqual( document.endpoint, - "doc-demo-userdomain.us-east-1.cloudsearch.amazonaws.com") + "doc-demo.us-east-1.cloudsearch.amazonaws.com") def test_cloudsearch_searchservice_creation(self): self.set_http_response(status_code=200) api_response = self.service_connection.create_domain('demo') - domain = Domain(self, api_response) + domain = Domain(self, api_response['CreateDomainResponse'] + ['CreateDomainResult'] + ['DomainStatus']) search = domain.get_search_service() self.assertEqual( search.endpoint, - "search-demo-userdomain.us-east-1.cloudsearch.amazonaws.com") + "search-demo.us-east-1.cloudsearch.amazonaws.com") class CloudSearchConnectionDeletionTest(AWSMockServiceTestCase): - connection_class = Layer1 + connection_class = CloudSearchConnection def default_body(self): return """ -<DeleteDomainResponse xmlns="http://cloudsearch.amazonaws.com/doc/2013-01-01"> - <DeleteDomainResult> - <DomainStatus> - <SearchPartitionCount>0</SearchPartitionCount> - <SearchService> - <Arn>arn:aws:cs:us-east-1:1234567890:search/demo</Arn> - <Endpoint>search-demo-userdomain.us-east-1.cloudsearch.amazonaws.com</Endpoint> - </SearchService> - <Created>true</Created> - <DomainId>1234567890/demo</DomainId> - <Processing>false</Processing> - <SearchInstanceCount>0</SearchInstanceCount> - <DomainName>demo</DomainName> - <RequiresIndexDocuments>false</RequiresIndexDocuments> - <Deleted>false</Deleted> - <DocService> - <Arn>arn:aws:cs:us-east-1:1234567890:doc/demo</Arn> - <Endpoint>doc-demo-userdomain.us-east-1.cloudsearch.amazonaws.com</Endpoint> - </DocService> - </DomainStatus> - </DeleteDomainResult> - <ResponseMetadata> - <RequestId>00000000-0000-0000-0000-000000000000</RequestId> - </ResponseMetadata> -</DeleteDomainResponse> +{ + "DeleteDomainResponse": { + "DeleteDomainResult": { + "DomainStatus": { + "SearchInstanceType": null, + "DomainId": "1234567890/demo", + "DomainName": "test", + "Deleted": true, + "SearchInstanceCount": 0, + "Created": true, + "SearchService": { + "Endpoint": null + }, + "RequiresIndexDocuments": false, + "Processing": false, + "DocService": { + "Endpoint": null + }, + "ARN": "arn:aws:cs:us-east-1:1234567890:domain/demo", + "SearchPartitionCount": 0 + } + }, + "ResponseMetadata": { + "RequestId": "00000000-0000-0000-0000-000000000000" + } + } +} """ def test_cloudsearch_deletion(self): @@ -146,52 +160,55 @@ class CloudSearchConnectionDeletionTest(AWSMockServiceTestCase): cloudsearch connection. """ self.set_http_response(status_code=200) - api_response = self.service_connection.delete_domain('demo') + self.service_connection.delete_domain('demo') self.assert_request_parameters({ 'Action': 'DeleteDomain', + 'ContentType': 'JSON', 'DomainName': 'demo', 'Version': '2013-01-01', }) class CloudSearchConnectionIndexDocumentTest(AWSMockServiceTestCase): - connection_class = Layer1 + connection_class = CloudSearchConnection def default_body(self): return """ -<IndexDocumentsResponse xmlns="http://cloudsearch.amazonaws.com/doc/2013-01-01"> - <IndexDocumentsResult> - <FieldNames> - <member>average_score</member> - <member>brand_id</member> - <member>colors</member> - <member>context</member> - <member>context_owner</member> - <member>created_at</member> - <member>creator_id</member> - <member>description</member> - <member>file_size</member> - <member>format</member> - <member>has_logo</member> - <member>has_messaging</member> - <member>height</member> - <member>image_id</member> - <member>ingested_from</member> - <member>is_advertising</member> - <member>is_photo</member> - <member>is_reviewed</member> - <member>modified_at</member> - <member>subject_date</member> - <member>tags</member> - <member>title</member> - <member>width</member> - </FieldNames> - </IndexDocumentsResult> - <ResponseMetadata> - <RequestId>eb2b2390-6bbd-11e2-ab66-93f3a90dcf2a</RequestId> - </ResponseMetadata> -</IndexDocumentsResponse> +{ + "IndexDocumentsResponse": { + "IndexDocumentsResult": { + "FieldNames": [ + "average_score", + "brand_id", + "colors", + "context", + "context_owner", + "created_at", + "creator_id", + "description", + "file_size", + "format", + "has_logo", + "has_messaging", + "height", + "image_id", + "ingested_from", + "is_advertising", + "is_photo", + "is_reviewed", + "modified_at", + "subject_date", + "tags", + "title", + "width" + ] + }, + "ResponseMetadata": { + "RequestId": "42e618d9-c4d9-11e3-8242-c32da3041159" + } + } +} """ def test_cloudsearch_index_documents(self): @@ -200,10 +217,11 @@ class CloudSearchConnectionIndexDocumentTest(AWSMockServiceTestCase): domain. """ self.set_http_response(status_code=200) - api_response = self.service_connection.index_documents('demo') + self.service_connection.index_documents('demo') self.assert_request_parameters({ 'Action': 'IndexDocuments', + 'ContentType': 'JSON', 'DomainName': 'demo', 'Version': '2013-01-01', }) @@ -216,13 +234,17 @@ class CloudSearchConnectionIndexDocumentTest(AWSMockServiceTestCase): self.set_http_response(status_code=200) api_response = self.service_connection.index_documents('demo') - self.assertEqual(api_response, ['average_score', 'brand_id', 'colors', - 'context', 'context_owner', - 'created_at', 'creator_id', - 'description', 'file_size', 'format', - 'has_logo', 'has_messaging', 'height', - 'image_id', 'ingested_from', - 'is_advertising', 'is_photo', - 'is_reviewed', 'modified_at', - 'subject_date', 'tags', 'title', - 'width']) + fields = (api_response['IndexDocumentsResponse'] + ['IndexDocumentsResult'] + ['FieldNames']) + + self.assertEqual(fields, ['average_score', 'brand_id', 'colors', + 'context', 'context_owner', + 'created_at', 'creator_id', + 'description', 'file_size', 'format', + 'has_logo', 'has_messaging', 'height', + 'image_id', 'ingested_from', + 'is_advertising', 'is_photo', + 'is_reviewed', 'modified_at', + 'subject_date', 'tags', 'title', + 'width']) diff --git a/tests/unit/cloudsearch2/test_document.py b/tests/unit/cloudsearch2/test_document.py index 7d9d011f..fc42403c 100644 --- a/tests/unit/cloudsearch2/test_document.py +++ b/tests/unit/cloudsearch2/test_document.py @@ -41,13 +41,12 @@ class CloudSearchDocumentSingleTest(CloudSearchDocumentTest): """ document = DocumentServiceConnection( endpoint="doc-demo-userdomain.us-east-1.cloudsearch.amazonaws.com") - document.add("1234", 10, {"id": "1234", "title": "Title 1", - "category": ["cat_a", "cat_b", "cat_c"]}) + document.add("1234", {"id": "1234", "title": "Title 1", + "category": ["cat_a", "cat_b", "cat_c"]}) document.commit() args = json.loads(HTTPretty.last_request.body)[0] - self.assertEqual(args['lang'], 'en') self.assertEqual(args['type'], 'add') def test_cloudsearch_add_single_basic(self): @@ -57,14 +56,13 @@ class CloudSearchDocumentSingleTest(CloudSearchDocumentTest): """ document = DocumentServiceConnection( endpoint="doc-demo-userdomain.us-east-1.cloudsearch.amazonaws.com") - document.add("1234", 10, {"id": "1234", "title": "Title 1", - "category": ["cat_a", "cat_b", "cat_c"]}) + document.add("1234", {"id": "1234", "title": "Title 1", + "category": ["cat_a", "cat_b", "cat_c"]}) document.commit() args = json.loads(HTTPretty.last_request.body)[0] self.assertEqual(args['id'], '1234') - self.assertEqual(args['version'], 10) self.assertEqual(args['type'], 'add') def test_cloudsearch_add_single_fields(self): @@ -73,8 +71,8 @@ class CloudSearchDocumentSingleTest(CloudSearchDocumentTest): """ document = DocumentServiceConnection( endpoint="doc-demo-userdomain.us-east-1.cloudsearch.amazonaws.com") - document.add("1234", 10, {"id": "1234", "title": "Title 1", - "category": ["cat_a", "cat_b", "cat_c"]}) + document.add("1234", {"id": "1234", "title": "Title 1", + "category": ["cat_a", "cat_b", "cat_c"]}) document.commit() args = json.loads(HTTPretty.last_request.body)[0] @@ -90,8 +88,8 @@ class CloudSearchDocumentSingleTest(CloudSearchDocumentTest): """ document = DocumentServiceConnection( endpoint="doc-demo-userdomain.us-east-1.cloudsearch.amazonaws.com") - document.add("1234", 10, {"id": "1234", "title": "Title 1", - "category": ["cat_a", "cat_b", "cat_c"]}) + document.add("1234", {"id": "1234", "title": "Title 1", + "category": ["cat_a", "cat_b", "cat_c"]}) doc = document.commit() self.assertEqual(doc.status, 'success') @@ -111,17 +109,15 @@ class CloudSearchDocumentMultipleAddTest(CloudSearchDocumentTest): objs = { '1234': { - 'version': 10, 'fields': {"id": "1234", "title": "Title 1", - "category": ["cat_a", "cat_b", - "cat_c"]}}, + 'fields': {"id": "1234", "title": "Title 1", + "category": ["cat_a", "cat_b", "cat_c"]}}, '1235': { - 'version': 11, 'fields': {"id": "1235", "title": "Title 2", + 'fields': {"id": "1235", "title": "Title 2", "category": ["cat_b", "cat_c", "cat_d"]}}, '1236': { - 'version': 12, 'fields': {"id": "1236", "title": "Title 3", - "category": ["cat_e", "cat_f", - "cat_g"]}}, + 'fields': {"id": "1236", "title": "Title 3", + "category": ["cat_e", "cat_f", "cat_g"]}}, } @@ -130,14 +126,13 @@ class CloudSearchDocumentMultipleAddTest(CloudSearchDocumentTest): document = DocumentServiceConnection( endpoint="doc-demo-userdomain.us-east-1.cloudsearch.amazonaws.com") for (key, obj) in self.objs.items(): - document.add(key, obj['version'], obj['fields']) + document.add(key, obj['fields']) document.commit() args = json.loads(HTTPretty.last_request.body) for arg in args: self.assertTrue(arg['id'] in self.objs) - self.assertEqual(arg['version'], self.objs[arg['id']]['version']) self.assertEqual(arg['fields']['id'], self.objs[arg['id']]['fields']['id']) self.assertEqual(arg['fields']['title'], @@ -153,7 +148,7 @@ class CloudSearchDocumentMultipleAddTest(CloudSearchDocumentTest): document = DocumentServiceConnection( endpoint="doc-demo-userdomain.us-east-1.cloudsearch.amazonaws.com") for (key, obj) in self.objs.items(): - document.add(key, obj['version'], obj['fields']) + document.add(key, obj['fields']) doc = document.commit() self.assertEqual(doc.status, 'success') @@ -175,11 +170,10 @@ class CloudSearchDocumentDelete(CloudSearchDocumentTest): """ document = DocumentServiceConnection( endpoint="doc-demo-userdomain.us-east-1.cloudsearch.amazonaws.com") - document.delete("5", "10") + document.delete("5") document.commit() args = json.loads(HTTPretty.last_request.body)[0] - self.assertEqual(args['version'], '10') self.assertEqual(args['type'], 'delete') self.assertEqual(args['id'], '5') @@ -189,7 +183,7 @@ class CloudSearchDocumentDelete(CloudSearchDocumentTest): """ document = DocumentServiceConnection( endpoint="doc-demo-userdomain.us-east-1.cloudsearch.amazonaws.com") - document.delete("5", "10") + document.delete("5") doc = document.commit() self.assertEqual(doc.status, 'success') @@ -207,8 +201,8 @@ class CloudSearchDocumentDeleteMultiple(CloudSearchDocumentTest): def test_cloudsearch_delete_multiples(self): document = DocumentServiceConnection( endpoint="doc-demo-userdomain.us-east-1.cloudsearch.amazonaws.com") - document.delete("5", "10") - document.delete("6", "11") + document.delete("5") + document.delete("6") document.commit() args = json.loads(HTTPretty.last_request.body) @@ -216,13 +210,6 @@ class CloudSearchDocumentDeleteMultiple(CloudSearchDocumentTest): for arg in args: self.assertEqual(arg['type'], 'delete') - if arg['id'] == '5': - self.assertEqual(arg['version'], '10') - elif arg['id'] == '6': - self.assertEqual(arg['version'], '11') - else: # Unknown result out of AWS that shouldn't be there - self.assertTrue(False) - class CloudSearchSDFManipulation(CloudSearchDocumentTest): response = { @@ -242,8 +229,8 @@ class CloudSearchSDFManipulation(CloudSearchDocumentTest): document = DocumentServiceConnection( endpoint="doc-demo-userdomain.us-east-1.cloudsearch.amazonaws.com") - document.add("1234", 10, {"id": "1234", "title": "Title 1", - "category": ["cat_a", "cat_b", "cat_c"]}) + document.add("1234", {"id": "1234", "title": "Title 1", + "category": ["cat_a", "cat_b", "cat_c"]}) self.assertNotEqual(document.get_sdf(), '[]') @@ -264,8 +251,8 @@ class CloudSearchBadSDFTesting(CloudSearchDocumentTest): document = DocumentServiceConnection( endpoint="doc-demo-userdomain.us-east-1.cloudsearch.amazonaws.com") - document.add("1234", 10, {"id": "1234", "title": None, - "category": ["cat_a", "cat_b", "cat_c"]}) + document.add("1234", {"id": "1234", "title": None, + "category": ["cat_a", "cat_b", "cat_c"]}) document.commit() self.assertNotEqual(len(boto.log.error.call_args_list), 1) @@ -284,8 +271,8 @@ class CloudSearchDocumentErrorBadUnicode(CloudSearchDocumentTest): def test_fake_bad_unicode(self): document = DocumentServiceConnection( endpoint="doc-demo-userdomain.us-east-1.cloudsearch.amazonaws.com") - document.add("1234", 10, {"id": "1234", "title": "Title 1", - "category": ["cat_a", "cat_b", "cat_c"]}) + document.add("1234", {"id": "1234", "title": "Title 1", + "category": ["cat_a", "cat_b", "cat_c"]}) self.assertRaises(EncodingError, document.commit) @@ -300,8 +287,8 @@ class CloudSearchDocumentErrorDocsTooBig(CloudSearchDocumentTest): def test_fake_docs_too_big(self): document = DocumentServiceConnection( endpoint="doc-demo-userdomain.us-east-1.cloudsearch.amazonaws.com") - document.add("1234", 10, {"id": "1234", "title": "Title 1", - "category": ["cat_a", "cat_b", "cat_c"]}) + document.add("1234", {"id": "1234", "title": "Title 1", + "category": ["cat_a", "cat_b", "cat_c"]}) self.assertRaises(ContentTooLongError, document.commit) @@ -318,7 +305,7 @@ class CloudSearchDocumentErrorMismatch(CloudSearchDocumentTest): document = DocumentServiceConnection( endpoint="doc-demo-userdomain.us-east-1.cloudsearch.amazonaws.com") - document.add("1234", 10, {"id": "1234", "title": "Title 1", - "category": ["cat_a", "cat_b", "cat_c"]}) + document.add("1234", {"id": "1234", "title": "Title 1", + "category": ["cat_a", "cat_b", "cat_c"]}) self.assertRaises(CommitMismatchError, document.commit) diff --git a/tests/unit/cloudsearch2/test_search.py b/tests/unit/cloudsearch2/test_search.py index 63ac2f4f..e40dc396 100644 --- a/tests/unit/cloudsearch2/test_search.py +++ b/tests/unit/cloudsearch2/test_search.py @@ -19,31 +19,52 @@ class CloudSearchSearchBaseTest(unittest.TestCase): hits = [ { 'id': '12341', - 'title': 'Document 1', + 'fields': { + 'title': 'Document 1', + 'rank': 1 + } }, { 'id': '12342', - 'title': 'Document 2', + 'fields': { + 'title': 'Document 2', + 'rank': 2 + } }, { 'id': '12343', - 'title': 'Document 3', + 'fields': { + 'title': 'Document 3', + 'rank': 3 + } }, { 'id': '12344', - 'title': 'Document 4', + 'fields': { + 'title': 'Document 4', + 'rank': 4 + } }, { 'id': '12345', - 'title': 'Document 5', + 'fields': { + 'title': 'Document 5', + 'rank': 5 + } }, { 'id': '12346', - 'title': 'Document 6', + 'fields': { + 'title': 'Document 6', + 'rank': 6 + } }, { 'id': '12347', - 'title': 'Document 7', + 'fields': { + 'title': 'Document 7', + 'rank': 7 + } }, ] @@ -80,7 +101,7 @@ class CloudSearchSearchTest(CloudSearchSearchBaseTest): 'start': 0, 'hit':CloudSearchSearchBaseTest.hits }, - 'info': { + 'status': { 'rid':'b7c167f6c2da6d93531b9a7b314ad030b3a74803b4b7797edb905ba5a6a08', 'time-ms': 2, 'cpu-time-ms': 0 @@ -99,14 +120,6 @@ class CloudSearchSearchTest(CloudSearchSearchBaseTest): self.assertEqual(args['start'], ["0"]) self.assertEqual(args['size'], ["10"]) - def test_cloudsearch_bqsearch(self): - search = SearchConnection(endpoint=HOSTNAME) - - search.search(bq="'Test'") - - args = self.get_args(HTTPretty.last_request.raw_requestline) - - self.assertEqual(args['bq'], ["'Test'"]) def test_cloudsearch_search_details(self): search = SearchConnection(endpoint=HOSTNAME) @@ -119,34 +132,16 @@ class CloudSearchSearchTest(CloudSearchSearchBaseTest): self.assertEqual(args['size'], ["50"]) self.assertEqual(args['start'], ["20"]) - def test_cloudsearch_facet_single(self): - search = SearchConnection(endpoint=HOSTNAME) - - search.search(q='Test', facet=["Author"]) - - args = self.get_args(HTTPretty.last_request.raw_requestline) - - self.assertEqual(args['facet'], ["Author"]) - - def test_cloudsearch_facet_multiple(self): - search = SearchConnection(endpoint=HOSTNAME) - - search.search(q='Test', facet=["author", "cat"]) - - args = self.get_args(HTTPretty.last_request.raw_requestline) - - self.assertEqual(args['facet'], ["author,cat"]) - def test_cloudsearch_facet_constraint_single(self): search = SearchConnection(endpoint=HOSTNAME) search.search( q='Test', - facet_constraints={'author': "'John Smith','Mark Smith'"}) + facet={'author': "'John Smith','Mark Smith'"}) args = self.get_args(HTTPretty.last_request.raw_requestline) - self.assertEqual(args['facet-author-constraints'], + self.assertEqual(args['facet.author'], ["'John Smith','Mark Smith'"]) def test_cloudsearch_facet_constraint_multiple(self): @@ -154,72 +149,37 @@ class CloudSearchSearchTest(CloudSearchSearchBaseTest): search.search( q='Test', - facet_constraints={'author': "'John Smith','Mark Smith'", - 'category': "'News','Reviews'"}) + facet={'author': "'John Smith','Mark Smith'", + 'category': "'News','Reviews'"}) args = self.get_args(HTTPretty.last_request.raw_requestline) - self.assertEqual(args['facet-author-constraints'], + self.assertEqual(args['facet.author'], ["'John Smith','Mark Smith'"]) - self.assertEqual(args['facet-category-constraints'], + self.assertEqual(args['facet.category'], ["'News','Reviews'"]) def test_cloudsearch_facet_sort_single(self): search = SearchConnection(endpoint=HOSTNAME) - search.search(q='Test', facet_sort={'author': 'alpha'}) - - args = self.get_args(HTTPretty.last_request.raw_requestline) - - self.assertEqual(args['facet-author-sort'], ['alpha']) - - def test_cloudsearch_facet_sort_multiple(self): - search = SearchConnection(endpoint=HOSTNAME) - - search.search(q='Test', facet_sort={'author': 'alpha', - 'cat': 'count'}) - - args = self.get_args(HTTPretty.last_request.raw_requestline) - - self.assertEqual(args['facet-author-sort'], ['alpha']) - self.assertEqual(args['facet-cat-sort'], ['count']) - - def test_cloudsearch_top_n_single(self): - search = SearchConnection(endpoint=HOSTNAME) - - search.search(q='Test', facet_top_n={'author': 5}) - - args = self.get_args(HTTPretty.last_request.raw_requestline) - - self.assertEqual(args['facet-author-top-n'], ['5']) - - def test_cloudsearch_top_n_multiple(self): - search = SearchConnection(endpoint=HOSTNAME) - - search.search(q='Test', facet_top_n={'author': 5, 'cat': 10}) + search.search(q='Test', facet={'author': {'sort':'alpha'}}) args = self.get_args(HTTPretty.last_request.raw_requestline) - self.assertEqual(args['facet-author-top-n'], ['5']) - self.assertEqual(args['facet-cat-top-n'], ['10']) - - def test_cloudsearch_rank_single(self): - search = SearchConnection(endpoint=HOSTNAME) + print args - search.search(q='Test', rank=["date"]) + self.assertEqual(args['facet.author'], ['{"sort": "alpha"}']) - args = self.get_args(HTTPretty.last_request.raw_requestline) - - self.assertEqual(args['rank'], ['date']) - - def test_cloudsearch_rank_multiple(self): + def test_cloudsearch_facet_sort_multiple(self): search = SearchConnection(endpoint=HOSTNAME) - search.search(q='Test', rank=["date", "score"]) + search.search(q='Test', facet={'author': {'sort': 'alpha'}, + 'cat': {'sort': 'count'}}) args = self.get_args(HTTPretty.last_request.raw_requestline) - self.assertEqual(args['rank'], ['date,score']) + self.assertEqual(args['facet.author'], ['{"sort": "alpha"}']) + self.assertEqual(args['facet.cat'], ['{"sort": "count"}']) def test_cloudsearch_result_fields_single(self): search = SearchConnection(endpoint=HOSTNAME) @@ -228,7 +188,7 @@ class CloudSearchSearchTest(CloudSearchSearchBaseTest): args = self.get_args(HTTPretty.last_request.raw_requestline) - self.assertEqual(args['return-fields'], ['author']) + self.assertEqual(args['return'], ['author']) def test_cloudsearch_result_fields_multiple(self): search = SearchConnection(endpoint=HOSTNAME) @@ -237,28 +197,7 @@ class CloudSearchSearchTest(CloudSearchSearchBaseTest): args = self.get_args(HTTPretty.last_request.raw_requestline) - self.assertEqual(args['return-fields'], ['author,title']) - - - def test_cloudsearch_t_field_single(self): - search = SearchConnection(endpoint=HOSTNAME) - - search.search(q='Test', t={'year':'2001..2007'}) - - args = self.get_args(HTTPretty.last_request.raw_requestline) - - self.assertEqual(args['t-year'], ['2001..2007']) - - def test_cloudsearch_t_field_multiple(self): - search = SearchConnection(endpoint=HOSTNAME) - - search.search(q='Test', t={'year':'2001..2007', 'score':'10..50'}) - - args = self.get_args(HTTPretty.last_request.raw_requestline) - - self.assertEqual(args['t-year'], ['2001..2007']) - self.assertEqual(args['t-score'], ['10..50']) - + self.assertEqual(args['return'], ['author,title']) def test_cloudsearch_results_meta(self): """Check returned metadata is parsed correctly""" @@ -267,8 +206,8 @@ class CloudSearchSearchTest(CloudSearchSearchBaseTest): results = search.search(q='Test') # These rely on the default response which is fed into HTTPretty - self.assertEqual(results.rank, "-text_relevance") - self.assertEqual(results.match_expression, "Test") + self.assertEqual(results.hits, 30) + self.assertEqual(results.docs[0]['fields']['rank'], 1) def test_cloudsearch_results_info(self): """Check num_pages_needed is calculated correctly""" @@ -345,14 +284,14 @@ class CloudSearchSearchFacetTest(CloudSearchSearchBaseTest): 'start': 0, 'hit':CloudSearchSearchBaseTest.hits }, - 'info': { + 'status': { 'rid':'b7c167f6c2da6d93531b9a7b314ad030b3a74803b4b7797edb905ba5a6a08', 'time-ms': 2, 'cpu-time-ms': 0 }, 'facets': { 'tags': {}, - 'animals': {'constraints': [{'count': '2', 'value': 'fish'}, {'count': '1', 'value':'lions'}]}, + 'animals': {'buckets': [{'count': '2', 'value': 'fish'}, {'count': '1', 'value':'lions'}]}, } } @@ -361,7 +300,7 @@ class CloudSearchSearchFacetTest(CloudSearchSearchBaseTest): search = SearchConnection(endpoint=HOSTNAME) - results = search.search(q='Test', facet=['tags']) + results = search.search(q='Test', facet={'tags': {}}) self.assertTrue('tags' not in results.facets) self.assertEqual(results.facets['animals'], {u'lions': u'1', u'fish': u'2'}) |