Electricmonk

Ferry Boender

Programmer, DevOpper, Open Source enthusiast.

Blog

Dependency Injection in web.py

Friday, March 6th, 2015

webpyweb.py is a lightweight Python web framework that gets out of your way and just let’s you write Python.

Here’s a simple program written in web.py:

import web

class index:
    def GET(self):
        return "Hello, World!`"

urls = (
    '/', 'index',
)

if __name__ == "__main__":
    app = web.application(urls, globals())
    app.run()

I quickly ran into the issue of writing larger well-structured applications in web.py though. If our program becomes bigger, we really want to break up our program into multiple files. This is of course no problem with web.py:

frontpage.py

class index:
    def GET(self):
        return "Hello, World!`"

webapp.py

import web

import frontpage

urls = (
    '/', 'frontpage.index',
)

if __name__ == "__main__":
    app = web.application(urls, globals())
    app.run()

In the example above, we put some of our routes in a seperate file and import it. web.py’s urls definition understands this and happily use the Index class from the module. However, what if we want to pass some application-wide settings to the Index route? web.py’s examples all use globals, but that’s not gonna work if our route lives in another file. Besides, globals are annoying and make unit testing more difficult.

The way to get around this is with a technique called Dependency Injection. I couldn’t find any best practices on how to do this with web.py, so I came up with the following:

frontpage.py

import web

class index:
    def GET(self):
        smtp_server = web.ctx.deps['config']['smtp_server']
        smtp_port = web.ctx.deps['config']['smtp_port']
        return "Sending email via %s:%s" % (smtp_server, smtp_port)

webapp.py

import web
import frontpage


class InjectorClass:
    def __init__(self, deps):
        self.deps = deps

    def __call__(self, handler):
        web.ctx.deps = self.deps
        return handler()

urls = (
    '/', 'frontpage.index',
)

if __name__ == "__main__":
    config = {
        'smtp_server': '127.0.0.1',
        'smtp_port': 25,
    }

    app = web.application(urls, globals())
    app.add_processor(InjectorClass({'config': config}))
    app.run()

If we run the webapp, we’ll see:

Sending email via 127.0.0.1:25

The way this works is that we define an InjectorClass which simply holds a variable for us. In this case a dictionary containing a ‘config’ key with our configuration values. The InjectorClass also defines a __call__ method. This means any instances of the class become executable, as if it was a function. This lets us pass it to web.py as a processor (add_processor()).

Whenever a new request comes in, web.py does some magic with web.ctx (context) to ensure that the values it contains only apply to the current request. No other request sees values of any other request’s web.ctx. For each request, web.py also calls every processor. In our case, that’s an instance of the InjectorClass. When called, the __call__ method is invoked, which adds the dependencies to the web.ctx so the current request can access them.

So now we can pass any value to our InjectorClass on application startup, and it will automatically become available in each request.

You should be careful about what dependencies you inject. Generally, read-only values are fine, but you should realize that injected dependencies are shared among every request and should therefor be threadsafe.

I feel I should also note that we could have gone with a closure, but as I explained in an earlier article, I prefer a class.

 

The text of all posts on this blog, unless specificly mentioned otherwise, are licensed under this license.