Search code examples
pythonpython-2.7tornadokeyword-argument

Handling of arbitrary options using Tornado options, i.e. like **kwargs


I'm using Tornado options to define command-line arguments. However, I would like to be able to throw arbitrary configuration options, not defined in code, to my program. These will differ, depending on what the program is supposed to do. For instance, connect to a bluetooth device using a MAC address or connect to a serial device using a TTY.

If I define a set of "mandatory" options in code and then add an additional when calling the program, I get an exception thrown by parse_command_line().

It would be very handy to get e.g. a dictionary with the remaining (undefined) options. That is, much in the same way as **kwargs works in functions.

Can this be done?

(A work-around is to define a string option named e.g. configuration and throw everything in there, possibly encoded in some clever way. As the program is being called by another program I can e.g. base64-encode a serialized dict.)


Update: I've noticed that if you add command-line args without leading dashes, Tornado will ignore them and return a list with remaining (undefined) options.


Solution

  • You can create subclass of OptionParser singleton and modify parsing method. Example:

    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    
    import tornado.httpserver
    import tornado.ioloop
    import tornado.options
    import tornado.web
    
    from tornado.options import OptionParser
    import sys
    
    class MyOptionParser(OptionParser):
        def parse_command_line(self, args=None, final=True):
            if args is None:
                args = sys.argv
            remaining = []
            for i in range(1, len(args)):
                # All things after the last option are command line arguments
                if not args[i].startswith("-"):
                    remaining = args[i:]
                    break
                if args[i] == "--":
                    remaining = args[i + 1:]
                    break
                arg = args[i].lstrip("-")
                name, equals, value = arg.partition("=")
                name = name.replace('-', '_')
                if not name in self._options:
    #   modified line       self.print_help()
    #   modified line       raise Error('Unrecognized command line option: %r' % name)
                    self.define(name, help="Arbitrary option") # new line
                option = self._options[name]
                if not equals:
                    if option.type == bool:
                        value = "true"
                    else:
                        raise Error('Option %r requires a value' % name)
                option.parse(value)
    
            if final:
                self.run_parse_callbacks()
    
            return remaining
    
    options = MyOptionParser()
    options.define("port", default=8000, help="run on the given port", type=int)
    
    class IndexHandler(tornado.web.RequestHandler):
        def get(self):
            self.write(options.myoption) 
    
    if __name__ == "__main__":
        options.parse_command_line()
        app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
        http_server = tornado.httpserver.HTTPServer(app)
        http_server.listen(options.port)
        tornado.ioloop.IOLoop.instance().start()
    

    The only difference from source is I'm defining passed option instead of throwing an Exception. Running:

    python test.py --myoption="test"