Django: Where does the application start ?


Posted:   |  More posts about django wsgi learn

The key to understand any kind of application is to know where does it start. Until then you'll keep banging your head, shooting in the dark and lastly, at the mercy of Google when trying to debug or solve any issues you have.

Majority of web developers don't really know, or even care where actually their application start. This particularly true for those developing in PHP which abstract most of the web parts and it appear to developer they just writing a regular command line or desktop application.

For Django developer, you can probably ignore this throughout your dev carrier but knowing it will help you a lot in.

Django application can start (at least) from 2 possible entry point. The first entry point is through the development server and the second entry point is when you deploy it to production web server, either mod_wsgi, gunicorn, Paste, Rocket, Waitress, Circus or dozen more WSGI server you can find on PyPI. I can still remember the day when there's none pure Python WSGI server exists (except CherryPy) and how I'd really envy Rails's community for having Mongrel. That day has long gone.

Let's dive to the first entry point - when you run your django application through the manage.py runserver command. Typical django application (as generated) by the startproject command has a file called manage.py at the root of the project directory. This is the file that developer use to interact with their application, doing things such as running development server, synching database tables with the models definition, loading/exporting data and so much more.

If you peek into manage.py, it's pretty simple actually:-

#!./bin/python
import os
import sys

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "acara.settings")

    from django.core.management import execute_from_command_line

    execute_from_command_line(sys.argv)

They key line here is at line 10 where a function called execute_from_command_line() being called. From here we can traverse to module django.core.management to find out what that function is doing. If you're sure where the actual module is located, just open the python console and import the module to inspect it's location:-

$ python
Python 2.7.3 (default, Aug  1 2012, 05:16:07) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.

>>> import django.core.management
>>> django.core.management
<module 'django.core.management' from '/home../local/lib/...django/core/management/__init__.pyc'>

Now we know that module was in $HOME/local/lib/python2.7/site-packages/django/core/management/__init__.py. This one thing I love in python. Everything is explicit. You can easily trace down where things comes from without even having to understand the whole framework yet. In some other framework or languages where implicit import is a norm, you'll have hard time tracing down where certain functions or variables comes from until you know the mechanism of the framework.

Let's look what inside django.core.management module. The function execute_from_command_line() was defined at the bottom of the source code. It just a wrapper to other function actually.

def execute_from_command_line(argv=None):
    """
    A simple method that runs a ManagementUtility.
    """
    utility = ManagementUtility(argv)
    utility.execute()

ManagementUtility class however not really interesting. It just some logic to find out which command module to execute based on user's command parameter. For manage.py runserver, the python module that will be executed is django/core/management/commands/runserver.py. So let's take a look into this module:-

def get_handler(self, *args, **options):
        """
        Returns the default WSGI handler for the runner.
        """
        return get_internal_wsgi_application()

This is around line 56 in current master on Github. The import at the top of the module look like this:-

from django.core.servers.basehttp import get_internal_wsgi_application, run

So that would be our next target - django/core/servers/basehttp.py. The code is like this:-

def get_internal_wsgi_application():
    """
    Loads and returns the WSGI application as configured by the user in
    ``settings.WSGI_APPLICATION``. With the default ``startproject`` layout,
    this will be the ``application`` object in ``projectname/wsgi.py``.
    This function, and the ``WSGI_APPLICATION`` setting itself, are only useful
    for Django's internal server (runserver); external WSGI servers should just
    be configured to point to the correct application object directly.
    If settings.WSGI_APPLICATION is not set (is ``None``), we just return
    whatever ``django.core.wsgi.get_wsgi_application`` returns.
    """
    from django.conf import settings
    app_path = getattr(settings, 'WSGI_APPLICATION')
    if app_path is None:
        return get_wsgi_application()

    try:
        return import_string(app_path)
    except ImportError as e:
        msg = (
            "WSGI application '%(app_path)s' could not be loaded; "
            "Error importing module: '%(exception)s'" % ({
                'app_path': app_path,
                'exception': e,
            })
        )
        six.reraise(ImproperlyConfigured, ImproperlyConfigured(msg),
                    sys.exc_info()[2])

So now we have the full trace of how from running the command manage.py runserver, which code is being executed. At this point, it really helpful if we understand the basic of WSGI spec. Django, at the very core is nothing more than a WSGI application. The basic of WSGI is like this:-

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return ['hello world']

And the whole Django framework basically just an expansion of above function. So let's see where does the above snippet from Django would bring us:-

from django.core.wsgi import get_wsgi_application
import django
from django.core.handlers.wsgi import WSGIHandler

def get_wsgi_application():
    """
    The public interface to Django's WSGI support. Should return a WSGI
    callable.
    Allows us to avoid making django.core.handlers.WSGIHandler public API, in
    case the internal WSGI implementation changes or moves in the future.
    """
    django.setup()
    return WSGIHandler()

In django/core/handlers/wsgi.py:-

class WSGIHandler(base.BaseHandler):
    initLock = Lock()
    request_class = WSGIRequest

    def __call__(self, environ, start_response):
        # Set up middleware if needed. We couldn't do this earlier, because
        # settings weren't available.
        if self._request_middleware is None:
            with self.initLock:
                try:
                    # Check that middleware is still uninitialized.
                    if self._request_middleware is None:
                        self.load_middleware()
                except:
                    # Unload whatever middleware we got
                    self._request_middleware = None
                    raise

Notice the similarity between the above code and the basic WSGI application we have seen here ? The difference is that the Django code above is using class, instead of simple function but the interface still remained similar as you can see in the method __call__(self, environ, start_response) above. This class, when instantiated, the instance will be callable just like function.

So far we've only gone through the request/response part of Django. There's still a lot to cover - ORM, templates, middlewares, forms etc. This post was written in hurry so if I missed some key details, feel free to ask in the comments below.

Comments powered by Disqus

About me

Web developer in Malaysia. Currently work at MARIMORE Inc building internet services using Python and Django web framework.

ImportError is an error message emitted by Python when it failed to load certain module as requested by programmer. It's a very common error when someone new to the language trying it out. This website on the same theme, will try provide help for newcomers on any technologies to overcome their first hurdle.

Try most of the examples you may find here on Digital Ocean cloud service. They provide excellent VPS at a very cheaper price. Using this referral link you'll get USD10 credits upon sign up. That's enough to run single VPS with 1GB RAM for a month.

Others

I can also be found at the following sites:-

  • http://k4ml.blogspot.com/
  • http://k4ml.github.io/
  • http://metak4ml.blogspot.com/
  • http://www.mydev.my/
  • http://github.com/k4ml/

Disclaimers

The postings on this site are my own and don't necessarily represent my employer's positions, strategies or opinions.

Share