For testability reasons, we’ve begun using a lot of dependency injection in our Python projects. Imagine that I have a function that does some work and then ultimately calls an API over-the-wire:


import simplejson
from google.appengine.api import urlfetch # App Engine example
def my_method(user_id):
  url = 'http://www.example.com/lookup-user/%d.json' % user_id
  result = urlfetch.fetch(url)
  if result.status_code == 200:
    return simplejson.loads(result.content)
  return {}

This is bad for testability because when my test code executes my_method(), it will either go out over the wire (super bad!) unless I monkey-patch urlfetch to be some other class (really inconvenient).

So, instead, we can accept an alternate urlfetch mechanism optionally in the method:


from google.appengine.api import urlfetch as urlfetch_builtin
def my_method(user_id, urlfetch=urlfetch_builtin):
  url = 'http://www.example.com/lookup-user/%d.json' % user_id
  result = urlfetch.fetch(url)
  if result.status_code == 200:
    return simplejson.loads(result.content)
  return {}

So now the test client can pass in an alternate implementation of urlfetch that can return instrumented results and exceptions. This proves to be extremely handy.

One problem with this particular implementation is that it forces the client to do some odd things. Imagine that I have a piece of client code that itself is dependency injected:


def client_function(urlfetch=None):
  # here, I must check for an injection so that I know to provide it to my_method
  if urlfetch:
    return my_method(123, urlfetch=urlfetch)
  else:
    return my_method(123)

This is unfortunate. A better way to implement dependency injection and prevent this client if statement is to adjust my_method:


from google.appengine.api import urlfetch as urlfetch_builtin
def my_method(user_id, urlfetch=None):
  urlfetch = urlfetch or urlfetch_builtin
  # ...

Since urlfetch is required by the method, we can take None to mean “use the default implementation”. Now the client code becomes simply:


def client_function(urlfetch=None):
  return my_method(123, urlfetch=urlfetch)

Make sure you setup your dependency injection params in this way and save your clients some headaches!



No Responses Yet to “Dependency Injection in Python”  

  1. No Comments Yet

Leave a Reply