Move AWS query-specifics to class AWSQueryRequest
Hey, the name is actually quite appropriate.
This commit is contained in:
@@ -50,9 +50,6 @@ class BaseRequest(BaseCommand):
|
||||
|
||||
Important members of this class include:
|
||||
- SERVICE_CLASS: a class corresponding to the web service in use
|
||||
- API_VERSION: the API version to send along with the request. This is
|
||||
only necessary to override the service class's API
|
||||
version for a specific request.
|
||||
- NAME: a string containing the Action query parameter. This
|
||||
defaults to the class's name.
|
||||
- DESCRIPTION: a string describing the tool. This becomes part of the
|
||||
@@ -69,7 +66,6 @@ class BaseRequest(BaseCommand):
|
||||
'''
|
||||
|
||||
SERVICE_CLASS = BaseService
|
||||
API_VERSION = None
|
||||
NAME = None
|
||||
|
||||
FILTERS = []
|
||||
@@ -79,12 +75,10 @@ class BaseRequest(BaseCommand):
|
||||
def __init__(self, service=None, **kwargs):
|
||||
self.service = service
|
||||
# Parts of the HTTP request to be sent to the server.
|
||||
# Note that self.serialize_params will update self.params for each
|
||||
# entry in self.args that routes to PARAMS.
|
||||
self.method = 'GET'
|
||||
self.headers = {}
|
||||
self.params = {}
|
||||
self.post_data = None
|
||||
self.method = 'GET'
|
||||
self.body = None
|
||||
|
||||
# HTTP response obtained from the server
|
||||
self.response = None
|
||||
@@ -111,22 +105,6 @@ class BaseRequest(BaseCommand):
|
||||
def preprocess_arg_objs(self, arg_objs):
|
||||
self.service.preprocess_arg_objs(arg_objs)
|
||||
|
||||
def populate_parser(self, parser, arg_objs):
|
||||
BaseCommand.populate_parser(self, parser, arg_objs)
|
||||
if self.FILTERS:
|
||||
parser.add_argument('--filter', metavar='NAME=VALUE',
|
||||
action='append', dest='filters',
|
||||
help='restrict results to those that meet criteria',
|
||||
type=partial(_parse_filter, filter_objs=self.FILTERS))
|
||||
parser.epilog = self.__build_filter_help()
|
||||
self._arg_routes['filters'] = None
|
||||
|
||||
def process_cli_args(self):
|
||||
BaseCommand.process_cli_args(self)
|
||||
if 'filters' in self.args:
|
||||
self.args['Filter'] = _process_filters(self.args.pop('filters'))
|
||||
self._arg_routes['Filter'] = self.params
|
||||
|
||||
def configure(self):
|
||||
self.service.configure()
|
||||
|
||||
@@ -158,7 +136,119 @@ class BaseRequest(BaseCommand):
|
||||
else:
|
||||
return None
|
||||
|
||||
def serialize_params(self, args, prefix=None):
|
||||
def send(self):
|
||||
headers = dict(self.headers or {})
|
||||
headers.setdefault('User-Agent', self.user_agent)
|
||||
params = self.prepare_params()
|
||||
self.response = self.service.send_request(method=self.method,
|
||||
headers=headers, params=params, data=self.body)
|
||||
try:
|
||||
if 200 <= self.response.status_code < 300:
|
||||
parsed = self.parse_response(self.response)
|
||||
self.log.info('result: success')
|
||||
return parsed
|
||||
else:
|
||||
self.log.debug('-- response content --\n',
|
||||
extra={'append': True})
|
||||
self.log.debug(self.response.text, extra={'append': True})
|
||||
self.log.debug('-- end of response content --')
|
||||
self.log.info('result: failure')
|
||||
raise ServerError(self.response.status_code,
|
||||
self.response.content)
|
||||
finally:
|
||||
# Empty the socket buffer so it can be reused
|
||||
try:
|
||||
self.response.content
|
||||
except RuntimeError:
|
||||
# The content was already consumed
|
||||
pass
|
||||
|
||||
def prepare_params(self):
|
||||
return self.params or {}
|
||||
|
||||
def parse_response(self, response):
|
||||
return response
|
||||
|
||||
def main(self):
|
||||
'''
|
||||
The main processing method for this type of request. In this method,
|
||||
inheriting classes generally populate self.headers, self.params, and
|
||||
self.body with information gathered from self.args or elsewhere,
|
||||
call self.send, and return the response. BaseRequest's default
|
||||
behavior is to simply return the result of a request with everything
|
||||
that routes to PARAMS.
|
||||
'''
|
||||
self.preprocess()
|
||||
response = self.send()
|
||||
self.postprocess(response)
|
||||
return response
|
||||
|
||||
def preprocess(self):
|
||||
pass
|
||||
|
||||
def postprocess(self, response):
|
||||
pass
|
||||
|
||||
def handle_cli_exception(self, err):
|
||||
if isinstance(err, ServerError):
|
||||
if err.code:
|
||||
print >> sys.stderr, 'error ({code}) {msg}'.format(
|
||||
code=err.code, msg=err.message or '')
|
||||
else:
|
||||
print >> sys.stderr, 'error {msg}'.format(
|
||||
msg=err.message or '')
|
||||
if self.debug:
|
||||
raise
|
||||
sys.exit(1)
|
||||
else:
|
||||
BaseCommand.handle_cli_exception(self, err)
|
||||
|
||||
|
||||
class AWSQueryRequest(BaseRequest):
|
||||
API_VERSION = None
|
||||
|
||||
def populate_parser(self, parser, arg_objs):
|
||||
BaseRequest.populate_parser(self, parser, arg_objs)
|
||||
if self.FILTERS:
|
||||
parser.add_argument('--filter', metavar='NAME=VALUE',
|
||||
action='append', dest='filters',
|
||||
help='restrict results to those that meet criteria',
|
||||
type=partial(_parse_filter, filter_objs=self.FILTERS))
|
||||
parser.epilog = self.__build_filter_help()
|
||||
self._arg_routes['filters'] = None
|
||||
|
||||
def process_cli_args(self):
|
||||
BaseRequest.process_cli_args(self)
|
||||
if 'filters' in self.args:
|
||||
self.args['Filter'] = _process_filters(self.args.pop('filters'))
|
||||
self._arg_routes['Filter'] = self.params
|
||||
|
||||
def prepare_params(self):
|
||||
params = self.flatten_params(self.params)
|
||||
params['Action'] = self.name
|
||||
params['Version'] = self.API_VERSION or self.service.API_VERSION
|
||||
self.log.info('parameters: %s', params)
|
||||
return params
|
||||
|
||||
def parse_response(self, response):
|
||||
# Parser for list-delimited responses like EC2's
|
||||
|
||||
# We do some extra handling here to log stuff as it comes in rather
|
||||
# than reading it all into memory at once.
|
||||
self.log.debug('-- response content --\n', extra={'append': True})
|
||||
# Using Response.iter_content gives us automatic decoding, but we then
|
||||
# have to make the generator look like a file so etree can use it.
|
||||
with _IteratorFileObjAdapter(self.response.iter_content(16384)) \
|
||||
as content_fileobj:
|
||||
logged_fileobj = _ReadLoggingFileWrapper(content_fileobj, self.log,
|
||||
logging.DEBUG)
|
||||
response_dict = parse_listdelimited_aws_xml(logged_fileobj,
|
||||
self.LIST_MARKERS)
|
||||
self.log.debug('-- end of response content --')
|
||||
# Strip off the root element
|
||||
return response_dict[list(response_dict.keys())[0]]
|
||||
|
||||
def flatten_params(self, args, prefix=None):
|
||||
'''
|
||||
Given a possibly-nested dict of args and an arg routing destination,
|
||||
transform each element in the dict that matches the corresponding
|
||||
@@ -204,8 +294,7 @@ class BaseRequest(BaseCommand):
|
||||
prefixed_key = str(key)
|
||||
|
||||
if isinstance(val, dict) or isinstance(val, list):
|
||||
flattened.update(self.serialize_params(val,
|
||||
prefixed_key))
|
||||
flattened.update(self.flatten_params(val, prefixed_key))
|
||||
elif isinstance(val, file):
|
||||
flattened[prefixed_key] = val.read()
|
||||
elif val or val is 0:
|
||||
@@ -221,7 +310,7 @@ class BaseRequest(BaseCommand):
|
||||
prefixed_key = str(i_item)
|
||||
|
||||
if isinstance(item, dict) or isinstance(item, list):
|
||||
flattened.update(self.serialize_params(item, prefixed_key))
|
||||
flattened.update(self.flatten_params(item, prefixed_key))
|
||||
elif isinstance(item, file):
|
||||
flattened[prefixed_key] = item.read()
|
||||
elif item or item == 0:
|
||||
@@ -232,102 +321,6 @@ class BaseRequest(BaseCommand):
|
||||
raise TypeError('non-flattenable type: ' + args.__class__.__name__)
|
||||
return flattened
|
||||
|
||||
def send(self):
|
||||
'''
|
||||
Send a request to the server and return its response. More precisely:
|
||||
|
||||
1. Build a dict of params suitable for submission as HTTP request
|
||||
parameters, based first upon the content of self.params, and
|
||||
second upon everything in self.args that routes to PARAMS.
|
||||
2. Send an HTTP request via self.service with the HTTP method given
|
||||
in self.method using query parameters from the aforementioned
|
||||
serialized dict, headers based on self.headers, and POST data based
|
||||
on self.post_data.
|
||||
3. If the response's status code indicates success, parse the
|
||||
response with self.parse_response and return the result.
|
||||
4. If the response's status code does not indicate success, log an
|
||||
error and raise a ServerError.
|
||||
'''
|
||||
params = self.serialize_params(self.params)
|
||||
headers = dict(self.headers or {})
|
||||
headers.setdefault('User-Agent', self.user_agent)
|
||||
self.log.info('parameters: %s', params)
|
||||
self.response = self.service.send_request(self.name,
|
||||
method=self.method, headers=headers, params=params,
|
||||
data=self.post_data, api_version=self.API_VERSION)
|
||||
try:
|
||||
if 200 <= self.response.status_code < 300:
|
||||
parsed = self.parse_response(self.response)
|
||||
self.log.info('result: success')
|
||||
return parsed
|
||||
else:
|
||||
self.log.debug('-- response content --\n',
|
||||
extra={'append': True})
|
||||
self.log.debug(self.response.text, extra={'append': True})
|
||||
self.log.debug('-- end of response content --')
|
||||
self.log.info('result: failure')
|
||||
raise ServerError(self.response.status_code,
|
||||
self.response.content)
|
||||
finally:
|
||||
# Empty the socket buffer so it can be reused
|
||||
try:
|
||||
self.response.content
|
||||
except RuntimeError:
|
||||
# The content was already consumed
|
||||
pass
|
||||
|
||||
def parse_response(self, response):
|
||||
# Parser for list-delimited responses like EC2's
|
||||
|
||||
# We do some extra handling here to log stuff as it comes in rather
|
||||
# than reading it all into memory at once.
|
||||
self.log.debug('-- response content --\n', extra={'append': True})
|
||||
# Using Response.iter_content gives us automatic decoding, but we then
|
||||
# have to make the generator look like a file so etree can use it.
|
||||
with _IteratorFileObjAdapter(self.response.iter_content(16384)) \
|
||||
as content_fileobj:
|
||||
logged_fileobj = _ReadLoggingFileWrapper(content_fileobj, self.log,
|
||||
logging.DEBUG)
|
||||
response_dict = parse_listdelimited_aws_xml(logged_fileobj,
|
||||
self.LIST_MARKERS)
|
||||
self.log.debug('-- end of response content --')
|
||||
# Strip off the root element
|
||||
return response_dict[list(response_dict.keys())[0]]
|
||||
|
||||
def main(self):
|
||||
'''
|
||||
The main processing method for this type of request. In this method,
|
||||
inheriting classes generally populate self.headers, self.params, and
|
||||
self.post_data with information gathered from self.args or elsewhere,
|
||||
call self.send, and return the response. BaseRequest's default
|
||||
behavior is to simply return the result of a request with everything
|
||||
that routes to PARAMS.
|
||||
'''
|
||||
self.preprocess()
|
||||
response = self.send()
|
||||
self.postprocess(response)
|
||||
return response
|
||||
|
||||
def preprocess(self):
|
||||
pass
|
||||
|
||||
def postprocess(self, response):
|
||||
pass
|
||||
|
||||
def handle_cli_exception(self, err):
|
||||
if isinstance(err, ServerError):
|
||||
if err.code:
|
||||
print >> sys.stderr, 'error ({code}) {msg}'.format(
|
||||
code=err.code, msg=err.message or '')
|
||||
else:
|
||||
print >> sys.stderr, 'error {msg}'.format(
|
||||
msg=err.message or '')
|
||||
if self.debug:
|
||||
raise
|
||||
sys.exit(1)
|
||||
else:
|
||||
BaseCommand.handle_cli_exception(self, err)
|
||||
|
||||
def __build_filter_help(self, force=False):
|
||||
'''
|
||||
Return a pre-formatted help string for all of the filters defined in
|
||||
|
||||
@@ -19,7 +19,6 @@ import requests.exceptions
|
||||
import time
|
||||
import urlparse
|
||||
|
||||
from .auth import QuerySigV2Auth
|
||||
from .exceptions import ClientError, ServiceInitError
|
||||
from .util import aggregate_subclass_fields
|
||||
|
||||
@@ -124,19 +123,8 @@ class BaseService(object):
|
||||
if user and self.config.current_user is None:
|
||||
self.config.current_user = user
|
||||
|
||||
## TODO: nuke Action; the request should make it a param instead
|
||||
## TODO: the same should probably happen with API versions, but the
|
||||
## request would have to deal with service.API_VERSION, too
|
||||
def send_request(self, action, method='GET', path=None, params=None,
|
||||
headers=None, data=None, api_version=None):
|
||||
params = params or {}
|
||||
if action:
|
||||
params['Action'] = action
|
||||
if api_version:
|
||||
params['Version'] = api_version
|
||||
elif self.API_VERSION:
|
||||
params['Version'] = self.API_VERSION
|
||||
|
||||
def send_request(self, method='GET', path=None, params=None, headers=None,
|
||||
data=None):
|
||||
## TODO: test url-encoding
|
||||
if path:
|
||||
# We can't simply use urljoin because a path might start with '/'
|
||||
|
||||
Reference in New Issue
Block a user