Files
deb-python-requestbuilder/requestbuilder/__init__.py
Garrett Holmstrom a4f8ed4277 Gather args from services and auth handlers
This change allows services and auth handlers to specify their own ARGS
lists that requests can then gather and feed into the ArgumentParsers
they create.  Arg routes are now actual objects (or for declarative use,
attrgetters) as opposed to magic values.  Requests needing to call other
requests can re-use their own service objects to propagate the args that
the latter gathered from the command line.

Configuration files are parsed much earlier than they used to be and can
now cause BaseCommand.__init__() to fail.  BaseCommand.run() handles
this.

Config objects gained the concept of a "current" region and user, which
services can set as they parse their command line args.  These values
are used as defaults when one goes to look up options.

BaseCommand.DEFAULT_ROUTE is now an attribute/property, default_route.

BaesCommand.print_result is now a noop.  Define it yourself if you need
a tool to output something during that step.

BaseRequest.ACTION is now BaseRequest.NAME.

This change breaks a large number of internal APIs.  Docstrings are
horribly out of date at this point and should be fixed fairly soon in a
future commit.
2013-02-01 22:51:47 -08:00

125 lines
4.6 KiB
Python

# Copyright (c) 2012-2013, 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 operator
__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):
if 'route_to' in kwargs:
self.route = kwargs.pop('route_to')
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 name/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 name=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, type=str, choices=None, help=None):
self.name = name
self.type = type
self.choices = choices
self.help = help
def matches_argval(self, argval):
return argval.startswith(self.name + '=')
def convert(self, argval):
'''
Given an argument to --filter of the form "<name>=<value>", convert
the value to the appropriate type by calling self.type on it, then
return a (name, converted_value) tuple. If the value's type conversion
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.
'''
if '=' not in argval:
msg = 'filter {0} must have format "NAME=VALUE"'.format(argval)
raise argparse.ArgumentTypeError(msg)
(name, value_str) = argval.split('=', 1)
try:
value = self.type(value_str)
except ValueError:
msg = 'filter {0} must have type {1}'.format(
value_str, self.type.__name__)
raise argparse.ArgumentTypeError(msg)
if self.choices and value not in self.choices:
msg = 'filter value {0} must match one of {1}'.format(
value, ', '.join([str(choice) for choice in self.choices]))
raise argparse.ArgumentTypeError(msg)
return (name, value)
class GenericTagFilter(Filter):
'''
A filter that accepts "tag:<key>=<value>" values
'''
def matches_argval(self, argval):
return argval.startswith('tag:') and '=' in argval
########## SINGLETONS ##########
# Indicates a parameter that should be sent to the server without a value.
# Contrast this with empty strings, with are omitted from requests entirely.
EMPTY = type('EMPTY', (), {'__repr__': lambda self: "''",
'__str__': lambda self: ''})()
# Getters used for arg routing
AUTH = operator.attrgetter('service.auth.args')
PARAMS = operator.attrgetter('params')
SERVICE = operator.attrgetter('service.args')
SESSION = operator.attrgetter('service.session_args')