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.
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()
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).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.
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