diff options
author | Daniel Lindsley <daniel@toastdriven.com> | 2013-05-20 16:49:28 -0700 |
---|---|---|
committer | Daniel Lindsley <daniel@toastdriven.com> | 2013-05-20 16:49:28 -0700 |
commit | 9bffa78496b63f54ddb22460af570f6f1e69c9f9 (patch) | |
tree | 62be007798a805b2eb2d08794b03bb5e70f3b401 | |
parent | fc8d7dd54eba7e51c3b425d46bc0643f12be6fa2 (diff) | |
parent | 2cdd72908e941c0e6ea34f72c415c85d86f0da2a (diff) | |
download | boto-2.9.4.tar.gz |
Merge branch 'release-2.9.4'2.9.4
* release-2.9.4:
Bumping version to 2.9.4
Finalized release notes for v2.9.4.
Added tests for the cloudformation millisecond bug.
Fixing cloudformation time stamps when API returns milliseconds
A few PEP8 fixes and added some missing files to the EC2 ref docs index.
Fixed regex that failed on S3 buckets with uppercase letters.
Add release note item for bug fix
Adjust part size as needed in create_archive_from_file
Started the release notes for 2.9.4.
Added pypi downloads badge.
change os.path.normpath to posixpath.normpath because on windows os.path.normpath converts forward-slashes to backward-slashes
Made the release & date line up again.
Add Released: prefix for date in README
Fixed up a test failure in the DynamoDB suite.
Started the 2.9.4-dev cycle.
-rw-r--r-- | README.rst | 8 | ||||
-rw-r--r-- | boto/__init__.py | 4 | ||||
-rw-r--r-- | boto/auth.py | 4 | ||||
-rw-r--r-- | boto/cloudformation/stack.py | 12 | ||||
-rw-r--r-- | boto/ec2/instancestatus.py | 21 | ||||
-rw-r--r-- | boto/ec2/tag.py | 11 | ||||
-rw-r--r-- | boto/ec2/vmtype.py | 9 | ||||
-rw-r--r-- | boto/ec2/volumestatus.py | 21 | ||||
-rw-r--r-- | boto/glacier/vault.py | 3 | ||||
-rw-r--r-- | docs/source/ref/ec2.rst | 57 | ||||
-rw-r--r-- | docs/source/releasenotes/v2.9.4.rst | 30 | ||||
-rw-r--r-- | tests/unit/cloudformation/test_stack.py | 13 | ||||
-rw-r--r-- | tests/unit/dynamodb2/test_table.py | 12 | ||||
-rw-r--r-- | tests/unit/glacier/test_vault.py | 14 |
14 files changed, 177 insertions, 42 deletions
@@ -1,11 +1,15 @@ #### boto #### -boto 2.9.3 -30-Apr-2013 +boto 2.9.4 + +Released: 20-May-2013 .. image:: https://travis-ci.org/boto/boto.png?branch=develop :target: https://travis-ci.org/boto/boto + +.. image:: https://pypip.in/d/boto/badge.png + :target: https://crate.io/packages/boto/ ************ Introduction diff --git a/boto/__init__.py b/boto/__init__.py index 1ea07916..45ae1d04 100644 --- a/boto/__init__.py +++ b/boto/__init__.py @@ -36,14 +36,14 @@ import logging.config import urlparse from boto.exception import InvalidUriError -__version__ = '2.9.3' +__version__ = '2.9.4' Version = __version__ # for backware compatibility UserAgent = 'Boto/%s (%s)' % (__version__, sys.platform) config = Config() # Regex to disallow buckets violating charset or not [3..255] chars total. -BUCKET_NAME_RE = re.compile(r'^[a-z0-9][a-z0-9\._-]{1,253}[a-z0-9]$') +BUCKET_NAME_RE = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9\._-]{1,253}[a-zA-Z0-9]$') # Regex to disallow buckets with individual DNS labels longer than 63. TOO_LONG_DNS_NAME_COMP = re.compile(r'[-_a-z0-9]{64}') GENERATION_RE = re.compile(r'(?P<versionless_uri_str>.+)' diff --git a/boto/auth.py b/boto/auth.py index 3d694569..cd7ac68f 100644 --- a/boto/auth.py +++ b/boto/auth.py @@ -36,10 +36,10 @@ import copy import datetime from email.utils import formatdate import hmac -import os import sys import time import urllib +import posixpath from boto.auth_handler import AuthHandler from boto.exception import BotoClientError @@ -385,7 +385,7 @@ class HmacAuthV4Handler(AuthHandler, HmacKeys): def canonical_uri(self, http_request): # Normalize the path. - normalized = os.path.normpath(http_request.auth_path) + normalized = posixpath.normpath(http_request.auth_path) # Then urlencode whatever's left. encoded = urllib.quote(normalized) return encoded diff --git a/boto/cloudformation/stack.py b/boto/cloudformation/stack.py index a27df49b..289e18f4 100644 --- a/boto/cloudformation/stack.py +++ b/boto/cloudformation/stack.py @@ -41,7 +41,10 @@ class Stack(object): def endElement(self, name, value, connection): if name == 'CreationTime': - self.creation_time = datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ') + try: + self.creation_time = datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ') + except ValueError: + self.creation_time = datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%fZ') elif name == "Description": self.description = value elif name == "DisableRollback": @@ -212,7 +215,7 @@ class Tag(dict): self._current_value = value else: setattr(self, name, value) - + if self._current_key and self._current_value: self[self._current_key] = self._current_value self._current_key = None @@ -351,7 +354,10 @@ class StackEvent(object): elif name == "StackName": self.stack_name = value elif name == "Timestamp": - self.timestamp = datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ') + try: + self.timestamp = datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ') + except ValueError: + self.timestamp = datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%fZ') else: setattr(self, name, value) diff --git a/boto/ec2/instancestatus.py b/boto/ec2/instancestatus.py index 3a9b5434..166732eb 100644 --- a/boto/ec2/instancestatus.py +++ b/boto/ec2/instancestatus.py @@ -16,11 +16,12 @@ # 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. + class Details(dict): """ A dict object that contains name/value pairs which provide @@ -38,7 +39,8 @@ class Details(dict): self[self._name] = value else: setattr(self, name, value) - + + class Event(object): """ A status event for an instance. @@ -57,7 +59,7 @@ class Event(object): self.description = description self.not_before = not_before self.not_after = not_after - + def __repr__(self): return 'Event:%s' % self.code @@ -76,6 +78,7 @@ class Event(object): else: setattr(self, name, value) + class Status(object): """ A generic Status object used for system status and instance status. @@ -90,7 +93,7 @@ class Status(object): if not details: details = Details() self.details = details - + def __repr__(self): return 'Status:%s' % self.status @@ -105,8 +108,9 @@ class Status(object): else: setattr(self, name, value) + class EventSet(list): - + def startElement(self, name, attrs, connection): if name == 'item': event = Event() @@ -118,6 +122,7 @@ class EventSet(list): def endElement(self, name, value, connection): setattr(self, name, value) + class InstanceStatus(object): """ Represents an EC2 Instance status as reported by @@ -137,7 +142,7 @@ class InstanceStatus(object): :ivar instance_status: A Status object that reports impaired functionality that arises from problems internal to the instance. """ - + def __init__(self, id=None, zone=None, events=None, state_code=None, state_name=None): self.id = id @@ -174,6 +179,7 @@ class InstanceStatus(object): else: setattr(self, name, value) + class InstanceStatusSet(list): """ A list object that contains the results of a call to @@ -191,7 +197,7 @@ class InstanceStatusSet(list): list.__init__(self) self.connection = connection self.next_token = None - + def startElement(self, name, attrs, connection): if name == 'item': status = InstanceStatus() @@ -204,4 +210,3 @@ class InstanceStatusSet(list): if name == 'NextToken': self.next_token = value setattr(self, name, value) - diff --git a/boto/ec2/tag.py b/boto/ec2/tag.py index 8032e6ff..deb2c788 100644 --- a/boto/ec2/tag.py +++ b/boto/ec2/tag.py @@ -15,11 +15,12 @@ # 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. + class TagSet(dict): """ A TagSet is used to collect the tags associated with a particular @@ -27,7 +28,7 @@ class TagSet(dict): can, this dict object will be used to collect those values. See :class:`boto.ec2.ec2object.TaggedEC2Object` for more details. """ - + def __init__(self, connection=None): self.connection = connection self._current_key = None @@ -55,7 +56,7 @@ class Tag(object): also the ID of the resource to which the tag is attached as well as the type of the resource. """ - + def __init__(self, connection=None, res_id=None, res_type=None, name=None, value=None): self.connection = connection @@ -81,7 +82,3 @@ class Tag(object): self.value = value else: setattr(self, name, value) - - - - diff --git a/boto/ec2/vmtype.py b/boto/ec2/vmtype.py index 423ddbfb..fdb4f369 100644 --- a/boto/ec2/vmtype.py +++ b/boto/ec2/vmtype.py @@ -22,6 +22,7 @@ from boto.ec2.ec2object import EC2Object + class VmType(EC2Object): """ Represents an EC2 VM Type @@ -32,7 +33,8 @@ class VmType(EC2Object): :ivar disk: The amount of disk space in gigabytes for this vm type """ - def __init__(self, connection=None, name=None, cores=None, memory=None, disk=None): + def __init__(self, connection=None, name=None, cores=None, + memory=None, disk=None): EC2Object.__init__(self, connection) self.connection = connection self.name = name @@ -41,7 +43,8 @@ class VmType(EC2Object): self.disk = disk def __repr__(self): - return 'VmType:%s-%s,%s,%s' % (self.name, self.cores, self.memory, self.disk) + return 'VmType:%s-%s,%s,%s' % (self.name, self.cores, + self.memory, self.disk) def endElement(self, name, value, connection): if name == 'euca:name': @@ -54,5 +57,3 @@ class VmType(EC2Object): self.memory = value else: setattr(self, name, value) - - diff --git a/boto/ec2/volumestatus.py b/boto/ec2/volumestatus.py index 7bbc173c..78de2bb0 100644 --- a/boto/ec2/volumestatus.py +++ b/boto/ec2/volumestatus.py @@ -16,13 +16,14 @@ # 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. from boto.ec2.instancestatus import Status, Details + class Event(object): """ A status event for an instance. @@ -43,7 +44,7 @@ class Event(object): self.description = description self.not_before = not_before self.not_after = not_after - + def __repr__(self): return 'Event:%s' % self.type @@ -64,8 +65,9 @@ class Event(object): else: setattr(self, name, value) + class EventSet(list): - + def startElement(self, name, attrs, connection): if name == 'item': event = Event() @@ -77,6 +79,7 @@ class EventSet(list): def endElement(self, name, value, connection): setattr(self, name, value) + class Action(object): """ An action for an instance. @@ -92,7 +95,7 @@ class Action(object): self.id = id self.type = type self.description = description - + def __repr__(self): return 'Action:%s' % self.code @@ -111,8 +114,9 @@ class Action(object): else: setattr(self, name, value) + class ActionSet(list): - + def startElement(self, name, attrs, connection): if name == 'item': action = Action() @@ -124,6 +128,7 @@ class ActionSet(list): def endElement(self, name, value, connection): setattr(self, name, value) + class VolumeStatus(object): """ Represents an EC2 Volume status as reported by @@ -136,7 +141,7 @@ class VolumeStatus(object): :ivar events: A list of events relevant to the instance. :ivar actions: A list of events relevant to the instance. """ - + def __init__(self, id=None, zone=None): self.id = id self.zone = zone @@ -167,6 +172,7 @@ class VolumeStatus(object): else: setattr(self, name, value) + class VolumeStatusSet(list): """ A list object that contains the results of a call to @@ -184,7 +190,7 @@ class VolumeStatusSet(list): list.__init__(self) self.connection = connection self.next_token = None - + def startElement(self, name, attrs, connection): if name == 'item': status = VolumeStatus() @@ -197,4 +203,3 @@ class VolumeStatusSet(list): if name == 'NextToken': self.next_token = value setattr(self, name, value) - diff --git a/boto/glacier/vault.py b/boto/glacier/vault.py index 6e47bf76..ac019ac9 100644 --- a/boto/glacier/vault.py +++ b/boto/glacier/vault.py @@ -161,8 +161,7 @@ class Vault(object): if not file_obj: file_size = os.path.getsize(filename) try: - min_part_size = minimum_part_size(file_size, - self.DefaultPartSize) + part_size = minimum_part_size(file_size, part_size) except ValueError: raise UploadArchiveError("File size of %s bytes exceeds " "40,000 GB archive limit of Glacier.") diff --git a/docs/source/ref/ec2.rst b/docs/source/ref/ec2.rst index 96f511e2..db98293c 100644 --- a/docs/source/ref/ec2.rst +++ b/docs/source/ref/ec2.rst @@ -23,6 +23,13 @@ boto.ec2.autoscale See the :doc:`Auto Scaling Reference <autoscale>`. +boto.ec2.blockdevicemapping +--------------------------- + +.. automodule:: boto.ec2.blockdevicemapping + :members: + :undoc-members: + boto.ec2.buyreservation ----------------------- @@ -54,6 +61,13 @@ boto.ec2.elb See the :doc:`ELB Reference <elb>`. +boto.ec2.group +---------------- + +.. automodule:: boto.ec2.group + :members: + :undoc-members: + boto.ec2.image -------------- @@ -89,6 +103,27 @@ boto.ec2.keypair :members: :undoc-members: +boto.ec2.launchspecification +---------------- + +.. automodule:: boto.ec2.launchspecification + :members: + :undoc-members: + +boto.ec2.networkinterface +---------------- + +.. automodule:: boto.ec2.networkinterface + :members: + :undoc-members: + +boto.ec2.placementgroup +---------------- + +.. automodule:: boto.ec2.placementgroup + :members: + :undoc-members: + boto.ec2.regioninfo ------------------- @@ -124,6 +159,20 @@ boto.ec2.spotinstancerequest :members: :undoc-members: +boto.ec2.tag +---------------------------- + +.. automodule:: boto.ec2.tag + :members: + :undoc-members: + +boto.ec2.vmtype +---------------------------- + +.. automodule:: boto.ec2.vmtype + :members: + :undoc-members: + boto.ec2.volume --------------- @@ -131,10 +180,16 @@ boto.ec2.volume :members: :undoc-members: +boto.ec2.volumestatus +--------------------- + +.. automodule:: boto.ec2.volumestatus + :members: + :undoc-members: + boto.ec2.zone ------------- .. automodule:: boto.ec2.zone :members: :undoc-members: - diff --git a/docs/source/releasenotes/v2.9.4.rst b/docs/source/releasenotes/v2.9.4.rst new file mode 100644 index 00000000..1956dbff --- /dev/null +++ b/docs/source/releasenotes/v2.9.4.rst @@ -0,0 +1,30 @@ +boto v2.9.4 +=========== + +:date: 2013/05/20 + +This release adds updated Elastic Transcoder support & fixes several bugs +from recent releases & API updates. + + +Features +-------- + +* Updated Elastic Transcoder support - It now supports HLS, WebM, MPEG2-TS & a + host of `other features`_. (SHA: 89196a) + + .. _`other features`: http://aws.typepad.com/aws/2013/05/new-features-for-the-amazon-elastic-transcoder.html + + +Bugfixes +-------- + +* Fixed a bug in the canonicalization of URLs on Windows. (SHA: 09ef8c) +* Fixed glacier part size bug (issue: 1478, SHA: 9e04171) +* Fixed a bug in the bucket regex for S3 involving capital letters. + (SHA: 950031) +* Fixed a bug where timestamps from Cloudformation would fail to be parsed. + (SHA: b40542) +* Several documentation improvements/fixes: + + * Added autodocs for many of the EC2 apis. (SHA: 79f939) diff --git a/tests/unit/cloudformation/test_stack.py b/tests/unit/cloudformation/test_stack.py index 4f0db6aa..54d2dc90 100644 --- a/tests/unit/cloudformation/test_stack.py +++ b/tests/unit/cloudformation/test_stack.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +import datetime import xml.sax import unittest import boto.handler @@ -59,5 +60,17 @@ class TestStackParse(unittest.TestCase): tags = rs[0].tags self.assertEqual(tags, {u'key0': u'value0', u'key1': u'value1'}) + def test_creation_time_with_millis(self): + millis_xml = SAMPLE_XML.replace( + "<CreationTime>2013-01-10T05:04:56Z</CreationTime>", + "<CreationTime>2013-01-10T05:04:56.102342Z</CreationTime>" + ) + + rs = boto.resultset.ResultSet([('member', boto.cloudformation.stack.Stack)]) + h = boto.handler.XmlHandler(rs, None) + xml.sax.parseString(millis_xml, h) + creation_time = rs[0].creation_time + self.assertEqual(creation_time, datetime.datetime(2013, 1, 10, 5, 4, 56, 102342)) + if __name__ == '__main__': unittest.main() diff --git a/tests/unit/dynamodb2/test_table.py b/tests/unit/dynamodb2/test_table.py index 053ca099..591f69c4 100644 --- a/tests/unit/dynamodb2/test_table.py +++ b/tests/unit/dynamodb2/test_table.py @@ -1621,7 +1621,9 @@ class TableTestCase(unittest.TestCase): 'ComparisonOperator': 'LE', } }, - limit=2 + limit=2, + segment=None, + total_segments=None ) # Now alter the expected. @@ -1640,7 +1642,9 @@ class TableTestCase(unittest.TestCase): friend_count__lte=2, exclusive_start_key={ 'username': 'adam', - } + }, + segment=None, + total_segments=None ) usernames = [res['username'] for res in results['results']] self.assertEqual(usernames, ['alice', 'bob', 'jane']) @@ -1659,7 +1663,9 @@ class TableTestCase(unittest.TestCase): 'username': { 'S': 'adam', }, - } + }, + segment=None, + total_segments=None ) def test_query(self): diff --git a/tests/unit/glacier/test_vault.py b/tests/unit/glacier/test_vault.py index f61d5874..a4ef008b 100644 --- a/tests/unit/glacier/test_vault.py +++ b/tests/unit/glacier/test_vault.py @@ -82,6 +82,20 @@ class TestVault(unittest.TestCase): self.vault.create_archive_writer.assert_called_with( description=mock.ANY, part_size=self.vault.DefaultPartSize) + def test_part_size_needs_to_be_adjusted(self): + # If we have a large file (400 GB) + self.getsize.return_value = 400 * 1024 * 1024 * 1024 + self.vault.create_archive_writer = mock.Mock() + # When we try to upload the file. + with mock.patch('boto.glacier.vault.open', self.mock_open, + create=True): + self.vault.create_archive_from_file('myfile') + # We should automatically bump up the part size used to + # 64 MB. + expected_part_size = 64 * 1024 * 1024 + self.vault.create_archive_writer.assert_called_with( + description=mock.ANY, part_size=expected_part_size) + class TestConcurrentUploads(unittest.TestCase): |