Hello community, here is the log from the commit of package python-boto3 for openSUSE:Factory checked in at 2016-04-14 13:05:47 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-boto3 (Old) and /work/SRC/openSUSE:Factory/.python-boto3.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "python-boto3" Changes: -------- --- /work/SRC/openSUSE:Factory/python-boto3/python-boto3.changes 2016-02-11 12:37:52.000000000 +0100 +++ /work/SRC/openSUSE:Factory/.python-boto3.new/python-boto3.changes 2016-04-14 13:05:48.000000000 +0200 @@ -1,0 +2,21 @@ +Fri Apr 8 18:44:56 UTC 2016 - rjschwei@suse.com + +- Fix version requirement for botocore + +------------------------------------------------------------------- +Fri Apr 8 17:47:00 UTC 2016 - rjschwei@suse.com + +- Update to version 1.3.0 (bsc#974705) + + feature:Session: Add get_available_partitions and get_available_regions + methods to determine partitions and a service's available regions. + + feature:EC2: Update resource model to include Route resources. (issue 532) +- From 1.2.6 - (2016-03-01) + + bugfix:Resources: Properly alias identifiers which are also in the shape. +- From 1.2.5 - (2016-02-25) + + bugfix:S3: Forward extra_args when using multipart downloads. (issue 503) +- From 1.2.4 - (2016-02-18) + + feature:EC2: Add delete_tags() action to Instance resource. (issue 459) + + feature:Session: Add region_name property on session. (issue 414) + + bugfix:S3: Fix issue with hanging downloads. (issue 471) + +------------------------------------------------------------------- Old: ---- boto3-1.2.3.tar.gz New: ---- boto3-1.3.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-boto3.spec ++++++ --- /var/tmp/diff_new_pack.qF9c00/_old 2016-04-14 13:05:49.000000000 +0200 +++ /var/tmp/diff_new_pack.qF9c00/_new 2016-04-14 13:05:49.000000000 +0200 @@ -17,7 +17,7 @@ Name: python-boto3 -Version: 1.2.3 +Version: 1.3.0 Release: 0 Url: https://github.com/boto/boto3 Summary: Amazon Web Services Library @@ -28,8 +28,8 @@ BuildRequires: python-devel BuildRequires: python-futures BuildRequires: python-setuptools -Requires: python-botocore < 1.4 -Requires: python-botocore >= 1.3.0 +Requires: python-botocore < 1.5 +Requires: python-botocore >= 1.4.1 Requires: python-futures Requires: python-jmespath < 1.0.0 Requires: python-jmespath >= 0.7.1 ++++++ boto3-1.2.3.tar.gz -> boto3-1.3.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/LICENSE new/boto3-1.3.0/LICENSE --- old/boto3-1.2.3/LICENSE 2015-12-17 23:21:35.000000000 +0100 +++ new/boto3-1.3.0/LICENSE 2016-03-16 01:14:39.000000000 +0100 @@ -1,4 +1,4 @@ -Copyright 2013-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. +Copyright 2013-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/PKG-INFO new/boto3-1.3.0/PKG-INFO --- old/boto3-1.2.3/PKG-INFO 2015-12-17 23:21:36.000000000 +0100 +++ new/boto3-1.3.0/PKG-INFO 2016-03-16 01:14:40.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: boto3 -Version: 1.2.3 +Version: 1.3.0 Summary: The AWS SDK for Python Home-page: https://github.com/boto/boto3 Author: Amazon Web Services @@ -130,3 +130,4 @@ Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/boto3/__init__.py new/boto3-1.3.0/boto3/__init__.py --- old/boto3-1.2.3/boto3/__init__.py 2015-12-17 23:21:35.000000000 +0100 +++ new/boto3-1.3.0/boto3/__init__.py 2016-03-16 01:14:40.000000000 +0100 @@ -17,7 +17,7 @@ __author__ = 'Amazon Web Services' -__version__ = '1.2.3' +__version__ = '1.3.0' # The default Boto3 session; autoloaded when needed. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/boto3/compat.py new/boto3-1.3.0/boto3/compat.py --- old/boto3-1.2.3/boto3/compat.py 2015-12-17 23:21:35.000000000 +0100 +++ new/boto3-1.3.0/boto3/compat.py 2016-03-16 01:14:39.000000000 +0100 @@ -13,6 +13,18 @@ import sys import os import errno +import socket + +from botocore.vendored import six + +if six.PY3: + # In python3, socket.error is OSError, which is too general + # for what we want (i.e FileNotFoundError is a subclass of OSError). + # In py3 all the socket related errors are in a newly created + # ConnectionError + SOCKET_ERROR = ConnectionError +else: + SOCKET_ERROR = socket.error if sys.platform.startswith('win'): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/boto3/data/dynamodb/2012-08-10/resources-1.json new/boto3-1.3.0/boto3/data/dynamodb/2012-08-10/resources-1.json --- old/boto3-1.2.3/boto3/data/dynamodb/2012-08-10/resources-1.json 2015-12-17 23:21:35.000000000 +0100 +++ new/boto3-1.3.0/boto3/data/dynamodb/2012-08-10/resources-1.json 2016-03-16 01:14:40.000000000 +0100 @@ -130,6 +130,20 @@ ] } } + }, + "waiters":{ + "Exists": { + "waiterName": "TableExists", + "params": [ + { "target": "TableName", "source": "identifier", "name": "Name" } + ] + }, + "NotExists": { + "waiterName": "TableNotExists", + "params": [ + { "target": "TableName", "source": "identifier", "name": "Name" } + ] + } } } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/boto3/data/ec2/2015-10-01/resources-1.json new/boto3-1.3.0/boto3/data/ec2/2015-10-01/resources-1.json --- old/boto3-1.2.3/boto3/data/ec2/2015-10-01/resources-1.json 2015-12-17 23:21:35.000000000 +0100 +++ new/boto3-1.3.0/boto3/data/ec2/2015-10-01/resources-1.json 2016-03-16 01:14:40.000000000 +0100 @@ -491,7 +491,7 @@ "request": { "operation": "DescribeAddresses", "params": [ - { "target": "PublicIp", "source": "identifier", "name": "PublicIp" } + { "target": "PublicIps[]", "source": "identifier", "name": "PublicIp" } ] }, "path": "Addresses[0]" @@ -1356,6 +1356,16 @@ } ], "shape": "InstanceNetworkInterfaceAssociation", + "load": { + "request": { + "operation": "DescribeNetworkInterfaces", + "params": [ + { "target": "Filters[0].Name", "source": "string", "value": "association.association-id" }, + { "target": "Filters[0].Values[0]", "source": "identifier", "name": "Id" } + ] + }, + "path": "NetworkInterfaces[0].Association" + }, "actions": { "Delete": { "request": { @@ -1423,6 +1433,46 @@ } } }, + "Route": { + "identifiers": [ + { "name": "RouteTableId" }, + { + "name": "DestinationCidrBlock", + "memberName": "DestinationCidrBlock" + } + ], + "shape": "Route", + "actions": { + "Delete": { + "request": { + "operation": "DeleteRoute", + "params": [ + { "target": "RouteTableId", "source": "identifier", "name": "RouteTableId" }, + { "target": "DestinationCidrBlock", "source": "identifier", "name": "DestinationCidrBlock" } + ] + } + }, + "Replace": { + "request": { + "operation": "ReplaceRoute", + "params": [ + { "target": "RouteTableId", "source": "identifier", "name": "RouteTableId" }, + { "target": "DestinationCidrBlock", "source": "identifier", "name": "DestinationCidrBlock" } + ] + } + } + }, + "has": { + "RouteTable": { + "resource": { + "type": "RouteTable", + "identifiers": [ + { "target": "Id", "source": "identifier", "name": "RouteTableId" } + ] + } + } + } + }, "RouteTable": { "identifiers": [ { @@ -1461,6 +1511,13 @@ "params": [ { "target": "RouteTableId", "source": "identifier", "name": "Id" } ] + }, + "resource": { + "type": "Route", + "identifiers": [ + { "target": "RouteTableId", "source": "identifier", "name": "Id" }, + { "target": "DestinationCidrBlock", "source": "requestParameter", "path": "DestinationCidrBlock" } + ] } }, "CreateTags": { @@ -1489,6 +1546,16 @@ } }, "has": { + "Routes": { + "resource": { + "type": "Route", + "identifiers": [ + { "target": "RouteTableId", "source": "identifier", "name": "Id" }, + { "target": "DestinationCidrBlock", "source": "data", "path": "Routes[].DestinationCidrBlock" } + ], + "path": "Routes[]" + } + }, "Vpc": { "resource": { "type": "Vpc", @@ -2423,6 +2490,15 @@ } } }, + "waiters": { + "Exists": { + "waiterName": "VpcPeeringConnectionExists", + "params": [ + { "target": "VpcPeeringConnectionIds[]", "source": "identifier", "name": "Id" } + ], + "path": "VpcPeeringConnections[0]" + } + }, "has": { "AccepterVpc": { "resource": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/boto3/docs/action.py new/boto3-1.3.0/boto3/docs/action.py --- old/boto3-1.2.3/boto3/docs/action.py 2015-12-17 23:21:35.000000000 +0100 +++ new/boto3-1.3.0/boto3/docs/action.py 2016-03-16 01:14:39.000000000 +0100 @@ -40,6 +40,7 @@ 'automatically handle the passing in of arguments set ' 'from identifiers and some attributes.'), intro_link='actions_intro') + for action_name in sorted(resource_actions): action_section = section.add_new_section(action_name) if action_name in ['load', 'reload'] and self._resource_model.load: @@ -61,7 +62,7 @@ ) else: document_custom_method( - section, action_name, resource_actions[action_name]) + action_section, action_name, resource_actions[action_name]) def document_action(section, resource_name, event_emitter, action_model, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/boto3/docs/attr.py new/boto3-1.3.0/boto3/docs/attr.py --- old/boto3-1.2.3/boto3/docs/attr.py 2015-12-17 23:21:35.000000000 +0100 +++ new/boto3-1.3.0/boto3/docs/attr.py 2016-03-16 01:14:40.000000000 +0100 @@ -11,16 +11,28 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. from botocore.docs.utils import py_type_name +from botocore.docs.params import ResponseParamsDocumenter from boto3.docs.utils import get_identifier_description -def document_attribute(section, attr_name, attr_model, include_signature=True): +class ResourceShapeDocumenter(ResponseParamsDocumenter): + EVENT_NAME = 'resource-shape' + + +def document_attribute(section, service_name, resource_name, attr_name, + event_emitter, attr_model, include_signature=True): if include_signature: section.style.start_sphinx_py_attr(attr_name) - attr_type = '*(%s)* ' % py_type_name(attr_model.type_name) - section.write(attr_type) - section.include_doc_string(attr_model.documentation) + # Note that an attribute may have one, may have many, or may have no + # operations that back the resource's shape. So we just set the + # operation_name to the resource name if we ever to hook in and modify + # a particular attribute. + ResourceShapeDocumenter( + service_name=service_name, operation_name=resource_name, + event_emitter=event_emitter).document_params( + section=section, + shape=attr_model) def document_identifier(section, resource_name, identifier_model, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/boto3/docs/resource.py new/boto3-1.3.0/boto3/docs/resource.py --- old/boto3-1.2.3/boto3/docs/resource.py 2015-12-17 23:21:35.000000000 +0100 +++ new/boto3-1.3.0/boto3/docs/resource.py 2016-03-16 01:14:40.000000000 +0100 @@ -169,7 +169,10 @@ attribute_list.append(attr_name) document_attribute( section=attribute_section, + service_name=self._service_name, + resource_name=self._resource_name, attr_name=attr_name, + event_emitter=self._resource.meta.client.meta.events, attr_model=attr_shape ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/boto3/docs/service.py new/boto3-1.3.0/boto3/docs/service.py --- old/boto3-1.2.3/boto3/docs/service.py 2015-12-17 23:21:35.000000000 +0100 +++ new/boto3-1.3.0/boto3/docs/service.py 2016-03-16 01:14:39.000000000 +0100 @@ -14,8 +14,7 @@ import boto3 from botocore.exceptions import DataNotFoundError -from botocore.docs.paginator import PaginatorDocumenter -from botocore.docs.waiter import WaiterDocumenter +from botocore.docs.service import ServiceDocumenter as BaseServiceDocumenter from botocore.docs.bcdoc.restdoc import DocumentStructure from boto3.utils import ServiceContext @@ -24,20 +23,20 @@ from boto3.docs.resource import ServiceResourceDocumenter -class ServiceDocumenter(object): +class ServiceDocumenter(BaseServiceDocumenter): # The path used to find examples EXAMPLE_PATH = os.path.join(os.path.dirname(boto3.__file__), 'examples') def __init__(self, service_name, session): self._service_name = service_name - self._session = session + self._boto3_session = session # I know that this is an internal attribute, but the botocore session # is needed to load the paginator and waiter models. - self._botocore_session = session._session - self._client = self._session.client(service_name) + self._session = session._session + self._client = self._boto3_session.client(service_name) self._service_resource = None - if self._service_name in self._session.get_available_resources(): - self._service_resource = self._session.resource(service_name) + if self._service_name in self._boto3_session.get_available_resources(): + self._service_resource = self._boto3_session.resource(service_name) self.sections = [ 'title', 'table-of-contents', @@ -57,12 +56,12 @@ doc_structure = DocumentStructure( self._service_name, section_names=self.sections, target='html') - self._document_title(doc_structure.get_section('title')) - self._document_table_of_contents( - doc_structure.get_section('table-of-contents')) - self._document_client(doc_structure.get_section('client')) - self._document_paginators(doc_structure.get_section('paginators')) - self._document_waiters(doc_structure.get_section('waiters')) + self.title(doc_structure.get_section('title')) + self.table_of_contents(doc_structure.get_section('table-of-contents')) + + self.client_api(doc_structure.get_section('client')) + self.paginator_api(doc_structure.get_section('paginators')) + self.waiter_api(doc_structure.get_section('waiters')) if self._service_resource: self._document_service_resource( doc_structure.get_section('service-resource')) @@ -70,63 +69,47 @@ self._document_examples(doc_structure.get_section('examples')) return doc_structure.flush_structure() - def _document_title(self, section): - section.style.h1(self._client.__class__.__name__) - - def _document_table_of_contents(self, section): - section.style.table_of_contents(title='Table of Contents', depth=2) - - def _document_client(self, section): - Boto3ClientDocumenter(self._client).document_client(section) - - def _document_paginators(self, section): + def client_api(self, section): + examples = None try: - paginator_model = self._botocore_session.get_paginator_model( - self._service_name) + examples = self.get_examples(self._service_name) except DataNotFoundError: - return - paginator_documenter = PaginatorDocumenter( - self._client, paginator_model) - paginator_documenter.document_paginators(section) - - def _document_waiters(self, section): - if self._client.waiter_names: - service_waiter_model = self._botocore_session.get_waiter_model( - self._service_name) - waiter_documenter = WaiterDocumenter( - self._client, service_waiter_model) - waiter_documenter.document_waiters(section) + pass + + Boto3ClientDocumenter(self._client, examples).document_client(section) def _document_service_resource(self, section): ServiceResourceDocumenter( - self._service_resource, self._botocore_session).document_resource( + self._service_resource, self._session).document_resource( section) def _document_resources(self, section): temp_identifier_value = 'foo' - loader = self._botocore_session.get_component('data_loader') + loader = self._session.get_component('data_loader') json_resource_model = loader.load_service_model( self._service_name, 'resources-1') service_model = self._service_resource.meta.client.meta.service_model for resource_name in json_resource_model['resources']: resource_model = json_resource_model['resources'][resource_name] - resource_cls = self._session.resource_factory.load_from_definition( - resource_name=resource_name, - single_resource_json_definition=resource_model, - service_context=ServiceContext( - service_name=self._service_name, - resource_json_definitions=json_resource_model['resources'], - service_model=service_model, - service_waiter_model=None + resource_cls = self._boto3_session.resource_factory.\ + load_from_definition( + resource_name=resource_name, + single_resource_json_definition=resource_model, + service_context=ServiceContext( + service_name=self._service_name, + resource_json_definitions=json_resource_model[ + 'resources'], + service_model=service_model, + service_waiter_model=None + ) ) - ) identifiers = resource_cls.meta.resource_model.identifiers args = [] for _ in identifiers: args.append(temp_identifier_value) resource = resource_cls(*args, client=self._client) ResourceDocumenter( - resource, self._botocore_session).document_resource( + resource, self._session).document_resource( section.add_new_section(resource.meta.resource_model.name)) def _get_example_file(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/boto3/ec2/deletetags.py new/boto3-1.3.0/boto3/ec2/deletetags.py --- old/boto3-1.2.3/boto3/ec2/deletetags.py 1970-01-01 01:00:00.000000000 +0100 +++ new/boto3-1.3.0/boto3/ec2/deletetags.py 2016-03-16 01:14:39.000000000 +0100 @@ -0,0 +1,34 @@ +# Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +from boto3.resources.action import CustomModeledAction + + +def inject_delete_tags(event_emitter, **kwargs): + action_model = { + 'request': { + 'operation': 'DeleteTags', + 'params': [{ + 'target': 'Resources[0]', + 'source': 'identifier', + 'name': 'Id' + }] + } + } + action = CustomModeledAction( + 'delete_tags', action_model, delete_tags, event_emitter) + action.inject(**kwargs) + + +def delete_tags(self, **kwargs): + kwargs['Resources'] = [self.id] + return self.meta.client.delete_tags(**kwargs) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/boto3/exceptions.py new/boto3-1.3.0/boto3/exceptions.py --- old/boto3-1.2.3/boto3/exceptions.py 2015-12-17 23:21:35.000000000 +0100 +++ new/boto3-1.3.0/boto3/exceptions.py 2016-03-16 01:14:40.000000000 +0100 @@ -11,29 +11,77 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -class ResourceLoadException(Exception): +# All exceptions in this class should subclass from Boto3Error. +import botocore.exceptions + + +# All exceptions should subclass from Boto3Error in this module. +class Boto3Error(Exception): + """Base class for all Boto3 errors.""" + + +class ResourceLoadException(Boto3Error): pass -class NoVersionFound(Exception): +# NOTE: This doesn't appear to be used anywhere. +# It's probably safe to remove this. +class NoVersionFound(Boto3Error): pass -class RetriesExceededError(Exception): +# We're subclassing from botocore.exceptions.DataNotFoundError +# to keep backwards compatibility with anyone that was catching +# this low level Botocore error before this exception was +# introduced in boto3. +# Same thing for ResourceNotExistsError below. +class UnknownAPIVersionError(Boto3Error, + botocore.exceptions.DataNotFoundError): + def __init__(self, service_name, bad_api_version, + available_api_versions): + msg = ( + "The '%s' resource does not an API version of: %s\n" + "Valid API versions are: %s" + % (service_name, bad_api_version, available_api_versions) + ) + # Not using super because we don't want the DataNotFoundError + # to be called, it has a different __init__ signature. + Boto3Error.__init__(self, msg) + + +class ResourceNotExistsError(Boto3Error, + botocore.exceptions.DataNotFoundError): + """Raised when you attempt to create a resource that does not exist.""" + def __init__(self, service_name, available_services, has_low_level_client): + msg = ( + "The '%s' resource does not exist.\n" + "The available resources are:\n" + " - %s\n" % (service_name, '\n - '.join(available_services)) + ) + if has_low_level_client: + msg += ( + "\nConsider using a boto3.client('%s') instead " + "of a resource for '%s'" % (service_name, service_name)) + # Not using super because we don't want the DataNotFoundError + # to be called, it has a different __init__ signature. + Boto3Error.__init__(self, msg) + + +class RetriesExceededError(Boto3Error): def __init__(self, last_exception, msg='Max Retries Exceeded'): super(RetriesExceededError, self).__init__(msg) self.last_exception = last_exception -class S3TransferFailedError(Exception): +class S3TransferFailedError(Boto3Error): pass -class S3UploadFailedError(Exception): +class S3UploadFailedError(Boto3Error): pass -class DynamoDBOperationNotSupportedError(Exception): +class DynamoDBOperationNotSupportedError(Boto3Error): """Raised for operantions that are not supported for an operand""" def __init__(self, operation, value): msg = ( @@ -46,7 +94,7 @@ # FIXME: Backward compatibility DynanmoDBOperationNotSupportedError = DynamoDBOperationNotSupportedError -class DynamoDBNeedsConditionError(Exception): +class DynamoDBNeedsConditionError(Boto3Error): """Raised when input is not a condition""" def __init__(self, value): msg = ( @@ -56,5 +104,5 @@ Exception.__init__(self, msg) -class DynamoDBNeedsKeyConditionError(Exception): +class DynamoDBNeedsKeyConditionError(Boto3Error): pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/boto3/resources/action.py new/boto3-1.3.0/boto3/resources/action.py --- old/boto3-1.2.3/boto3/resources/action.py 2015-12-17 23:21:35.000000000 +0100 +++ new/boto3-1.3.0/boto3/resources/action.py 2016-03-16 01:14:39.000000000 +0100 @@ -17,6 +17,10 @@ from .params import create_request_parameters from .response import RawHandler, ResourceHandler +from .model import Action + +from boto3.docs.docstring import ActionDocstring +from boto3.utils import inject_attribute logger = logging.getLogger(__name__) @@ -197,3 +201,42 @@ response = waiter.wait(**params) logger.debug('Response: %r', response) + + +class CustomModeledAction(object): + """A custom, modeled action to inject into a resource.""" + def __init__(self, action_name, action_model, + function, event_emitter): + """ + :type action_name: str + :param action_name: The name of the action to inject, e.g. 'delete_tags' + + :type action_model: dict + :param action_model: A JSON definition of the action, as if it were + part of the resource model. + + :type function: function + :param function: The function to perform when the action is called. + The first argument should be 'self', which will be the resource + the function is to be called on. + + :type event_emitter: :py:class:`botocore.hooks.BaseEventHooks` + :param event_emitter: The session event emitter. + """ + self.name = action_name + self.model = action_model + self.function = function + self.emitter = event_emitter + + def inject(self, class_attributes, service_context, event_name, **kwargs): + resource_name = event_name.rsplit(".")[-1] + action = Action(self.name, self.model, {}) + self.function.__name__ = self.name + self.function.__doc__ = ActionDocstring( + resource_name=resource_name, + event_emitter=self.emitter, + action_model=action, + service_model=service_context.service_model, + include_signature=False + ) + inject_attribute(class_attributes, self.name, self.function) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/boto3/resources/factory.py new/boto3-1.3.0/boto3/resources/factory.py --- old/boto3-1.2.3/boto3/resources/factory.py 2015-12-17 23:21:35.000000000 +0100 +++ new/boto3-1.3.0/boto3/resources/factory.py 2016-03-16 01:14:40.000000000 +0100 @@ -105,7 +105,8 @@ # Attributes that get auto-loaded self._load_attributes( - attrs=attrs, meta=meta, resource_model=resource_model, + attrs=attrs, meta=meta, resource_name=resource_name, + resource_model=resource_model, service_context=service_context) # Collections and their corresponding methods @@ -133,9 +134,10 @@ base_classes = [ServiceResource] if self._emitter is not None: - self._emitter.emit('creating-resource-class.%s' % cls_name, - class_attributes=attrs, - base_classes=base_classes) + self._emitter.emit( + 'creating-resource-class.%s' % cls_name, + class_attributes=attrs, base_classes=base_classes, + service_context=service_context) return type(str(cls_name), tuple(base_classes), attrs) def _load_identifiers(self, attrs, meta, resource_model, resource_name): @@ -167,21 +169,39 @@ action_model=action, resource_name=resource_name, service_context=service_context) - def _load_attributes(self, attrs, meta, resource_model, service_context): + def _load_attributes(self, attrs, meta, resource_name, resource_model, + service_context): """ Load resource attributes based on the resource shape. The shape name is referenced in the resource JSON, but the shape itself is defined in the Botocore service JSON, hence the need for access to the ``service_model``. """ - if resource_model.shape: - shape = service_context.service_model.shape_for( - resource_model.shape) - - attributes = resource_model.get_attributes(shape) - for name, (orig_name, member) in attributes.items(): - attrs[name] = self._create_autoload_property( - name=orig_name, snake_cased=name, member_model=member) + if not resource_model.shape: + return + + shape = service_context.service_model.shape_for( + resource_model.shape) + + identifiers = dict((i.member_name, i) + for i in resource_model.identifiers if i.member_name) + attributes = resource_model.get_attributes(shape) + for name, (orig_name, member) in attributes.items(): + if name in identifiers: + prop = self._create_identifier_alias( + resource_name=resource_name, + identifier=identifiers[name], + member_model=member, + service_context=service_context + ) + else: + prop = self._create_autoload_property( + resource_name=resource_name, + name=orig_name, snake_cased=name, + member_model=member, + service_context=service_context + ) + attrs[name] = prop def _load_collections(self, attrs, resource_model, service_context): """ @@ -264,8 +284,28 @@ return property(get_identifier) - def _create_autoload_property(factory_self, name, snake_cased, - member_model): + def _create_identifier_alias(factory_self, resource_name, identifier, + member_model, service_context): + """ + Creates a read-only property that aliases an identifier. + """ + def get_identifier(self): + return getattr(self, '_' + identifier.name, None) + + get_identifier.__name__ = str(identifier.member_name) + get_identifier.__doc__ = docstring.AttributeDocstring( + service_name=service_context.service_name, + resource_name=resource_name, + attr_name=identifier.member_name, + event_emitter=factory_self._emitter, + attr_model=member_model, + include_signature=False + ) + + return property(get_identifier) + + def _create_autoload_property(factory_self, resource_name, name, + snake_cased, member_model, service_context): """ Creates a new property on the resource to lazy-load its value via the resource's ``load`` method (if it exists). @@ -286,7 +326,10 @@ property_loader.__name__ = str(snake_cased) property_loader.__doc__ = docstring.AttributeDocstring( + service_name=service_context.service_name, + resource_name=resource_name, attr_name=snake_cased, + event_emitter=factory_self._emitter, attr_model=member_model, include_signature=False ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/boto3/resources/model.py new/boto3-1.3.0/boto3/resources/model.py --- old/boto3-1.2.3/boto3/resources/model.py 2015-12-17 23:21:35.000000000 +0100 +++ new/boto3-1.3.0/boto3/resources/model.py 2016-03-16 01:14:39.000000000 +0100 @@ -38,9 +38,10 @@ :type name: string :param name: The name of the identifier """ - def __init__(self, name): + def __init__(self, name, member_name=None): #: (``string``) The name of the identifier self.name = name + self.member_name = member_name class Action(object): @@ -428,7 +429,10 @@ for item in self._definition.get('identifiers', []): name = self._get_name('identifier', item['name']) - identifiers.append(Identifier(name)) + member_name = item.get('memberName', None) + if member_name: + member_name = self._get_name('attribute', member_name) + identifiers.append(Identifier(name, member_name)) return identifiers diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/boto3/s3/transfer.py new/boto3-1.3.0/boto3/s3/transfer.py --- old/boto3-1.2.3/boto3/s3/transfer.py 2015-12-17 23:21:35.000000000 +0100 +++ new/boto3-1.3.0/boto3/s3/transfer.py 2016-03-16 01:14:39.000000000 +0100 @@ -148,6 +148,10 @@ MB = 1024 * 1024 SHUTDOWN_SENTINEL = object() +S3_RETRYABLE_ERRORS = ( + socket.timeout, boto3.compat.SOCKET_ERROR, + ReadTimeoutError, IncompleteReadError +) def random_file_extension(num_digits=8): @@ -463,7 +467,7 @@ # 1 thread for the future that manages IO writes. download_parts_handler = functools.partial( self._download_file_as_future, - bucket, key, filename, object_size, callback) + bucket, key, filename, object_size, extra_args, callback) parts_future = controller.submit(download_parts_handler) io_writes_handler = functools.partial( @@ -479,13 +483,13 @@ future.result() def _download_file_as_future(self, bucket, key, filename, object_size, - callback): + extra_args, callback): part_size = self._config.multipart_chunksize num_parts = int(math.ceil(object_size / float(part_size))) max_workers = self._config.max_concurrency download_partial = functools.partial( self._download_range, bucket, key, filename, - part_size, num_parts, callback) + part_size, num_parts, extra_args, callback) try: with self._executor_cls(max_workers=max_workers) as executor: list(executor.map(download_partial, range(num_parts))) @@ -502,7 +506,8 @@ return range_param def _download_range(self, bucket, key, filename, - part_size, num_parts, callback, part_index): + part_size, num_parts, + extra_args, callback, part_index): try: range_param = self._calculate_range_param( part_size, part_index, num_parts) @@ -513,7 +518,8 @@ try: logger.debug("Making get_object call.") response = self._client.get_object( - Bucket=bucket, Key=key, Range=range_param) + Bucket=bucket, Key=key, Range=range_param, + **extra_args) streaming_body = StreamReaderProgress( response['Body'], callback) buffer_size = 1024 * 16 @@ -523,8 +529,7 @@ self._ioqueue.put((current_index, chunk)) current_index += len(chunk) return - except (socket.timeout, socket.error, - ReadTimeoutError, IncompleteReadError) as e: + except S3_RETRYABLE_ERRORS as e: logger.debug("Retrying exception caught (%s), " "retrying request, (attempt %s / %s)", e, i, max_attempts, exc_info=True) @@ -535,6 +540,15 @@ logger.debug("EXITING _download_range for part: %s", part_index) def _perform_io_writes(self, filename): + try: + self._loop_on_io_writes(filename) + except Exception as e: + logger.debug("Caught exception in IO thread: %s", + e, exc_info=True) + self._ioqueue.trigger_shutdown() + raise + + def _loop_on_io_writes(self, filename): with self._os.open(filename, 'wb') as f: while True: task = self._ioqueue.get() @@ -543,15 +557,9 @@ "shutting down IO handler.") return else: - try: - offset, data = task - f.seek(offset) - f.write(data) - except Exception as e: - logger.debug("Caught exception in IO thread: %s", - e, exc_info=True) - self._ioqueue.trigger_shutdown() - raise + offset, data = task + f.seek(offset) + f.write(data) class TransferConfig(object): @@ -699,10 +707,7 @@ try: return self._do_get_object(bucket, key, filename, extra_args, callback) - except (socket.timeout, socket.error, - ReadTimeoutError, IncompleteReadError) as e: - # TODO: we need a way to reset the callback if the - # download failed. + except S3_RETRYABLE_ERRORS as e: logger.debug("Retrying exception caught (%s), " "retrying request, (attempt %s / %s)", e, i, max_attempts, exc_info=True) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/boto3/session.py new/boto3-1.3.0/boto3/session.py --- old/boto3-1.2.3/boto3/session.py 2015-12-17 23:21:35.000000000 +0100 +++ new/boto3-1.3.0/boto3/session.py 2016-03-16 01:14:40.000000000 +0100 @@ -16,9 +16,11 @@ import botocore.session from botocore.client import Config +from botocore.exceptions import DataNotFoundError, UnknownServiceError import boto3 import boto3.utils +from boto3.exceptions import ResourceNotExistsError, UnknownAPIVersionError from .resources.factory import ResourceFactory @@ -90,6 +92,13 @@ return self._session.profile or 'default' @property + def region_name(self): + """ + The **read-only** region name. + """ + return self._session.get_config_variable('region') + + @property def events(self): """ The event emitter for a session @@ -102,7 +111,7 @@ """ self._loader = self._session.get_component('data_loader') self._loader.search_paths.append( - os.path.join(os.path.dirname(__file__), 'data')) + os.path.join(os.path.dirname(__file__), 'data')) def get_available_services(self): """ @@ -124,6 +133,37 @@ """ return self._loader.list_available_services(type_name='resources-1') + def get_available_partitions(self): + """Lists the available partitions + + :rtype: list + :return: Returns a list of partition names (e.g., ["aws", "aws-cn"]) + """ + return self._session.get_available_partitions() + + def get_available_regions(self, service_name, partition_name='aws', + allow_non_regional=False): + """Lists the region and endpoint names of a particular partition. + + :type service_name: string + :param service_name: Name of a service to list endpoint for (e.g., s3). + + :type partition_name: string + :param partition_name: Name of the partition to limit endpoints to. + (e.g., aws for the public AWS endpoints, aws-cn for AWS China + endpoints, aws-us-gov for AWS GovCloud (US) Endpoints, etc.) + + :type allow_non_regional: bool + :param allow_non_regional: Set to True to include endpoints that are + not regional endpoints (e.g., s3-external-1, + fips-us-gov-west-1, etc). + + :return: Returns a list of endpoint names (e.g., ["us-east-1"]). + """ + return self._session.get_available_regions( + service_name=service_name, partition_name=partition_name, + allow_non_regional=allow_non_regional) + def client(self, service_name, region_name=None, api_version=None, use_ssl=True, verify=None, endpoint_url=None, aws_access_key_id=None, aws_secret_access_key=None, @@ -200,9 +240,9 @@ aws_session_token=aws_session_token, config=config) def resource(self, service_name, region_name=None, api_version=None, - use_ssl=True, verify=None, endpoint_url=None, - aws_access_key_id=None, aws_secret_access_key=None, - aws_session_token=None, config=None): + use_ssl=True, verify=None, endpoint_url=None, + aws_access_key_id=None, aws_secret_access_key=None, + aws_session_token=None, config=None): """ Create a resource service client by name. @@ -268,11 +308,40 @@ :return: Subclass of :py:class:`~boto3.resources.base.ServiceResource` """ + try: + resource_model = self._loader.load_service_model( + service_name, 'resources-1', api_version) + except UnknownServiceError as e: + available = self.get_available_resources() + has_low_level_client = ( + service_name in self.get_available_services()) + raise ResourceNotExistsError(service_name, available, + has_low_level_client) + except DataNotFoundError as e: + # This is because we've provided an invalid API version. + available_api_versions = self._loader.list_api_versions( + service_name, 'resources-1') + raise UnknownAPIVersionError( + service_name, api_version, ', '.join(available_api_versions)) + if api_version is None: + # Even though botocore's load_service_model() can handle + # using the latest api_version if not provided, we need + # to track this api_version in boto3 in order to ensure + # we're pairing a resource model with a client model + # of the same API version. It's possible for the latest + # API version of a resource model in boto3 to not be + # the same API version as a service model in botocore. + # So we need to look up the api_version if one is not + # provided to ensure we load the same API version of the + # client. + # + # Note: This is relying on the fact that + # loader.load_service_model(..., api_version=None) + # and loader.determine_latest_version(..., 'resources-1') + # both load the same api version of the file. api_version = self._loader.determine_latest_version( service_name, 'resources-1') - resource_model = self._loader.load_service_model( - service_name, 'resources-1', api_version) # Creating a new resource instance requires the low-level client # and service model, the resource version and resource JSON data. @@ -343,3 +412,9 @@ 'creating-resource-class.ec2.ServiceResource', boto3.utils.lazy_call( 'boto3.ec2.createtags.inject_create_tags')) + + self._session.register( + 'creating-resource-class.ec2.Instance', + boto3.utils.lazy_call( + 'boto3.ec2.deletetags.inject_delete_tags', + event_emitter=self.events)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/boto3/utils.py new/boto3-1.3.0/boto3/utils.py --- old/boto3-1.2.3/boto3/utils.py 2015-12-17 23:21:35.000000000 +0100 +++ new/boto3-1.3.0/boto3/utils.py 2016-03-16 01:14:39.000000000 +0100 @@ -53,11 +53,15 @@ return sys.modules[name] -def lazy_call(full_name): +def lazy_call(full_name, **kwargs): + parent_kwargs = kwargs + def _handler(**kwargs): module, function_name = full_name.rsplit('.', 1) module = import_module(module) + kwargs.update(parent_kwargs) return getattr(module, function_name)(**kwargs) + return _handler diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/boto3.egg-info/PKG-INFO new/boto3-1.3.0/boto3.egg-info/PKG-INFO --- old/boto3-1.2.3/boto3.egg-info/PKG-INFO 2015-12-17 23:21:36.000000000 +0100 +++ new/boto3-1.3.0/boto3.egg-info/PKG-INFO 2016-03-16 01:14:40.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: boto3 -Version: 1.2.3 +Version: 1.3.0 Summary: The AWS SDK for Python Home-page: https://github.com/boto/boto3 Author: Amazon Web Services @@ -130,3 +130,4 @@ Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/boto3.egg-info/SOURCES.txt new/boto3-1.3.0/boto3.egg-info/SOURCES.txt --- old/boto3-1.2.3/boto3.egg-info/SOURCES.txt 2015-12-17 23:21:36.000000000 +0100 +++ new/boto3-1.3.0/boto3.egg-info/SOURCES.txt 2016-03-16 01:14:40.000000000 +0100 @@ -48,6 +48,7 @@ boto3/dynamodb/types.py boto3/ec2/__init__.py boto3/ec2/createtags.py +boto3/ec2/deletetags.py boto3/resources/__init__.py boto3/resources/action.py boto3/resources/base.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/boto3.egg-info/requires.txt new/boto3-1.3.0/boto3.egg-info/requires.txt --- old/boto3-1.2.3/boto3.egg-info/requires.txt 2015-12-17 23:21:36.000000000 +0100 +++ new/boto3-1.3.0/boto3.egg-info/requires.txt 2016-03-16 01:14:40.000000000 +0100 @@ -1,4 +1,4 @@ -botocore>=1.3.0,<1.4.0 +botocore>=1.4.1,<1.5.0 jmespath>=0.7.1,<1.0.0 futures>=2.2.0,<4.0.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/requirements.txt new/boto3-1.3.0/requirements.txt --- old/boto3-1.2.3/requirements.txt 2015-12-17 23:21:35.000000000 +0100 +++ new/boto3-1.3.0/requirements.txt 2016-03-16 01:14:39.000000000 +0100 @@ -1,5 +1,5 @@ -e git://github.com/boto/botocore.git@develop#egg=botocore -e git://github.com/boto/jmespath.git@develop#egg=jmespath nose==1.3.3 -mock==1.0.1 +mock==1.3.0 wheel==0.24.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/setup.cfg new/boto3-1.3.0/setup.cfg --- old/boto3-1.2.3/setup.cfg 2015-12-17 23:21:36.000000000 +0100 +++ new/boto3-1.3.0/setup.cfg 2016-03-16 01:14:40.000000000 +0100 @@ -3,7 +3,7 @@ [metadata] requires-dist = - botocore>=1.3.0,<1.4.0 + botocore>=1.4.1,<1.5.0 jmespath>=0.7.1,<1.0.0 futures>=2.2.0,<4.0.0; python_version=="2.6" or python_version=="2.7" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/boto3-1.2.3/setup.py new/boto3-1.3.0/setup.py --- old/boto3-1.2.3/setup.py 2015-12-17 23:21:35.000000000 +0100 +++ new/boto3-1.3.0/setup.py 2016-03-16 01:14:40.000000000 +0100 @@ -15,7 +15,7 @@ requires = [ - 'botocore>=1.3.0,<1.4.0', + 'botocore>=1.4.1,<1.5.0', 'jmespath>=0.7.1,<1.0.0', ] @@ -64,5 +64,6 @@ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', ], )