Dependency Injection - renzon/zenwarch GitHub Wiki
Dependency Injection is a concept where you provide a class or function dependency from outside itself.
It is very useful for testing software, once it make easy mocking such dependencies.
ZenWArch provides a very simple dependency injection system base on convention. It is implemented in a Middleware layer. This Middleware layer is inspired by Django Middlewares, But it uses functions most of time instead of classes.
Lets see the dependency injection that provided the write_tmpl for us on last chapter:
For Jinja2 configuration, we've created the tmpl.py script inside project_template/src. We used it on a middleware to provide useful methods: render, that is used to render a template and write_tmpl, a function that renders the template and write it on response. Let's exam tmpl_middleware.py:
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
import tmpl
def execute(next_process, handler, dependencies, **kwargs):
def write_tmpl(template_name, values=None):
values = values or {}
return handler.response.write(tmpl.render(template_name, values))
dependencies["_write_tmpl"] = write_tmpl
dependencies["_render"] = tmpl.render
next_process(dependencies, **kwargs)
The execute function receives some important arguments:
- next_process: a function that execute the next middleware from setttings.MIDDLEWARES list
- handler: a instance of webapp2 RequestHandler object. It contains the web interface object, like Response and Request
- **kwargs: named arguments extracted from request
- dependencies: a dictionary containing the dependencies to be inject
Using the underline as a name for the dependencies is a convention, so we can easily distinguish dependencies from ordinary arguments that we receive from user. So when we update dependencies dict:
dependencies["_write_tmpl"] = write_tmpl
we allow a developer receive that dependency by name convention:
def index(_write_tmpl, name):
_write_tmpl('templates/form.html', {'name': name})
One important thing is that all dependencies must be the appear as first arguments of the function.
After creating the middleware, you need register it on settings.MIDDLEWARES:
MIDDLEWARES = [tmpl_middleware.execute, email_errors.execute, webapp2_dependencies.execute, parameter.execute, router_middleware.execute]
The project comes with some useful middlewares:
- tmpl_middleware: provide functions for Jinja2 template system
- email_errors: send app's admin a email containing details about errors when an Exception is raised on server
- webapp2_dependencies: provides access to RequestHandler, Responde and Request objects from webapp2
- parameter: extracts parameters from GET, POST or AngularJS AJAX calls
- router_middleware: use router.py to find the correct function to be executed, using the dependencies and **kwargs as function arguments
Let's see webapp2_dependencies.py code:
# -*- coding]= utf-8 -*-
from __future__ import absolute_import, unicode_literals
def execute(next_process, handler, dependencies, **kwargs):
dependencies["_req"] = handler.request
dependencies["_resp"] = handler.response
dependencies["_handler"] = handler
dependencies["_dependencies"] = dependencies
next_process(dependencies, **kwargs)
Looking for this code, you can see that we make the Response from webapp2 available through the argument name "_resp".
So if in some function you are not interested in returning a template, but only a string, like a json, you could write:
def api(_resp):
_resp.write('returning string')
The important thing to note is that you can edit settings.MIDDLEWARE, removing middlewares that you are not interested in. More than that, you can build your own middlewares and add to the list. Some common examples are:
- Providing access to the current logged user
- Write a middleware to log all http call in your server and meadure how long it is taking to process it
Furthermore, this dependeny injection make easy mocking http calls. Let's see how to test on Testing section