summaryrefslogtreecommitdiff
path: root/boto
diff options
context:
space:
mode:
Diffstat (limited to 'boto')
-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
32 files changed, 1189 insertions, 272 deletions
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