Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I pass parameters to a RequestHandler?

Tags:

python

The Python documentation includes an example of creating an HTTP server:

def run(server_class=HTTPServer, handler_class=BaseHTTPRequestHandler):     server_address = ('', 8000)     httpd = server_class(server_address, handler_class)     httpd.serve_forever() 

A RequestHandler class is provided to the Server, which then takes care of instantiating the handler automatically.

Let's say I want to pass in custom parameters to the request handler when it's created. How can and should I do that?

More specifically, I want to pass in parameters from the command line, and having to access sys.argv inside the request handler class seems unnecessarily clunky.

It seems like this should be possible by overriding parts of the Server class, but I feel like I'm overlooking a simpler and better solution.

like image 682
Jakob Avatar asked Feb 07 '14 15:02

Jakob


2 Answers

Use a class factory:

def MakeHandlerClassFromArgv(init_args):     class CustomHandler(BaseHTTPRequestHandler):         def __init__(self, *args, **kwargs):              super(CustomHandler, self).__init__(*args, **kwargs)              do_stuff_with(self, init_args)     return CustomHandler  if __name__ == "__main__":     server_address = ('', 8000)     HandlerClass = MakeHandlerClassFromArgv(sys.argv)     httpd = HTTPServer(server_address, HandlerClass)     httpd.serve_forever() 
like image 103
Thomas Orozco Avatar answered Oct 04 '22 04:10

Thomas Orozco


I solved this in my code using "partial application".

Example is written using Python 3, but partial application works the same way in Python 2:

from functools import partial from http.server import HTTPServer, BaseHTTPRequestHandler  class ExampleHandler(BaseHTTPRequestHandler):     def __init__(self, foo, bar, qux, *args, **kwargs):         self.foo = foo         self.bar = bar         self.qux = qux         # BaseHTTPRequestHandler calls do_GET **inside** __init__ !!!         # So we have to call super().__init__ after setting attributes.         super().__init__(*args, **kwargs)      def do_HEAD(self):         self.send_response(200)         self.send_header('Content-type', 'text/plain')         self.end_headers()      def do_GET(self):         self.do_HEAD()         self.wfile.write('{!r} {!r} {!r}\n'                          .format(self.foo, self.bar, self.qux)                          .encode('utf8'))  # We "partially apply" the first three arguments to the ExampleHandler handler = partial(ExampleHandler, sys.argv[1], sys.argv[2], sys.argv[3]) # .. then pass it to HTTPHandler as normal: server = HTTPServer(('', 8000), handler) server.serve_forever() 

This is very similar to a class factory, but in my opinion it has a couple of subtle advantages:

  • partial objects are much easier to introspect for what's inside them than nested classes defined and returned by factory functions.
  • partial objects can be serialized with pickle in modern Python, whereas nested class definitions inside factory functions cannot (at least not without going out of your way to code a __reduce__ method on the class to make it possible).
  • In my limited experience explicit "pre-attaching" of arguments with partial to an otherwise Pythonic and normal class definition is easier (less cognitive load) to read, understand, and verify for correctness than a nested class definition with the parameters of the wrapping function buried somewhere inside it.

The only real disadvantage is that many people are unfamiliar with partial - but in my experience it is better for everyone to become familiar with partial anyway, because partial has a way of popping up as an easy and composable solution in many places, sometimes unexpectedly, like here.

like image 29
mtraceur Avatar answered Oct 04 '22 02:10

mtraceur