summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.rst6
-rw-r--r--boto/__init__.py46
-rw-r--r--boto/auth.py24
-rw-r--r--boto/auth_handler.py10
-rw-r--r--boto/cognito/__init__.py21
-rw-r--r--boto/cognito/identity/__init__.py42
-rw-r--r--boto/cognito/identity/exceptions.py40
-rw-r--r--boto/cognito/identity/layer1.py303
-rw-r--r--boto/cognito/sync/__init__.py41
-rw-r--r--boto/cognito/sync/exceptions.py50
-rw-r--r--boto/cognito/sync/layer1.py307
-rw-r--r--boto/connection.py81
-rw-r--r--boto/contrib/__init__.py3
-rw-r--r--boto/contrib/ymlmessage.py1
-rw-r--r--boto/ec2/autoscale/__init__.py5
-rw-r--r--boto/ec2/autoscale/launchconfig.py4
-rw-r--r--boto/ec2/elb/__init__.py6
-rw-r--r--boto/endpoints.json9
-rw-r--r--boto/exception.py33
-rw-r--r--boto/handler.py3
-rw-r--r--boto/https_connection.py189
-rw-r--r--boto/jsonresponse.py5
-rw-r--r--boto/plugin.py25
-rw-r--r--boto/provider.py6
-rw-r--r--boto/regioninfo.py2
-rw-r--r--boto/requestlog.py14
-rw-r--r--boto/resultset.py12
-rw-r--r--boto/route53/connection.py10
-rw-r--r--boto/route53/zone.py9
-rwxr-xr-xboto/storage_uri.py145
-rw-r--r--boto/sts/connection.py2
-rw-r--r--boto/utils.py7
-rw-r--r--boto/vpc/routetable.py6
-rw-r--r--docs/source/index.rst2
-rw-r--r--docs/source/ref/cognito-identity.rst26
-rw-r--r--docs/source/ref/cognito-sync.rst26
-rw-r--r--docs/source/s3_tut.rst2
-rwxr-xr-xscripts/git-release-notes.py2
-rw-r--r--scripts/rebuild_endpoints.py1
-rw-r--r--setup.py3
-rw-r--r--tests/__init__.py2
-rw-r--r--tests/integration/__init__.py2
-rw-r--r--tests/integration/cognito/__init__.py41
-rw-r--r--tests/integration/cognito/identity/__init__.py21
-rw-r--r--tests/integration/cognito/identity/test_cognito_identity.py52
-rw-r--r--tests/integration/cognito/sync/__init__.py21
-rw-r--r--tests/integration/cognito/sync/test_cognito_sync.py46
-rwxr-xr-xtests/test.py3
-rw-r--r--tests/unit/__init__.py2
-rw-r--r--[-rwxr-xr-x]tests/unit/ec2/autoscale/test_group.py10
-rw-r--r--tests/unit/route53/test_connection.py17
-rw-r--r--tests/unit/route53/test_zone.py63
-rw-r--r--tests/unit/s3/test_connection.py20
-rw-r--r--tests/unit/sts/test_connection.py20
-rw-r--r--tests/unit/test_connection.py56
-rw-r--r--tests/unit/test_exception.py17
-rw-r--r--tests/unit/test_regioninfo.py2
-rw-r--r--tests/unit/vpc/test_routetable.py20
58 files changed, 1615 insertions, 329 deletions
diff --git a/README.rst b/README.rst
index e78a0ad4..c7de6205 100644
--- a/README.rst
+++ b/README.rst
@@ -5,10 +5,10 @@ boto 2.32.1
Released: 04-Aug-2014
-.. image:: https://travis-ci.org/boto/boto.png?branch=develop
+.. image:: https://travis-ci.org/boto/boto.svg?branch=develop
:target: https://travis-ci.org/boto/boto
-.. image:: https://pypip.in/d/boto/badge.png
+.. image:: https://pypip.in/d/boto/badge.svg
:target: https://pypi.python.org/pypi/boto/
************
@@ -67,6 +67,8 @@ At the moment, boto supports:
* Amazon Simple Queue Service (SQS) (Python 3)
* Amazon Simple Notification Server (SNS) (Python 3)
* Amazon Simple Email Service (SES) (Python 3)
+ * Amazon Cognito Identity (Python 3)
+ * Amazon Cognito Sync (Python 3)
* Monitoring
diff --git a/boto/__init__.py b/boto/__init__.py
index d76b67cc..1737f45c 100644
--- a/boto/__init__.py
+++ b/boto/__init__.py
@@ -882,6 +882,52 @@ def connect_route53domains(aws_access_key_id=None,
)
+def connect_cognito_identity(aws_access_key_id=None,
+ aws_secret_access_key=None,
+ **kwargs):
+ """
+ Connect to Amazon Cognito Identity
+
+ :type aws_access_key_id: string
+ :param aws_access_key_id: Your AWS Access Key ID
+
+ :type aws_secret_access_key: string
+ :param aws_secret_access_key: Your AWS Secret Access Key
+
+ rtype: :class:`boto.cognito.identity.layer1.CognitoIdentityConnection`
+ :return: A connection to the Amazon Cognito Identity service
+ """
+ from boto.cognito.identity.layer1 import CognitoIdentityConnection
+ return CognitoIdentityConnection(
+ aws_access_key_id=aws_access_key_id,
+ aws_secret_access_key=aws_secret_access_key,
+ **kwargs
+ )
+
+
+def connect_cognito_sync(aws_access_key_id=None,
+ aws_secret_access_key=None,
+ **kwargs):
+ """
+ Connect to Amazon Cognito Sync
+
+ :type aws_access_key_id: string
+ :param aws_access_key_id: Your AWS Access Key ID
+
+ :type aws_secret_access_key: string
+ :param aws_secret_access_key: Your AWS Secret Access Key
+
+ rtype: :class:`boto.cognito.sync.layer1.CognitoSyncConnection`
+ :return: A connection to the Amazon Cognito Sync service
+ """
+ from boto.cognito.sync.layer1 import CognitoSyncConnection
+ return CognitoSyncConnection(
+ aws_access_key_id=aws_access_key_id,
+ aws_secret_access_key=aws_secret_access_key,
+ **kwargs
+ )
+
+
def storage_uri(uri_str, default_scheme='file', debug=0, validate=True,
bucket_storage_uri_class=BucketStorageUri,
suppress_consec_slashes=True, is_latest=False):
diff --git a/boto/auth.py b/boto/auth.py
index df8dcccd..f05bb275 100644
--- a/boto/auth.py
+++ b/boto/auth.py
@@ -37,8 +37,6 @@ import datetime
from email.utils import formatdate
import hmac
import os
-import sys
-import time
import posixpath
from boto.compat import urllib, encodebytes
@@ -378,7 +376,7 @@ class HmacAuthV4Handler(AuthHandler, HmacKeys):
path = http_request.auth_path
# Normalize the path
# in windows normpath('/') will be '\\' so we chane it back to '/'
- normalized = posixpath.normpath(path).replace('\\','/')
+ normalized = posixpath.normpath(path).replace('\\', '/')
# Then urlencode whatever's left.
encoded = urllib.parse.quote(normalized)
if len(path) > 1 and path.endswith('/'):
@@ -474,7 +472,7 @@ class HmacAuthV4Handler(AuthHandler, HmacKeys):
def signature(self, http_request, string_to_sign):
key = self._provider.secret_key
k_date = self._sign(('AWS4' + key).encode('utf-8'),
- http_request.timestamp)
+ http_request.timestamp)
k_region = self._sign(k_date, http_request.region_name)
k_service = self._sign(k_region, http_request.service_name)
k_signing = self._sign(k_service, 'aws4_request')
@@ -570,7 +568,7 @@ class S3HmacAuthV4Handler(HmacAuthV4Handler, AuthHandler):
# Hooray for the only difference! The main SigV4 signer only does
# ``Host`` + ``x-amz-*``. But S3 wants pretty much everything
# signed, except for authorization itself.
- if not lname in ['authorization']:
+ if lname not in ['authorization']:
headers_to_sign[name] = value
return headers_to_sign
@@ -667,7 +665,7 @@ class S3HmacAuthV4Handler(HmacAuthV4Handler, AuthHandler):
return super(S3HmacAuthV4Handler, self).payload(http_request)
def add_auth(self, req, **kwargs):
- if not 'x-amz-content-sha256' in req.headers:
+ if 'x-amz-content-sha256' not in req.headers:
if '_sha256' in req.headers:
req.headers['x-amz-content-sha256'] = req.headers.pop('_sha256')
else:
@@ -705,6 +703,10 @@ class S3HmacAuthV4Handler(HmacAuthV4Handler, AuthHandler):
if self._provider.security_token:
params['X-Amz-Security-Token'] = self._provider.security_token
+ headers_to_sign = self.headers_to_sign(req)
+ l = sorted(['%s' % n.lower().strip() for n in headers_to_sign])
+ params['X-Amz-SignedHeaders'] = ';'.join(l)
+
req.params.update(params)
cr = self.canonical_request(req)
@@ -751,7 +753,6 @@ class QueryAuthHandler(AuthHandler):
def add_auth(self, http_request, **kwargs):
headers = http_request.headers
- params = http_request.params
qs = self._build_query_string(
http_request.params
)
@@ -899,7 +900,7 @@ class POSTPathQSV2AuthHandler(QuerySignatureV2AuthHandler, AuthHandler):
# already be there, we need to get rid of that and rebuild it
req.path = req.path.split('?')[0]
req.path = (req.path + '?' + qs +
- '&Signature=' + urllib.parse.quote_plus(signature))
+ '&Signature=' + urllib.parse.quote_plus(signature))
def get_auth_handler(host, config, provider, requested_capability=None):
@@ -925,7 +926,6 @@ def get_auth_handler(host, config, provider, requested_capability=None):
"""
ready_handlers = []
auth_handlers = boto.plugin.get_plugin(AuthHandler, requested_capability)
- total_handlers = len(auth_handlers)
for handler in auth_handlers:
try:
ready_handlers.append(handler(host, config, provider))
@@ -936,9 +936,9 @@ def get_auth_handler(host, config, provider, requested_capability=None):
checked_handlers = auth_handlers
names = [handler.__name__ for handler in checked_handlers]
raise boto.exception.NoAuthHandlerFound(
- 'No handler was ready to authenticate. %d handlers were checked.'
- ' %s '
- 'Check your credentials' % (len(names), str(names)))
+ 'No handler was ready to authenticate. %d handlers were checked.'
+ ' %s '
+ 'Check your credentials' % (len(names), str(names)))
# We select the last ready auth handler that was loaded, to allow users to
# customize how auth works in environments where there are shared boto
diff --git a/boto/auth_handler.py b/boto/auth_handler.py
index e6d131af..a8583f8a 100644
--- a/boto/auth_handler.py
+++ b/boto/auth_handler.py
@@ -14,7 +14,7 @@
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
-# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# 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.
@@ -25,8 +25,10 @@ Defines an interface which all Auth handlers need to implement.
from boto.plugin import Plugin
+
class NotReadyToAuthenticate(Exception):
- pass
+ pass
+
class AuthHandler(Plugin):
@@ -37,10 +39,10 @@ class AuthHandler(Plugin):
:type host: string
:param host: The host to which the request is being sent.
- :type config: boto.pyami.Config
+ :type config: boto.pyami.Config
:param config: Boto configuration.
- :type provider: boto.provider.Provider
+ :type provider: boto.provider.Provider
:param provider: Provider details.
Raises:
diff --git a/boto/cognito/__init__.py b/boto/cognito/__init__.py
new file mode 100644
index 00000000..70cc23fe
--- /dev/null
+++ b/boto/cognito/__init__.py
@@ -0,0 +1,21 @@
+# 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
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# 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.
+#
diff --git a/boto/cognito/identity/__init__.py b/boto/cognito/identity/__init__.py
new file mode 100644
index 00000000..e58b480b
--- /dev/null
+++ b/boto/cognito/identity/__init__.py
@@ -0,0 +1,42 @@
+# 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
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# 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
+
+
+def regions():
+ """
+ Get all available regions for the Amazon Cognito Identity service.
+
+ :rtype: list
+ :return: A list of :class:`boto.regioninfo.RegionInfo`
+ """
+ from boto.cognito.identity.layer1 import CognitoIdentityConnection
+ return get_regions('cognito-identity',
+ connection_cls=CognitoIdentityConnection)
+
+
+def connect_to_region(region_name, **kw_params):
+ for region in regions():
+ if region.name == region_name:
+ return region.connect(**kw_params)
+ return None
diff --git a/boto/cognito/identity/exceptions.py b/boto/cognito/identity/exceptions.py
new file mode 100644
index 00000000..179089df
--- /dev/null
+++ b/boto/cognito/identity/exceptions.py
@@ -0,0 +1,40 @@
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# 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.exception import BotoServerError
+
+
+class LimitExceededException(BotoServerError):
+ pass
+
+
+class ResourceConflictException(BotoServerError):
+ pass
+
+
+class TooManyRequestsException(BotoServerError):
+ pass
+
+
+class InvalidParameterException(BotoServerError):
+ pass
+
+
+class ResourceNotFoundException(BotoServerError):
+ pass
+
+
+class InternalErrorException(BotoServerError):
+ pass
+
+
+class NotAuthorizedException(BotoServerError):
+ pass
diff --git a/boto/cognito/identity/layer1.py b/boto/cognito/identity/layer1.py
new file mode 100644
index 00000000..0a9c8e4e
--- /dev/null
+++ b/boto/cognito/identity/layer1.py
@@ -0,0 +1,303 @@
+# 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
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# 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.
+#
+
+import boto
+from boto.compat import json
+from boto.connection import AWSQueryConnection
+from boto.regioninfo import RegionInfo
+from boto.exception import JSONResponseError
+from boto.cognito.identity import exceptions
+
+
+class CognitoIdentityConnection(AWSQueryConnection):
+ """
+ Amazon Cognito
+ Amazon Cognito is a web service that facilitates the delivery of
+ scoped, temporary credentials to mobile devices or other untrusted
+ environments. Amazon Cognito uniquely identifies a device or user
+ and supplies the user with a consistent identity throughout the
+ lifetime of an application.
+
+ Amazon Cognito lets users authenticate with third-party identity
+ providers (Facebook, Google, or Login with Amazon). As a
+ developer, you decide which identity providers to trust. You can
+ also choose to support unauthenticated access from your
+ application. Your users are provided with Cognito tokens that
+ uniquely identify their device and any information provided about
+ third-party logins.
+ """
+ APIVersion = "2014-06-30"
+ DefaultRegionName = "us-east-1"
+ DefaultRegionEndpoint = "cognito-identity.us-east-1.amazonaws.com"
+ ServiceName = "CognitoIdentity"
+ TargetPrefix = "AWSCognitoIdentityService"
+ ResponseError = JSONResponseError
+
+ _faults = {
+ "LimitExceededException": exceptions.LimitExceededException,
+ "ResourceConflictException": exceptions.ResourceConflictException,
+ "TooManyRequestsException": exceptions.TooManyRequestsException,
+ "InvalidParameterException": exceptions.InvalidParameterException,
+ "ResourceNotFoundException": exceptions.ResourceNotFoundException,
+ "InternalErrorException": exceptions.InternalErrorException,
+ "NotAuthorizedException": exceptions.NotAuthorizedException,
+ }
+
+
+ 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(CognitoIdentityConnection, self).__init__(**kwargs)
+ self.region = region
+
+ def _required_auth_capability(self):
+ return ['hmac-v4']
+
+ def create_identity_pool(self, identity_pool_name,
+ allow_unauthenticated_identities,
+ supported_login_providers=None):
+ """
+ Creates a new identity pool. The identity pool is a store of
+ user identity information that is specific to your AWS
+ account.
+
+ :type identity_pool_name: string
+ :param identity_pool_name: A string that you provide.
+
+ :type allow_unauthenticated_identities: boolean
+ :param allow_unauthenticated_identities: TRUE if the identity pool
+ supports unauthenticated logins.
+
+ :type supported_login_providers: map
+ :param supported_login_providers: Optional key:value pairs mapping
+ provider names to provider app IDs.
+
+ """
+ params = {
+ 'IdentityPoolName': identity_pool_name,
+ 'AllowUnauthenticatedIdentities': allow_unauthenticated_identities,
+ }
+ if supported_login_providers is not None:
+ params['SupportedLoginProviders'] = supported_login_providers
+ return self.make_request(action='CreateIdentityPool',
+ body=json.dumps(params))
+
+ def delete_identity_pool(self, identity_pool_id):
+ """
+ Deletes a user pool. Once a pool is deleted, users will not be
+ able to authenticate with the pool.
+
+ :type identity_pool_id: string
+ :param identity_pool_id: An identity pool ID in the format REGION:GUID.
+
+ """
+ params = {'IdentityPoolId': identity_pool_id, }
+ return self.make_request(action='DeleteIdentityPool',
+ body=json.dumps(params))
+
+ def describe_identity_pool(self, identity_pool_id):
+ """
+ Gets details about a particular identity pool, including the
+ pool name, ID description, creation date, and current number
+ of users.
+
+ :type identity_pool_id: string
+ :param identity_pool_id: An identity pool ID in the format REGION:GUID.
+
+ """
+ params = {'IdentityPoolId': identity_pool_id, }
+ return self.make_request(action='DescribeIdentityPool',
+ body=json.dumps(params))
+
+ def get_id(self, account_id, identity_pool_id, logins=None):
+ """
+ Generates (or retrieves) a Cognito ID. Supplying multiple
+ logins will create an implicit linked account.
+
+ :type account_id: string
+ :param account_id: A standard AWS account ID (9+ digits).
+
+ :type identity_pool_id: string
+ :param identity_pool_id: An identity pool ID in the format REGION:GUID.
+
+ :type logins: map
+ :param logins: A set of optional name/value pairs that map provider
+ names to provider tokens.
+
+ """
+ params = {
+ 'AccountId': account_id,
+ 'IdentityPoolId': identity_pool_id,
+ }
+ if logins is not None:
+ params['Logins'] = logins
+ return self.make_request(action='GetId',
+ body=json.dumps(params))
+
+ def get_open_id_token(self, identity_id, logins=None):
+ """
+ Gets an OpenID token, using a known Cognito ID. This known
+ Cognito ID is returned from GetId. You can optionally add
+ additional logins for the identity. Supplying multiple logins
+ creates an implicit link.
+
+ :type identity_id: string
+ :param identity_id: A unique identifier in the format REGION:GUID.
+
+ :type logins: map
+ :param logins: A set of optional name/value pairs that map provider
+ names to provider tokens.
+
+ """
+ params = {'IdentityId': identity_id, }
+ if logins is not None:
+ params['Logins'] = logins
+ return self.make_request(action='GetOpenIdToken',
+ body=json.dumps(params))
+
+ def list_identities(self, identity_pool_id, max_results, next_token=None):
+ """
+ Lists the identities in a pool.
+
+ :type identity_pool_id: string
+ :param identity_pool_id: An identity pool ID in the format REGION:GUID.
+
+ :type max_results: integer
+ :param max_results: The maximum number of identities to return.
+
+ :type next_token: string
+ :param next_token: A pagination token.
+
+ """
+ params = {
+ 'IdentityPoolId': identity_pool_id,
+ 'MaxResults': max_results,
+ }
+ if next_token is not None:
+ params['NextToken'] = next_token
+ return self.make_request(action='ListIdentities',
+ body=json.dumps(params))
+
+ def list_identity_pools(self, max_results, next_token=None):
+ """
+ Lists all of the Cognito identity pools registered for your
+ account.
+
+ :type max_results: integer
+ :param max_results: The maximum number of identities to return.
+
+ :type next_token: string
+ :param next_token: A pagination token.
+
+ """
+ params = {'MaxResults': max_results, }
+ if next_token is not None:
+ params['NextToken'] = next_token
+ return self.make_request(action='ListIdentityPools',
+ body=json.dumps(params))
+
+ def unlink_identity(self, identity_id, logins, logins_to_remove):
+ """
+ Unlinks a federated identity from an existing account.
+ Unlinked logins will be considered new identities next time
+ they are seen. Removing the last linked login will make this
+ identity inaccessible.
+
+ :type identity_id: string
+ :param identity_id: A unique identifier in the format REGION:GUID.
+
+ :type logins: map
+ :param logins: A set of optional name/value pairs that map provider
+ names to provider tokens.
+
+ :type logins_to_remove: list
+ :param logins_to_remove: Provider names to unlink from this identity.
+
+ """
+ params = {
+ 'IdentityId': identity_id,
+ 'Logins': logins,
+ 'LoginsToRemove': logins_to_remove,
+ }
+ return self.make_request(action='UnlinkIdentity',
+ body=json.dumps(params))
+
+ def update_identity_pool(self, identity_pool_id, identity_pool_name,
+ allow_unauthenticated_identities,
+ supported_login_providers=None):
+ """
+ Updates a user pool.
+
+ :type identity_pool_id: string
+ :param identity_pool_id: An identity pool ID in the format REGION:GUID.
+
+ :type identity_pool_name: string
+ :param identity_pool_name: A string that you provide.
+
+ :type allow_unauthenticated_identities: boolean
+ :param allow_unauthenticated_identities: TRUE if the identity pool
+ supports unauthenticated logins.
+
+ :type supported_login_providers: map
+ :param supported_login_providers: Optional key:value pairs mapping
+ provider names to provider app IDs.
+
+ """
+ params = {
+ 'IdentityPoolId': identity_pool_id,
+ 'IdentityPoolName': identity_pool_name,
+ 'AllowUnauthenticatedIdentities': allow_unauthenticated_identities,
+ }
+ if supported_login_providers is not None:
+ params['SupportedLoginProviders'] = supported_login_providers
+ return self.make_request(action='UpdateIdentityPool',
+ body=json.dumps(params))
+
+ def make_request(self, action, body):
+ headers = {
+ 'X-Amz-Target': '%s.%s' % (self.TargetPrefix, action),
+ 'Host': self.region.endpoint,
+ 'Content-Type': 'application/x-amz-json-1.1',
+ 'Content-Length': str(len(body)),
+ }
+ http_request = self.build_base_http_request(
+ method='POST', path='/', auth_path='/', params={},
+ headers=headers, data=body)
+ response = self._mexe(http_request, sender=None,
+ override_num_retries=10)
+ response_body = response.read().decode('utf-8')
+ boto.log.debug(response_body)
+ if response.status == 200:
+ if response_body:
+ return json.loads(response_body)
+ else:
+ json_body = json.loads(response_body)
+ fault_name = json_body.get('__type', None)
+ exception_class = self._faults.get(fault_name, self.ResponseError)
+ raise exception_class(response.status, response.reason,
+ body=json_body)
diff --git a/boto/cognito/sync/__init__.py b/boto/cognito/sync/__init__.py
new file mode 100644
index 00000000..3d48bd18
--- /dev/null
+++ b/boto/cognito/sync/__init__.py
@@ -0,0 +1,41 @@
+# 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
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# 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
+
+
+def regions():
+ """
+ Get all available regions for the Amazon Cognito Sync service.
+
+ :rtype: list
+ :return: A list of :class:`boto.regioninfo.RegionInfo`
+ """
+ from boto.cognito.sync.layer1 import CognitoSyncConnection
+ return get_regions('cognito-sync', connection_cls=CognitoSyncConnection)
+
+
+def connect_to_region(region_name, **kw_params):
+ for region in regions():
+ if region.name == region_name:
+ return region.connect(**kw_params)
+ return None
diff --git a/boto/cognito/sync/exceptions.py b/boto/cognito/sync/exceptions.py
new file mode 100644
index 00000000..d64fb278
--- /dev/null
+++ b/boto/cognito/sync/exceptions.py
@@ -0,0 +1,50 @@
+# 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
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# 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.exception import BotoServerError
+
+
+class LimitExceededException(BotoServerError):
+ pass
+
+
+class ResourceConflictException(BotoServerError):
+ pass
+
+
+class TooManyRequestsException(BotoServerError):
+ pass
+
+
+class InvalidParameterException(BotoServerError):
+ pass
+
+
+class ResourceNotFoundException(BotoServerError):
+ pass
+
+
+class InternalErrorException(BotoServerError):
+ pass
+
+
+class NotAuthorizedException(BotoServerError):
+ pass
diff --git a/boto/cognito/sync/layer1.py b/boto/cognito/sync/layer1.py
new file mode 100644
index 00000000..545af5eb
--- /dev/null
+++ b/boto/cognito/sync/layer1.py
@@ -0,0 +1,307 @@
+# 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
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# 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.compat import json
+from boto.exception import JSONResponseError
+from boto.connection import AWSAuthConnection
+from boto.regioninfo import RegionInfo
+from boto.cognito.sync import exceptions
+
+
+class CognitoSyncConnection(AWSAuthConnection):
+ """
+ Amazon Cognito Sync
+ Amazon Cognito Sync provides an AWS service and client library
+ that enable cross-device syncing of application-related user data.
+ High-level client libraries are available for both iOS and
+ Android. You can use these libraries to persist data locally so
+ that it's available even if the device is offline. Developer
+ credentials don't need to be stored on the mobile device to access
+ the service. You can use Amazon Cognito to obtain a normalized
+ user ID and credentials. User data is persisted in a dataset that
+ can store up to 1 MB of key-value pairs, and you can have up to 20
+ datasets per user identity.
+ """
+ APIVersion = "2014-06-30"
+ DefaultRegionName = "us-east-1"
+ DefaultRegionEndpoint = "cognito-sync.us-east-1.amazonaws.com"
+ ResponseError = JSONResponseError
+
+ _faults = {
+ "LimitExceededException": exceptions.LimitExceededException,
+ "ResourceConflictException": exceptions.ResourceConflictException,
+ "TooManyRequestsException": exceptions.TooManyRequestsException,
+ "InvalidParameterException": exceptions.InvalidParameterException,
+ "ResourceNotFoundException": exceptions.ResourceNotFoundException,
+ "InternalErrorException": exceptions.InternalErrorException,
+ "NotAuthorizedException": exceptions.NotAuthorizedException,
+ }
+
+
+ def __init__(self, **kwargs):
+ region = kwargs.get('region')
+ if not region:
+ region = RegionInfo(self, self.DefaultRegionName,
+ self.DefaultRegionEndpoint)
+ else:
+ del kwargs['region']
+ kwargs['host'] = region.endpoint
+ super(CognitoSyncConnection, self).__init__(**kwargs)
+ self.region = region
+
+ def _required_auth_capability(self):
+ return ['hmac-v4']
+
+ def delete_dataset(self, identity_pool_id, identity_id, dataset_name):
+ """
+ Deletes the specific dataset. The dataset will be deleted
+ permanently, and the action can't be undone. Datasets that
+ this dataset was merged with will no longer report the merge.
+ Any consequent operation on this dataset will result in a
+ ResourceNotFoundException.
+
+ :type identity_pool_id: string
+ :param identity_pool_id: A name-spaced GUID (for example, us-
+ east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
+ Cognito. GUID generation is unique within a region.
+
+ :type identity_id: string
+ :param identity_id: A name-spaced GUID (for example, us-
+ east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
+ Cognito. GUID generation is unique within a region.
+
+ :type dataset_name: string
+ :param dataset_name: A string of up to 128 characters. Allowed
+ characters are a-z, A-Z, 0-9, '_' (underscore), '-' (dash), and '.'
+ (dot).
+
+ """
+ uri = '/identitypools/{0}/identities/{1}/datasets/{2}'.format(
+ identity_pool_id, identity_id, dataset_name)
+ return self.make_request('DELETE', uri, expected_status=200)
+
+ def describe_dataset(self, identity_pool_id, identity_id, dataset_name):
+ """
+ Gets metadata about a dataset by identity and dataset name.
+
+ :type identity_pool_id: string
+ :param identity_pool_id: A name-spaced GUID (for example, us-
+ east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
+ Cognito. GUID generation is unique within a region.
+
+ :type identity_id: string
+ :param identity_id: A name-spaced GUID (for example, us-
+ east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
+ Cognito. GUID generation is unique within a region.
+
+ :type dataset_name: string
+ :param dataset_name: A string of up to 128 characters. Allowed
+ characters are a-z, A-Z, 0-9, '_' (underscore), '-' (dash), and '.'
+ (dot).
+
+ """
+ uri = '/identitypools/{0}/identities/{1}/datasets/{2}'.format(
+ identity_pool_id, identity_id, dataset_name)
+ return self.make_request('GET', uri, expected_status=200)
+
+ def describe_identity_pool_usage(self, identity_pool_id):
+ """
+ Gets usage details (for example, data storage) about a
+ particular identity pool.
+
+ :type identity_pool_id: string
+ :param identity_pool_id: A name-spaced GUID (for example, us-
+ east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
+ Cognito. GUID generation is unique within a region.
+
+ """
+ uri = '/identitypools/{0}'.format(identity_pool_id)
+ return self.make_request('GET', uri, expected_status=200)
+
+ def describe_identity_usage(self, identity_pool_id, identity_id):
+ """
+ Gets usage information for an identity, including number of
+ datasets and data usage.
+
+ :type identity_pool_id: string
+ :param identity_pool_id: A name-spaced GUID (for example, us-
+ east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
+ Cognito. GUID generation is unique within a region.
+
+ :type identity_id: string
+ :param identity_id: A name-spaced GUID (for example, us-
+ east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
+ Cognito. GUID generation is unique within a region.
+
+ """
+ uri = '/identitypools/{0}/identities/{1}'.format(
+ identity_pool_id, identity_id)
+ return self.make_request('GET', uri, expected_status=200)
+
+ def list_datasets(self, identity_pool_id, identity_id, next_token=None,
+ max_results=None):
+ """
+ Lists datasets for an identity.
+
+ :type identity_pool_id: string
+ :param identity_pool_id: A name-spaced GUID (for example, us-
+ east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
+ Cognito. GUID generation is unique within a region.
+
+ :type identity_id: string
+ :param identity_id: A name-spaced GUID (for example, us-
+ east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
+ Cognito. GUID generation is unique within a region.
+
+ :type next_token: string
+ :param next_token: A pagination token for obtaining the next page of
+ results.
+
+ :type max_results: integer
+ :param max_results: The maximum number of results to be returned.
+
+ """
+ uri = '/identitypools/{0}/identities/{1}/datasets'.format(
+ identity_pool_id, identity_id)
+ params = {}
+ headers = {}
+ return self.make_request('GET', uri, expected_status=200,
+ data=json.dumps(params), headers=headers)
+
+ def list_identity_pool_usage(self, next_token=None, max_results=None):
+ """
+ Gets a list of identity pools registered with Cognito.
+
+ :type next_token: string
+ :param next_token: A pagination token for obtaining the next page of
+ results.
+
+ :type max_results: integer
+ :param max_results: The maximum number of results to be returned.
+
+ """
+ uri = '/identitypools'
+ params = {}
+ headers = {}
+ return self.make_request('GET', uri, expected_status=200,
+ data=json.dumps(params), headers=headers)
+
+ def list_records(self, identity_pool_id, identity_id, dataset_name,
+ last_sync_count=None, next_token=None, max_results=None,
+ sync_session_token=None):
+ """
+ Gets paginated records, optionally changed after a particular
+ sync count for a dataset and identity.
+
+ :type identity_pool_id: string
+ :param identity_pool_id: A name-spaced GUID (for example, us-
+ east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
+ Cognito. GUID generation is unique within a region.
+
+ :type identity_id: string
+ :param identity_id: A name-spaced GUID (for example, us-
+ east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
+ Cognito. GUID generation is unique within a region.
+
+ :type dataset_name: string
+ :param dataset_name: A string of up to 128 characters. Allowed
+ characters are a-z, A-Z, 0-9, '_' (underscore), '-' (dash), and '.'
+ (dot).
+
+ :type last_sync_count: long
+ :param last_sync_count: The last server sync count for this record.
+
+ :type next_token: string
+ :param next_token: A pagination token for obtaining the next page of
+ results.
+
+ :type max_results: integer
+ :param max_results: The maximum number of results to be returned.
+
+ :type sync_session_token: string
+ :param sync_session_token: A token containing a session ID, identity
+ ID, and expiration.
+
+ """
+ uri = '/identitypools/{0}/identities/{1}/datasets/{2}/records'.format(
+ identity_pool_id, identity_id, dataset_name)
+ params = {}
+ headers = {}
+ return self.make_request('GET', uri, expected_status=200,
+ data=json.dumps(params), headers=headers)
+
+ def update_records(self, identity_pool_id, identity_id, dataset_name,
+ sync_session_token, record_patches=None,
+ client_context=None):
+ """
+ Posts updates to records and add and delete records for a
+ dataset and user.
+
+ :type identity_pool_id: string
+ :param identity_pool_id: A name-spaced GUID (for example, us-
+ east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
+ Cognito. GUID generation is unique within a region.
+
+ :type identity_id: string
+ :param identity_id: A name-spaced GUID (for example, us-
+ east-1:23EC4050-6AEA-7089-A2DD-08002EXAMPLE) created by Amazon
+ Cognito. GUID generation is unique within a region.
+
+ :type dataset_name: string
+ :param dataset_name: A string of up to 128 characters. Allowed
+ characters are a-z, A-Z, 0-9, '_' (underscore), '-' (dash), and '.'
+ (dot).
+
+ :type record_patches: list
+ :param record_patches:
+
+ :type sync_session_token: string
+ :param sync_session_token: The SyncSessionToken returned by a previous
+ call to ListRecords for this dataset and identity.
+
+ :type client_context: string
+ :param client_context:
+
+ """
+ uri = '/identitypools/{0}/identities/{1}/datasets/{2}'.format(
+ identity_pool_id, identity_id, dataset_name)
+ params = {'SyncSessionToken': sync_session_token, }
+ headers = {}
+ if record_patches is not None:
+ params['RecordPatches'] = record_patches
+ if client_context is not None:
+ headers['x-amz-Client-Context'] = client_context
+ return self.make_request('POST', uri, expected_status=200,
+ data=json.dumps(params), headers=headers)
+
+ def make_request(self, verb, resource, headers=None, data='',
+ expected_status=None, params=None):
+ if headers is None:
+ headers = {}
+ response = AWSAuthConnection.make_request(
+ self, verb, resource, headers=headers, data=data, params=params)
+ body = json.loads(response.read().decode('utf-8'))
+ if response.status == expected_status:
+ return body
+ else:
+ error_type = response.getheader('x-amzn-ErrorType').split(':')[0]
+ error_class = self._faults.get(error_type, self.ResponseError)
+ raise error_class(response.status, response.reason, body)
diff --git a/boto/connection.py b/boto/connection.py
index 8640ec48..40db69a7 100644
--- a/boto/connection.py
+++ b/boto/connection.py
@@ -90,7 +90,7 @@ ON_APP_ENGINE = all(key in os.environ for key in (
PORTS_BY_SECURITY = {True: 443,
False: 80}
-DEFAULT_CA_CERTS_FILE = os.path.join(os.path.dirname(os.path.abspath(boto.cacerts.__file__ )), "cacerts.txt")
+DEFAULT_CA_CERTS_FILE = os.path.join(os.path.dirname(os.path.abspath(boto.cacerts.__file__)), "cacerts.txt")
class HostConnectionPool(object):
@@ -372,9 +372,10 @@ class HTTPRequest(object):
self.headers[key] = quote(val.encode('utf-8'), safe)
setattr(self, '_headers_quoted', True)
+ self.headers['User-Agent'] = UserAgent
+
connection._auth_handler.add_auth(self, **kwargs)
- self.headers['User-Agent'] = UserAgent
# I'm not sure if this is still needed, now that add_auth is
# setting the content-length for POST requests.
if 'Content-Length' not in self.headers:
@@ -485,13 +486,13 @@ class AWSAuthConnection(object):
validate_certs)
if self.https_validate_certificates and not HAVE_HTTPS_CONNECTION:
raise BotoClientError(
- "SSL server certificate validation is enabled in boto "
- "configuration, but Python dependencies required to "
- "support this feature are not available. Certificate "
- "validation is only supported when running under Python "
- "2.6 or later.")
+ "SSL server certificate validation is enabled in boto "
+ "configuration, but Python dependencies required to "
+ "support this feature are not available. Certificate "
+ "validation is only supported when running under Python "
+ "2.6 or later.")
certs_file = config.get_value(
- 'Boto', 'ca_certificates_file', DEFAULT_CA_CERTS_FILE)
+ 'Boto', 'ca_certificates_file', DEFAULT_CA_CERTS_FILE)
if certs_file == 'system':
certs_file = None
self.ca_certificates_file = certs_file
@@ -508,7 +509,7 @@ class AWSAuthConnection(object):
self.http_unretryable_exceptions = []
if HAVE_HTTPS_CONNECTION:
self.http_unretryable_exceptions.append(
- https_connection.InvalidCertificateException)
+ https_connection.InvalidCertificateException)
# define values in socket exceptions we don't want to catch
self.socket_exception_values = (errno.EINTR,)
@@ -565,7 +566,7 @@ class AWSAuthConnection(object):
self._connection = (self.host, self.port, self.is_secure)
self._last_rs = None
self._auth_handler = auth.get_auth_handler(
- host, config, self.provider, self._required_auth_capability())
+ host, config, self.provider, self._required_auth_capability())
if getattr(self, 'AuthServiceName', None) is not None:
self.auth_service_name = self.AuthServiceName
self.request_hook = None
@@ -667,9 +668,9 @@ class AWSAuthConnection(object):
self.proxy_pass = proxy_pass
if 'http_proxy' in os.environ and not self.proxy:
pattern = re.compile(
- '(?:http://)?' \
- '(?:(?P<user>[\w\-\.]+):(?P<pass>.*)@)?' \
- '(?P<host>[\w\-\.]+)' \
+ '(?:http://)?'
+ '(?:(?P<user>[\w\-\.]+):(?P<pass>.*)@)?'
+ '(?P<host>[\w\-\.]+)'
'(?::(?P<port>\d+))?'
)
match = pattern.match(os.environ['http_proxy'])
@@ -689,8 +690,8 @@ class AWSAuthConnection(object):
self.proxy_pass = config.get_value('Boto', 'proxy_pass', None)
if not self.proxy_port and self.proxy:
- print("http_proxy environment variable does not specify " \
- "a port, using default")
+ print("http_proxy environment variable does not specify "
+ "a port, using default")
self.proxy_port = self.port
self.no_proxy = os.environ.get('no_proxy', '') or os.environ.get('NO_PROXY', '')
@@ -740,30 +741,30 @@ class AWSAuthConnection(object):
if is_secure:
boto.log.debug(
- 'establishing HTTPS connection: host=%s, kwargs=%s',
- host, http_connection_kwargs)
+ 'establishing HTTPS connection: host=%s, kwargs=%s',
+ host, http_connection_kwargs)
if self.use_proxy and not self.skip_proxy(host):
connection = self.proxy_ssl(host, is_secure and 443 or 80)
elif self.https_connection_factory:
connection = self.https_connection_factory(host)
elif self.https_validate_certificates and HAVE_HTTPS_CONNECTION:
connection = https_connection.CertValidatingHTTPSConnection(
- host, ca_certs=self.ca_certificates_file,
- **http_connection_kwargs)
+ host, ca_certs=self.ca_certificates_file,
+ **http_connection_kwargs)
else:
- connection = http_client.HTTPSConnection(host,
- **http_connection_kwargs)
+ connection = http_client.HTTPSConnection(
+ host, **http_connection_kwargs)
else:
boto.log.debug('establishing HTTP connection: kwargs=%s' %
- http_connection_kwargs)
+ http_connection_kwargs)
if self.https_connection_factory:
# even though the factory says https, this is too handy
# to not be able to allow overriding for http also.
- connection = self.https_connection_factory(host,
- **http_connection_kwargs)
+ connection = self.https_connection_factory(
+ host, **http_connection_kwargs)
else:
- connection = http_client.HTTPConnection(host,
- **http_connection_kwargs)
+ connection = http_client.HTTPConnection(
+ host, **http_connection_kwargs)
if self.debug > 1:
connection.set_debuglevel(self.debug)
# self.connection must be maintained for backwards-compatibility
@@ -822,7 +823,7 @@ class AWSAuthConnection(object):
if self.https_validate_certificates and HAVE_HTTPS_CONNECTION:
msg = "wrapping ssl socket for proxied connection; "
if self.ca_certificates_file:
- msg += "CA certificate file=%s" %self.ca_certificates_file
+ msg += "CA certificate file=%s" % self.ca_certificates_file
else:
msg += "using system provided SSL certs"
boto.log.debug(msg)
@@ -836,7 +837,7 @@ class AWSAuthConnection(object):
hostname = self.host.split(':', 0)[0]
if not https_connection.ValidateCertificateHostname(cert, hostname):
raise https_connection.InvalidCertificateException(
- hostname, cert, 'hostname mismatch')
+ hostname, cert, 'hostname mismatch')
else:
# Fallback for old Python without ssl.wrap_socket
if hasattr(http_client, 'ssl'):
@@ -1008,8 +1009,8 @@ class AWSAuthConnection(object):
'encountered unretryable %s exception, re-raising' %
e.__class__.__name__)
raise
- boto.log.debug('encountered %s exception, reconnecting' % \
- e.__class__.__name__)
+ boto.log.debug('encountered %s exception, reconnecting' %
+ e.__class__.__name__)
connection = self.new_http_connection(request.host, request.port,
self.is_secure)
time.sleep(next_sleep)
@@ -1041,8 +1042,7 @@ class AWSAuthConnection(object):
headers = {}
else:
headers = headers.copy()
- if (self.host_header and
- not boto.utils.find_matching_headers('host', headers)):
+ if self.host_header and not boto.utils.find_matching_headers('host', headers):
headers['host'] = self.host_header
host = host or self.host
if self.use_proxy:
@@ -1085,14 +1085,15 @@ class AWSQueryConnection(AWSAuthConnection):
proxy_user=None, proxy_pass=None, host=None, debug=0,
https_connection_factory=None, path='/', security_token=None,
validate_certs=True, profile_name=None):
- super(AWSQueryConnection, self).__init__(host, aws_access_key_id,
- aws_secret_access_key,
- is_secure, port, proxy,
- proxy_port, proxy_user, proxy_pass,
- debug, https_connection_factory, path,
- security_token=security_token,
- validate_certs=validate_certs,
- profile_name=profile_name)
+ super(AWSQueryConnection, self).__init__(
+ host, aws_access_key_id,
+ aws_secret_access_key,
+ is_secure, port, proxy,
+ proxy_port, proxy_user, proxy_pass,
+ debug, https_connection_factory, path,
+ security_token=security_token,
+ validate_certs=validate_certs,
+ profile_name=profile_name)
def _required_auth_capability(self):
return []
diff --git a/boto/contrib/__init__.py b/boto/contrib/__init__.py
index 303dbb66..a7e571e2 100644
--- a/boto/contrib/__init__.py
+++ b/boto/contrib/__init__.py
@@ -14,9 +14,8 @@
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
-# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# 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.
#
-
diff --git a/boto/contrib/ymlmessage.py b/boto/contrib/ymlmessage.py
index 6f3dd20c..ae6aea48 100644
--- a/boto/contrib/ymlmessage.py
+++ b/boto/contrib/ymlmessage.py
@@ -28,6 +28,7 @@ This module requires the yaml module.
from boto.sqs.message import Message
import yaml
+
class YAMLMessage(Message):
"""
The YAMLMessage class provides a YAML compatible message. Encoding and
diff --git a/boto/ec2/autoscale/__init__.py b/boto/ec2/autoscale/__init__.py
index 96c42848..5a58748d 100644
--- a/boto/ec2/autoscale/__init__.py
+++ b/boto/ec2/autoscale/__init__.py
@@ -222,7 +222,10 @@ class AutoScaleConnection(AWSQueryConnection):
if launch_config.key_name:
params['KeyName'] = launch_config.key_name
if launch_config.user_data:
- params['UserData'] = base64.b64encode(launch_config.user_data).decode('utf-8')
+ user_data = launch_config.user_data
+ if isinstance(user_data, six.text_type):
+ user_data = user_data.encode('utf-8')
+ params['UserData'] = base64.b64encode(user_data).decode('utf-8')
if launch_config.kernel_id:
params['KernelId'] = launch_config.kernel_id
if launch_config.ramdisk_id:
diff --git a/boto/ec2/autoscale/launchconfig.py b/boto/ec2/autoscale/launchconfig.py
index 6d897fd2..889cee21 100644
--- a/boto/ec2/autoscale/launchconfig.py
+++ b/boto/ec2/autoscale/launchconfig.py
@@ -128,8 +128,8 @@ class LaunchConfiguration(object):
:type instance_type: str
:param instance_type: The instance type
- :type kern_id: str
- :param kern_id: Kernel id for instance
+ :type kernel_id: str
+ :param kernel_id: Kernel id for instance
:type ramdisk_id: str
:param ramdisk_id: RAM disk id for instance
diff --git a/boto/ec2/elb/__init__.py b/boto/ec2/elb/__init__.py
index 5ae272f7..6123909c 100644
--- a/boto/ec2/elb/__init__.py
+++ b/boto/ec2/elb/__init__.py
@@ -400,7 +400,7 @@ class ELBConnection(AWSQueryConnection):
:param attribute: The attribute you wish to change.
* crossZoneLoadBalancing - Boolean (true)
- * connectionSettings - :py:class:`ConnectionSettingAttribute` instance
+ * connectingSettings - :py:class:`ConnectionSettingAttribute` instance
* accessLog - :py:class:`AccessLogAttribute` instance
* connectionDraining - :py:class:`ConnectionDrainingAttribute` instance
@@ -437,7 +437,7 @@ class ELBConnection(AWSQueryConnection):
value.enabled and 'true' or 'false'
params['LoadBalancerAttributes.ConnectionDraining.Timeout'] = \
value.timeout
- elif attribute.lower == 'connectingsettings':
+ elif attribute.lower() == 'connectingsettings':
params['LoadBalancerAttributes.ConnectionSettings.IdleTimeout'] = \
value.idle_timeout
else:
@@ -472,7 +472,7 @@ class ELBConnection(AWSQueryConnection):
* accessLog - :py:class:`AccessLogAttribute` instance
* crossZoneLoadBalancing - Boolean
- * connectionSettings - :py:class:`ConnectionSettingAttribute` instance
+ * connectingSettings - :py:class:`ConnectionSettingAttribute` instance
* connectionDraining - :py:class:`ConnectionDrainingAttribute`
instance
diff --git a/boto/endpoints.json b/boto/endpoints.json
index 5c95b5ca..46276438 100644
--- a/boto/endpoints.json
+++ b/boto/endpoints.json
@@ -35,6 +35,9 @@
},
"cloudsearch": {
"ap-southeast-1": "cloudsearch.ap-southeast-1.amazonaws.com",
+ "ap-southeast-2": "cloudsearch.ap-southeast-2.amazonaws.com",
+ "ap-northeast-1": "cloudsearch.ap-northeast-1.amazonaws.com",
+ "sa-east-1": "cloudsearch.sa-east-1.amazonaws.com",
"eu-west-1": "cloudsearch.eu-west-1.amazonaws.com",
"us-east-1": "cloudsearch.us-east-1.amazonaws.com",
"us-west-1": "cloudsearch.us-west-1.amazonaws.com",
@@ -62,6 +65,12 @@
"us-west-1": "monitoring.us-west-1.amazonaws.com",
"us-west-2": "monitoring.us-west-2.amazonaws.com"
},
+ "cognito-identity": {
+ "us-east-1": "cognito-identity.us-east-1.amazonaws.com"
+ },
+ "cognito-sync": {
+ "us-east-1": "cognito-sync.us-east-1.amazonaws.com"
+ },
"datapipeline": {
"us-east-1": "datapipeline.us-east-1.amazonaws.com",
"us-west-2": "datapipeline.us-west-2.amazonaws.com",
diff --git a/boto/exception.py b/boto/exception.py
index 9baa0999..36c226fa 100644
--- a/boto/exception.py
+++ b/boto/exception.py
@@ -30,9 +30,10 @@ import xml.sax
import boto
from boto import handler
-from boto.compat import json, six, StandardError
+from boto.compat import json, StandardError
from boto.resultset import ResultSet
+
class BotoClientError(StandardError):
"""
General Boto Client error (error accessing AWS)
@@ -112,7 +113,7 @@ class BotoServerError(StandardError):
try:
h = handler.XmlHandlerWrapper(self, self)
h.parseString(self.body)
- except (TypeError, xml.sax.SAXParseException) as pe:
+ except (TypeError, xml.sax.SAXParseException):
# What if it's JSON? Let's try that.
try:
parsed = json.loads(self.body)
@@ -209,6 +210,7 @@ class StorageCreateError(BotoServerError):
else:
return super(StorageCreateError, self).endElement(name, value, connection)
+
class S3CreateError(StorageCreateError):
"""
Error creating a bucket or key on S3.
@@ -294,15 +296,15 @@ class StorageResponseError(BotoServerError):
super(StorageResponseError, self).__init__(status, reason, body)
def startElement(self, name, attrs, connection):
- return super(StorageResponseError, self).startElement(name, attrs,
- connection)
+ return super(StorageResponseError, self).startElement(
+ name, attrs, connection)
def endElement(self, name, value, connection):
if name == 'Resource':
self.resource = value
else:
- return super(StorageResponseError, self).endElement(name, value,
- connection)
+ return super(StorageResponseError, self).endElement(
+ name, value, connection)
def _cleanupParsedProperties(self):
super(StorageResponseError, self)._cleanupParsedProperties()
@@ -332,8 +334,8 @@ class EC2ResponseError(BotoServerError):
self.errors = None
self._errorResultSet = []
super(EC2ResponseError, self).__init__(status, reason, body)
- self.errors = [ (e.error_code, e.error_message) \
- for e in self._errorResultSet ]
+ self.errors = [
+ (e.error_code, e.error_message) for e in self._errorResultSet]
if len(self.errors):
self.error_code, self.error_message = self.errors[0]
@@ -348,7 +350,7 @@ class EC2ResponseError(BotoServerError):
if name == 'RequestID':
self.request_id = value
else:
- return None # don't call subclass here
+ return None # don't call subclass here
def _cleanupParsedProperties(self):
super(EC2ResponseError, self)._cleanupParsedProperties()
@@ -420,30 +422,35 @@ class SDBResponseError(BotoServerError):
"""
pass
+
class AWSConnectionError(BotoClientError):
"""
General error connecting to Amazon Web Services.
"""
pass
+
class StorageDataError(BotoClientError):
"""
Error receiving data from a storage service.
"""
pass
+
class S3DataError(StorageDataError):
"""
Error receiving data from S3.
"""
pass
+
class GSDataError(StorageDataError):
"""
Error receiving data from GS.
"""
pass
+
class InvalidUriError(Exception):
"""Exception raised when URI is invalid."""
@@ -451,6 +458,7 @@ class InvalidUriError(Exception):
super(InvalidUriError, self).__init__(message)
self.message = message
+
class InvalidAclError(Exception):
"""Exception raised when ACL XML is invalid."""
@@ -458,6 +466,7 @@ class InvalidAclError(Exception):
super(InvalidAclError, self).__init__(message)
self.message = message
+
class InvalidCorsError(Exception):
"""Exception raised when CORS XML is invalid."""
@@ -465,10 +474,12 @@ class InvalidCorsError(Exception):
super(InvalidCorsError, self).__init__(message)
self.message = message
+
class NoAuthHandlerFound(Exception):
"""Is raised when no auth handlers were found ready to authenticate."""
pass
+
class InvalidLifecycleConfigError(Exception):
"""Exception raised when GCS lifecycle configuration XML is invalid."""
@@ -476,6 +487,7 @@ class InvalidLifecycleConfigError(Exception):
super(InvalidLifecycleConfigError, self).__init__(message)
self.message = message
+
# Enum class for resumable upload failure disposition.
class ResumableTransferDisposition(object):
# START_OVER means an attempt to resume an existing transfer failed,
@@ -500,6 +512,7 @@ class ResumableTransferDisposition(object):
# upload ID.
ABORT = 'ABORT'
+
class ResumableUploadException(Exception):
"""
Exception raised for various resumable upload problems.
@@ -516,6 +529,7 @@ class ResumableUploadException(Exception):
return 'ResumableUploadException("%s", %s)' % (
self.message, self.disposition)
+
class ResumableDownloadException(Exception):
"""
Exception raised for various resumable download problems.
@@ -532,6 +546,7 @@ class ResumableDownloadException(Exception):
return 'ResumableDownloadException("%s", %s)' % (
self.message, self.disposition)
+
class TooManyRecordsException(Exception):
"""
Exception raised when a search of Route53 records returns more
diff --git a/boto/handler.py b/boto/handler.py
index b079ada6..3b5f0732 100644
--- a/boto/handler.py
+++ b/boto/handler.py
@@ -14,7 +14,7 @@
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
-# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# 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.
@@ -23,6 +23,7 @@ import xml.sax
from boto.compat import StringIO
+
class XmlHandler(xml.sax.ContentHandler):
def __init__(self, root_node, connection):
diff --git a/boto/https_connection.py b/boto/https_connection.py
index 9222fbde..ddc31a15 100644
--- a/boto/https_connection.py
+++ b/boto/https_connection.py
@@ -27,111 +27,112 @@ import boto
from boto.compat import six, http_client
-class InvalidCertificateException(http_client.HTTPException):
- """Raised when a certificate is provided with an invalid hostname."""
- def __init__(self, host, cert, reason):
- """Constructor.
+class InvalidCertificateException(http_client.HTTPException):
+ """Raised when a certificate is provided with an invalid hostname."""
- Args:
- host: The hostname the connection was made to.
- cert: The SSL certificate (as a dictionary) the host returned.
- """
- http_client.HTTPException.__init__(self)
- self.host = host
- self.cert = cert
- self.reason = reason
+ def __init__(self, host, cert, reason):
+ """Constructor.
- def __str__(self):
- return ('Host %s returned an invalid certificate (%s): %s' %
- (self.host, self.reason, self.cert))
+ Args:
+ host: The hostname the connection was made to.
+ cert: The SSL certificate (as a dictionary) the host returned.
+ """
+ http_client.HTTPException.__init__(self)
+ self.host = host
+ self.cert = cert
+ self.reason = reason
-def GetValidHostsForCert(cert):
- """Returns a list of valid host globs for an SSL certificate.
-
- Args:
- cert: A dictionary representing an SSL certificate.
- Returns:
- list: A list of valid host globs.
- """
- if 'subjectAltName' in cert:
- return [x[1] for x in cert['subjectAltName'] if x[0].lower() == 'dns']
- else:
- return [x[0][1] for x in cert['subject']
- if x[0][0].lower() == 'commonname']
+ def __str__(self):
+ return ('Host %s returned an invalid certificate (%s): %s' %
+ (self.host, self.reason, self.cert))
-def ValidateCertificateHostname(cert, hostname):
- """Validates that a given hostname is valid for an SSL certificate.
-
- Args:
- cert: A dictionary representing an SSL certificate.
- hostname: The hostname to test.
- Returns:
- bool: Whether or not the hostname is valid for this certificate.
- """
- hosts = GetValidHostsForCert(cert)
- boto.log.debug(
- "validating server certificate: hostname=%s, certificate hosts=%s",
- hostname, hosts)
- for host in hosts:
- host_re = host.replace('.', '\.').replace('*', '[^.]*')
- if re.search('^%s$' % (host_re,), hostname, re.I):
- return True
- return False
+def GetValidHostsForCert(cert):
+ """Returns a list of valid host globs for an SSL certificate.
-class CertValidatingHTTPSConnection(http_client.HTTPConnection):
- """An HTTPConnection that connects over SSL and validates certificates."""
+ Args:
+ cert: A dictionary representing an SSL certificate.
+ Returns:
+ list: A list of valid host globs.
+ """
+ if 'subjectAltName' in cert:
+ return [x[1] for x in cert['subjectAltName'] if x[0].lower() == 'dns']
+ else:
+ return [x[0][1] for x in cert['subject']
+ if x[0][0].lower() == 'commonname']
- default_port = http_client.HTTPS_PORT
- def __init__(self, host, port=default_port, key_file=None, cert_file=None,
- ca_certs=None, strict=None, **kwargs):
- """Constructor.
+def ValidateCertificateHostname(cert, hostname):
+ """Validates that a given hostname is valid for an SSL certificate.
Args:
- host: The hostname. Can be in 'host:port' form.
- port: The port. Defaults to 443.
- key_file: A file containing the client's private key
- cert_file: A file containing the client's certificates
- ca_certs: A file contianing a set of concatenated certificate authority
- certs for validating the server against.
- strict: When true, causes BadStatusLine to be raised if the status line
- can't be parsed as a valid HTTP/1.0 or 1.1 status line.
+ cert: A dictionary representing an SSL certificate.
+ hostname: The hostname to test.
+ Returns:
+ bool: Whether or not the hostname is valid for this certificate.
"""
- if six.PY2:
- # Python 3.2 and newer have deprecated and removed the strict
- # parameter. Since the params are supported as keyword arguments
- # we conditionally add it here.
- kwargs['strict'] = strict
-
- http_client.HTTPConnection.__init__(self, host=host, port=port, **kwargs)
- self.key_file = key_file
- self.cert_file = cert_file
- self.ca_certs = ca_certs
-
- def connect(self):
- "Connect to a host on a given (SSL) port."
- if hasattr(self, "timeout"):
- sock = socket.create_connection((self.host, self.port), self.timeout)
- else:
- sock = socket.create_connection((self.host, self.port))
- msg = "wrapping ssl socket; "
- if self.ca_certs:
- msg += "CA certificate file=%s" %self.ca_certs
- else:
- msg += "using system provided SSL certs"
- boto.log.debug(msg)
- self.sock = ssl.wrap_socket(sock, keyfile=self.key_file,
- certfile=self.cert_file,
- cert_reqs=ssl.CERT_REQUIRED,
- ca_certs=self.ca_certs)
- cert = self.sock.getpeercert()
- hostname = self.host.split(':', 0)[0]
- if not ValidateCertificateHostname(cert, hostname):
- raise InvalidCertificateException(hostname,
- cert,
- 'remote hostname "%s" does not match '\
- 'certificate' % hostname)
+ hosts = GetValidHostsForCert(cert)
+ boto.log.debug(
+ "validating server certificate: hostname=%s, certificate hosts=%s",
+ hostname, hosts)
+ for host in hosts:
+ host_re = host.replace('.', '\.').replace('*', '[^.]*')
+ if re.search('^%s$' % (host_re,), hostname, re.I):
+ return True
+ return False
+class CertValidatingHTTPSConnection(http_client.HTTPConnection):
+ """An HTTPConnection that connects over SSL and validates certificates."""
+
+ default_port = http_client.HTTPS_PORT
+
+ def __init__(self, host, port=default_port, key_file=None, cert_file=None,
+ ca_certs=None, strict=None, **kwargs):
+ """Constructor.
+
+ Args:
+ host: The hostname. Can be in 'host:port' form.
+ port: The port. Defaults to 443.
+ key_file: A file containing the client's private key
+ cert_file: A file containing the client's certificates
+ ca_certs: A file contianing a set of concatenated certificate authority
+ certs for validating the server against.
+ strict: When true, causes BadStatusLine to be raised if the status line
+ can't be parsed as a valid HTTP/1.0 or 1.1 status line.
+ """
+ if six.PY2:
+ # Python 3.2 and newer have deprecated and removed the strict
+ # parameter. Since the params are supported as keyword arguments
+ # we conditionally add it here.
+ kwargs['strict'] = strict
+
+ http_client.HTTPConnection.__init__(self, host=host, port=port, **kwargs)
+ self.key_file = key_file
+ self.cert_file = cert_file
+ self.ca_certs = ca_certs
+
+ def connect(self):
+ "Connect to a host on a given (SSL) port."
+ if hasattr(self, "timeout"):
+ sock = socket.create_connection((self.host, self.port), self.timeout)
+ else:
+ sock = socket.create_connection((self.host, self.port))
+ msg = "wrapping ssl socket; "
+ if self.ca_certs:
+ msg += "CA certificate file=%s" % self.ca_certs
+ else:
+ msg += "using system provided SSL certs"
+ boto.log.debug(msg)
+ self.sock = ssl.wrap_socket(sock, keyfile=self.key_file,
+ certfile=self.cert_file,
+ cert_reqs=ssl.CERT_REQUIRED,
+ ca_certs=self.ca_certs)
+ cert = self.sock.getpeercert()
+ hostname = self.host.split(':', 0)[0]
+ if not ValidateCertificateHostname(cert, hostname):
+ raise InvalidCertificateException(hostname,
+ cert,
+ 'remote hostname "%s" does not match '
+ 'certificate' % hostname)
diff --git a/boto/jsonresponse.py b/boto/jsonresponse.py
index ac3f1b4a..f872b429 100644
--- a/boto/jsonresponse.py
+++ b/boto/jsonresponse.py
@@ -23,6 +23,7 @@
import xml.sax
from boto import utils
+
class XmlHandler(xml.sax.ContentHandler):
def __init__(self, root_node, connection):
@@ -52,7 +53,8 @@ class XmlHandler(xml.sax.ContentHandler):
if not isinstance(s, bytes):
s = s.encode('utf-8')
xml.sax.parseString(s, self)
-
+
+
class Element(dict):
def __init__(self, connection=None, element_name=None,
@@ -116,6 +118,7 @@ class Element(dict):
elif isinstance(self.parent, ListElement):
self.parent.append(value)
+
class ListElement(list):
def __init__(self, connection=None, element_name=None,
diff --git a/boto/plugin.py b/boto/plugin.py
index f8b592cc..2c2931c9 100644
--- a/boto/plugin.py
+++ b/boto/plugin.py
@@ -14,7 +14,7 @@
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
-# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# 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.
@@ -26,19 +26,20 @@ Implements plugin related api.
To define a new plugin just subclass Plugin, like this.
class AuthPlugin(Plugin):
- pass
+ pass
Then start creating subclasses of your new plugin.
class MyFancyAuth(AuthPlugin):
- capability = ['sign', 'vmac']
+ capability = ['sign', 'vmac']
The actual interface is duck typed.
-
"""
import glob
-import imp, os.path
+import imp
+import os.path
+
class Plugin(object):
"""Base class for all plugins."""
@@ -50,10 +51,11 @@ class Plugin(object):
"""Returns true if the requested capability is supported by this plugin
"""
for c in requested_capability:
- if not c in cls.capability:
+ if c not in cls.capability:
return False
return True
+
def get_plugin(cls, requested_capability=None):
if not requested_capability:
requested_capability = []
@@ -63,18 +65,20 @@ def get_plugin(cls, requested_capability=None):
result.append(handler)
return result
+
def _import_module(filename):
(path, name) = os.path.split(filename)
(name, ext) = os.path.splitext(name)
(file, filename, data) = imp.find_module(name, [path])
try:
- return imp.load_module(name, file, filename, data)
+ return imp.load_module(name, file, filename, data)
finally:
- if file:
- file.close()
+ if file:
+ file.close()
+
+_plugin_loaded = False
-_plugin_loaded = False
def load_plugins(config):
global _plugin_loaded
@@ -87,4 +91,3 @@ def load_plugins(config):
directory = config.get('Plugin', 'plugin_directory')
for file in glob.glob(os.path.join(directory, '*.py')):
_import_module(file)
-
diff --git a/boto/provider.py b/boto/provider.py
index 8e04bff3..0da2f78a 100644
--- a/boto/provider.py
+++ b/boto/provider.py
@@ -69,7 +69,8 @@ STORAGE_PERMISSIONS_ERROR = 'StoragePermissionsError'
STORAGE_RESPONSE_ERROR = 'StorageResponseError'
-class ProfileNotFoundError(ValueError): pass
+class ProfileNotFoundError(ValueError):
+ pass
class Provider(object):
@@ -252,7 +253,7 @@ class Provider(object):
# datetime docs.
seconds_left = (
(delta.microseconds + (delta.seconds + delta.days * 24 * 3600)
- * 10**6) / 10**6)
+ * 10 ** 6) / 10 ** 6)
if seconds_left < (5 * 60):
boto.log.debug("Credentials need to be refreshed.")
return True
@@ -444,6 +445,7 @@ class Provider(object):
def supports_chunked_transfer(self):
return self.ChunkedTransferSupport[self.name]
+
# Static utility method for getting default Provider.
def get_default():
return Provider('aws')
diff --git a/boto/regioninfo.py b/boto/regioninfo.py
index 5862f16d..6aeda122 100644
--- a/boto/regioninfo.py
+++ b/boto/regioninfo.py
@@ -124,7 +124,7 @@ def get_regions(service_name, region_cls=None, connection_cls=None):
"""
endpoints = load_regions()
- if not service_name in endpoints:
+ if service_name not in endpoints:
raise BotoClientError(
"Service '%s' not found in endpoints." % service_name
)
diff --git a/boto/requestlog.py b/boto/requestlog.py
index 5f1c2551..d8009fe7 100644
--- a/boto/requestlog.py
+++ b/boto/requestlog.py
@@ -1,9 +1,11 @@
-
+import sys
from datetime import datetime
from threading import Thread
import Queue
from boto.utils import RequestHook
+from boto.compat import long_type
+
class RequestLogger(RequestHook):
"""
@@ -14,18 +16,16 @@ class RequestLogger(RequestHook):
self.request_log_file = open(filename, 'w')
self.request_log_queue = Queue.Queue(100)
Thread(target=self._request_log_worker).start()
-
def handle_request_data(self, request, response, error=False):
len = 0 if error else response.getheader('Content-Length')
now = datetime.now()
time = now.strftime('%Y-%m-%d %H:%M:%S')
td = (now - request.start_time)
- duration = (td.microseconds + long(td.seconds + td.days*24*3600) * 1e6) / 1e6
-
+ duration = (td.microseconds + long_type(td.seconds + td.days * 24 * 3600) * 1e6) / 1e6
+
# write output including timestamp, status code, response time, response size, request action
self.request_log_queue.put("'%s', '%s', '%s', '%s', '%s'\n" % (time, response.status, duration, len, request.params['Action']))
-
def _request_log_worker(self):
while True:
@@ -35,5 +35,5 @@ class RequestLogger(RequestHook):
self.request_log_file.flush()
self.request_log_queue.task_done()
except:
- import traceback; traceback.print_exc(file=sys.stdout)
-
+ import traceback
+ traceback.print_exc(file=sys.stdout)
diff --git a/boto/resultset.py b/boto/resultset.py
index 83052582..189a47a3 100644
--- a/boto/resultset.py
+++ b/boto/resultset.py
@@ -21,14 +21,15 @@
from boto.s3.user import User
+
class ResultSet(list):
"""
The ResultSet is used to pass results back from the Amazon services
to the client. It is light wrapper around Python's :py:class:`list` class,
- with some additional methods for parsing XML results from AWS.
- Because I don't really want any dependencies on external libraries,
- I'm using the standard SAX parser that comes with Python. The good news is
- that it's quite fast and efficient but it makes some things rather
+ with some additional methods for parsing XML results from AWS.
+ Because I don't really want any dependencies on external libraries,
+ I'm using the standard SAX parser that comes with Python. The good news is
+ that it's quite fast and efficient but it makes some things rather
difficult.
You can pass in, as the marker_elem parameter, a list of tuples.
@@ -54,7 +55,7 @@ class ResultSet(list):
self.next_key_marker = None
self.next_upload_id_marker = None
self.next_version_id_marker = None
- self.next_generation_marker= None
+ self.next_generation_marker = None
self.version_id_marker = None
self.is_truncated = False
self.next_token = None
@@ -132,6 +133,7 @@ class ResultSet(list):
else:
setattr(self, name, value)
+
class BooleanResult(object):
def __init__(self, marker_elem=None):
diff --git a/boto/route53/connection.py b/boto/route53/connection.py
index 9f17781a..c13ab2e0 100644
--- a/boto/route53/connection.py
+++ b/boto/route53/connection.py
@@ -521,12 +521,18 @@ class Route53Connection(AWSAuthConnection):
if response.status == 400:
code = response.getheader('Code')
- if code and 'PriorRequestNotComplete' in code:
+ if code:
# This is a case where we need to ignore a 400 error, as
# Route53 returns this. See
# http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html
+ if 'PriorRequestNotComplete' in code:
+ error = 'PriorRequestNotComplete'
+ elif 'Throttling' in code:
+ error = 'Throttling'
+ else:
+ return status
msg = "%s, retry attempt %s" % (
- 'PriorRequestNotComplete',
+ error,
i
)
next_sleep = min(random.random() * (2 ** i),
diff --git a/boto/route53/zone.py b/boto/route53/zone.py
index 167a0891..b21c8de4 100644
--- a/boto/route53/zone.py
+++ b/boto/route53/zone.py
@@ -233,7 +233,14 @@ class Zone(object):
# name/type for get_all_rrsets sets the starting record; they
# are not a filter
- results = [r for r in returned if r.name == name and r.type == type]
+ results = []
+ for r in returned:
+ if r.name == name and r.type == type:
+ results.append(r)
+ # Is at the end of the list of matched records. No need to continue
+ # since the records are sorted by name and type.
+ else:
+ break
weight = None
region = None
diff --git a/boto/storage_uri.py b/boto/storage_uri.py
index 40fee473..34b7b060 100755
--- a/boto/storage_uri.py
+++ b/boto/storage_uri.py
@@ -87,8 +87,8 @@ class StorageUri(object):
for arg in args:
if args[arg]:
sys.stderr.write(
- 'Warning: %s ignores argument: %s=%s\n' %
- (function_name, arg, str(args[arg])))
+ 'Warning: %s ignores argument: %s=%s\n' %
+ (function_name, arg, str(args[arg])))
def connect(self, access_key_id=None, secret_access_key=None, **kwargs):
"""
@@ -103,7 +103,7 @@ class StorageUri(object):
connection_args = dict(self.connection_args or ())
if (hasattr(self, 'suppress_consec_slashes') and
- 'suppress_consec_slashes' not in connection_args):
+ 'suppress_consec_slashes' not in connection_args):
connection_args['suppress_consec_slashes'] = (
self.suppress_consec_slashes)
connection_args.update(kwargs)
@@ -167,7 +167,7 @@ class StorageUri(object):
if all_versions:
return (v for v in bucket.list_versions(
prefix=prefix, delimiter=delimiter, headers=headers)
- if not isinstance(v, DeleteMarker))
+ if not isinstance(v, DeleteMarker))
else:
return bucket.list(prefix=prefix, delimiter=delimiter,
headers=headers)
@@ -247,7 +247,7 @@ class BucketStorageUri(StorageUri):
"""
delim = '/'
- capabilities = set([]) # A set of additional capabilities.
+ capabilities = set([]) # A set of additional capabilities.
def __init__(self, scheme, bucket_name=None, object_name=None,
debug=0, connection_args=None, suppress_consec_slashes=True,
@@ -299,37 +299,37 @@ class BucketStorageUri(StorageUri):
self._build_uri_strings()
def _build_uri_strings(self):
- if self.bucket_name and self.object_name:
- self.versionless_uri = '%s://%s/%s' % (self.scheme, self.bucket_name,
- self.object_name)
- if self.generation:
- self.version_specific_uri = '%s#%s' % (self.versionless_uri,
- self.generation)
- elif self.version_id:
- self.version_specific_uri = '%s#%s' % (
- self.versionless_uri, self.version_id)
- if self.is_version_specific:
- self.uri = self.version_specific_uri
- else:
- self.uri = self.versionless_uri
- elif self.bucket_name:
- self.uri = ('%s://%s/' % (self.scheme, self.bucket_name))
- else:
- self.uri = ('%s://' % self.scheme)
+ if self.bucket_name and self.object_name:
+ self.versionless_uri = '%s://%s/%s' % (self.scheme, self.bucket_name,
+ self.object_name)
+ if self.generation:
+ self.version_specific_uri = '%s#%s' % (self.versionless_uri,
+ self.generation)
+ elif self.version_id:
+ self.version_specific_uri = '%s#%s' % (
+ self.versionless_uri, self.version_id)
+ if self.is_version_specific:
+ self.uri = self.version_specific_uri
+ else:
+ self.uri = self.versionless_uri
+ elif self.bucket_name:
+ self.uri = ('%s://%s/' % (self.scheme, self.bucket_name))
+ else:
+ self.uri = ('%s://' % self.scheme)
def _update_from_key(self, key):
- self._update_from_values(
- getattr(key, 'version_id', None),
- getattr(key, 'generation', None),
- getattr(key, 'is_latest', None),
- getattr(key, 'md5', None))
+ self._update_from_values(
+ getattr(key, 'version_id', None),
+ getattr(key, 'generation', None),
+ getattr(key, 'is_latest', None),
+ getattr(key, 'md5', None))
def _update_from_values(self, version_id, generation, is_latest, md5):
- self.version_id = version_id
- self.generation = generation
- self.is_latest = is_latest
- self._build_uri_strings()
- self.md5 = md5
+ self.version_id = version_id
+ self.generation = generation
+ self.is_latest = is_latest
+ self._build_uri_strings()
+ self.md5 = md5
def get_key(self, validate=False, headers=None, version_id=None):
self._check_object_uri('get_key')
@@ -388,14 +388,14 @@ class BucketStorageUri(StorageUri):
is_latest = key.is_latest
return BucketStorageUri(
- key.provider.get_provider_name(),
- bucket_name=key.bucket.name,
- object_name=key.name,
- debug=self.debug,
- suppress_consec_slashes=self.suppress_consec_slashes,
- version_id=version_id,
- generation=generation,
- is_latest=is_latest)
+ key.provider.get_provider_name(),
+ bucket_name=key.bucket.name,
+ object_name=key.name,
+ debug=self.debug,
+ suppress_consec_slashes=self.suppress_consec_slashes,
+ version_id=version_id,
+ generation=generation,
+ is_latest=is_latest)
def get_acl(self, validate=False, headers=None, version_id=None):
"""returns a bucket's acl"""
@@ -464,8 +464,8 @@ class BucketStorageUri(StorageUri):
'URIs.' % self.scheme)
if self.object_name:
if recursive:
- raise ValueError('add_group_email_grant() on key-ful URI cannot '
- 'specify recursive=True')
+ raise ValueError('add_group_email_grant() on key-ful URI cannot '
+ 'specify recursive=True')
key = self.get_key(validate, headers)
self.check_response(key, 'key', self.uri)
key.add_group_email_grant(permission, email_address, headers)
@@ -556,10 +556,10 @@ class BucketStorageUri(StorageUri):
# Pass storage_class param only if this is a GCS bucket. (In S3 the
# storage class is specified on the key object.)
if self.scheme == 'gs':
- return conn.create_bucket(self.bucket_name, headers, location, policy,
- storage_class)
+ return conn.create_bucket(self.bucket_name, headers, location, policy,
+ storage_class)
else:
- return conn.create_bucket(self.bucket_name, headers, location, policy)
+ return conn.create_bucket(self.bucket_name, headers, location, policy)
def delete_bucket(self, headers=None):
self._check_bucket_uri('delete_bucket')
@@ -583,27 +583,27 @@ class BucketStorageUri(StorageUri):
key_name = key_name or self.object_name or ''
bucket = self.get_bucket(validate, headers)
if self.generation:
- bucket.set_acl(
- acl_or_str, key_name, headers, generation=self.generation,
- if_generation=if_generation, if_metageneration=if_metageneration)
+ bucket.set_acl(
+ acl_or_str, key_name, headers, generation=self.generation,
+ if_generation=if_generation, if_metageneration=if_metageneration)
else:
- version_id = version_id or self.version_id
- bucket.set_acl(acl_or_str, key_name, headers, version_id)
+ version_id = version_id or self.version_id
+ bucket.set_acl(acl_or_str, key_name, headers, version_id)
def set_xml_acl(self, xmlstring, key_name='', validate=False, headers=None,
- version_id=None, if_generation=None, if_metageneration=None):
+ version_id=None, if_generation=None, if_metageneration=None):
"""Sets or updates a bucket's ACL with an XML string."""
self._check_bucket_uri('set_xml_acl')
key_name = key_name or self.object_name or ''
bucket = self.get_bucket(validate, headers)
if self.generation:
- bucket.set_xml_acl(
- xmlstring, key_name, headers, generation=self.generation,
- if_generation=if_generation, if_metageneration=if_metageneration)
+ bucket.set_xml_acl(
+ xmlstring, key_name, headers, generation=self.generation,
+ if_generation=if_generation, if_metageneration=if_metageneration)
else:
- version_id = version_id or self.version_id
- bucket.set_xml_acl(xmlstring, key_name, headers,
- version_id=version_id)
+ version_id = version_id or self.version_id
+ bucket.set_xml_acl(xmlstring, key_name, headers,
+ version_id=version_id)
def set_def_xml_acl(self, xmlstring, validate=False, headers=None):
"""Sets or updates a bucket's default object ACL with an XML string."""
@@ -699,14 +699,16 @@ class BucketStorageUri(StorageUri):
self._check_object_uri('copy_key')
dst_bucket = self.get_bucket(validate=False, headers=headers)
if src_generation:
- return dst_bucket.copy_key(new_key_name=self.object_name,
+ return dst_bucket.copy_key(
+ new_key_name=self.object_name,
src_bucket_name=src_bucket_name,
src_key_name=src_key_name, metadata=metadata,
storage_class=storage_class, preserve_acl=preserve_acl,
encrypt_key=encrypt_key, headers=headers, query_args=query_args,
src_generation=src_generation)
else:
- return dst_bucket.copy_key(new_key_name=self.object_name,
+ return dst_bucket.copy_key(
+ new_key_name=self.object_name,
src_bucket_name=src_bucket_name, src_key_name=src_key_name,
metadata=metadata, src_version_id=src_version_id,
storage_class=storage_class, preserve_acl=preserve_acl,
@@ -766,7 +768,7 @@ class BucketStorageUri(StorageUri):
component_keys.append(suri.new_key())
component_keys[-1].generation = suri.generation
self.generation = self.new_key().compose(
- component_keys, content_type=content_type, headers=headers)
+ component_keys, content_type=content_type, headers=headers)
self._build_uri_strings()
return self
@@ -786,12 +788,13 @@ class BucketStorageUri(StorageUri):
bucket.configure_lifecycle(lifecycle_config, headers)
def exists(self, headers=None):
- """Returns True if the object exists or False if it doesn't"""
- if not self.object_name:
- raise InvalidUriError('exists on object-less URI (%s)' % self.uri)
- bucket = self.get_bucket()
- key = bucket.get_key(self.object_name, headers=headers)
- return bool(key)
+ """Returns True if the object exists or False if it doesn't"""
+ if not self.object_name:
+ raise InvalidUriError('exists on object-less URI (%s)' % self.uri)
+ bucket = self.get_bucket()
+ key = bucket.get_key(self.object_name, headers=headers)
+ return bool(key)
+
class FileStorageUri(StorageUri):
"""
@@ -881,8 +884,8 @@ class FileStorageUri(StorageUri):
self.get_key().close()
def exists(self, _headers_not_used=None):
- """Returns True if the file exists or False if it doesn't"""
- # The _headers_not_used parameter is ignored. It is only there to ensure
- # that this method's signature is identical to the exists method on the
- # BucketStorageUri class.
- return os.path.exists(self.object_name)
+ """Returns True if the file exists or False if it doesn't"""
+ # The _headers_not_used parameter is ignored. It is only there to ensure
+ # that this method's signature is identical to the exists method on the
+ # BucketStorageUri class.
+ return os.path.exists(self.object_name)
diff --git a/boto/sts/connection.py b/boto/sts/connection.py
index 59526222..e02f0f1e 100644
--- a/boto/sts/connection.py
+++ b/boto/sts/connection.py
@@ -92,7 +92,7 @@ class STSConnection(AWSQueryConnection):
if self.anon:
return ['pure-query']
else:
- return ['sign-v2']
+ return ['hmac-v4']
def _check_token_cache(self, token_key, duration=None, window_seconds=60):
token = _session_token_cache.get(token_key, None)
diff --git a/boto/utils.py b/boto/utils.py
index dca332fe..0e7e3a79 100644
--- a/boto/utils.py
+++ b/boto/utils.py
@@ -392,7 +392,7 @@ def get_instance_metadata(version='latest', url='http://169.254.169.254',
try:
metadata_url = _build_instance_metadata_url(url, version, data)
return _get_instance_metadata(metadata_url, num_retries=num_retries, timeout=timeout)
- except urllib.error.URLError as e:
+ except urllib.error.URLError:
return None
@@ -414,7 +414,7 @@ def get_instance_identity(version='latest', url='http://169.254.169.254',
if field:
iid[field] = val
return iid
- except urllib.error.URLError as e:
+ except urllib.error.URLError:
return None
@@ -436,6 +436,7 @@ ISO8601_MS = '%Y-%m-%dT%H:%M:%S.%fZ'
RFC1123 = '%a, %d %b %Y %H:%M:%S %Z'
LOCALE_LOCK = threading.Lock()
+
@contextmanager
def setlocale(name):
"""
@@ -449,6 +450,7 @@ def setlocale(name):
finally:
locale.setlocale(locale.LC_ALL, saved)
+
def get_ts(ts=None):
if not ts:
ts = time.gmtime()
@@ -1038,6 +1040,7 @@ def merge_headers_by_name(name, headers):
return ','.join(str(headers[h]) for h in matching_headers
if headers[h] is not None)
+
class RequestHook(object):
"""
This can be extended and supplied to the connection object
diff --git a/boto/vpc/routetable.py b/boto/vpc/routetable.py
index 7f83717e..21060ee9 100644
--- a/boto/vpc/routetable.py
+++ b/boto/vpc/routetable.py
@@ -67,6 +67,8 @@ class Route(object):
self.destination_cidr_block = None
self.gateway_id = None
self.instance_id = None
+ self.interface_id = None
+ self.vpc_peering_connection_id = None
self.state = None
def __repr__(self):
@@ -82,6 +84,10 @@ class Route(object):
self.gateway_id = value
elif name == 'instanceId':
self.instance_id = value
+ elif name == 'networkInterfaceId':
+ self.interface_id = value
+ elif name == 'vpcPeeringConnectionId':
+ self.vpc_peering_connection_id = value
elif name == 'state':
self.state = value
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 00f26417..914c5328 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -73,6 +73,8 @@ Currently Supported Services
* :doc:`Simple Queue Service (SQS) <sqs_tut>` -- (:doc:`API Reference <ref/sqs>`) (Python 3)
* Simple Notification Service (SNS) -- (:doc:`API Reference <ref/sns>`) (Python 3)
* :doc:`Simple Email Service (SES) <ses_tut>` -- (:doc:`API Reference <ref/ses>`) (Python 3)
+ * Amazon Cognito Identity -- (:doc:`API Reference <ref/cognito-identity`) (Python 3)
+ * Amazon Cognito Sync -- (:doc:`API Reference <ref/cognito-sync`) (Python 3)
* **Monitoring**
diff --git a/docs/source/ref/cognito-identity.rst b/docs/source/ref/cognito-identity.rst
new file mode 100644
index 00000000..65ec98ff
--- /dev/null
+++ b/docs/source/ref/cognito-identity.rst
@@ -0,0 +1,26 @@
+.. ref-cognito-identity
+
+================
+Cognito Identity
+================
+
+boto.cognito.identity
+---------------------
+
+.. automodule:: boto.cognito.identity
+ :members:
+ :undoc-members:
+
+boto.cognito.identity.layer1
+----------------------------
+
+.. automodule:: boto.cognito.identity.layer1
+ :members:
+ :undoc-members:
+
+boto.cognito.identity.exceptions
+--------------------------------
+
+.. automodule:: boto.cognito.identity.exceptions
+ :members:
+ :undoc-members:
diff --git a/docs/source/ref/cognito-sync.rst b/docs/source/ref/cognito-sync.rst
new file mode 100644
index 00000000..a8a6dd40
--- /dev/null
+++ b/docs/source/ref/cognito-sync.rst
@@ -0,0 +1,26 @@
+.. ref-cognito-sync
+
+============
+Cognito Sync
+============
+
+boto.cognito.sync
+-----------------
+
+.. automodule:: boto.cognito.sync
+ :members:
+ :undoc-members:
+
+boto.cognito.sync.layer1
+------------------------
+
+.. automodule:: boto.cognito.sync.layer1
+ :members:
+ :undoc-members:
+
+boto.cognito.sync.exceptions
+----------------------------
+
+.. automodule:: boto.cognito.sync.exceptions
+ :members:
+ :undoc-members:
diff --git a/docs/source/s3_tut.rst b/docs/source/s3_tut.rst
index e5de8af9..98cd235a 100644
--- a/docs/source/s3_tut.rst
+++ b/docs/source/s3_tut.rst
@@ -53,7 +53,7 @@ later, first let's just create a bucket. That can be accomplished like this::
raise S3CreateError(response.status, response.reason)
boto.exception.S3CreateError: S3Error[409]: Conflict
-Whoa. What happended there? Well, the thing you have to know about
+Whoa. What happened there? Well, the thing you have to know about
buckets is that they are kind of like domain names. It's one flat name
space that everyone who uses S3 shares. So, someone has already create
a bucket called "mybucket" in S3 and that means no one else can grab that
diff --git a/scripts/git-release-notes.py b/scripts/git-release-notes.py
index 6655b61c..5b6faaaf 100755
--- a/scripts/git-release-notes.py
+++ b/scripts/git-release-notes.py
@@ -30,7 +30,7 @@ for hunk in revisions.split('~~~')[:-1]:
parents = lines[1].split(' ', 1)[1].split(' ')
message = ' '.join(lines[2:])
- #print(commit, parents)
+ # print(commit, parents)
if RELEASE.search(message):
print('Found release commit, stopping:')
diff --git a/scripts/rebuild_endpoints.py b/scripts/rebuild_endpoints.py
index f5f12809..37ac37d8 100644
--- a/scripts/rebuild_endpoints.py
+++ b/scripts/rebuild_endpoints.py
@@ -19,6 +19,7 @@ def fetch_endpoints():
return resp.text
+
def parse_xml(raw_xml):
return pq(raw_xml, parser='xml')
diff --git a/setup.py b/setup.py
index af7db329..b46e683e 100644
--- a/setup.py
+++ b/setup.py
@@ -77,7 +77,8 @@ setup(name = "boto",
"boto.dynamodb2", "boto.support", "boto.cloudtrail",
"boto.directconnect", "boto.kinesis", "boto.rds2",
"boto.cloudsearch2", "boto.logs", "boto.vendored",
- "boto.route53.domains"],
+ "boto.route53.domains", "boto.cognito",
+ "boto.cognito.identity", "boto.cognito.sync"],
package_data = {
"boto.cacerts": ["cacerts.txt"],
"boto": ["endpoints.json"],
diff --git a/tests/__init__.py b/tests/__init__.py
index b3fc3a0c..771ca94b 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -14,7 +14,7 @@
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
-# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# 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.
diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py
index ed359279..f03a609b 100644
--- a/tests/integration/__init__.py
+++ b/tests/integration/__init__.py
@@ -24,6 +24,7 @@
Base class to make checking the certs easier.
"""
+
# We subclass from ``object`` instead of ``TestCase`` here so that this doesn't
# add noise to the test suite (otherwise these no-ops would run on every
# import).
@@ -60,4 +61,3 @@ class ServiceCertVerificationTest(object):
always succeed (like fetch a list, even if it's empty).
"""
pass
-
diff --git a/tests/integration/cognito/__init__.py b/tests/integration/cognito/__init__.py
new file mode 100644
index 00000000..571bc50d
--- /dev/null
+++ b/tests/integration/cognito/__init__.py
@@ -0,0 +1,41 @@
+# 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
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# 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.
+#
+
+import boto
+from tests.compat import unittest
+
+
+class CognitoTest(unittest.TestCase):
+ def setUp(self):
+ self.cognito_identity = boto.connect_cognito_identity()
+ self.cognito_sync = boto.connect_cognito_sync()
+ self.identity_pool_name = 'myIdentityPool'
+ response = self.cognito_identity.create_identity_pool(
+ identity_pool_name=self.identity_pool_name,
+ allow_unauthenticated_identities=False
+ )
+ self.identity_pool_id = response['IdentityPoolId']
+
+ def tearDown(self):
+ self.cognito_identity.delete_identity_pool(
+ identity_pool_id=self.identity_pool_id
+ )
diff --git a/tests/integration/cognito/identity/__init__.py b/tests/integration/cognito/identity/__init__.py
new file mode 100644
index 00000000..70cc23fe
--- /dev/null
+++ b/tests/integration/cognito/identity/__init__.py
@@ -0,0 +1,21 @@
+# 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
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# 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.
+#
diff --git a/tests/integration/cognito/identity/test_cognito_identity.py b/tests/integration/cognito/identity/test_cognito_identity.py
new file mode 100644
index 00000000..7d9f6647
--- /dev/null
+++ b/tests/integration/cognito/identity/test_cognito_identity.py
@@ -0,0 +1,52 @@
+# 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
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# 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.cognito.identity.exceptions import ResourceNotFoundException
+from tests.integration.cognito import CognitoTest
+
+
+class TestCognitoIdentity(CognitoTest):
+ """
+ Test Cognitoy identity pools operations since individual Cognito identities
+ require an AWS account ID.
+ """
+ def test_cognito_identity(self):
+ # Ensure the identity pool is in the list of pools.
+ response = self.cognito_identity.list_identity_pools(max_results=5)
+ expected_identity = {'IdentityPoolId': self.identity_pool_id,
+ 'IdentityPoolName': self.identity_pool_name}
+ self.assertIn(expected_identity, response['IdentityPools'])
+
+ # Ensure the pool's attributes are as expected.
+ response = self.cognito_identity.describe_identity_pool(
+ identity_pool_id=self.identity_pool_id
+ )
+ self.assertEqual(response['IdentityPoolName'], self.identity_pool_name)
+ self.assertEqual(response['IdentityPoolId'], self.identity_pool_id)
+ self.assertFalse(response['AllowUnauthenticatedIdentities'])
+
+ def test_resource_not_found_exception(self):
+ with self.assertRaises(ResourceNotFoundException):
+ # Note the region is us-east-0 which is an invalid region name.
+ self.cognito_identity.describe_identity_pool(
+ identity_pool_id='us-east-0:c09e640-b014-4822-86b9-ec77c40d8d6f'
+ )
diff --git a/tests/integration/cognito/sync/__init__.py b/tests/integration/cognito/sync/__init__.py
new file mode 100644
index 00000000..70cc23fe
--- /dev/null
+++ b/tests/integration/cognito/sync/__init__.py
@@ -0,0 +1,21 @@
+# 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
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# 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.
+#
diff --git a/tests/integration/cognito/sync/test_cognito_sync.py b/tests/integration/cognito/sync/test_cognito_sync.py
new file mode 100644
index 00000000..fdfd40ee
--- /dev/null
+++ b/tests/integration/cognito/sync/test_cognito_sync.py
@@ -0,0 +1,46 @@
+# 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
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# 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.cognito.sync.exceptions import ResourceNotFoundException
+from tests.integration.cognito import CognitoTest
+
+
+class TestCognitoSync(CognitoTest):
+ """
+ Even more so for Cognito Sync, Cognito identites are required. However,
+ AWS account IDs are required to aqcuire a Cognito identity so only
+ Cognito pool identity related operations are tested.
+ """
+ def test_cognito_sync(self):
+ response = self.cognito_sync.describe_identity_pool_usage(
+ identity_pool_id=self.identity_pool_id
+ )
+ identity_pool_usage = response['IdentityPoolUsage']
+ self.assertEqual(identity_pool_usage['SyncSessionsCount'], None)
+ self.assertEqual(identity_pool_usage['DataStorage'], 0)
+
+ def test_resource_not_found_exception(self):
+ with self.assertRaises(ResourceNotFoundException):
+ # Note the region is us-east-0 which is an invalid region name.
+ self.cognito_sync.describe_identity_pool_usage(
+ identity_pool_id='us-east-0:c09e640-b014-4822-86b9-ec77c40d8d6f'
+ )
diff --git a/tests/test.py b/tests/test.py
index 7e91af35..692ed4dd 100755
--- a/tests/test.py
+++ b/tests/test.py
@@ -71,6 +71,7 @@ PY3_WHITELIST = (
'tests/unit/test_regioninfo.py',
)
+
def main(whitelist=[]):
description = ("Runs boto unit and/or integration tests. "
"Arguments will be passed on to nosetests. "
@@ -83,7 +84,7 @@ def main(whitelist=[]):
known_args, remaining_args = parser.parse_known_args()
attribute_args = []
for service_attribute in known_args.service_tests:
- attribute_args.extend(['-a', '!notdefault,' +service_attribute])
+ attribute_args.extend(['-a', '!notdefault,' + service_attribute])
if not attribute_args:
# If the user did not specify any filtering criteria, we at least
# will filter out any test tagged 'notdefault'.
diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py
index 56d386f8..9751decd 100644
--- a/tests/unit/__init__.py
+++ b/tests/unit/__init__.py
@@ -1,6 +1,7 @@
from boto.compat import http_client
from tests.compat import mock, unittest
+
class AWSMockServiceTestCase(unittest.TestCase):
"""Base class for mocking aws services."""
# This param is used by the unittest module to display a full
@@ -46,6 +47,7 @@ class AWSMockServiceTestCase(unittest.TestCase):
response.getheaders.return_value = header
response.msg = dict(header)
+
def overwrite_header(arg, default=None):
header_dict = dict(header)
if arg in header_dict:
diff --git a/tests/unit/ec2/autoscale/test_group.py b/tests/unit/ec2/autoscale/test_group.py
index 19e2c7ca..5c5b4dcd 100755..100644
--- a/tests/unit/ec2/autoscale/test_group.py
+++ b/tests/unit/ec2/autoscale/test_group.py
@@ -351,11 +351,9 @@ class TestLaunchConfiguration(AWSMockServiceTestCase):
# This unit test is based on #753 and #1343
self.set_http_response(status_code=200)
dev_sdf = EBSBlockDeviceType(snapshot_id='snap-12345')
- dev_sdg = EBSBlockDeviceType(snapshot_id='snap-12346')
bdm = BlockDeviceMapping()
bdm['/dev/sdf'] = dev_sdf
- bdm['/dev/sdg'] = dev_sdg
lc = launchconfig.LaunchConfiguration(
connection=self.service_connection,
@@ -363,7 +361,7 @@ class TestLaunchConfiguration(AWSMockServiceTestCase):
image_id='123456',
instance_type='m1.large',
user_data='#!/bin/bash',
- security_groups=['group1', 'group2'],
+ security_groups=['group1'],
spot_price='price',
block_device_mappings=[bdm],
associate_public_ip_address=True,
@@ -379,17 +377,13 @@ class TestLaunchConfiguration(AWSMockServiceTestCase):
'BlockDeviceMappings.member.1.DeviceName': '/dev/sdf',
'BlockDeviceMappings.member.1.Ebs.DeleteOnTermination': 'false',
'BlockDeviceMappings.member.1.Ebs.SnapshotId': 'snap-12345',
- 'BlockDeviceMappings.member.2.DeviceName': '/dev/sdg',
- 'BlockDeviceMappings.member.2.Ebs.DeleteOnTermination': 'false',
- 'BlockDeviceMappings.member.2.Ebs.SnapshotId': 'snap-12346',
'EbsOptimized': 'false',
'LaunchConfigurationName': 'launch_config',
'ImageId': '123456',
- 'UserData': base64.b64encode('#!/bin/bash').decode('utf-8'),
+ 'UserData': base64.b64encode(b'#!/bin/bash').decode('utf-8'),
'InstanceMonitoring.Enabled': 'false',
'InstanceType': 'm1.large',
'SecurityGroups.member.1': 'group1',
- 'SecurityGroups.member.2': 'group2',
'SpotPrice': 'price',
'AssociatePublicIpAddress': 'true',
'VolumeType': 'atype',
diff --git a/tests/unit/route53/test_connection.py b/tests/unit/route53/test_connection.py
index a748f307..3c696c7a 100644
--- a/tests/unit/route53/test_connection.py
+++ b/tests/unit/route53/test_connection.py
@@ -54,7 +54,7 @@ class TestRoute53Connection(AWSMockServiceTestCase):
def test_typical_400(self):
self.set_http_response(status_code=400, header=[
- ['Code', 'Throttling'],
+ ['Code', 'AccessDenied'],
])
with self.assertRaises(DNSServerError) as err:
@@ -62,11 +62,22 @@ class TestRoute53Connection(AWSMockServiceTestCase):
self.assertTrue('It failed.' in str(err.exception))
- @mock.patch('time.sleep')
- def test_retryable_400(self, sleep_mock):
+ def test_retryable_400_prior_request_not_complete(self):
+ # Test ability to retry on ``PriorRequestNotComplete``.
self.set_http_response(status_code=400, header=[
['Code', 'PriorRequestNotComplete'],
])
+ self.do_retry_handler()
+
+ def test_retryable_400_throttling(self):
+ # Test ability to rety on ``Throttling``.
+ self.set_http_response(status_code=400, header=[
+ ['Code', 'Throttling'],
+ ])
+ self.do_retry_handler()
+
+ @mock.patch('time.sleep')
+ def do_retry_handler(self, sleep_mock):
def incr_retry_handler(func):
def _wrapper(*args, **kwargs):
diff --git a/tests/unit/route53/test_zone.py b/tests/unit/route53/test_zone.py
new file mode 100644
index 00000000..12d1d254
--- /dev/null
+++ b/tests/unit/route53/test_zone.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+# 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
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish, dis-
+# tribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the fol-
+# lowing conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# 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.route53.zone import Zone
+from tests.compat import mock, unittest
+
+
+class TestZone(unittest.TestCase):
+ def test_find_records(self):
+ mock_connection = mock.Mock()
+ zone = Zone(mock_connection, {})
+ zone.id = None
+ rr_names = ['amazon.com', 'amazon.com', 'aws.amazon.com',
+ 'aws.amazon.com']
+ mock_rrs = []
+ # Create some mock resource records.
+ for rr_name in rr_names:
+ mock_rr = mock.Mock()
+ mock_rr.name = rr_name
+ mock_rr.type = 'A'
+ mock_rr.weight = None
+ mock_rr.region = None
+ mock_rrs.append(mock_rr)
+
+ # Set the last resource record to ``None``. The ``find_records`` loop
+ # should never hit this.
+ mock_rrs[3] = None
+
+ mock_connection.get_all_rrsets.return_value = mock_rrs
+ mock_connection._make_qualified.return_value = 'amazon.com'
+
+ # Ensure that the ``None`` type object was not iterated over.
+ try:
+ result_rrs = zone.find_records('amazon.com', 'A', all=True)
+ except AttributeError as e:
+ self.fail("find_records() iterated too far into resource"
+ " record list.")
+
+ # Determine that the resulting records are correct.
+ self.assertEqual(result_rrs, [mock_rrs[0], mock_rrs[1]])
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/unit/s3/test_connection.py b/tests/unit/s3/test_connection.py
index 35ebce97..5839a6a2 100644
--- a/tests/unit/s3/test_connection.py
+++ b/tests/unit/s3/test_connection.py
@@ -135,6 +135,26 @@ class TestSigV4Presigned(MockServiceWithConfigTestCase):
self.assertIn('VersionId=2', url)
self.assertIn('X-Amz-Security-Token=token', url)
+ def test_sigv4_presign_headers(self):
+ self.config = {
+ 's3': {
+ 'use-sigv4': True,
+ }
+ }
+
+ conn = self.connection_class(
+ aws_access_key_id='less',
+ aws_secret_access_key='more',
+ host='s3.amazonaws.com'
+ )
+
+ headers = {'x-amz-meta-key': 'val'}
+ url = conn.generate_url_sigv4(86400, 'GET', bucket='examplebucket',
+ key='test.txt', headers=headers)
+
+ self.assertIn('host', url)
+ self.assertIn('x-amz-meta-key', url)
+
class TestUnicodeCallingFormat(AWSMockServiceTestCase):
connection_class = S3Connection
diff --git a/tests/unit/sts/test_connection.py b/tests/unit/sts/test_connection.py
index 9bf70304..dd97c770 100644
--- a/tests/unit/sts/test_connection.py
+++ b/tests/unit/sts/test_connection.py
@@ -72,9 +72,7 @@ class TestSTSConnection(AWSMockServiceTestCase):
{'Action': 'AssumeRole',
'RoleArn': 'arn:role',
'RoleSessionName': 'mysession'},
- ignore_params_values=['Timestamp', 'AWSAccessKeyId',
- 'SignatureMethod', 'SignatureVersion',
- 'Version'])
+ ignore_params_values=['Version'])
self.assertEqual(response.credentials.access_key, 'accesskey')
self.assertEqual(response.credentials.secret_key, 'secretkey')
self.assertEqual(response.credentials.session_token, 'session_token')
@@ -95,9 +93,7 @@ class TestSTSConnection(AWSMockServiceTestCase):
'RoleSessionName': 'mysession',
'SerialNumber': 'GAHT12345678',
'TokenCode': 'abc123'},
- ignore_params_values=['Timestamp', 'AWSAccessKeyId',
- 'SignatureMethod', 'SignatureVersion',
- 'Version'])
+ ignore_params_values=['Version'])
self.assertEqual(response.credentials.access_key, 'accesskey')
self.assertEqual(response.credentials.secret_key, 'secretkey')
self.assertEqual(response.credentials.session_token, 'session_token')
@@ -160,16 +156,12 @@ class TestSTSWebIdentityConnection(AWSMockServiceTestCase):
)
self.assert_request_parameters({
'RoleSessionName': 'guestuser',
- 'AWSAccessKeyId': 'aws_access_key_id',
'RoleArn': arn,
'WebIdentityToken': wit,
'ProviderId': 'www.amazon.com',
'Action': 'AssumeRoleWithWebIdentity'
}, ignore_params_values=[
- 'SignatureMethod',
- 'Timestamp',
- 'SignatureVersion',
- 'Version',
+ 'Version'
])
self.assertEqual(
response.credentials.access_key.strip(),
@@ -239,11 +231,7 @@ class TestSTSSAMLConnection(AWSMockServiceTestCase):
'SAMLAssertion': assertion,
'Action': 'AssumeRoleWithSAML'
}, ignore_params_values=[
- 'AWSAccessKeyId',
- 'SignatureMethod',
- 'Timestamp',
- 'SignatureVersion',
- 'Version',
+ 'Version'
])
self.assertEqual(response.credentials.access_key, 'accesskey')
self.assertEqual(response.credentials.secret_key, 'secretkey')
diff --git a/tests/unit/test_connection.py b/tests/unit/test_connection.py
index 97514361..ec9fe997 100644
--- a/tests/unit/test_connection.py
+++ b/tests/unit/test_connection.py
@@ -25,8 +25,9 @@ import socket
from tests.compat import mock, unittest
from httpretty import HTTPretty
+from boto import UserAgent
from boto.compat import json, parse_qs
-from boto.connection import AWSQueryConnection, AWSAuthConnection
+from boto.connection import AWSQueryConnection, AWSAuthConnection, HTTPRequest
from boto.exception import BotoServerError
from boto.regioninfo import RegionInfo
@@ -72,6 +73,7 @@ class MockAWSService(AWSQueryConnection):
"""
APIVersion = '2012-01-01'
+
def _required_auth_capability(self):
return ['sign-v2']
@@ -95,6 +97,7 @@ class MockAWSService(AWSQueryConnection):
validate_certs=validate_certs,
profile_name=profile_name)
+
class TestAWSAuthConnection(unittest.TestCase):
def test_get_path(self):
conn = AWSAuthConnection(
@@ -182,8 +185,9 @@ class TestAWSAuthConnection(unittest.TestCase):
'testhost',
aws_access_key_id='access_key',
aws_secret_access_key='secret')
- request = conn.build_base_http_request(method='POST', path='/',
- auth_path=None, params=None, headers=None, data='', host=None)
+ request = conn.build_base_http_request(
+ method='POST', path='/', auth_path=None, params=None, headers=None,
+ data='', host=None)
conn.set_host_header(request)
self.assertEqual(request.headers['Host'], 'testhost')
@@ -193,15 +197,17 @@ class TestAWSAuthConnection(unittest.TestCase):
aws_access_key_id='access_key',
aws_secret_access_key='secret',
port=8773)
- request = conn.build_base_http_request(method='POST', path='/',
- auth_path=None, params=None, headers=None, data='', host=None)
+ request = conn.build_base_http_request(
+ method='POST', path='/', auth_path=None, params=None, headers=None,
+ data='', host=None)
conn.set_host_header(request)
self.assertEqual(request.headers['Host'], 'testhost:8773')
+
class V4AuthConnection(AWSAuthConnection):
def __init__(self, host, aws_access_key_id, aws_secret_access_key, port=443):
- AWSAuthConnection.__init__(self, host, aws_access_key_id,
- aws_secret_access_key, port=port)
+ AWSAuthConnection.__init__(
+ self, host, aws_access_key_id, aws_secret_access_key, port=port)
def _required_auth_capability(self):
return ['hmac-v4']
@@ -209,15 +215,17 @@ class V4AuthConnection(AWSAuthConnection):
class TestAWSQueryConnection(unittest.TestCase):
def setUp(self):
- self.region = RegionInfo(name='cc-zone-1',
- endpoint='mockservice.cc-zone-1.amazonaws.com',
- connection_cls=MockAWSService)
+ self.region = RegionInfo(
+ name='cc-zone-1',
+ endpoint='mockservice.cc-zone-1.amazonaws.com',
+ connection_cls=MockAWSService)
HTTPretty.enable()
def tearDown(self):
HTTPretty.disable()
+
class TestAWSQueryConnectionSimple(TestAWSQueryConnection):
def test_query_connection_basis(self):
HTTPretty.register_uri(HTTPretty.POST,
@@ -263,7 +271,7 @@ class TestAWSQueryConnectionSimple(TestAWSQueryConnection):
aws_secret_access_key='secret',
proxy="NON_EXISTENT_HOSTNAME",
proxy_port="3128",
- is_secure = False)
+ is_secure=False)
resp = conn.make_request('myCmd',
{'par1': 'foo', 'par2': 'baz'},
@@ -453,7 +461,7 @@ class TestAWSQueryStatus(TestAWSQueryConnection):
content_type='text/xml')
conn = self.region.connect(aws_access_key_id='access_key',
- aws_secret_access_key='secret')
+ aws_secret_access_key='secret')
with self.assertRaises(BotoServerError):
resp = conn.get_status('getStatus',
{'par1': 'foo', 'par2': 'baz'},
@@ -473,5 +481,29 @@ class TestAWSQueryStatus(TestAWSQueryConnection):
{'par1': 'foo', 'par2': 'baz'},
'status')
+
+class TestHTTPRequest(unittest.TestCase):
+ def test_user_agent_not_url_encoded(self):
+ headers = {'Some-Header': u'should be url encoded',
+ 'User-Agent': UserAgent}
+ request = HTTPRequest('PUT', 'https', 'amazon.com', 443, None,
+ None, {}, headers, 'Body')
+ mock_connection = mock.Mock()
+
+ # Create a method that preserves the headers at the time of
+ # authorization.
+ def mock_add_auth(req, **kwargs):
+ mock_connection.headers_at_auth = req.headers.copy()
+
+ mock_connection._auth_handler.add_auth = mock_add_auth
+
+ request.authorize(mock_connection)
+ # Ensure the headers at authorization are as expected i.e.
+ # the user agent header was not url encoded but the other header was.
+ self.assertEqual(mock_connection.headers_at_auth,
+ {'Some-Header': 'should%20be%20url%20encoded',
+ 'User-Agent': UserAgent})
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/unit/test_exception.py b/tests/unit/test_exception.py
index a14f0dca..d9a2bdd3 100644
--- a/tests/unit/test_exception.py
+++ b/tests/unit/test_exception.py
@@ -4,6 +4,7 @@ from boto.exception import BotoServerError, S3CreateError, JSONResponseError
from httpretty import HTTPretty, httprettified
+
class TestBotoServerError(unittest.TestCase):
def test_botoservererror_basics(self):
@@ -45,7 +46,8 @@ class TestBotoServerError(unittest.TestCase):
<RequestID>e73bb2bb-63e3-9cdc-f220-6332de66dbbe</RequestID>
</Response>"""
bse = BotoServerError('403', 'Forbidden', body=xml)
- self.assertEqual(bse.error_message,
+ self.assertEqual(
+ bse.error_message,
'Session does not have permission to perform (sdb:CreateDomain) on '
'resource (arn:aws:sdb:us-east-1:xxxxxxx:domain/test_domain). '
'Contact account owner.')
@@ -84,18 +86,19 @@ class TestBotoServerError(unittest.TestCase):
self.assertEqual(s3ce.error_code, 'BucketAlreadyOwnedByYou')
self.assertEqual(s3ce.status, '409')
self.assertEqual(s3ce.reason, 'Conflict')
- self.assertEqual(s3ce.error_message,
- 'Your previous request to create the named bucket succeeded '
- 'and you already own it.')
+ self.assertEqual(
+ s3ce.error_message,
+ 'Your previous request to create the named bucket succeeded '
+ 'and you already own it.')
self.assertEqual(s3ce.error_message, s3ce.message)
self.assertEqual(s3ce.request_id, 'FF8B86A32CC3FE4F')
def test_message_json_response_error(self):
# This test comes from https://forums.aws.amazon.com/thread.jspa?messageID=374936
body = {
- '__type': 'com.amazon.coral.validate#ValidationException',
- 'message': 'The attempted filter operation is not supported '
- 'for the provided filter argument count'}
+ '__type': 'com.amazon.coral.validate#ValidationException',
+ 'message': 'The attempted filter operation is not supported '
+ 'for the provided filter argument count'}
jre = JSONResponseError('400', 'Bad Request', body=body)
diff --git a/tests/unit/test_regioninfo.py b/tests/unit/test_regioninfo.py
index 0f492788..c46614d4 100644
--- a/tests/unit/test_regioninfo.py
+++ b/tests/unit/test_regioninfo.py
@@ -23,8 +23,6 @@ import os
from tests.unit import unittest
import boto
-from boto.compat import json
-from boto.exception import BotoServerError
from boto.regioninfo import RegionInfo, load_endpoint_json, merge_endpoints
from boto.regioninfo import load_regions, get_regions
diff --git a/tests/unit/vpc/test_routetable.py b/tests/unit/vpc/test_routetable.py
index 3518fd74..c90e56c4 100644
--- a/tests/unit/vpc/test_routetable.py
+++ b/tests/unit/vpc/test_routetable.py
@@ -48,6 +48,18 @@ class TestDescribeRouteTables(AWSMockServiceTestCase):
<gatewayId>igw-eaad4883</gatewayId>
<state>active</state>
</item>
+ <item>
+ <destinationCidrBlock>10.0.0.0/21</destinationCidrBlock>
+ <networkInterfaceId>eni-884ec1d1</networkInterfaceId>
+ <state>blackhole</state>
+ <origin>CreateRoute</origin>
+ </item>
+ <item>
+ <destinationCidrBlock>11.0.0.0/22</destinationCidrBlock>
+ <vpcPeeringConnectionId>pcx-efc52b86</vpcPeeringConnectionId>
+ <state>blackhole</state>
+ <origin>CreateRoute</origin>
+ </item>
</routeSet>
<associationSet>
<item>
@@ -88,13 +100,19 @@ class TestDescribeRouteTables(AWSMockServiceTestCase):
self.assertIsNone(api_response[0].associations[0].subnet_id)
self.assertEquals(api_response[0].associations[0].main, True)
self.assertEquals(api_response[1].id, 'rtb-f9ad4890')
- self.assertEquals(len(api_response[1].routes), 2)
+ self.assertEquals(len(api_response[1].routes), 4)
self.assertEquals(api_response[1].routes[0].destination_cidr_block, '10.0.0.0/22')
self.assertEquals(api_response[1].routes[0].gateway_id, 'local')
self.assertEquals(api_response[1].routes[0].state, 'active')
self.assertEquals(api_response[1].routes[1].destination_cidr_block, '0.0.0.0/0')
self.assertEquals(api_response[1].routes[1].gateway_id, 'igw-eaad4883')
self.assertEquals(api_response[1].routes[1].state, 'active')
+ self.assertEquals(api_response[1].routes[2].destination_cidr_block, '10.0.0.0/21')
+ self.assertEquals(api_response[1].routes[2].interface_id, 'eni-884ec1d1')
+ self.assertEquals(api_response[1].routes[2].state, 'blackhole')
+ self.assertEquals(api_response[1].routes[3].destination_cidr_block, '11.0.0.0/22')
+ self.assertEquals(api_response[1].routes[3].vpc_peering_connection_id, 'pcx-efc52b86')
+ self.assertEquals(api_response[1].routes[3].state, 'blackhole')
self.assertEquals(len(api_response[1].associations), 1)
self.assertEquals(api_response[1].associations[0].id, 'rtbassoc-faad4893')
self.assertEquals(api_response[1].associations[0].route_table_id, 'rtb-f9ad4890')