python-botocore/botocore/client.py

321 lines
13 KiB
Python
Raw Normal View History

2015-10-08 20:16:07 +02:00
# Copyright 2014 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.
2015-10-08 20:16:11 +02:00
import copy
2015-10-08 20:16:07 +02:00
from botocore.model import ServiceModel
from botocore.exceptions import DataNotFoundError
from botocore.exceptions import OperationNotPageableError
2015-10-08 20:16:11 +02:00
from botocore.exceptions import ClientError
2015-10-08 20:16:07 +02:00
from botocore import waiter
from botocore import xform_name
from botocore.paginate import Paginator
2015-10-08 20:16:11 +02:00
from botocore.utils import CachedProperty
2015-10-08 20:16:07 +02:00
import botocore.validate
import botocore.serialize
from botocore import credentials
class ClientCreator(object):
"""Creates client objects for a service."""
2015-10-08 20:16:11 +02:00
def __init__(self, loader, endpoint_creator, event_emitter,
response_parser_factory=None):
2015-10-08 20:16:07 +02:00
self._loader = loader
self._endpoint_creator = endpoint_creator
self._event_emitter = event_emitter
2015-10-08 20:16:11 +02:00
self._response_parser_factory = response_parser_factory
2015-10-08 20:16:07 +02:00
def create_client(self, service_name, region_name, is_secure=True,
endpoint_url=None, verify=None,
aws_access_key_id=None, aws_secret_access_key=None,
aws_session_token=None):
service_model = self._load_service_model(service_name)
cls = self.create_client_class(service_name)
client_args = self._get_client_args(
service_model, region_name, is_secure, endpoint_url,
verify, aws_access_key_id, aws_secret_access_key,
aws_session_token)
return cls(**client_args)
def create_client_class(self, service_name):
service_model = self._load_service_model(service_name)
methods = self._create_methods(service_model)
py_name_to_operation_name = self._create_name_mapping(service_model)
self._add_pagination_methods(service_model, methods,
py_name_to_operation_name)
self._add_waiter_methods(service_model, methods,
py_name_to_operation_name)
cls = type(service_name, (BaseClient,), methods)
return cls
def _add_pagination_methods(self, service_model, methods, name_mapping):
loader = self._loader
def get_paginator(self, operation_name):
"""Create a paginator for an operation.
:type operation_name: string
:param operation_name: The operation name. This is the same name
as the method name on the client. For example, if the
method name is ``create_foo``, and you'd normally invoke the
operation as ``client.create_foo(**kwargs)``, if the
``create_foo`` operation can be paginated, you can use the
call ``client.get_paginator("create_foo")``.
:raise OperationNotPageableError: Raised if the operation is not
pageable. You can use the ``client.can_paginate`` method to
check if an operation is pageable.
:rtype: L{botocore.paginate.Paginator}
:return: A paginator object.
"""
# Note that the 'self' in this method refers to the self on
# BaseClient, not on ClientCreator.
if not self.can_paginate(operation_name):
raise OperationNotPageableError(operation_name=operation_name)
else:
actual_operation_name = name_mapping[operation_name]
paginator = Paginator(
getattr(self, operation_name),
self._cache['page_config'][actual_operation_name])
return paginator
def can_paginate(self, operation_name):
"""Check if an operation can be paginated.
:type operation_name: string
:param operation_name: The operation name. This is the same name
as the method name on the client. For example, if the
method name is ``create_foo``, and you'd normally invoke the
operation as ``client.create_foo(**kwargs)``, if the
``create_foo`` operation can be paginated, you can use the
call ``client.get_paginator("create_foo")``.
:return: ``True`` if the operation can be paginated,
``False`` otherwise.
"""
if 'page_config' not in self._cache:
try:
page_config = loader.load_data('aws/%s/%s.paginators' % (
2015-10-08 20:16:11 +02:00
service_model.service_name,
2015-10-08 20:16:07 +02:00
service_model.api_version))['pagination']
self._cache['page_config'] = page_config
except DataNotFoundError:
self._cache['page_config'] = {}
actual_operation_name = name_mapping[operation_name]
return actual_operation_name in self._cache['page_config']
methods['get_paginator'] = get_paginator
methods['can_paginate'] = can_paginate
def _add_waiter_methods(self, service_model, methods_dict,
method_name_map):
loader = self._loader
def _get_waiter_config(self):
if 'waiter_config' not in self._cache:
try:
waiter_config = loader.load_data('aws/%s/%s.waiters' % (
2015-10-08 20:16:11 +02:00
service_model.service_name,
service_model.api_version))
self._cache['waiter_config'] = waiter_config
2015-10-08 20:16:07 +02:00
except DataNotFoundError:
self._cache['waiter_config'] = {}
return self._cache['waiter_config']
def get_waiter(self, waiter_name):
config = self._get_waiter_config()
2015-10-08 20:16:11 +02:00
if not config:
raise ValueError("Waiter does not exist: %s" % waiter_name)
model = waiter.WaiterModel(config)
2015-10-08 20:16:07 +02:00
mapping = {}
2015-10-08 20:16:11 +02:00
for name in model.waiter_names:
2015-10-08 20:16:07 +02:00
mapping[xform_name(name)] = name
if waiter_name not in mapping:
raise ValueError("Waiter does not exist: %s" % waiter_name)
2015-10-08 20:16:11 +02:00
return waiter.create_waiter_with_client(
mapping[waiter_name], model, self)
@CachedProperty
def waiter_names(self):
2015-10-08 20:16:07 +02:00
"""Returns a list of all available waiters."""
2015-10-08 20:16:11 +02:00
config = self._get_waiter_config()
if not config:
return[]
model = waiter.WaiterModel(config)
2015-10-08 20:16:07 +02:00
# Waiter configs is a dict, we just want the waiter names
# which are the keys in the dict.
2015-10-08 20:16:11 +02:00
return [xform_name(name) for name in model.waiter_names]
2015-10-08 20:16:07 +02:00
methods_dict['_get_waiter_config'] = _get_waiter_config
methods_dict['get_waiter'] = get_waiter
2015-10-08 20:16:11 +02:00
methods_dict['waiter_names'] = waiter_names
2015-10-08 20:16:07 +02:00
def _load_service_model(self, service_name):
json_model = self._loader.load_service_model('aws/%s' % service_name)
2015-10-08 20:16:11 +02:00
service_model = ServiceModel(json_model, service_name=service_name)
2015-10-08 20:16:07 +02:00
return service_model
def _get_client_args(self, service_model, region_name, is_secure,
endpoint_url, verify, aws_access_key_id,
aws_secret_access_key, aws_session_token):
# A client needs:
#
# * serializer
# * endpoint
# * response parser
protocol = service_model.metadata['protocol']
serializer = botocore.serialize.create_serializer(
protocol, include_validation=True)
creds = None
if aws_secret_access_key is not None:
creds = credentials.Credentials(
access_key=aws_access_key_id,
secret_key=aws_secret_access_key,
token=aws_session_token)
endpoint = self._endpoint_creator.create_endpoint(
service_model, region_name, is_secure=is_secure,
endpoint_url=endpoint_url, verify=verify,
2015-10-08 20:16:11 +02:00
credentials=creds,
response_parser_factory=self._response_parser_factory)
2015-10-08 20:16:07 +02:00
response_parser = botocore.parsers.create_parser(protocol)
return {
'serializer': serializer,
'endpoint': endpoint,
'response_parser': response_parser,
2015-10-08 20:16:11 +02:00
'event_emitter': copy.copy(self._event_emitter),
2015-10-08 20:16:07 +02:00
}
def _create_methods(self, service_model):
op_dict = {}
for operation_name in service_model.operation_names:
py_operation_name = xform_name(operation_name)
op_dict[py_operation_name] = self._create_api_method(
py_operation_name, operation_name, service_model)
return op_dict
def _create_name_mapping(self, service_model):
# py_name -> OperationName, for every operation available
# for a service.
mapping = {}
for operation_name in service_model.operation_names:
py_operation_name = xform_name(operation_name)
mapping[py_operation_name] = operation_name
return mapping
def _create_api_method(self, py_operation_name, operation_name,
service_model):
def _api_call(self, **kwargs):
operation_model = service_model.operation_model(operation_name)
2015-10-08 20:16:11 +02:00
event_name = (
'before-parameter-build.{endpoint_prefix}.{operation_name}')
self.meta.events.emit(
event_name.format(
endpoint_prefix=service_model.endpoint_prefix,
operation_name=operation_name),
2015-10-08 20:16:07 +02:00
params=kwargs, model=operation_model)
request_dict = self._serializer.serialize_to_request(
kwargs, operation_model)
2015-10-08 20:16:11 +02:00
self.meta.events.emit(
2015-10-08 20:16:07 +02:00
'before-call.{endpoint_prefix}.{operation_name}'.format(
endpoint_prefix=service_model.endpoint_prefix,
operation_name=operation_name),
model=operation_model, params=request_dict
)
http, parsed_response = self._endpoint.make_request(
operation_model, request_dict)
2015-10-08 20:16:11 +02:00
self.meta.events.emit(
2015-10-08 20:16:07 +02:00
'after-call.{endpoint_prefix}.{operation_name}'.format(
endpoint_prefix=service_model.endpoint_prefix,
operation_name=operation_name),
http_response=http, parsed=parsed_response,
model=operation_model
)
if http.status_code >= 300:
raise ClientError(parsed_response, operation_name)
else:
return parsed_response
_api_call.__name__ = str(py_operation_name)
# TODO: docstrings.
return _api_call
class BaseClient(object):
def __init__(self, serializer, endpoint, response_parser,
event_emitter):
self._serializer = serializer
self._endpoint = endpoint
self._response_parser = response_parser
self._cache = {}
2015-10-08 20:16:11 +02:00
self.meta = ClientMeta(event_emitter)
def clone_client(self, serializer=None, endpoint=None,
response_parser=None):
"""Create a copy of the client object.
This method will create a clone of an existing client. By default, the
same internal attributes are used when creating a clone of the client,
with the exception of the event emitter. A copy of the event handlers
are created when a clone of the client is created.
You can also provide any of the above arguments as an override. This
allows you to create a client that has the same values except for the
args you pass in as overrides.
:return: A new copy of the botocore client.
"""
kwargs = {
'serializer': serializer,
'endpoint': endpoint,
'response_parser': response_parser,
}
for key, value in kwargs.items():
if value is None:
kwargs[key] = getattr(self, '_%s' % key)
# This will be swapped out in the ClientMeta class.
kwargs['event_emitter'] = None
new_object = self.__class__(**kwargs)
new_object.meta = copy.copy(self.meta)
return new_object
class ClientMeta(object):
"""Holds additional client methods.
This class holds additional information for clients. It exists for
two reasons:
* To give advanced functionality to clients
* To namespace additional client attributes from the operation
names which are mapped to methods at runtime. This avoids
ever running into collisions with operation names.
"""
def __init__(self, events):
self.events = events
def __copy__(self):
return ClientMeta(**copy.deepcopy(self.__dict__))