Initial commit

This commit is contained in:
Garrett Holmstrom 2012-04-07 17:19:56 -07:00
commit 69c6710c2e
6 changed files with 753 additions and 0 deletions

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
.project
.nfs*
*~
.sw*
.*.sw*
.DS_Store
core
core.*
*.pyc
*.pyo

13
COPYING Normal file
View File

@ -0,0 +1,13 @@
Software license (ISC license):
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

104
requestbuilder/__init__.py Normal file
View File

@ -0,0 +1,104 @@
# Copyright (c) 2012, Eucalyptus Systems, Inc.
#
# Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the
# above copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import argparse
__version__ = '0.0'
class Arg(object):
"""
A command line argument. Positional and keyword arguments to __init__
are the same as those to argparse.ArgumentParser.add_argument.
The value specified by the 'dest' argument (or the one inferred if
none is specified) is used as the name of the parameter to server
queries unless send=False is also supplied.
"""
def __init__(self, *pargs, **kwargs):
self.route = kwargs.pop('route_to', PARAMS)
self.pargs = pargs
self.kwargs = kwargs
def __eq__(self, other):
if isinstance(other, Arg):
return sorted(self.pargs) == sorted(other.pargs)
return False
class MutuallyExclusiveArgList(list):
"""
Pass Args as positional arguments to __init__ to create a set of
command line arguments that are mutually exclusive. If the first
argument passed to __init__ is True then the user must specify
exactly one of them.
Example: MutuallyExclusiveArgList(Arg('--one'), Arg('--two'))
"""
def __init__(self, *args):
if len(args) > 0 and isinstance(args[0], bool):
self.required = args[0]
list.__init__(self, args[1:])
else:
self.required = False
list.__init__(self, args)
class Filter(object):
"""
An AWS API filter. For APIs that support filtering by key/value
pairs, adding a Filter to a request's list of filters will allow a
user to send an output filter to the server with '--filter key=value'
at the command line.
The value specified by the 'dest' argument (or the 'name' argument,
if none is given) is used as the name of a filter in queries.
"""
def __init__(self, name, dest=None, type=str, choices=None, help=None):
self.name = name # the name as shown on the command line
self.dest = dest or name # the name as sent to the server
self.type = type
self.choices = choices
self.help = help
def convert(self, str_value):
"""
Convert a value to the data type expected by this filter by calling
self.type on the value. If this doesn't work then an ArgumentTypeError
will result. If the conversion succeeds but does not appear in
self.choices when it exists, an ArgumentTypeError will result as well.
"""
try:
value = self.type(str_value)
except ValueError:
raise argparse.ArgumentTypeError('%s does not have type %s' %
(str_value, self.type.__name__))
if self.choices and value not in self.choices:
msg = 'filter value %s does not match any of %s' % (value,
', '.join([str(choice) for choice in self.choices]))
raise argparse.ArgumentTypeError(msg)
return value
# Constants (enums?) used for arg routing
CONNECTION = '==CONNECTION=='
PARAMS = '==PARAMS=='
STD_AUTH_ARGS = [
Arg('-I', '--access-key-id', dest='aws_access_key_id',
metavar='KEY_ID', route_to=CONNECTION),
Arg('-S', '--secret-key', dest='aws_secret_access_key', metavar='KEY',
route_to=CONNECTION)]

477
requestbuilder/request.py Normal file
View File

