summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel G. Taylor <danielgtaylor@gmail.com>2014-08-04 15:42:30 -0700
committerDaniel G. Taylor <danielgtaylor@gmail.com>2014-08-04 15:42:30 -0700
commitff3d8159af3c816303785e023a4182aacb6aabf5 (patch)
tree3f9ccb173904eecef829d07f9acbddc6e4b444e5
parent60603b6514268a76d9f84a8f381fd645271492a8 (diff)
parent8aea0d3381d22d7dc0a58c488db6f5d429083c9c (diff)
downloadboto-ff3d8159af3c816303785e023a4182aacb6aabf5.tar.gz
Merge branch 'release-2.32.1'2.32.1
Conflicts: docs/source/index.rst
-rw-r--r--README.rst4
-rwxr-xr-xbin/fetch_file7
-rwxr-xr-xbin/glacier14
-rwxr-xr-xbin/kill_instance4
-rwxr-xr-xbin/launch_instance12
-rwxr-xr-xbin/list_instances10
-rwxr-xr-xbin/lss320
-rwxr-xr-xbin/s3put46
-rw-r--r--boto/__init__.py2
-rw-r--r--boto/auth.py6
-rw-r--r--boto/cloudsearch/document.py3
-rw-r--r--boto/compat.py10
-rw-r--r--boto/connection.py7
-rw-r--r--boto/dynamodb/types.py8
-rw-r--r--boto/emr/emrobject.py6
-rw-r--r--boto/fps/connection.py1
-rw-r--r--boto/glacier/concurrent.py2
-rw-r--r--boto/glacier/job.py3
-rw-r--r--boto/glacier/layer1.py27
-rw-r--r--boto/glacier/utils.py13
-rw-r--r--boto/glacier/vault.py2
-rw-r--r--boto/gs/resumable_upload_handler.py5
-rw-r--r--boto/manage/volume.py2
-rw-r--r--boto/mws/connection.py5
-rw-r--r--boto/pyami/config.py14
-rw-r--r--boto/pyami/installers/ubuntu/mysql.py4
-rw-r--r--boto/regioninfo.py1
-rw-r--r--boto/s3/key.py12
-rw-r--r--boto/sdb/db/manager/xmlmanager.py5
-rw-r--r--boto/utils.py24
-rw-r--r--boto/vpc/vpc_peering_connection.py2
-rw-r--r--docs/source/index.rst1
-rw-r--r--docs/source/releasenotes/v2.32.1.rst32
-rw-r--r--requirements.txt1
-rw-r--r--setup.cfg2
-rw-r--r--setup.py2
-rw-r--r--tests/integration/dynamodb2/test_highlevel.py2
-rw-r--r--tests/integration/glacier/test_layer1.py2
-rw-r--r--tests/integration/s3/mock_storage_service.py6
-rw-r--r--tests/integration/ses/test_connection.py2
-rw-r--r--tests/integration/sns/test_connection.py2
-rw-r--r--tests/integration/sqs/test_bigmessage.py8
-rw-r--r--tests/unit/auth/test_sigv4.py14
-rw-r--r--tests/unit/cloudsearch/test_document.py16
-rw-r--r--tests/unit/dynamodb/test_types.py9
-rw-r--r--tests/unit/emr/test_connection.py95
-rw-r--r--tests/unit/glacier/test_job.py2
-rw-r--r--tests/unit/glacier/test_layer1.py2
-rw-r--r--tests/unit/glacier/test_layer2.py148
-rw-r--r--tests/unit/glacier/test_utils.py53
-rw-r--r--tests/unit/glacier/test_vault.py6
-rw-r--r--tests/unit/glacier/test_writer.py3
-rw-r--r--tests/unit/s3/test_key.py2
-rw-r--r--tests/unit/test_connection.py8
-rw-r--r--tests/unit/utils/test_utils.py16
-rw-r--r--tests/unit/vpc/test_vpc_peering_connection.py54
-rw-r--r--tox.ini9
57 files changed, 524 insertions, 254 deletions
diff --git a/README.rst b/README.rst
index da60e76a..f14e4832 100644
--- a/README.rst
+++ b/README.rst
@@ -1,9 +1,9 @@
####
boto
####
-boto 2.32.0
+boto 2.32.1
-Released: 30-Jul-2014
+Released: 04-Aug-2014
.. image:: https://travis-ci.org/boto/boto.png?branch=develop
:target: https://travis-ci.org/boto/boto
diff --git a/bin/fetch_file b/bin/fetch_file
index 9315aec7..defb8b0e 100755
--- a/bin/fetch_file
+++ b/bin/fetch_file
@@ -20,6 +20,9 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#
+import sys
+
+
if __name__ == "__main__":
from optparse import OptionParser
usage = """%prog [options] URI
@@ -34,10 +37,10 @@ The URI can be either an HTTP URL, or "s3://bucket_name/key_name"
(options, args) = parser.parse_args()
if len(args) < 1:
parser.print_help()
- exit(1)
+ sys.exit(1)
from boto.utils import fetch_file
f = fetch_file(args[0])
if options.outfile:
open(options.outfile, "w").write(f.read())
else:
- print f.read()
+ print(f.read())
diff --git a/bin/glacier b/bin/glacier
index a3763e06..ec0364a0 100755
--- a/bin/glacier
+++ b/bin/glacier
@@ -41,7 +41,7 @@ COMMANDS = ('vaults', 'jobs', 'upload')
def usage():
- print """
+ print("""
glacier <command> [args]
Commands
@@ -78,7 +78,7 @@ glacier <command> [args]
Examples :
glacier upload pics *.jpg
glacier upload pics a.jpg b.jpg
-"""
+""")
sys.exit()
@@ -89,7 +89,7 @@ def connect(region, debug_level=0, access_key=None, secret_key=None):
aws_secret_access_key=secret_key,
debug=debug_level)
if layer2 is None:
- print 'Invalid region (%s)' % region
+ print('Invalid region (%s)' % region)
sys.exit(1)
return layer2
@@ -97,12 +97,12 @@ def connect(region, debug_level=0, access_key=None, secret_key=None):
def list_vaults(region, access_key=None, secret_key=None):
layer2 = connect(region, access_key = access_key, secret_key = secret_key)
for vault in layer2.list_vaults():
- print vault.arn
+ print(vault.arn)
def list_jobs(vault_name, region, access_key=None, secret_key=None):
layer2 = connect(region, access_key = access_key, secret_key = secret_key)
- print layer2.layer1.list_jobs(vault_name)
+ print(layer2.layer1.list_jobs(vault_name))
def upload_files(vault_name, filenames, region, access_key=None, secret_key=None):
@@ -111,7 +111,7 @@ def upload_files(vault_name, filenames, region, access_key=None, secret_key=None
glacier_vault = layer2.get_vault(vault_name)
for filename in filenames:
if isfile(filename):
- print 'Uploading %s to %s' % (filename, vault_name)
+ print('Uploading %s to %s' % (filename, vault_name))
glacier_vault.upload_archive(filename)
@@ -128,7 +128,7 @@ def main():
long_options = ['access_key=', 'secret_key=', 'region=']
try:
opts, args = getopt(argv, options, long_options)
- except GetoptError, e:
+ except GetoptError as e:
usage()
# Parse agument
diff --git a/bin/kill_instance b/bin/kill_instance
index 0c637413..6683bb83 100755
--- a/bin/kill_instance
+++ b/bin/kill_instance
@@ -13,7 +13,7 @@ def kill_instance(region, ids):
# Connect the region
ec2 = boto.connect_ec2(region=region)
for instance_id in ids:
- print "Stopping instance: %s" % instance_id
+ print("Stopping instance: %s" % instance_id)
ec2.terminate_instances([instance_id])
@@ -29,7 +29,7 @@ if __name__ == "__main__":
region = r
break
else:
- print "Region %s not found." % options.region
+ print("Region %s not found." % options.region)
sys.exit(1)
kill_instance(region, args)
diff --git a/bin/launch_instance b/bin/launch_instance
index 77a5419c..62bc5e4d 100755
--- a/bin/launch_instance
+++ b/bin/launch_instance
@@ -33,7 +33,7 @@ f.close()
import boto.pyami.config
import boto.utils
import re, os
-import ConfigParser
+from boto.compat import ConfigParser
class Config(boto.pyami.config.Config):
"""A special config class that also adds import abilities
@@ -43,7 +43,7 @@ class Config(boto.pyami.config.Config):
"""
def __init__(self):
- ConfigParser.SafeConfigParser.__init__(self, {'working_dir' : '/mnt/pyami', 'debug' : '0'})
+ ConfigParser.__init__(self, {'working_dir' : '/mnt/pyami', 'debug' : '0'})
def add_config(self, file_url):
"""Add a config file to this configuration
@@ -150,7 +150,7 @@ if __name__ == "__main__":
region = r
break
else:
- print "Region %s not found." % options.region
+ print("Region %s not found." % options.region)
sys.exit(1)
ec2 = boto.connect_ec2(region=region)
if not options.nocred and not options.role:
@@ -206,7 +206,7 @@ if __name__ == "__main__":
scr_url = "file://%s" % scr_url
try:
scriptuples.append((scr, add_script(scr_url)))
- except Exception, e:
+ except Exception as e:
pass
user_data = boto.utils.write_mime_multipart(scriptuples, compress=True)
@@ -248,5 +248,5 @@ if __name__ == "__main__":
time.sleep(3)
if options.dns:
- print "Public DNS name: %s" % instance.public_dns_name
- print "Private DNS name: %s" % instance.private_dns_name
+ print("Public DNS name: %s" % instance.public_dns_name)
+ print("Private DNS name: %s" % instance.private_dns_name)
diff --git a/bin/list_instances b/bin/list_instances
index 8cb743c0..e48ec390 100755
--- a/bin/list_instances
+++ b/bin/list_instances
@@ -45,7 +45,7 @@ def main():
region = r
break
else:
- print "Region %s not found." % options.region
+ print("Region %s not found." % options.region)
sys.exit(1)
ec2 = boto.connect_ec2(region=region)
@@ -73,17 +73,17 @@ def main():
# List and print
if not options.tab:
- print format_string % headers
- print "-" * len(format_string % headers)
+ print(format_string % headers)
+ print("-" * len(format_string % headers))
for r in ec2.get_all_reservations(filters=filters):
groups = [g.name for g in r.groups]
for i in r.instances:
i.groups = ','.join(groups)
if options.tab:
- print "\t".join(tuple(get_column(h, i) for h in headers))
+ print("\t".join(tuple(get_column(h, i) for h in headers)))
else:
- print format_string % tuple(get_column(h, i) for h in headers)
+ print(format_string % tuple(get_column(h, i) for h in headers))
if __name__ == "__main__":
diff --git a/bin/lss3 b/bin/lss3
index cffbb96d..0add2647 100755
--- a/bin/lss3
+++ b/bin/lss3
@@ -22,7 +22,7 @@ def list_bucket(b, prefix=None, marker=None):
if not prefix.endswith("/"):
prefix = prefix + "/"
query = b.list(prefix=prefix, delimiter="/", marker=marker)
- print "%s" % prefix
+ print("%s" % prefix)
else:
query = b.list(delimiter="/", marker=marker)
@@ -42,27 +42,27 @@ def list_bucket(b, prefix=None, marker=None):
elif g.permission == "FULL_CONTROL":
mode = "-rwxrwx"
if isinstance(k, Key):
- print "%s\t%s\t%010s\t%s" % (mode, k.last_modified,
- sizeof_fmt(size), k.name)
+ print("%s\t%s\t%010s\t%s" % (mode, k.last_modified,
+ sizeof_fmt(size), k.name))
else:
#If it's not a Key object, it doesn't have a last_modified time, so
#print nothing instead
- print "%s\t%s\t%010s\t%s" % (mode, ' ' * 24,
- sizeof_fmt(size), k.name)
+ print("%s\t%s\t%010s\t%s" % (mode, ' ' * 24,
+ sizeof_fmt(size), k.name))
total += size
- print "=" * 80
- print "\t\tTOTAL: \t%010s \t%i Files" % (sizeof_fmt(total), num)
+ print ("=" * 80)
+ print ("\t\tTOTAL: \t%010s \t%i Files" % (sizeof_fmt(total), num))
def list_buckets(s3, display_tags=False):
"""List all the buckets"""
for b in s3.get_all_buckets():
- print b.name
+ print(b.name)
if display_tags:
try:
tags = b.get_tags()
for tag in tags[0]:
- print " %s:%s" % (tag.key, tag.value)
+ print(" %s:%s" % (tag.key, tag.value))
except S3ResponseError as e:
if e.status != 404:
raise
@@ -87,7 +87,7 @@ def main():
sys.exit(0)
if options.tags:
- print "-t option only works for the overall bucket list"
+ print("-t option only works for the overall bucket list")
sys.exit(1)
pairs = []
diff --git a/bin/s3put b/bin/s3put
index 0a2088d3..da025b35 100755
--- a/bin/s3put
+++ b/bin/s3put
@@ -25,6 +25,8 @@ import sys
import os
import boto
+from boto.compat import six
+
try:
# multipart portions copyright Fabian Topfstedt
# https://gist.github.com/924094
@@ -43,8 +45,12 @@ try:
except ImportError as err:
multipart_capable = False
usage_flag_multipart_capable = ""
+ if six.PY2:
+ attribute = 'message'
+ else:
+ attribute = 'msg'
usage_string_multipart_capable = '\n\n "' + \
- err.message[len('No module named '):] + \
+ getattr(err, attribute)[len('No module named '):] + \
'" is missing for multipart support '
@@ -121,12 +127,12 @@ SYNOPSIS
def usage(status=1):
- print usage_string
+ print(usage_string)
sys.exit(status)
def submit_cb(bytes_so_far, total_bytes):
- print '%d bytes transferred / %d bytes total' % (bytes_so_far, total_bytes)
+ print('%d bytes transferred / %d bytes total' % (bytes_so_far, total_bytes))
def get_key_name(fullpath, prefix, key_prefix):
@@ -145,12 +151,12 @@ def _upload_part(bucketname, aws_key, aws_secret, multipart_id, part_num,
Uploads a part with retries.
"""
if debug == 1:
- print "_upload_part(%s, %s, %s)" % (source_path, offset, bytes)
+ print("_upload_part(%s, %s, %s)" % (source_path, offset, bytes))
def _upload(retries_left=amount_of_retries):
try:
if debug == 1:
- print 'Start uploading part #%d ...' % part_num
+ print('Start uploading part #%d ...' % part_num)
conn = S3Connection(aws_key, aws_secret)
conn.debug = debug
bucket = conn.get_bucket(bucketname)
@@ -161,21 +167,21 @@ def _upload_part(bucketname, aws_key, aws_secret, multipart_id, part_num,
mp.upload_part_from_file(fp=fp, part_num=part_num,
cb=cb, num_cb=num_cb)
break
- except Exception, exc:
+ except Exception as exc:
if retries_left:
_upload(retries_left=retries_left - 1)
else:
- print 'Failed uploading part #%d' % part_num
+ print('Failed uploading part #%d' % part_num)
raise exc
else:
if debug == 1:
- print '... Uploaded part #%d' % part_num
+ print('... Uploaded part #%d' % part_num)
_upload()
def check_valid_region(conn, region):
if conn is None:
- print 'Invalid region (%s)' % region
+ print('Invalid region (%s)' % region)
sys.exit(1)
def multipart_upload(bucketname, aws_key, aws_secret, source_path, keyname,
@@ -312,7 +318,7 @@ def main():
if multipart_capable:
multipart_requested = True
else:
- print "multipart upload requested but not capable"
+ print("multipart upload requested but not capable")
sys.exit(4)
if o == '--region':
regions = boto.s3.regions()
@@ -327,7 +333,7 @@ def main():
usage(2)
if not bucket_name:
- print "bucket name is required!"
+ print("bucket name is required!")
usage(3)
connect_args = {
@@ -352,23 +358,23 @@ def main():
# Classic region will be '', any other will have a name
if location:
- print 'Bucket exists in %s but no host or region given!' % location
+ print('Bucket exists in %s but no host or region given!' % location)
# Override for EU, which is really Ireland according to the docs
if location == 'EU':
location = 'eu-west-1'
- print 'Automatically setting region to %s' % location
+ print('Automatically setting region to %s' % location)
# Here we create a new connection, and then take the existing
# bucket and set it to use the new connection
c = boto.s3.connect_to_region(location, **connect_args)
c.debug = debug
b.connection = c
- except Exception, e:
+ except Exception as e:
if debug > 0:
- print e
- print 'Could not get bucket region info, skipping...'
+ print(e)
+ print('Could not get bucket region info, skipping...')
existing_keys_to_check_against = []
files_to_check_for_upload = []
@@ -379,7 +385,7 @@ def main():
if os.path.isdir(path):
if no_overwrite:
if not quiet:
- print 'Getting list of existing keys to check against'
+ print('Getting list of existing keys to check against')
for key in b.list(get_key_name(path, prefix, key_prefix)):
existing_keys_to_check_against.append(key.name)
for root, dirs, files in os.walk(path):
@@ -400,7 +406,7 @@ def main():
# we are trying to upload something unknown
else:
- print "I don't know what %s is, so i can't upload it" % path
+ print("I don't know what %s is, so i can't upload it" % path)
for fullpath in files_to_check_for_upload:
key_name = get_key_name(fullpath, prefix, key_prefix)
@@ -408,11 +414,11 @@ def main():
if no_overwrite and key_name in existing_keys_to_check_against:
if b.get_key(key_name):
if not quiet:
- print 'Skipping %s as it exists in s3' % fullpath
+ print('Skipping %s as it exists in s3' % fullpath)
continue
if not quiet:
- print 'Copying %s to %s/%s' % (fullpath, bucket_name, key_name)
+ print('Copying %s to %s/%s' % (fullpath, bucket_name, key_name))
if not no_op:
# 0-byte files don't work and also don't need multipart upload
diff --git a/boto/__init__.py b/boto/__init__.py
index 5b142e97..3d90e901 100644
--- a/boto/__init__.py
+++ b/boto/__init__.py
@@ -38,7 +38,7 @@ import logging.config
from boto.compat import urlparse
from boto.exception import InvalidUriError
-__version__ = '2.32.0'
+__version__ = '2.32.1'
Version = __version__ # for backware compatibility
# http://bugs.python.org/issue7980
diff --git a/boto/auth.py b/boto/auth.py
index 6d431461..6012962a 100644
--- a/boto/auth.py
+++ b/boto/auth.py
@@ -41,7 +41,7 @@ import sys
import time
import posixpath
-from boto.compat import urllib
+from boto.compat import urllib, encodebytes
from boto.auth_handler import AuthHandler
from boto.exception import BotoClientError
@@ -89,7 +89,7 @@ class HmacKeys(object):
def sign_string(self, string_to_sign):
new_hmac = self._get_hmac()
new_hmac.update(string_to_sign.encode('utf-8'))
- return base64.encodestring(new_hmac.digest()).decode('utf-8').strip()
+ return encodebytes(new_hmac.digest()).decode('utf-8').strip()
def __getstate__(self):
pickled_dict = copy.copy(self.__dict__)
@@ -99,7 +99,7 @@ class HmacKeys(object):
def __setstate__(self, dct):
self.__dict__ = dct
- self.update_provider(self._provider.encode('utf-8'))
+ self.update_provider(self._provider)
class AnonAuthHandler(AuthHandler, HmacKeys):
diff --git a/boto/cloudsearch/document.py b/boto/cloudsearch/document.py
index b5a6b455..0a1d9db2 100644
--- a/boto/cloudsearch/document.py
+++ b/boto/cloudsearch/document.py
@@ -240,6 +240,9 @@ class CommitResponse(object):
raise EncodingError("Illegal Unicode character in document")
elif e == "The Content-Length is too long":
raise ContentTooLongError("Content was too long")
+ if 'adds' not in self.content or 'deletes' not in self.content:
+ raise SearchServiceException("Error indexing documents"
+ " => %s" % self.content.get('message', ''))
else:
self.errors = []
diff --git a/boto/compat.py b/boto/compat.py
index 21a9193d..a7503f01 100644
--- a/boto/compat.py
+++ b/boto/compat.py
@@ -29,6 +29,13 @@ except ImportError:
import json
+# Switch to use encodebytes, which deprecates encodestring in Python 3
+try:
+ from base64 import encodebytes
+except ImportError:
+ from base64 import encodestring as encodebytes
+
+
# If running in Google App Engine there is no "user" and
# os.path.expanduser() will fail. Attempt to detect this case and use a
# no-op expanduser function in this case.
@@ -45,7 +52,6 @@ from boto.vendored.six import BytesIO, StringIO
from boto.vendored.six.moves import filter, http_client, map, _thread, \
urllib, zip
from boto.vendored.six.moves.queue import Queue
-from boto.vendored.six.moves.configparser import SafeConfigParser
from boto.vendored.six.moves.urllib.parse import parse_qs, quote, unquote, \
urlparse, urlsplit
from boto.vendored.six.moves.urllib.request import urlopen
@@ -54,6 +60,8 @@ if six.PY3:
# StandardError was removed, so use the base exception type instead
StandardError = Exception
long_type = int
+ from configparser import ConfigParser
else:
StandardError = StandardError
long_type = long
+ from ConfigParser import SafeConfigParser as ConfigParser
diff --git a/boto/connection.py b/boto/connection.py
index 9846a879..5fe9c198 100644
--- a/boto/connection.py
+++ b/boto/connection.py
@@ -42,9 +42,6 @@
"""
Handles basic connections to AWS
"""
-from __future__ import with_statement
-
-import base64
from datetime import datetime
import errno
import os
@@ -64,7 +61,7 @@ import boto.handler
import boto.cacerts
from boto import config, UserAgent
-from boto.compat import six, http_client, urlparse, quote
+from boto.compat import six, http_client, urlparse, quote, encodebytes
from boto.exception import AWSConnectionError
from boto.exception import BotoClientError
from boto.exception import BotoServerError
@@ -857,7 +854,7 @@ class AWSAuthConnection(object):
return path
def get_proxy_auth_header(self):
- auth = base64.encodestring(self.proxy_user + ':' + self.proxy_pass)
+ auth = encodebytes(self.proxy_user + ':' + self.proxy_pass)
return {'Proxy-Authorization': 'Basic %s' % auth}
def set_host_header(self, request):
diff --git a/boto/dynamodb/types.py b/boto/dynamodb/types.py
index 5e3bae69..2049c219 100644
--- a/boto/dynamodb/types.py
+++ b/boto/dynamodb/types.py
@@ -148,10 +148,10 @@ def dynamize_value(val):
if six.PY2:
class Binary(object):
def __init__(self, value):
- if isinstance(value, six.text_type): # Support only PY2 for backward compatibility.
- value = value.encode('utf-8')
- elif not isinstance(value, bytes):
+ if not isinstance(value, (bytes, six.text_type)):
raise TypeError('Value must be a string of binary data!')
+ if not isinstance(value, bytes):
+ value = value.encode("utf-8")
self.value = value
@@ -171,7 +171,7 @@ if six.PY2:
return 'Binary(%r)' % self.value
def __str__(self):
- return self.value.decode('utf-8')
+ return self.value
def __hash__(self):
return hash(self.value)
diff --git a/boto/emr/emrobject.py b/boto/emr/emrobject.py
index 0906bfab..f605834c 100644
--- a/boto/emr/emrobject.py
+++ b/boto/emr/emrobject.py
@@ -301,7 +301,7 @@ class ClusterSummaryList(EmrObject):
class StepConfig(EmrObject):
Fields = set([
- 'Jar'
+ 'Jar',
'MainClass'
])
@@ -434,11 +434,15 @@ class StepSummary(EmrObject):
def __init__(self, connection=None):
self.connection = connection
self.status = None
+ self.config = None
def startElement(self, name, attrs, connection):
if name == 'Status':
self.status = ClusterStatus()
return self.status
+ elif name == 'Config':
+ self.config = StepConfig()
+ return self.config
else:
return None
diff --git a/boto/fps/connection.py b/boto/fps/connection.py
index e8124ab2..6dc90a24 100644
--- a/boto/fps/connection.py
+++ b/boto/fps/connection.py
@@ -23,7 +23,6 @@
import urllib
import uuid
-from boto.compat import filter, map
from boto.connection import AWSQueryConnection
from boto.fps.exception import ResponseErrorFactory
from boto.fps.response import ResponseFactory
diff --git a/boto/glacier/concurrent.py b/boto/glacier/concurrent.py
index 93a12d4f..a4f3a224 100644
--- a/boto/glacier/concurrent.py
+++ b/boto/glacier/concurrent.py
@@ -19,8 +19,6 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#
-from __future__ import with_statement
-
import os
import math
import threading
diff --git a/boto/glacier/job.py b/boto/glacier/job.py
index 097312a1..33e66a19 100644
--- a/boto/glacier/job.py
+++ b/boto/glacier/job.py
@@ -20,7 +20,6 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#
-from __future__ import with_statement
import math
import socket
@@ -124,7 +123,7 @@ class Job(object):
verify_hashes, retry_exceptions)
def download_to_fileobj(self, output_file, chunk_size=DefaultPartSize,
- verify_hashes=True,
+ verify_hashes=True,
retry_exceptions=(socket.error,)):
"""Download an archive to a file object.
diff --git a/boto/glacier/layer1.py b/boto/glacier/layer1.py
index 45a142d8..39136cf0 100644
--- a/boto/glacier/layer1.py
+++ b/boto/glacier/layer1.py
@@ -89,12 +89,13 @@ class Layer1(AWSAuthConnection):
self.region = region
self.account_id = account_id
super(Layer1, self).__init__(region.endpoint,
- aws_access_key_id, aws_secret_access_key,
- is_secure, port, proxy, proxy_port,
- proxy_user, proxy_pass, debug,
- https_connection_factory,
- path, provider, security_token,
- suppress_consec_slashes, profile_name=profile_name)
+ aws_access_key_id, aws_secret_access_key,
+ is_secure, port, proxy, proxy_port,
+ proxy_user, proxy_pass, debug,
+ https_connection_factory,
+ path, provider, security_token,
+ suppress_consec_slashes,
+ profile_name=profile_name)
def _required_auth_capability(self):
return ['hmac-v4']
@@ -107,10 +108,10 @@ class Layer1(AWSAuthConnection):
headers['x-amz-glacier-version'] = self.Version
uri = '/%s/%s' % (self.account_id, resource)
response = super(Layer1, self).make_request(verb, uri,
- params=params,
- headers=headers,
- sender=sender,
- data=data)
+ params=params,
+ headers=headers,
+ sender=sender,
+ data=data)
if response.status in ok_responses:
return GlacierResponse(response, response_headers)
else:
@@ -826,9 +827,9 @@ class Layer1(AWSAuthConnection):
else:
sender = None
return self.make_request('POST', uri, headers=headers,
- sender=sender,
- data=archive, ok_responses=(201,),
- response_headers=response_headers)
+ sender=sender,
+ data=archive, ok_responses=(201,),
+ response_headers=response_headers)
def _is_file_like(self, archive):
return hasattr(archive, 'seek') and hasattr(archive, 'tell')
diff --git a/boto/glacier/utils.py b/boto/glacier/utils.py
index 21da67ba..98847e3f 100644
--- a/boto/glacier/utils.py
+++ b/boto/glacier/utils.py
@@ -23,6 +23,8 @@ import hashlib
import math
import binascii
+from boto.compat import six
+
_MEGABYTE = 1024 * 1024
DEFAULT_PART_SIZE = 4 * _MEGABYTE
@@ -122,10 +124,19 @@ def compute_hashes_from_fileobj(fileobj, chunk_size=1024 * 1024):
are returned in hex.
"""
+ # Python 3+, not binary
+ if six.PY3 and hasattr(fileobj, 'mode') and 'b' not in fileobj.mode:
+ raise ValueError('File-like object must be opened in binary mode!')
+
linear_hash = hashlib.sha256()
chunks = []
- chunk = fileobj.read(chunk_size).encode('utf-8')
+ chunk = fileobj.read(chunk_size)
while chunk:
+ # It's possible to get a file-like object that has no mode (checked
+ # above) and returns something other than bytes (e.g. str). So here
+ # we try to catch that and encode to bytes.
+ if not isinstance(chunk, bytes):
+ chunk = chunk.encode(getattr(fileobj, 'encoding', '') or 'utf-8')
linear_hash.update(chunk)
chunks.append(hashlib.sha256(chunk).digest())
chunk = fileobj.read(chunk_size)
diff --git a/boto/glacier/vault.py b/boto/glacier/vault.py
index e447c188..45d276ca 100644
--- a/boto/glacier/vault.py
+++ b/boto/glacier/vault.py
@@ -21,9 +21,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#
-from __future__ import with_statement
import codecs
-from boto.compat import six
from boto.glacier.exceptions import UploadArchiveError
from boto.glacier.job import Job
from boto.glacier.writer import compute_hashes_from_fileobj, \
diff --git a/boto/gs/resumable_upload_handler.py b/boto/gs/resumable_upload_handler.py
index 5b88b621..d7443469 100644
--- a/boto/gs/resumable_upload_handler.py
+++ b/boto/gs/resumable_upload_handler.py
@@ -26,16 +26,13 @@ import re
import socket
import time
import urlparse
+from hashlib import md5
from boto import config, UserAgent
from boto.connection import AWSAuthConnection
from boto.exception import InvalidUriError
from boto.exception import ResumableTransferDisposition
from boto.exception import ResumableUploadException
from boto.s3.keyfile import KeyFile
-try:
- from hashlib import md5
-except ImportError:
- from md5 import md5
"""
Handler for Google Cloud Storage resumable uploads. See
diff --git a/boto/manage/volume.py b/boto/manage/volume.py
index a4fcb5f3..410414c7 100644
--- a/boto/manage/volume.py
+++ b/boto/manage/volume.py
@@ -18,7 +18,7 @@
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
-from __future__ import with_statement, print_function
+from __future__ import print_function
from boto.sdb.db.model import Model
from boto.sdb.db.property import StringProperty, IntegerProperty, ListProperty, ReferenceProperty, CalculatedProperty
diff --git a/boto/mws/connection.py b/boto/mws/connection.py
index 6759d028..01b0b30b 100644
--- a/boto/mws/connection.py
+++ b/boto/mws/connection.py
@@ -20,7 +20,6 @@
# IN THE SOFTWARE.
import xml.sax
import hashlib
-import base64
import string
import collections
from boto.connection import AWSQueryConnection
@@ -28,7 +27,7 @@ from boto.exception import BotoServerError
import boto.mws.exception
import boto.mws.response
from boto.handler import XmlHandler
-from boto.compat import filter, map, six
+from boto.compat import filter, map, six, encodebytes
__all__ = ['MWSConnection']
@@ -55,7 +54,7 @@ api_version_path = {
'OffAmazonPayments': ('2013-01-01', 'SellerId',
'/OffAmazonPayments/2013-01-01'),
}
-content_md5 = lambda c: base64.encodestring(hashlib.md5(c).digest()).strip()
+content_md5 = lambda c: encodebytes(hashlib.md5(c).digest()).strip()
decorated_attrs = ('action', 'response', 'section',
'quota', 'restore', 'version')
api_call_map = {}
diff --git a/boto/pyami/config.py b/boto/pyami/config.py
index 3789113b..37445f85 100644
--- a/boto/pyami/config.py
+++ b/boto/pyami/config.py
@@ -26,7 +26,7 @@ import warnings
import boto
-from boto.compat import expanduser, SafeConfigParser, StringIO
+from boto.compat import expanduser, ConfigParser, StringIO
# By default we use two locations for the boto configurations,
@@ -49,12 +49,12 @@ elif 'BOTO_PATH' in os.environ:
BotoConfigLocations.append(expanduser(path))
-class Config(SafeConfigParser):
+class Config(ConfigParser):
def __init__(self, path=None, fp=None, do_load=True):
# We don't use ``super`` here, because ``ConfigParser`` still uses
# old-style classes.
- SafeConfigParser.__init__(self, {'working_dir': '/mnt/pyami',
+ ConfigParser.__init__(self, {'working_dir': '/mnt/pyami',
'debug': '0'})
if do_load:
if path:
@@ -95,7 +95,7 @@ class Config(SafeConfigParser):
Replace any previous value. If the path doesn't exist, create it.
Also add the option the the in-memory config.
"""
- config = SafeConfigParser()
+ config = ConfigParser()
config.read(path)
if not config.has_section(section):
config.add_section(section)
@@ -139,21 +139,21 @@ class Config(SafeConfigParser):
def get(self, section, name, default=None):
try:
- val = SafeConfigParser.get(self, section, name)
+ val = ConfigParser.get(self, section, name)
except:
val = default
return val
def getint(self, section, name, default=0):
try:
- val = SafeConfigParser.getint(self, section, name)
+ val = ConfigParser.getint(self, section, name)
except:
val = int(default)
return val
def getfloat(self, section, name, default=0.0):
try:
- val = SafeConfigParser.getfloat(self, section, name)
+ val = ConfigParser.getfloat(self, section, name)
except:
val = float(default)
return val
diff --git a/boto/pyami/installers/ubuntu/mysql.py b/boto/pyami/installers/ubuntu/mysql.py
index 8dd643bf..5b0792ba 100644
--- a/boto/pyami/installers/ubuntu/mysql.py
+++ b/boto/pyami/installers/ubuntu/mysql.py
@@ -31,7 +31,7 @@ from boto.pyami.installers.ubuntu.installer import Installer
import os
import boto
from boto.utils import ShellCommand
-from ConfigParser import SafeConfigParser
+from boto.compat import ConfigParser
import time
ConfigSection = """
@@ -89,7 +89,7 @@ class MySQL(Installer):
self.start('mysql')
else:
#get the password ubuntu expects to use:
- config_parser = SafeConfigParser()
+ config_parser = ConfigParser()
config_parser.read('/etc/mysql/debian.cnf')
password = config_parser.get('client', 'password')
# start the mysql deamon, then mysql with the required grant statement piped into it:
diff --git a/boto/regioninfo.py b/boto/regioninfo.py
index eee20bbf..5862f16d 100644
--- a/boto/regioninfo.py
+++ b/boto/regioninfo.py
@@ -20,7 +20,6 @@
# 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 __future__ import with_statement
import os
import boto
diff --git a/boto/s3/key.py b/boto/s3/key.py
index ec9889b1..9674d356 100644
--- a/boto/s3/key.py
+++ b/boto/s3/key.py
@@ -20,8 +20,6 @@
# 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 __future__ import with_statement
-
import email.utils
import errno
import hashlib
@@ -31,8 +29,9 @@ import re
import base64
import binascii
import math
+from hashlib import md5
import boto.utils
-from boto.compat import BytesIO, six, urllib
+from boto.compat import BytesIO, six, urllib, encodebytes
from boto.exception import BotoClientError
from boto.exception import StorageDataError
@@ -45,11 +44,6 @@ from boto.utils import compute_md5, compute_hash
from boto.utils import find_matching_headers
from boto.utils import merge_headers_by_name
-try:
- from hashlib import md5
-except ImportError:
- from md5 import md5
-
class Key(object):
"""
@@ -211,7 +205,7 @@ class Key(object):
from just having a precalculated md5_hexdigest.
"""
digest = binascii.unhexlify(md5_hexdigest)
- base64md5 = base64.encodestring(digest)
+ base64md5 = encodebytes(digest)
if base64md5[-1] == '\n':
base64md5 = base64md5[0:-1]
return (md5_hexdigest, base64md5)
diff --git a/boto/sdb/db/manager/xmlmanager.py b/boto/sdb/db/manager/xmlmanager.py
index d19ad088..f457347a 100644
--- a/boto/sdb/db/manager/xmlmanager.py
+++ b/boto/sdb/db/manager/xmlmanager.py
@@ -22,7 +22,7 @@ import boto
from boto.utils import find_class, Password
from boto.sdb.db.key import Key
from boto.sdb.db.model import Model
-from boto.compat import six
+from boto.compat import six, encodebytes
from datetime import datetime
from xml.dom.minidom import getDOMImplementation, parse, parseString, Node
@@ -203,8 +203,7 @@ class XMLManager(object):
self.enable_ssl = enable_ssl
self.auth_header = None
if self.db_user:
- import base64
- base64string = base64.encodestring('%s:%s' % (self.db_user, self.db_passwd))[:-1]
+ base64string = encodebytes('%s:%s' % (self.db_user, self.db_passwd))[:-1]
authheader = "Basic %s" % base64string
self.auth_header = authheader
diff --git a/boto/utils.py b/boto/utils.py
index 1569209b..dd5f0956 100644
--- a/boto/utils.py
+++ b/boto/utils.py
@@ -57,25 +57,14 @@ import email.mime.text
import email.utils
import email.encoders
import gzip
-import base64
import threading
import locale
-from boto.compat import six, StringIO, urllib
+from boto.compat import six, StringIO, urllib, encodebytes
from contextlib import contextmanager
-try:
- from hashlib import md5
-except ImportError:
- from md5 import md5
-
-
-try:
- import hashlib
- _hashfn = hashlib.sha512
-except ImportError:
- import md5
- _hashfn = md5.md5
+from hashlib import md5, sha512
+_hashfn = sha512
from boto.compat import json
@@ -224,6 +213,11 @@ def retry_url(url, retry_on_404=True, num_retries=10):
req = urllib.request.Request(url)
r = opener.open(req)
result = r.read()
+
+ if(not isinstance(result, six.string_types) and
+ hasattr(result, 'decode')):
+ result = result.decode('utf-8')
+
return result
except urllib.error.HTTPError as e:
code = e.getcode()
@@ -1029,7 +1023,7 @@ def compute_hash(fp, buf_size=8192, size=None, hash_algorithm=md5):
else:
s = fp.read(buf_size)
hex_digest = hash_obj.hexdigest()
- base64_digest = base64.encodestring(hash_obj.digest()).decode('utf-8')
+ base64_digest = encodebytes(hash_obj.digest()).decode('utf-8')
if base64_digest[-1] == '\n':
base64_digest = base64_digest[0:-1]
# data_size based on bytes read.
diff --git a/boto/vpc/vpc_peering_connection.py b/boto/vpc/vpc_peering_connection.py
index d7c11e8e..cdb9af8d 100644
--- a/boto/vpc/vpc_peering_connection.py
+++ b/boto/vpc/vpc_peering_connection.py
@@ -145,7 +145,7 @@ class VpcPeeringConnection(TaggedEC2Object):
setattr(self, name, value)
def delete(self):
- return self.connection.delete_vpc(self.id)
+ return self.connection.delete_vpc_peering_connection(self.id)
def _update(self, updated):
self.__dict__.update(updated.__dict__)
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 36be2d2e..dd4ac6f5 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -132,6 +132,7 @@ Release Notes
.. toctree::
:titlesonly:
+ releasenotes/v2.32.1
releasenotes/v2.32.0
releasenotes/v2.31.1
releasenotes/v2.31.0
diff --git a/docs/source/releasenotes/v2.32.1.rst b/docs/source/releasenotes/v2.32.1.rst
new file mode 100644
index 00000000..4b06d797
--- /dev/null
+++ b/docs/source/releasenotes/v2.32.1.rst
@@ -0,0 +1,32 @@
+boto v2.32.1
+============
+
+:date: 2014/08/04
+
+This release fixes an incorrect Amazon VPC peering connection call, and fixes
+several minor issues related to Python 3 support including a regression when
+pickling authentication information.
+
+
+Fixes
+-----
+* Fix bin scripts for Python 3. (:issue:`2502`, :issue:`2490`, :sha:`cb78c52`)
+* Fix parsing of EMR step summary response. (:issue:`2456`, :sha:`2ffb00a`)
+* Update wheel to be universal for py2/py3. (:issue:`2478`, :sha:`e872d94`)
+* Add pypy to tox config. (:issue:`2458`, :sha:`16c6fbe`)
+* Fix Glacier file object hash calculation. (:issue:`2489`, :issue:`2488`,
+ :sha:`a9463c5`)
+* PEP8 fixes for Glacier. (:issue:`2469`, :sha:`0575a54`)
+* Use ConfigParser for Python 3 and SafeConfigParser for Python 2.
+ (:issue:`2498`, :issue:`2497`, :sha:`f580f73`)
+* Remove redundant __future__ imports. (:issue:`2496`, :sha:`e59e199`)
+* Fix dynamodb.types.Binary non-ASCII handling. (:issue:`2492`, :issue:`2491`,
+ :sha:`16284ea`)
+* Add missing dependency to requirements.txt. (:issue:`2494`, :sha:`33db71a`)
+* Fix TypeError when getting instance metadata under Python 3. (:issue:`2486`,
+ :issue:`2485`, :sha:`6ff525e`)
+* Handle Cloudsearch indexing errors. (:issue:`2370`, :sha:`494a091`)
+* Remove obsolete md5 import routine. (:issue:`2468`, :sha:`9808a77`)
+* Use encodebytes instead of encodestring. (:issue:`2484`, :issue:`2483`,
+ :sha:`984c5ff`)
+* Fix an auth class pickling bug. (:issue:`2479`, :sha:`07d6424`)
diff --git a/requirements.txt b/requirements.txt
index d4416cbc..22756525 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,3 +7,4 @@ httpretty>=0.7.0
paramiko>=1.10.0
PyYAML>=3.10
coverage==3.7.1
+mock==1.0.1
diff --git a/setup.cfg b/setup.cfg
index bda6d79c..2a9acf13 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,2 +1,2 @@
[bdist_wheel]
-python-tag = py2
+universal = 1
diff --git a/setup.py b/setup.py
index ec673db4..c7a98eaf 100644
--- a/setup.py
+++ b/setup.py
@@ -23,7 +23,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
-from __future__ import with_statement, print_function
+from __future__ import print_function
try:
from setuptools import setup
diff --git a/tests/integration/dynamodb2/test_highlevel.py b/tests/integration/dynamodb2/test_highlevel.py
index 90771422..0f893b14 100644
--- a/tests/integration/dynamodb2/test_highlevel.py
+++ b/tests/integration/dynamodb2/test_highlevel.py
@@ -23,8 +23,6 @@
"""
Tests for DynamoDB v2 high-level abstractions.
"""
-from __future__ import with_statement
-
import os
import time
diff --git a/tests/integration/glacier/test_layer1.py b/tests/integration/glacier/test_layer1.py
index effb5628..0d38da27 100644
--- a/tests/integration/glacier/test_layer1.py
+++ b/tests/integration/glacier/test_layer1.py
@@ -36,7 +36,7 @@ class TestGlacierLayer1(unittest.TestCase):
glacier = Layer1()
glacier.create_vault('l1testvault')
self.addCleanup(glacier.delete_vault, 'l1testvault')
- upload_id = glacier.initiate_multipart_upload('l1testvault', 4*1024*1024,
+ upload_id = glacier.initiate_multipart_upload('l1testvault', 4 * 1024 * 1024,
'double spaces here')['UploadId']
self.addCleanup(glacier.abort_multipart_upload, 'l1testvault', upload_id)
response = glacier.list_multipart_uploads('l1testvault')['UploadsList']
diff --git a/tests/integration/s3/mock_storage_service.py b/tests/integration/s3/mock_storage_service.py
index d7c59930..8b5ff28d 100644
--- a/tests/integration/s3/mock_storage_service.py
+++ b/tests/integration/s3/mock_storage_service.py
@@ -30,6 +30,7 @@ import copy
import boto
import base64
import re
+from hashlib import md5
from boto.utils import compute_md5
from boto.utils import find_matching_headers
@@ -37,11 +38,6 @@ from boto.utils import merge_headers_by_name
from boto.s3.prefix import Prefix
from boto.compat import six
-try:
- from hashlib import md5
-except ImportError:
- from md5 import md5
-
NOT_IMPL = None
diff --git a/tests/integration/ses/test_connection.py b/tests/integration/ses/test_connection.py
index f1d66e8c..83b99944 100644
--- a/tests/integration/ses/test_connection.py
+++ b/tests/integration/ses/test_connection.py
@@ -1,5 +1,3 @@
-from __future__ import with_statement
-
from tests.unit import unittest
from boto.ses.connection import SESConnection
diff --git a/tests/integration/sns/test_connection.py b/tests/integration/sns/test_connection.py
index e5af487e..6a359b1b 100644
--- a/tests/integration/sns/test_connection.py
+++ b/tests/integration/sns/test_connection.py
@@ -19,8 +19,6 @@
# 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 __future__ import with_statement
-
from tests.compat import mock, unittest
from boto.compat import http_client
from boto.sns import connect_to_region
diff --git a/tests/integration/sqs/test_bigmessage.py b/tests/integration/sqs/test_bigmessage.py
index ce1e983d..bb52dde1 100644
--- a/tests/integration/sqs/test_bigmessage.py
+++ b/tests/integration/sqs/test_bigmessage.py
@@ -24,8 +24,6 @@
"""
Some unit tests for the SQSConnection
"""
-from __future__ import with_statement
-
import time
from threading import Timer
from tests.unit import unittest
@@ -42,7 +40,7 @@ class TestBigMessage(unittest.TestCase):
def test_1_basic(self):
c = boto.connect_sqs()
-
+
# create a queue so we can test BigMessage
queue_name = 'test%d' % int(time.time())
timeout = 60
@@ -61,7 +59,7 @@ class TestBigMessage(unittest.TestCase):
fp = StringIO(msg_body)
s3_url = 's3://%s' % queue_name
message = queue.new_message(fp, s3_url=s3_url)
-
+
queue.write(message)
time.sleep(30)
@@ -69,7 +67,7 @@ class TestBigMessage(unittest.TestCase):
# Make sure msg body is in bucket
self.assertTrue(bucket.lookup(s3_object_name))
-
+
m = queue.read()
self.assertEqual(m.get_body().decode('utf-8'), msg_body)
diff --git a/tests/unit/auth/test_sigv4.py b/tests/unit/auth/test_sigv4.py
index 8c16ebd0..674ec0a7 100644
--- a/tests/unit/auth/test_sigv4.py
+++ b/tests/unit/auth/test_sigv4.py
@@ -20,6 +20,7 @@
# IN THE SOFTWARE.
#
import copy
+import pickle
import os
from tests.compat import unittest, mock
from tests.unit import MockServiceWithConfigTestCase
@@ -29,6 +30,7 @@ from boto.auth import S3HmacAuthV4Handler
from boto.auth import detect_potential_s3sigv4
from boto.auth import detect_potential_sigv4
from boto.connection import HTTPRequest
+from boto.provider import Provider
from boto.regioninfo import RegionInfo
@@ -237,6 +239,18 @@ class TestSigV4Handler(unittest.TestCase):
scope = auth.credential_scope(self.request)
self.assertEqual(scope, '20121121/us-west-2/sqs/aws4_request')
+ def test_pickle_works(self):
+ provider = Provider('aws', access_key='access_key',
+ secret_key='secret_key')
+ auth = HmacAuthV4Handler('queue.amazonaws.com', None, provider)
+
+ # Pickle it!
+ pickled = pickle.dumps(auth)
+
+ # Now restore it
+ auth2 = pickle.loads(pickled)
+ self.assertEqual(auth.host, auth2.host)
+
class TestS3HmacAuthV4Handler(unittest.TestCase):
def setUp(self):
diff --git a/tests/unit/cloudsearch/test_document.py b/tests/unit/cloudsearch/test_document.py
index 34c3cad2..929b62be 100644
--- a/tests/unit/cloudsearch/test_document.py
+++ b/tests/unit/cloudsearch/test_document.py
@@ -8,7 +8,7 @@ import json
from boto.cloudsearch.document import DocumentServiceConnection
from boto.cloudsearch.document import CommitMismatchError, EncodingError, \
- ContentTooLongError, DocumentServiceConnection
+ ContentTooLongError, DocumentServiceConnection, SearchServiceException
import boto
@@ -321,3 +321,17 @@ class CloudSearchDocumentErrorMismatch(CloudSearchDocumentTest):
"category": ["cat_a", "cat_b", "cat_c"]})
self.assertRaises(CommitMismatchError, document.commit)
+
+class CloudSearchDocumentsErrorMissingAdds(CloudSearchDocumentTest):
+ response = {
+ 'status': 'error',
+ 'deletes': 0,
+ 'errors': [{'message': 'Unknown error message'}]
+ }
+
+ def test_fake_failure(self):
+ document = DocumentServiceConnection(
+ endpoint="doc-demo-userdomain.us-east-1.cloudsearch.amazonaws.com")
+ document.add("1234", 10, {"id": "1234", "title": "Title 1",
+ "category": ["cat_a", "cat_b", "cat_c"]})
+ self.assertRaises(SearchServiceException, document.commit)
diff --git a/tests/unit/dynamodb/test_types.py b/tests/unit/dynamodb/test_types.py
index f4f7fb54..25b3f78f 100644
--- a/tests/unit/dynamodb/test_types.py
+++ b/tests/unit/dynamodb/test_types.py
@@ -86,6 +86,12 @@ class TestBinary(unittest.TestCase):
self.assertEqual(b'\x01', data)
self.assertEqual(b'\x01', bytes(data))
+ def test_non_ascii_good_input(self):
+ # Binary data that is out of ASCII range
+ data = types.Binary(b'\x88')
+ self.assertEqual(b'\x88', data)
+ self.assertEqual(b'\x88', bytes(data))
+
@unittest.skipUnless(six.PY2, "Python 2 only")
def test_bad_input(self):
with self.assertRaises(TypeError):
@@ -108,6 +114,9 @@ class TestBinary(unittest.TestCase):
# In Python 2.x these are considered equal
self.assertEqual(data, u'\x01')
+ # Check that the value field is of type bytes
+ self.assertEqual(type(data.value), bytes)
+
@unittest.skipUnless(six.PY3, "Python 3 only")
def test_unicode_py3(self):
with self.assertRaises(TypeError):
diff --git a/tests/unit/emr/test_connection.py b/tests/unit/emr/test_connection.py
index 510f0c1f..c60f04a4 100644
--- a/tests/unit/emr/test_connection.py
+++ b/tests/unit/emr/test_connection.py
@@ -19,8 +19,6 @@
# 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 __future__ import with_statement
-
import boto.utils
from datetime import datetime
@@ -374,7 +372,74 @@ class TestListSteps(AWSMockServiceTestCase):
connection_class = EmrConnection
def default_body(self):
- return b"""<ListStepsOutput><Steps><member><Name>Step 1</Name></member></Steps></ListStepsOutput>"""
+ return b"""<ListStepsResponse xmlns="http://elasticmapreduce.amazonaws.com/doc/2009-03-31">
+ <ListStepsResult>
+ <Steps>
+ <member>
+ <Id>abc123</Id>
+ <Status>
+ <StateChangeReason/>
+ <Timeline>
+ <CreationDateTime>2014-07-01T00:00:00.000Z</CreationDateTime>
+ </Timeline>
+ <State>PENDING</State>
+ </Status>
+ <Name>Step 1</Name>
+ <Config>
+ <Jar>/home/hadoop/lib/emr-s3distcp-1.0.jar</Jar>
+ <Args>
+ <member>--src</member>
+ <member>hdfs:///data/test/</member>
+ <member>--dest</member>
+ <member>s3n://test/data</member>
+ </Args>
+ <Properties/>
+ </Config>
+ <ActionOnFailure>CONTINUE</ActionOnFailure>
+ </member>
+ <member>
+ <Id>def456</Id>
+ <Status>
+ <StateChangeReason/>
+ <Timeline>
+ <CreationDateTime>2014-07-01T00:00:00.000Z</CreationDateTime>
+ </Timeline>
+ <State>COMPLETED</State>
+ </Status>
+ <Name>Step 2</Name>
+ <Config>
+ <MainClass>my.main.SomeClass</MainClass>
+ <Jar>s3n://test/jars/foo.jar</Jar>
+ </Config>
+ <ActionOnFailure>CONTINUE</ActionOnFailure>
+ </member>
+ <member>
+ <Id>ghi789</Id>
+ <Status>
+ <StateChangeReason/>
+ <Timeline>
+ <CreationDateTime>2014-07-01T00:00:00.000Z</CreationDateTime>
+ </Timeline>
+ <State>FAILED</State>
+ </Status>
+ <Name>Step 3</Name>
+ <Config>
+ <Jar>s3n://test/jars/bar.jar</Jar>
+ <Args>
+ <member>-arg</member>
+ <member>value</member>
+ </Args>
+ <Properties/>
+ </Config>
+ <ActionOnFailure>TERMINATE_CLUSTER</ActionOnFailure>
+ </member>
+ </Steps>
+ </ListStepsResult>
+ <ResponseMetadata>
+ <RequestId>eff31ee5-0342-11e4-b3c7-9de5a93f6fcb</RequestId>
+ </ResponseMetadata>
+</ListStepsResponse>
+"""
def test_list_steps(self):
self.set_http_response(200)
@@ -392,6 +457,30 @@ class TestListSteps(AWSMockServiceTestCase):
self.assertTrue(isinstance(response, StepSummaryList))
self.assertEqual(response.steps[0].name, 'Step 1')
+ valid_states = [
+ 'PENDING',
+ 'RUNNING',
+ 'COMPLETED',
+ 'CANCELLED',
+ 'FAILED',
+ 'INTERRUPTED'
+ ]
+
+ # Check for step states
+ for step in response.steps:
+ self.assertIn(step.status.state, valid_states)
+
+ # Check for step config
+ step = response.steps[0]
+ self.assertEqual(step.config.jar,
+ '/home/hadoop/lib/emr-s3distcp-1.0.jar')
+ self.assertEqual(len(step.config.args), 4)
+ self.assertEqual(step.config.args[0].value, '--src')
+ self.assertEqual(step.config.args[1].value, 'hdfs:///data/test/')
+
+ step = response.steps[1]
+ self.assertEqual(step.config.mainclass, 'my.main.SomeClass')
+
def test_list_steps_with_states(self):
self.set_http_response(200)
response = self.service_connection.list_steps(
diff --git a/tests/unit/glacier/test_job.py b/tests/unit/glacier/test_job.py
index ac47ad8d..c7b7b1fb 100644
--- a/tests/unit/glacier/test_job.py
+++ b/tests/unit/glacier/test_job.py
@@ -56,7 +56,7 @@ class TestJob(unittest.TestCase):
self.job.get_output(byte_range=(1, 1024), validate_checksum=False)
def test_download_to_fileobj(self):
- http_response=mock.Mock(read=mock.Mock(return_value='xyz'))
+ http_response = mock.Mock(read=mock.Mock(return_value='xyz'))
response = GlacierResponse(http_response, None)
response['TreeHash'] = 'tree_hash'
self.api.get_job_output.return_value = response
diff --git a/tests/unit/glacier/test_layer1.py b/tests/unit/glacier/test_layer1.py
index 1e6490bf..4c8f0cf7 100644
--- a/tests/unit/glacier/test_layer1.py
+++ b/tests/unit/glacier/test_layer1.py
@@ -76,7 +76,7 @@ class GlacierJobOperations(GlacierLayer1ConnectionBase):
self.set_http_response(status_code=200, header=header,
body=self.job_content)
response = self.service_connection.get_job_output(self.vault_name,
- 'example-job-id')
+ 'example-job-id')
self.assertEqual(self.job_content, response.read())
diff --git a/tests/unit/glacier/test_layer2.py b/tests/unit/glacier/test_layer2.py
index eec175d3..84b53aac 100644
--- a/tests/unit/glacier/test_layer2.py
+++ b/tests/unit/glacier/test_layer2.py
@@ -32,73 +32,71 @@ import boto.glacier.vault
from boto.glacier.vault import Vault
from boto.glacier.vault import Job
-from boto.compat import StringIO
-
from datetime import datetime, tzinfo, timedelta
# Some fixture data from the Glacier docs
FIXTURE_VAULT = {
- "CreationDate" : "2012-02-20T17:01:45.198Z",
- "LastInventoryDate" : "2012-03-20T17:03:43.221Z",
- "NumberOfArchives" : 192,
- "SizeInBytes" : 78088912,
- "VaultARN" : "arn:aws:glacier:us-east-1:012345678901:vaults/examplevault",
- "VaultName" : "examplevault"
+ "CreationDate": "2012-02-20T17:01:45.198Z",
+ "LastInventoryDate": "2012-03-20T17:03:43.221Z",
+ "NumberOfArchives": 192,
+ "SizeInBytes": 78088912,
+ "VaultARN": "arn:aws:glacier:us-east-1:012345678901:vaults/examplevault",
+ "VaultName": "examplevault"
}
FIXTURE_VAULTS = {
- 'RequestId': 'vuXO7SHTw-luynJ0Zu31AYjR3TcCn7X25r7ykpuulxY2lv8',
- 'VaultList': [{'SizeInBytes': 0, 'LastInventoryDate': None,
- 'VaultARN': 'arn:aws:glacier:us-east-1:686406519478:vaults/vault0',
- 'VaultName': 'vault0', 'NumberOfArchives': 0,
- 'CreationDate': '2013-05-17T02:38:39.049Z'},
- {'SizeInBytes': 0, 'LastInventoryDate': None,
- 'VaultARN': 'arn:aws:glacier:us-east-1:686406519478:vaults/vault3',
- 'VaultName': 'vault3', 'NumberOfArchives': 0,
- 'CreationDate': '2013-05-17T02:31:18.659Z'}]}
+ 'RequestId': 'vuXO7SHTw-luynJ0Zu31AYjR3TcCn7X25r7ykpuulxY2lv8',
+ 'VaultList': [{'SizeInBytes': 0, 'LastInventoryDate': None,
+ 'VaultARN': 'arn:aws:glacier:us-east-1:686406519478:vaults/vault0',
+ 'VaultName': 'vault0', 'NumberOfArchives': 0,
+ 'CreationDate': '2013-05-17T02:38:39.049Z'},
+ {'SizeInBytes': 0, 'LastInventoryDate': None,
+ 'VaultARN': 'arn:aws:glacier:us-east-1:686406519478:vaults/vault3',
+ 'VaultName': 'vault3', 'NumberOfArchives': 0,
+ 'CreationDate': '2013-05-17T02:31:18.659Z'}]}
FIXTURE_PAGINATED_VAULTS = {
- 'Marker': 'arn:aws:glacier:us-east-1:686406519478:vaults/vault2',
- 'RequestId': 'vuXO7SHTw-luynJ0Zu31AYjR3TcCn7X25r7ykpuulxY2lv8',
- 'VaultList': [{'SizeInBytes': 0, 'LastInventoryDate': None,
- 'VaultARN': 'arn:aws:glacier:us-east-1:686406519478:vaults/vault0',
- 'VaultName': 'vault0', 'NumberOfArchives': 0,
- 'CreationDate': '2013-05-17T02:38:39.049Z'},
- {'SizeInBytes': 0, 'LastInventoryDate': None,
- 'VaultARN': 'arn:aws:glacier:us-east-1:686406519478:vaults/vault1',
- 'VaultName': 'vault1', 'NumberOfArchives': 0,
- 'CreationDate': '2013-05-17T02:31:18.659Z'}]}
+ 'Marker': 'arn:aws:glacier:us-east-1:686406519478:vaults/vault2',
+ 'RequestId': 'vuXO7SHTw-luynJ0Zu31AYjR3TcCn7X25r7ykpuulxY2lv8',
+ 'VaultList': [{'SizeInBytes': 0, 'LastInventoryDate': None,
+ 'VaultARN': 'arn:aws:glacier:us-east-1:686406519478:vaults/vault0',
+ 'VaultName': 'vault0', 'NumberOfArchives': 0,
+ 'CreationDate': '2013-05-17T02:38:39.049Z'},
+ {'SizeInBytes': 0, 'LastInventoryDate': None,
+ 'VaultARN': 'arn:aws:glacier:us-east-1:686406519478:vaults/vault1',
+ 'VaultName': 'vault1', 'NumberOfArchives': 0,
+ 'CreationDate': '2013-05-17T02:31:18.659Z'}]}
FIXTURE_PAGINATED_VAULTS_CONT = {
- 'Marker': None,
- 'RequestId': 'vuXO7SHTw-luynJ0Zu31AYjR3TcCn7X25r7ykpuulxY2lv8',
- 'VaultList': [{'SizeInBytes': 0, 'LastInventoryDate': None,
- 'VaultARN': 'arn:aws:glacier:us-east-1:686406519478:vaults/vault2',
- 'VaultName': 'vault2', 'NumberOfArchives': 0,
- 'CreationDate': '2013-05-17T02:38:39.049Z'},
- {'SizeInBytes': 0, 'LastInventoryDate': None,
- 'VaultARN': 'arn:aws:glacier:us-east-1:686406519478:vaults/vault3',
- 'VaultName': 'vault3', 'NumberOfArchives': 0,
- 'CreationDate': '2013-05-17T02:31:18.659Z'}]}
+ 'Marker': None,
+ 'RequestId': 'vuXO7SHTw-luynJ0Zu31AYjR3TcCn7X25r7ykpuulxY2lv8',
+ 'VaultList': [{'SizeInBytes': 0, 'LastInventoryDate': None,
+ 'VaultARN': 'arn:aws:glacier:us-east-1:686406519478:vaults/vault2',
+ 'VaultName': 'vault2', 'NumberOfArchives': 0,
+ 'CreationDate': '2013-05-17T02:38:39.049Z'},
+ {'SizeInBytes': 0, 'LastInventoryDate': None,
+ 'VaultARN': 'arn:aws:glacier:us-east-1:686406519478:vaults/vault3',
+ 'VaultName': 'vault3', 'NumberOfArchives': 0,
+ 'CreationDate': '2013-05-17T02:31:18.659Z'}]}
FIXTURE_ARCHIVE_JOB = {
- "Action": "ArchiveRetrieval",
- "ArchiveId": ("NkbByEejwEggmBz2fTHgJrg0XBoDfjP4q6iu87-TjhqG6eGoOY9Z8i1_AUyUs"
- "uhPAdTqLHy8pTl5nfCFJmDl2yEZONi5L26Omw12vcs01MNGntHEQL8MBfGlqr"
- "EXAMPLEArchiveId"),
- "ArchiveSizeInBytes": 16777216,
- "Completed": False,
- "CreationDate": "2012-05-15T17:21:39.339Z",
- "CompletionDate": "2012-05-15T17:21:43.561Z",
- "InventorySizeInBytes": None,
- "JobDescription": "My ArchiveRetrieval Job",
- "JobId": ("HkF9p6o7yjhFx-K3CGl6fuSm6VzW9T7esGQfco8nUXVYwS0jlb5gq1JZ55yHgt5v"
- "P54ZShjoQzQVVh7vEXAMPLEjobID"),
- "SHA256TreeHash": ("beb0fe31a1c7ca8c6c04d574ea906e3f97b31fdca7571defb5b44dc"
- "a89b5af60"),
- "SNSTopic": "arn:aws:sns:us-east-1:012345678901:mytopic",
- "StatusCode": "InProgress",
- "StatusMessage": "Operation in progress.",
- "VaultARN": "arn:aws:glacier:us-east-1:012345678901:vaults/examplevault"
+ "Action": "ArchiveRetrieval",
+ "ArchiveId": ("NkbByEejwEggmBz2fTHgJrg0XBoDfjP4q6iu87-TjhqG6eGoOY9Z8i1_AUyUs"
+ "uhPAdTqLHy8pTl5nfCFJmDl2yEZONi5L26Omw12vcs01MNGntHEQL8MBfGlqr"
+ "EXAMPLEArchiveId"),
+ "ArchiveSizeInBytes": 16777216,
+ "Completed": False,
+ "CreationDate": "2012-05-15T17:21:39.339Z",
+ "CompletionDate": "2012-05-15T17:21:43.561Z",
+ "InventorySizeInBytes": None,
+ "JobDescription": "My ArchiveRetrieval Job",
+ "JobId": ("HkF9p6o7yjhFx-K3CGl6fuSm6VzW9T7esGQfco8nUXVYwS0jlb5gq1JZ55yHgt5v"
+ "P54ZShjoQzQVVh7vEXAMPLEjobID"),
+ "SHA256TreeHash": ("beb0fe31a1c7ca8c6c04d574ea906e3f97b31fdca7571defb5b44dc"
+ "a89b5af60"),
+ "SNSTopic": "arn:aws:sns:us-east-1:012345678901:mytopic",
+ "StatusCode": "InProgress",
+ "StatusMessage": "Operation in progress.",
+ "VaultARN": "arn:aws:glacier:us-east-1:012345678901:vaults/examplevault"
}
EXAMPLE_PART_LIST_RESULT_PAGE_1 = {
@@ -107,11 +105,10 @@ EXAMPLE_PART_LIST_RESULT_PAGE_1 = {
"Marker": "MfgsKHVjbQ6EldVl72bn3_n5h2TaGZQUO-Qb3B9j3TITf7WajQ",
"MultipartUploadId": "OW2fM5iVylEpFEMM9_HpKowRapC3vn5sSL39_396UW9zLFUWVrnRHaPjUJddQ5OxSHVXjYtrN47NBZ-khxOjyEXAMPLE",
"PartSizeInBytes": 4194304,
- "Parts":
- [ {
- "RangeInBytes": "4194304-8388607",
- "SHA256TreeHash": "01d34dabf7be316472c93b1ef80721f5d4"
- }],
+ "Parts": [{
+ "RangeInBytes": "4194304-8388607",
+ "SHA256TreeHash": "01d34dabf7be316472c93b1ef80721f5d4"
+ }],
"VaultARN": "arn:aws:glacier:us-east-1:012345678901:vaults/demo1-vault"
}
@@ -123,11 +120,10 @@ EXAMPLE_PART_LIST_RESULT_PAGE_2 = {
"Marker": None,
"MultipartUploadId": None,
"PartSizeInBytes": None,
- "Parts":
- [ {
- "RangeInBytes": "0-4194303",
- "SHA256TreeHash": "01d34dabf7be316472c93b1ef80721f5d4"
- }],
+ "Parts": [{
+ "RangeInBytes": "0-4194303",
+ "SHA256TreeHash": "01d34dabf7be316472c93b1ef80721f5d4"
+ }],
"VaultARN": None
}
@@ -137,14 +133,13 @@ EXAMPLE_PART_LIST_COMPLETE = {
"Marker": None,
"MultipartUploadId": "OW2fM5iVylEpFEMM9_HpKowRapC3vn5sSL39_396UW9zLFUWVrnRHaPjUJddQ5OxSHVXjYtrN47NBZ-khxOjyEXAMPLE",
"PartSizeInBytes": 4194304,
- "Parts":
- [ {
- "RangeInBytes": "4194304-8388607",
- "SHA256TreeHash": "01d34dabf7be316472c93b1ef80721f5d4"
+ "Parts": [{
+ "RangeInBytes": "4194304-8388607",
+ "SHA256TreeHash": "01d34dabf7be316472c93b1ef80721f5d4"
}, {
- "RangeInBytes": "0-4194303",
- "SHA256TreeHash": "01d34dabf7be316472c93b1ef80721f5d4"
- }],
+ "RangeInBytes": "0-4194303",
+ "SHA256TreeHash": "01d34dabf7be316472c93b1ef80721f5d4"
+ }],
"VaultARN": "arn:aws:glacier:us-east-1:012345678901:vaults/demo1-vault"
}
@@ -183,7 +178,7 @@ class TestGlacierLayer2Connection(GlacierLayer2Base):
def return_paginated_vaults_resp(marker=None, limit=None):
return resps.pop(0)
- self.mock_layer1.list_vaults = Mock(side_effect = return_paginated_vaults_resp)
+ self.mock_layer1.list_vaults = Mock(side_effect=return_paginated_vaults_resp)
vaults = self.layer2.list_vaults()
self.assertEqual(vaults[0].name, "vault0")
self.assertEqual(vaults[3].name, "vault3")
@@ -287,11 +282,11 @@ class TestVault(GlacierLayer2Base):
'Parts': [{
'RangeInBytes': '0-3',
'SHA256TreeHash': '12',
- }, {
+ }, {
'RangeInBytes': '4-6',
'SHA256TreeHash': '34',
- },
- ]}
+ }],
+ }
self.vault.list_all_parts = mock_list_parts
self.vault.resume_archive_from_file(
@@ -315,6 +310,7 @@ class TestJob(GlacierLayer2Base):
"HkF9p6o7yjhFx-K3CGl6fuSm6VzW9T7esGQfco8nUXVYwS0jlb5gq1JZ55yHgt5vP"
"54ZShjoQzQVVh7vEXAMPLEjobID", (0, 100))
+
class TestRangeStringParsing(unittest.TestCase):
def test_simple_range(self):
self.assertEquals(
diff --git a/tests/unit/glacier/test_utils.py b/tests/unit/glacier/test_utils.py
index a051b59e..bace2a38 100644
--- a/tests/unit/glacier/test_utils.py
+++ b/tests/unit/glacier/test_utils.py
@@ -19,13 +19,16 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#
-import time
import logging
+import os
+import tempfile
+import time
from hashlib import sha256
from tests.unit import unittest
+from boto.compat import BytesIO, six, StringIO
from boto.glacier.utils import minimum_part_size, chunk_hashes, tree_hash, \
- bytes_to_hex
+ bytes_to_hex, compute_hashes_from_fileobj
class TestPartSizeCalculations(unittest.TestCase):
@@ -114,3 +117,49 @@ class TestTreeHash(unittest.TestCase):
self.assertEqual(
self.calculate_tree_hash(''),
b'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855')
+
+
+class TestFileHash(unittest.TestCase):
+ def _gen_data(self):
+ # Generate some pseudo-random bytes of data. We include the
+ # hard-coded blob as an example that fails to decode via UTF-8.
+ return os.urandom(5000) + b'\xc2\x00'
+
+ def test_compute_hash_tempfile(self):
+ # Compute a hash from a file object. On Python 2 this uses a non-
+ # binary mode. On Python 3, however, binary mode is required for
+ # binary files. If not used, you will get UTF-8 code errors.
+ if six.PY2:
+ mode = "w+"
+ else:
+ mode = "wb+"
+
+ with tempfile.TemporaryFile(mode=mode) as f:
+ f.write(self._gen_data())
+ f.seek(0)
+
+ compute_hashes_from_fileobj(f, chunk_size=512)
+
+ @unittest.skipUnless(six.PY3, 'Python 3 requires reading binary!')
+ def test_compute_hash_tempfile_py3(self):
+ # Note the missing 'b' in the mode!
+ with tempfile.TemporaryFile(mode='w+') as f:
+ with self.assertRaises(ValueError):
+ compute_hashes_from_fileobj(f, chunk_size=512)
+
+ # What about file-like objects without a mode? If it has an
+ # encoding we use it, otherwise attempt UTF-8 encoding to
+ # bytes for hashing.
+ f = StringIO('test data' * 500)
+ compute_hashes_from_fileobj(f, chunk_size=512)
+
+ @unittest.skipUnless(six.PY2, 'Python 3 requires reading binary!')
+ def test_compute_hash_stringio(self):
+ # Python 2 binary data in StringIO example
+ f = StringIO(self._gen_data())
+ compute_hashes_from_fileobj(f, chunk_size=512)
+
+ def test_compute_hash_bytesio(self):
+ # Compute a hash from a file-like BytesIO object.
+ f = BytesIO(self._gen_data())
+ compute_hashes_from_fileobj(f, chunk_size=512)
diff --git a/tests/unit/glacier/test_vault.py b/tests/unit/glacier/test_vault.py
index 68d9d784..f532e3b9 100644
--- a/tests/unit/glacier/test_vault.py
+++ b/tests/unit/glacier/test_vault.py
@@ -44,7 +44,9 @@ class TestVault(unittest.TestCase):
def tearDown(self):
self.size_patch.stop()
- def test_upload_archive_small_file(self):
+ @mock.patch('boto.glacier.vault.compute_hashes_from_fileobj',
+ return_value=[b'abc', b'123'])
+ def test_upload_archive_small_file(self, compute_hashes):
self.getsize.return_value = 1
self.api.upload_archive.return_value = {'ArchiveId': 'archive_id'}
@@ -69,7 +71,7 @@ class TestVault(unittest.TestCase):
# The write should be created with the default part size of the
# instance (2 MB).
self.vault.create_archive_writer.assert_called_with(
- description=mock.ANY, part_size=self.vault.DefaultPartSize)
+ description=mock.ANY, part_size=self.vault.DefaultPartSize)
def test_large_part_size_is_obeyed(self):
self.vault.DefaultPartSize = 8 * 1024 * 1024
diff --git a/tests/unit/glacier/test_writer.py b/tests/unit/glacier/test_writer.py
index c7066b97..b2875f3c 100644
--- a/tests/unit/glacier/test_writer.py
+++ b/tests/unit/glacier/test_writer.py
@@ -27,7 +27,6 @@ from tests.unit import unittest
from mock import (
call,
Mock,
- patch,
sentinel,
)
from nose.tools import assert_equal
@@ -50,7 +49,7 @@ def create_mock_vault():
def partify(data, part_size):
for i in itertools.count(0):
start = i * part_size
- part = data[start:start+part_size]
+ part = data[start:start + part_size]
if part:
yield part
else:
diff --git a/tests/unit/s3/test_key.py b/tests/unit/s3/test_key.py
index ee8b686a..7752d9cd 100644
--- a/tests/unit/s3/test_key.py
+++ b/tests/unit/s3/test_key.py
@@ -20,8 +20,6 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#
-from __future__ import with_statement
-
from tests.compat import mock, unittest
from tests.unit import AWSMockServiceTestCase
diff --git a/tests/unit/test_connection.py b/tests/unit/test_connection.py
index 5d726e06..3d4a57b3 100644
--- a/tests/unit/test_connection.py
+++ b/tests/unit/test_connection.py
@@ -19,8 +19,6 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#
-from __future__ import with_statement
-
import os
import socket
@@ -116,7 +114,7 @@ class TestAWSAuthConnection(unittest.TestCase):
self.assertEqual(conn.get_path('/folder//image.jpg'), '/folder//image.jpg')
self.assertEqual(conn.get_path('/folder////image.jpg'), '/folder////image.jpg')
self.assertEqual(conn.get_path('///folder////image.jpg'), '///folder////image.jpg')
-
+
def test_connection_behind_proxy(self):
os.environ['http_proxy'] = "http://john.doe:p4ssw0rd@127.0.0.1:8180"
conn = AWSAuthConnection(
@@ -130,7 +128,7 @@ class TestAWSAuthConnection(unittest.TestCase):
self.assertEqual(conn.proxy_pass, 'p4ssw0rd')
self.assertEqual(conn.proxy_port, '8180')
del os.environ['http_proxy']
-
+
def test_connection_behind_proxy_without_explicit_port(self):
os.environ['http_proxy'] = "http://127.0.0.1"
conn = AWSAuthConnection(
@@ -139,7 +137,7 @@ class TestAWSAuthConnection(unittest.TestCase):
aws_secret_access_key='secret',
suppress_consec_slashes=False,
port=8180
- )
+ )
self.assertEqual(conn.proxy, '127.0.0.1')
self.assertEqual(conn.proxy_port, 8180)
del os.environ['http_proxy']
diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py
index 2e7ddbd3..57a04a66 100644
--- a/tests/unit/utils/test_utils.py
+++ b/tests/unit/utils/test_utils.py
@@ -207,6 +207,22 @@ class TestRetryURL(unittest.TestCase):
response = retry_url('http://10.10.10.10/foo', num_retries=1)
self.assertEqual(response, 'no proxy response')
+ def test_retry_url_using_bytes_and_string_response(self):
+ test_value = 'normal response'
+ fake_response = mock.Mock()
+
+ # test using unicode
+ fake_response.read.return_value = test_value
+ self.opener.return_value.open.return_value = fake_response
+ response = retry_url('http://10.10.10.10/foo', num_retries=1)
+ self.assertEqual(response, test_value)
+
+ # test using bytes
+ fake_response.read.return_value = test_value.encode('utf-8')
+ self.opener.return_value.open.return_value = fake_response
+ response = retry_url('http://10.10.10.10/foo', num_retries=1)
+ self.assertEqual(response, test_value)
+
class TestLazyLoadMetadata(unittest.TestCase):
def setUp(self):
diff --git a/tests/unit/vpc/test_vpc_peering_connection.py b/tests/unit/vpc/test_vpc_peering_connection.py
index 42e68d44..503e5606 100644
--- a/tests/unit/vpc/test_vpc_peering_connection.py
+++ b/tests/unit/vpc/test_vpc_peering_connection.py
@@ -19,7 +19,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
-from tests.unit import unittest
+from tests.unit import mock, unittest
from tests.unit import AWSMockServiceTestCase
from boto.vpc import VpcPeeringConnection, VPCConnection, Subnet
@@ -159,6 +159,58 @@ class TestDeleteVpcPeeringConnection(AWSMockServiceTestCase):
self.set_http_response(status_code=200)
self.assertEquals(self.service_connection.delete_vpc_peering_connection('pcx-12345678'), True)
+class TestDeleteVpcPeeringConnectionShortForm(unittest.TestCase):
+ DESCRIBE_VPC_PEERING_CONNECTIONS= b"""<?xml version="1.0" encoding="UTF-8"?>
+<DescribeVpcPeeringConnectionsResponse xmlns="http://ec2.amazonaws.com/doc/2014-05-01/">
+ <requestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</requestId>
+ <vpcPeeringConnectionSet>
+ <item>
+ <vpcPeeringConnectionId>pcx-111aaa22</vpcPeeringConnectionId>
+ <requesterVpcInfo>
+ <ownerId>777788889999</ownerId>
+ <vpcId>vpc-1a2b3c4d</vpcId>
+ <cidrBlock>172.31.0.0/16</cidrBlock>
+ </requesterVpcInfo>
+ <accepterVpcInfo>
+ <ownerId>111122223333</ownerId>
+ <vpcId>vpc-aa22cc33</vpcId>
+ </accepterVpcInfo>
+ <status>
+ <code>pending-acceptance</code>
+ <message>Pending Acceptance by 111122223333</message>
+ </status>
+ <expirationTime>2014-02-17T16:00:50.000Z</expirationTime>
+ </item>
+ </vpcPeeringConnectionSet>
+</DescribeVpcPeeringConnectionsResponse>"""
+
+ DELETE_VPC_PEERING_CONNECTION= b"""<DeleteVpcPeeringConnectionResponse xmlns="http://ec2.amazonaws.com/doc/2014-05-01/">
+ <requestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</requestId>
+ <return>true</return>
+</DeleteVpcPeeringConnectionResponse>"""
+
+ def test_delete_vpc_peering_connection(self):
+ vpc_conn = VPCConnection(aws_access_key_id='aws_access_key_id',
+ aws_secret_access_key='aws_secret_access_key')
+
+ mock_response = mock.Mock()
+ mock_response.read.return_value = self.DESCRIBE_VPC_PEERING_CONNECTIONS
+ mock_response.status = 200
+ vpc_conn.make_request = mock.Mock(return_value=mock_response)
+ vpc_peering_connections = vpc_conn.get_all_vpc_peering_connections()
+
+ self.assertEquals(1, len(vpc_peering_connections))
+ vpc_peering_connection = vpc_peering_connections[0]
+
+ mock_response = mock.Mock()
+ mock_response.read.return_value = self.DELETE_VPC_PEERING_CONNECTION
+ mock_response.status = 200
+ vpc_conn.make_request = mock.Mock(return_value=mock_response)
+ self.assertEquals(True, vpc_peering_connection.delete())
+
+ self.assertIn('DeleteVpcPeeringConnection', vpc_conn.make_request.call_args_list[0][0])
+ self.assertNotIn('DeleteVpc', vpc_conn.make_request.call_args_list[0][0])
+
class TestRejectVpcPeeringConnection(AWSMockServiceTestCase):
REJECT_VPC_PEERING_CONNECTION= b"""<RejectVpcPeeringConnectionResponse xmlns="http://ec2.amazonaws.com/doc/2014-05-01/">
<requestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</requestId>
diff --git a/tox.ini b/tox.ini
index 0b46af64..b523bd85 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = py26,py27,py33,py34
+envlist = py26,py27,py33,py34,pypy
# Comment to build sdist and install into virtualenv
# This is helpful to test installation but takes extra time
@@ -10,7 +10,6 @@ skipsdist = True
deps =
unittest2
ordereddict
- mock==1.0.1
-rrequirements.txt
# Some tests expect specific ordering, so we set the hash
# seed for Python 2.x until all tests are updated for Python 3.
@@ -22,12 +21,16 @@ setenv =
[testenv:py27]
deps =
- mock==1.0.1
-rrequirements.txt
# See comment above in py26 about hash seed.
setenv =
PYTHONHASHSEED = 0
+[testenv:pypy]
+# See comment above in py26 about hash seed.
+setenv =
+ PYTHONHASHSEED = 0
+
[testenv]
deps = -rrequirements.txt
commands =