I'm creating a small Python script to manage different classes of servers (FTP, HTTP, SSH, etc.)
On each type of server, we can perform different types of actions (deploy, configure, check, etc.)
I have a base Server
class, then a separate class for each type of server that inherits from this:
class Server:
...
def check():
...
class HTTPServer(Server):
def check():
super(HTTPServer, self).check()
...
class FTPServer(Server):
def check():
super(FTPServer, self).check()
...
A sample command line might be:
my_program deploy http
From the command-line, the two mandatory arguments I need are:
Previously, I was using argparse
and the store
operation, and using a dict
to match the command-line option to the actual class and function name. For example:
types_of_servers = {
'http': 'HTTPServer',
'ftp': 'FTPServer',
...
}
valid_operations = {
'check': 'check',
'build': 'build',
'deploy': 'deploy',
'configure': 'configure',
'verify': 'verify',
}
(In my actual code, valid_operations wasn't quite a naive 1:1 mapping.)
And then using rather horrible code to create the right type of object, and call the right class.
Then I thought I'd use argparse's subparsers
feature to do it instead. So I've made each operation (check, build, deploy, etc.) a subparser.
Normally, I could link each sub-command to a particular function, and have it call it. However, I don't want to just call a generic check()
function - I need to create the correct type of object first, and then call the appropriate function within that object.
Is there a good, or pythonic way to do this? Preferably one that doesn't involve a lot of hardcoding, or badly designed if/else loops?
A “subparser” is an argument parser bound to a namespace. In other words, it works with everything after a certain positional argument. Argh implements commands by creating a subparser for every function. Again, here's how we create two subparsers for commands foo and bar: parser = ArghParser() parser.
Optional Arguments To add an optional argument, simply omit the required parameter in add_argument() . args = parser. parse_args()if args.
Python argparse optional argument The example adds one argument having two options: a short -o and a long --ouput . These are optional arguments. The module is imported. An argument is added with add_argument .
If you are set on using a subparser for each command I would do something like this. Use argparse's type support to call a function that lookups the class you want to instantiate and returns it.
Then call the method on that instance dynamically with getattr()
import argparse
class Server:
def check(self):
return self.__class__.__name__
class FooServer(Server):
pass
class BarServer(Server):
pass
def get_server(server):
try:
klass = globals()[server.capitalize()+'Server']
if not issubclass(klass, Server):
raise KeyError
return klass()
except KeyError:
raise argparse.ArgumentTypeError("%s is not a valid server." % server)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='command')
check = subparsers.add_parser('check')
check.add_argument('server', type=get_server)
args = parser.parse_args()
print getattr(args.server, args.command)()
Output looks something like this:
$ python ./a.py check foo
FooServer
$ python ./a.py check bar
BarServer
$ python ./a.py check baz
usage: a.py check [-h] server
a.py check: error: argument server: baz is not a valid server.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With