@ -0,0 +1,477 @@
# Copyright (c) 2012, Eucalyptus Systems, Inc.
#
# Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the
# above copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import argparse
import bdb
import boto.jsonresponse
from functools import partial
import json
import pprint
import sys
import textwrap
import traceback
import types
try:
import epdb
except ImportError:
import pdb
from . import __version__, CONNECTION, PARAMS, Arg, MutuallyExclusiveArgList
from .service import BaseService, MissingCredentialsError
class InheritableRequestClass(type):
'''
Classes of this type allow one to specify 'Args', 'Filters', 'ListMarkers',
and 'ItemMarkers' lists as attributes of classes and have them be appended
to their superclasses' rather than clobbering them.
Additionally, all method calls are decorated with a function that renames
logs to that of the the object's name() return value.
'''
def __new__(mcs, name, bases, attrs):
for attrname in ('Args', 'Filters', 'ListMarkers', 'ItemMarkers'):
if attrname in attrs:
for base in bases:
for attr in getattr(base, attrname, []):
if attr not in attrs[attrname]:
attrs[attrname].append(attr)
return type.__new__(mcs, name, bases, attrs)
class BaseRequest(object):
'''
The basis for a command line tool that represents a request. To invoke
this as a command line tool, call the do_cli() method on an instance of the
class; arguments will be parsed from the command line. To invoke this in
another context, pass keyword args to __init__ with names that match those
stored by the argument parser and then call main().
Important methods in this class include:
- do_cli: command line entry point
- main: pre/post-request processing and request sending
- send: actually send a request to the server and return a
response (called by the main() method)
- print_result: format data from the main method and print it to stdout
To be useful a tool should inherit from this class and implement the main()
and print_result() methods. The do_cli() method functions as the entry
point for the command line, populating self.args from the command line and
then calling mein() and print_result() in sequence. Other tools may
instead supply arguments via __init__() and then call main() alone.
Important members of this class include:
- ServiceClass: a class corresponding to the web service in use
- APIVersion: 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.
- Action: 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
command line help string.
- Args: a list of Arg and/or MutuallyExclusiveArgGroup objects
are used to generate command line arguments. Inheriting
classes needing to add command line arguments should
contain their own Args lists, which are *prepended* to
those of their parent classes.
- Filters: a list of Filter objects that are used to generate filter
options at the command line. Inheriting classes needing
to add filters should contain their own Filters lists,
which are *prepended* to those of their parent classes.
'''
__metaclass__ = InheritableRequestClass
Version = 'requestbuilder ' + __version__
ServiceClass = BaseService
APIVersion = None
Action = None
Description = ''
ListMarkers = []
ItemMarkers = []
Args = [Arg('-D', '--debug', action='store_true', route_to=None,
help='show debugging output'),
Arg('--debugger', action='store_true', route_to=None,
help='enable interactive debugger on error')]
Filters = []
def name(self):
'''
The name of this action. Used when choosing what to supply for the
Action query parameter.
'''
return self.Action or self.__class__.__name__
def __init__(self, **kwargs):
# Arguments corresponding to those in self.Args. This may be used in
# lieu of (and will take priority over) arguments given at the CLI.
self.args = kwargs
# Parts of the HTTP request to be sent to the server.
# Note that self.flatten_params will update self.params for each entry
# in self.args that routes to PARAMS.
self.headers = None
self.params = None
self.post_data = None
self.verb = 'GET'
# HTTP response obtained from the server
self.http_response = None
self._arg_routes = {}
self._cli_parser = None
self._connection = None
self._parse_arg_lists()
@property
def connection(self):
if self._connection is None:
conn_args = {}
for (key, val) in self.args.iteritems():
if key in self._arg_routes.get(CONNECTION):
conn_args[key] = val
self._connection = self.ServiceClass(**conn_args)
return self._connection
@property
def status(self):
if self.http_response is not None:
return self.http_response.status
else:
return None
@property
def reason(self):
if self.http_response is not None:
return self.http_response.reason
else:
return None
def print_result(self, data):
'''
Format data for printing at the command line and print it to standard
out. The default formatter attempts to print JSON or something else
reasonable. Override this method if you want specific formatting.
'''
if data:
if isinstance(data, dict):
for (key, val) in data.iteritems():
if key not in ['ResponseMetadata', 'requestId']:
# Will there ever be more than one of these?
print json.dumps(val, indent=4)
elif isinstance(data, list):
print '\n'.join([str(item) for item in data])
elif isinstance(data, basestring):
print data
else:
pprint.pprint(data)
def flatten_params(self, args, route, prefix=None):
'''
Given a possibly-nested dict of args and an arg routing destination,
transform each element in the dict that matches the corresponding
arg routing table into a simple dict containing key-value pairs
suitable for use as query parameters. This implementation flattens
dicts and lists into the format given by the EC2 query API, which uses
dotted lists of dict keys and list indices to indicate nested
structures.
Keys with nonzero values that evaluate as false are ignored. If a
collection of keys is supplied with ignore then keys that do not
appear in that collection are also ignored.
Examples:
in: {'InstanceId': 'i-12345678', 'PublicIp': '1.2.3.4'}
out: {'InstanceId': 'i-12345678', 'PublicIp': '1.2.3.4'}
in: {'RegionName': ['us-east-1', 'us-west-1']}
out: {'RegionName.1': 'us-east-1',
'RegionName.2': 'us-west-1'}
in: {'Filter': [{'Name': 'image-id',
'Value': ['ami-12345678']},
{'Name': 'instance-type',
'Value': ['m1.small', 't1.micro']}],
'InstanceId': ['i-24680135']}
out: {'Filter.1.Name': 'image-id',
'Filter.1.Value.1': 'ami-12345678',
'Filter.2.Name': 'instance-type',
'Filter.2.Value.1': 'm1.small',
'Filter.2.Value.2': 't1.micro',
'InstanceId.1': 'i-24680135'}
'''
flattened = {}
if args is None:
return {}
elif isinstance(args, dict):
for (key, val) in args.iteritems():
# Prefix.Key1, Prefix.Key2, ...
if key in self._arg_routes.get(route, []) or route is _ALWAYS:
if prefix:
prefixed_key = prefix + '.' + str(key)
else:
prefixed_key = str(key)
if isinstance(val, dict) or isinstance(val, list):
flattened.update(self.flatten_params(val, route,
prefixed_key))
elif isinstance(val, file):
flattened[prefixed_key] = val.read()
elif val or val is 0:
flattened[prefixed_key] = str(val)
elif isinstance(args, list):
for (i_item, item) in enumerate(args, 1):
# Prefix.1, Prefix.2, ...
if prefix:
prefixed_key = prefix + '.' + str(i_item)
else:
prefixed_key = str(i_item)
if isinstance(item, dict) or isinstance(item, list):
flattened.update(self.flatten_params(item, route,
prefixed_key))
elif isinstance(item, file):
flattened[prefixed_key] = item.read()
elif item or item == 0:
flattened[prefixed_key] = str(item)
else:
raise TypeError('non-flattenable type: ' + args.__class__.__name__)
return flattened
def _parse_arg_lists(self):
'''
Use self.Args and self.Filters to build a command line argument parser
that is stored to self._cli_parser and to populate the argument routing
table.
'''
self._cli_parser = argparse.ArgumentParser(
description='\n'.join(textwrap.wrap(self.Description)),
formatter_class=argparse.RawDescriptionHelpFormatter)
for arg_obj in self.Args:
self.__add_arg_to_cli_parser(arg_obj, self._cli_parser)
if self.Filters:
self._cli_parser.add_argument('--filter', metavar='key=value',
action='append', dest='_filters', help='filter output',
type=partial(_parse_filter, filter_objs=self.Filters))
self._arg_routes[None].append('_filters')
self._cli_parser.epilog = self.__build_filter_help()
self._cli_parser.add_argument('--version', action='version',
version=self.Version)
def process_cli_args(self):
'''
Process CLI args to fill in missing parts of self.args and enable
debugging if necessary.
'''
cli_args = self._cli_parser.parse_args().__dict__
for (key, val) in cli_args.iteritems():
self.args.setdefault(key, val)
if self.args.get('debug'):
boto.set_stream_logger(self.name())
if self.args.get('debugger'):
sys.excepthook = _requestbuilder_except_hook(
self.args.get('debugger', False),
self.args.get('debug', False))
if '_filters' in self.args:
self.params['Filter'] = _process_filters(cli_args.pop('_filters'))
def send(self):
'''
Send a request to the server and return its response. More precisely:
1. Build a flattened 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.connection with the HTTP verb given
in self.verb using query parameters from the aforementioned
flattened 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's body with self.parse_http_response and return the
result.
4. If the response's status code does not indicate success, log an
error and raise a ResponseError.
'''
params = self.flatten_params(self.args, PARAMS)
params.update(self.flatten_params(self.params, _ALWAYS))
if self.headers:
boto.log.debug('Request headers: {0}'.format(self.headers))
if params:
boto.log.debug('Request params: {0}'.format(params))
self.http_response = self.connection.make_request(self.name(),
verb=self.verb, headers=self.headers, params=params,
data=self.post_data, api_version=self.APIVersion)
response_body = self.http_response.read()
boto.log.debug(response_body)
if 200 <= self.http_response.status < 300:
return self.parse_http_response(response_body)
else:
boto.log.error('{0} {1}'.format(self.http_response.status,
self.http_response.reason))
boto.log.error(response_body)
raise self.connection.ResponseError(self.http_response.status,
self.http_response.reason,
response_body)
def parse_http_response(self, response_body):
response = boto.jsonresponse.Element(list_marker=self.ListMarkers,
item_marker=self.ItemMarkers)
handler = boto.jsonresponse.XmlHandler(response, self)
handler.parse(response_body)
return response[response.keys()[0]] # Strip off the root element
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.
'''
pass
def do_cli(self):
'''
The entry point for the command line. This method parses command line
arguments using the class's Args and Filters lists to populate
self.args, obtains a response from the main method, then passes the
result to print_result.
'''
try:
self.process_cli_args() # self.args is populated
response = self.main()
self.print_result(response)
except self.ServiceClass.ResponseError as err:
sys.exit('error ({code}) {msg}'.format(code=err.error_code,
msg=err.error_message))
except MissingCredentialsError as err:
sys.exit('error: unable to find credentials')
except Exception as err:
if self.args.get('debug'):
raise
if '--debug' in sys.argv or '-D' in sys.argv:
# In case an error occurs during argument parsing
raise
sys.exit('error: {0}'.format(err))
def __add_arg_to_cli_parser(self, arglike_obj, parser):
if isinstance(arglike_obj, Arg):
arg = parser.add_argument(*arglike_obj.pargs, **arglike_obj.kwargs)
self._arg_routes.setdefault(arglike_obj.route, [])
self._arg_routes[arglike_obj.route].append(arg.dest)
elif isinstance(arglike_obj, MutuallyExclusiveArgList):
exgroup = parser.add_mutually_exclusive_group(
required=arglike_obj.required)
for group_arg in arglike_obj:
self.__add_arg_to_cli_parser(group_arg, exgroup)
else:
raise TypeError('Unknown argument type ' +
arglike_obj.__class__.__name__)
def __build_filter_help(self):
"""
Return a pre-formatted help string for all of the filters defined in
self.Filters. The result is meant to be used as command line help
output.
"""
max_len = 24
col_len = max([len(filter_obj.name) for filter_obj in self.Filters
if len(filter_obj.name) < max_len]) + 1
helplines = ['available filters:']
for filter_obj in self.Filters:
if filter_obj.help:
if len(filter_obj.name) <= col_len:
# filter-name Description of the filter that
# continues on the next line
right_space = ' ' * (max_len - len(filter_obj.name) - 2)
wrapper = textwrap.TextWrapper(fix_sentence_endings=True,
initial_indent=(' ' + filter_obj.name + right_space),
subsequent_indent=(' ' * max_len))
else:
# really-long-filter-name
# Description that begins on the next line
helplines.append(' ' + filter_obj.name)
wrapper = textwrap.TextWrapper(fix_sentence_endings=True,
initial_indent=( ' ' * max_len),
subsequent_indent=(' ' * max_len))
helplines.extend(wrapper.wrap(filter_obj.help))
else:
helplines.append(' ' + filter_obj.name)
return '\n'.join(helplines)
def _parse_filter(filter_str, filter_objs=None):
"""
Given a "key=value" string given as a command line parameter, return a pair
with the matching filter's dest member and the given value after converting
it to the type expected by the filter. If this is impossible, an
ArgumentTypeError will result instead.
"""
if '=' not in filter_str:
msg = 'filter %s must have format "key=value"' % filter_str
raise argparse.ArgumentTypeError(msg)
(key, val_as_str) = filter_str.split('=', 1)
# Find the appropriate filter object
try:
filter_obj = [obj for obj in (filter_objs or []) if obj.name == key][0]
val = filter_obj.convert(val_as_str)
except IndexError:
raise argparse.ArgumentTypeError('unknown filter: %s' % key)
return (filter_obj.dest, val)
def _process_filters(cli_filters):
"""
Change filters from the [(key, value), ...] format given at the command
line to [{'Name': key, 'Value': [value, ...]}, ...] format, which
flattens to the form the server expects.
"""
filter_args = {}
# Compile [(key, value), ...] pairs into {key: [value, ...], ...}
for (key, val) in cli_filters or {}:
filter_args.setdefault(key, [])
filter_args[key].append(val)
# Build the flattenable [{'Name': key, 'Value': [value, ...]}, ...]
filters = [{'Name': name, 'Value': values} for (name, values)
in filter_args.iteritems()]
return filters
def _requestbuilder_except_hook(debugger_enabled, debug_enabled):
"""
Wrapper for the debugger-launching except hook
"""
def excepthook(typ, value, tracebk):
"""
If the debugger option is enabled, launch epdb (or pdb if epdb is
unavailable) when an uncaught exception occurs.
"""
if typ is bdb.BdbQuit:
sys.exit(1)
sys.excepthook = sys.__excepthook__
if debugger_enabled and sys.stdout.isatty() and sys.stdin.isatty():
if 'epdb' in sys.modules:
epdb.post_mortem(tracebk, typ, value)
else:
pdb.post_mortem(tracebk)
elif debug_enabled:
traceback.print_tb(tracebk)
sys.exit(1)
else:
print value
sys.exit(1)
return excepthook
_ALWAYS = '==ALWAYS=='

113
requestbuilder/service.py Normal file
View File

@ -0,0 +1,113 @@
# Copyright (c) 2012, Eucalyptus Systems, Inc.
#
# Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the
# above copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import boto.connection
import boto.exception
import os.path
import urlparse
class BaseService(boto.connection.AWSAuthConnection):
Description = ''
APIVersion = ''
Authentication = 'sign-v2'
Path = '/'
Port = 443
Provider = 'aws'
EnvURL = 'AWS_URL'
ResponseError = boto.exception.BotoServerError
# Endpoint names (i.e. 'us-east-1') and their URLs
Endpoints = {}
def __init__(self, url=None, **kwargs):
self._init_args = kwargs
self.find_credentials()
self._read_url_info(url or os.getenv(self.EnvURL))
if self.Endpoints:
if 'region_name' in self._init_args:
endpoint = self.Endpoints.get(self._init_args['region_name'])
self._read_url_info(endpoint)
else:
self._read_url_info(self.Endpoints.values()[0])
self._init_args.setdefault('path', self.Path)
self._init_args.setdefault('port', self.Port)
self._init_args.setdefault('provider', self.Provider)
try:
boto.connection.AWSAuthConnection.__init__(self, **self._init_args)
except boto.exception.NoAuthHandlerFound:
raise MissingCredentialsError()
def find_credentials(self):
'''
If the 'AWS_CREDENTIAL_FILE' environment variable exists, parse that
file for access keys and use them if keys were not already supplied to
__init__.
'''
if 'AWS_CREDENTIAL_FILE' in os.environ:
path = os.getenv('AWS_CREDENTIAL_FILE')
path = os.path.expandvars(path)
path = os.path.expanduser(path)
with open(path) as credfile:
for line in credfile:
line = line.split('#', 1)[0]
if '=' in line:
(key, val) = line.split('=', 1)
if key.strip() == 'AWSAccessKeyId':
self._init_args.setdefault('aws_access_key_id',
val.strip())
elif key.strip() == 'AWSSecretKey':
self._init_args.setdefault('aws_secret_access_key',
val.strip())
def make_request(self, action, verb='GET', path='/', params=None,
headers=None, data='', api_version=None):
request = self.build_base_http_request(verb, path, None, params,
headers or {}, data)
if action:
request.params['Action'] = action
if api_version:
request.params['Version'] = api_version
elif self.APIVersion:
request.params['Version'] = self.APIVersion
return self._mexe(request)
def _read_url_info(self, url):
"""
Parse a URL and use it to fill in is_secure, host, port, and path if
any are missing.
"""
if url:
parse_result = urlparse.urlparse(url)
if parse_result[0] == 'https':
self._init_args.setdefault('is_secure', True)
else:
self._init_args.setdefault('is_secure', False)
if ':' in parse_result[1]:
(host, port) = parse_result[1].rsplit(':', 1)
self._init_args.setdefault('host', host)
self._init_args.setdefault('port', int(port))
else:
self._init_args.setdefault('host', parse_result[1])
if parse_result[2]:
self._init_args.setdefault('path', parse_result[2])
def _required_auth_capability(self):
return [self.Authentication]
class MissingCredentialsError(boto.exception.BotoClientError):
def __init__(self):
boto.exception.BotoClientError.__init__(self,
'Failed to find credentials')

36
setup.py Normal file
View File

@ -0,0 +1,36 @@
# Copyright (c) 2012, Eucalyptus Systems, Inc.
#
# Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the
# above copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
from distutils.core import setup
from requestbuilder import __version__
setup(name = 'requestbuilder',
version = __version__,
description = 'Command line-driven HTTP request builder',
author = 'Garrett Holmstrom (gholms)',
author_email = 'gholms@fedoraproject.org',
packages = ['requestbuilder'],
license = 'ISC',
platforms = 'Posix; MacOS X',
classifiers = ['Development Status :: 2 - Pre-Alpha',
'Intended Audience :: Developers',
'License :: OSI Approved :: ISC License (ISCL)',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Topic :: Internet'],
requires = ['boto (>= 2.2)'],
provides = ['requestbuilder'])