python-botocore/botocore/operation.py

201 lines
8.4 KiB
Python
Raw Normal View History

# Copyright (c) 2012-2013 Mitch Garnaat http://garnaat.org/
# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish, dis-
# tribute, sublicense, and/or sell copies of the Software, and to permit
# persons to whom the Software is furnished to do so, subject to the fol-
# lowing conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#
import logging
from botocore.parameters import get_parameter
from botocore.exceptions import MissingParametersError
from botocore.exceptions import UnknownParameterError
from botocore.paginate import Paginator
from botocore.payload import Payload, XMLPayload, JSONPayload
from botocore import BotoCoreObject
logger = logging.getLogger(__name__)
class Operation(BotoCoreObject):
_DEFAULT_PAGINATOR_CLS = Paginator
def __init__(self, service, op_data, paginator_cls=None):
self.input = {}
self.output = {}
BotoCoreObject.__init__(self, **op_data)
self.service = service
if self.service:
self.session = self.service.session
else:
self.session = None
self.type = 'operation'
self._params = None
if paginator_cls is None:
paginator_cls = self._DEFAULT_PAGINATOR_CLS
self._paginator_cls = paginator_cls
def __repr__(self):
return 'Operation:%s' % self.name
def call(self, endpoint, **kwargs):
logger.debug("%s called with kwargs: %s", self, kwargs)
# It probably seems a little weird to be firing two different
# events here. The reason is that the first event is fired
# with the parameters exactly as supplied. The second event
# is fired with the built parameters. Generally, it's easier
# to manipulate the former but at times, like with ReST operations
# that build an XML or JSON payload, you have to wait for
# build_parameters to do it's job and the latter is necessary.
event = self.session.create_event('before-parameter-build',
self.service.endpoint_prefix,
self.name)
self.session.emit(event, operation=self, endpoint=endpoint,
params=kwargs)
params = self.build_parameters(**kwargs)
event = self.session.create_event('before-call',
self.service.endpoint_prefix,
self.name)
self.session.emit(event, operation=self, endpoint=endpoint,
params=params)
response = endpoint.make_request(self, params)
event = self.session.create_event('after-call',
self.service.endpoint_prefix,
self.name)
self.session.emit(event, operation=self,
http_response=response[0],
parsed=response[1])
return response
@property
def can_paginate(self):
return hasattr(self, 'pagination')
def paginate(self, endpoint, **kwargs):
"""Iterate over the responses of an operation.
This will return an iterator with each element
being a tuple of (``http_response``, ``parsed_response``).
If the operation does not paginate, a ``TypeError`` will
be raised. You can check if an operation can be paginated
by using the ``can_paginate`` arg.
"""
if not self.can_paginate:
raise TypeError("Operation cannot be paginated: %s" % self)
paginator = self._paginator_cls(self)
return paginator.paginate(endpoint, **kwargs)
@property
def params(self):
if self._params is None:
self._params = self._create_parameter_objects()
return self._params
def _create_parameter_objects(self):
"""
Build the list of Parameter objects for this operation.
"""
logger.debug("Creating parameter objects for: %s", self)
params = []
if self.input and 'members' in self.input:
for name, data in self.input['members'].items():
param = get_parameter(self, name, data)
params.append(param)
return params
def _find_payload(self):
"""
Searches the parameters for an operation to find the payload
parameter, if it exists. Returns that param or None.
"""
payload = None
for param in self.params:
if hasattr(param, 'payload') and param.payload:
payload = param
break
return payload
def _get_built_params(self):
d = {}
if self.service.type in ('rest-xml', 'rest-json'):
d['uri_params'] = {}
d['headers'] = {}
if self.service.type == 'rest-xml':
payload = self._find_payload()
if payload and payload.type in ('blob', 'string'):
# If we have a payload parameter which is a scalar
# type (either string or blob) it means we need a
# simple payload object rather than an XMLPayload.
d['payload'] = Payload()
else:
# Otherwise, use an XMLPayload. Since Route53
# doesn't actually use the payload attribute, we
# have to err on the safe side.
namespace = self.service.xmlnamespace
root_element_name = None
if self.input and 'shape_name' in self.input:
root_element_name = self.input['shape_name']
d['payload'] = XMLPayload(root_element_name=root_element_name,
namespace=namespace)
else:
d['payload'] = JSONPayload()
return d
def build_parameters(self, **kwargs):
"""
Returns a dictionary containing the kwargs for the
given operation formatted as required to pass to the service
in a request.
"""
built_params = self._get_built_params()
missing = []
self._check_for_unknown_params(kwargs)
for param in self.params:
if param.required:
missing.append(param)
if param.py_name in kwargs:
if missing:
missing.pop()
param.build_parameter(self.service.type,
kwargs[param.py_name],
built_params)
if missing:
missing_str = ', '.join([p.py_name for p in missing])
raise MissingParametersError(missing=missing_str,
object_name=self)
return built_params
def _check_for_unknown_params(self, kwargs):
valid_names = [p.py_name for p in self.params]
for key in kwargs:
if key not in valid_names:
raise UnknownParameterError(name=key, operation=self,
choices=', '.join(valid_names))
def is_streaming(self):
is_streaming = False
if self.output:
for member_name in self.output['members']:
member_dict = self.output['members'][member_name]
if member_dict['type'] == 'blob':
if member_dict.get('payload', False):
if member_dict.get('streaming', False):
is_streaming = member_dict.get('xmlname',
member_name)
return is_streaming