CommandManager
March 22nd, 2010 by JoeHere’s what I think is a pretty awesome command manager. Here’s the meat of it:
class CommandManager(object): def __new__(cls, *args, **kwargs): manager = super(CommandManager, cls).__new__(cls, *args, **kwargs) manager.commands = dict() def decorator(*outer_args, **outer_kargs): def inner(func, *args): if func.func_name not in manager.commands: manager.commands[func.func_name] = func func.parser = None if outer_args: if not func.parser: func.parser = optparse.OptionParser(usage=optparse.SUPPRESS_USAGE, add_help_option=False) func.parser.add_option(*outer_args, **outer_kargs) return func return inner def runner(self, cmd_name, *args): #by returning this function, "self" will be an instance of the class # that defines all these commands. see the example below if cmd_name in manager.commands: cmd = manager.commands[cmd_name] if cmd.parser: options, args = cmd.parser.parse_args(list(args)) return cmd(self, options, *args) return cmd(self, *args) else: raise NoSuchCommandError(cmd_name) runner.manager = manager runner.command = decorator #by returning a function we get bound to instances of the class return runner
And here’s the usage:
class MyClass(object): manager = CommandManager() @manager.command() def important_command(self, firstthing, secondthing, optionalarg=None): print 'you passed %s, then %s' % (firstthing, secondthing) if optionalarg: print 'you also passed %s even though you didnt have to' % optionalarg @manager.command('-q', action="store_true", help="quiet") @manager.command('--thing', help="the thing argument") def command_with_opts(self, options, something=None): print 'you passed %s, even though you didnt have to' % something if options.q: print 'you told me to be quiet, but i wasnt' if options.thing: print 'you also specified that thing should be %s' % options.thing if __name__ == '__main__': my_object = MyClass() my_object.manager(*sys.argv[1:])
Then you can do
[jcarrafa@racecar ~]$ python test.py important_command hi there you passed 'hi', then 'there' [jcarrafa@racecar ~]$ python test.py important_command hi there guy you passed 'hi', then 'there' you also passed 'guy' even though you didnt have to [jcarrafa@racecar ~]$ python test.py important_command Hello World you passed 'Hello', then 'World' [jcarrafa@racecar ~]$ python test.py important_command Foo Bar you passed 'Foo', then 'Bar' [jcarrafa@racecar ~]$ python test.py important_command Foo Bar Baz you passed 'Foo', then 'Bar' you also passed 'Baz' even though you didnt have to [jcarrafa@racecar ~]$ python test.py command_with_opts Hello you passed 'Hello', even though you didnt have to [jcarrafa@racecar ~]$ python test.py command_with_opts -q Hello you passed 'Hello', even though you didnt have to you told me to be quiet, but i wasnt [jcarrafa@racecar ~]$ python test.py command_with_opts --thing THING! Hello you passed 'Hello', even though you didnt have to you also specified that thing should be 'THING!'
Possible errors are:
[jcarrafa@racecar ~]$ python test.py important_command Traceback (most recent call last): File "test.py", line 24, inmy_object.manager(*sys.argv[1:]) File "cmdmanager.py", line 39, in runner return cmd(self, *args) TypeError: important_command() takes at least 3 arguments (1 given) [jcarrafa@racecar ~]$ python test.py pork Traceback (most recent call last): File "test.py", line 24, in my_object.manager(*sys.argv[1:]) File "cmdmanager.py", line 41, in runner raise NoSuchCommandError(cmd_name) thgg.devel.cmdmanager.NoSuchCommandError: There is no such command named 'pork'
manager.command has arguments, it passes them straight on to OptionParser.add_option. One gotcha is that you need to remember the options argument incase you pass arguments to @manager.command, but thats not much different than remembering self.