Feed Sign in with OpenID OpenID

Simon Willison’s Weblog

djng—a Django powered microframework

djng is nearly two weeks old now, so it’s about time I wrote a bit about the project.

I presented a keynote at EuroDjangoCon in Prague earlier this month entitled Django Heresies. The talk followed the noble DjangoCon tradition (established last year with the help of Mark Ramm and Cal Henderson) of pointing a spotlight at Django’s flaws. In my case, it was a chance to apply the benefit of hindsight to some of the design decisions I helped make back at the Lawrence Journal-World in 2004.

I took a few cheap shots at things like the {% endifequal %} tag and error silencing in the template system, but the three substantial topics in my talk were class-based generic views (I’m a fan), my hatred of settings.py and my interest in turtles all the way down.

Why I hate settings.py

In the talk, I justified my dislike for settings.py by revisiting the problems behind PHP’s magic quotes feature (finally going away for good in PHP 6). Magic quotes were one of the main reasons I switched to Python from PHP.

My main problem with magic quotes was that they made it extremely difficult to write reusable PHP code. The feature was configured globally, which lead to a quandary. What if you have two libraries, one expecting magic quotes on and the other expecting it off? Your library could check get_magic_quotes_gpc() and stripslashes() from input if the setting was turned on, but this would break in the presence of the common idiom where stripslashes() is applied to all incoming $_GET and $_POST data.

Unfortunately, global settings configured using settings.py have a similar smell to them. Middleware and context processors are the best example here—a specific setting might be needed by just one installed application, but the effects are felt by everything in the system. While I haven’t yet seen two “reusable” Django apps that require conflicting settings, per-application settings are an obvious use case that settings.py fails to cover.

Global impact aside, my bigger problem with settings.py is that I almost always end up wanting to reconfigure them at run-time.

This is possible in Django today, but comes at a price:

  • Only some settings can actually be changed at run-time—others (such as USE_I18N) are lazily evaluated once and irreversibly reconfigure parts of Django’s plumbing. Figuring out which ones can be changed requires exploration of Django’s source code.
  • If you change a setting, you need to reliably change it back at the end of a request or your application will behave strangely. Uncaught exceptions could cause problems here, unless you remember to wrap dynamic setting changes in a try/finally block.
  • Changing a setting isn’t thread-safe (without doing some extra work).

Almost every setting in Django has legitimate use-cases for modification at run-time. Here are just a few examples:

  • Requests from mobile phones may need a different TEMPLATE_DIRS setting, to load the mobile-specific templates in preference to the site defaults.
  • Some sites offer premium accounts which in turn gain access to more reliable servers. Premium users might get to send e-mail via a separate pool of SMTP servers, for example.
  • Some sections of code may want to use a different cache backend, or talk to a different set of memcache servers—to reduce the chance of one rapidly changing component causing other component’s cache entries to expire too early.
  • Errors in one area of a site might need to be sent to a different team of developers.
  • Admin users might want DEBUG=True, while regular site visitors get DEBUG=False.

Finally, settings.py is behind the dreaded “Settings cannot be imported, because environment variable DJANGO_SETTINGS_MODULE is undefined” exception. Yuck.

Turtles all the way down

The final section of the talk was about turtles. More precisely, it was about their role as an “infinite regression belief about cosmology and the nature of the universe”. I want to apply that idea to Django.

My favourite thing about Django is something I’ve started to call the “Django Contract”: the idea that a Django view is a callable which takes a request object and returns a response object. I want to expand that concept to other parts of Django as well:

  • URLconf: takes a request, dispatches based on request.path, returns a response.
  • Application: takes a request, returns a response
  • Middleware: takes a request, returns a response (conditionally transforming either)
  • Django-powered site: hooked in to mod_wsgi/FastCGI/a Python web server, takes a request, returns a response

So instead of a Django site consisting of a settings.py, urls.py and various applications and middlewares, a site would just be a callable that obeys the Django Contract and composes together dozens of other callables.

At this point, Django starts to look a lot like WSGI. What if WSGI and the Django Contract were interchangeable? WSGI is a wrapper around HTTP, so what if that could be swapped in and out (through proxies) as well? Django, WSGI and HTTP, three breeds of turtle arranged on top of each other in various configurations. Turtles all the way down.

djng

djng is my experiment to see what Django would like without settings.py and with a whole lot more turtles. It’s Yet Another Python Microframework.

What’s a microframework? The best examples are probably web.py (itself a result of Aaron Swartz’s frustrations with Django) and Sinatra, my all time favourite example of Ruby DSL design. More recent examples in Python include juno, newf, mnml and itty.

Microframeworks let you build an entire web application in a single file, usually with only one import statement. They are becoming increasingly popular for building small, self-contained applications that perform only one task—Service Oriented Architecture reborn as a combination of the Unix development philosophy and RESTful API design. I first saw this idea expressed in code by Anders Pearson and Ian Bicking back in 2005.

Unlike most microframeworks, djng has a pretty big dependency: Django itself. The plan is to reuse everything I like about Django (the templates, the ORM, view functions, the form library etc) while replacing just the top level plumbing and removing the requirement for separate settings.py and urls.py files.

This is what “Hello, world” looks like in in djng:

import djng

def index(request):
    return djng.Response('Hello, world')

if __name__ == '__main__':
    djng.serve(index, '0.0.0.0', 8888)

djng.Response is an alias for Django’s HttpResponse. djng.serve is a utility function which converts up anything fulfilling the Django Contract in to a WSGI application, then exposes it over HTTP.

Let’s add URL routing to the example:

app = djng.Router(
    (r'^hello$', lambda request: djng.Response('Hello, world')),
    (r'^goodbye$', lambda request: djng.Response('Goodbye, world')),
)

if __name__ == '__main__':
    djng.serve(app, '0.0.0.0', 8888)

The implementation of djng.Router is just a few lines of glue code adding a nicer API to Django’s internal RegexURLResolver class.

Services, not settings

The trickiest problem I still need to solve is how to replace settings.py. A group of developers (including Adrian, Armin, Alex and myself) had an excellent brainstorming session at EuroDjangoCon about this. We realised that most of the stuff in settings.py can be recast as configuring services which Django makes available to the applications it is hosting. Services like the following:

  • Caching
  • Templating
  • Sending e-mail
  • Sessions
  • Database connection—django.db.connection
  • Higher level ORM
  • File storage

Each of the above needs to be configured, and each also might need to be reconfigured at runtime. Django already points in this direction by providing hooks for adding custom backends for caching, template loading, file storage and session support. What’s missing is an official way of swapping in different backends at runtime.

I’m currently leaning towards the idea of a “stack” of service implementations, one for each of the service categories listed above. A new implementation could be pushed on to the stack at any time during the Django request/response cycle, and will be automatically popped back off again before the next request is processed (all in a thread-safe manner). Applications would also be able to instantiate and use a particular service implementation directly should they need to do so.

A few days ago I heard about Contextual, which appears to be trying to solve a similar problem. Just a few minutes ago I stumbled across paste.registry’s StackedObjectProxy which seems to be exactly what I’ve been busily reinventing.

My current rough thoughts on an API for this can be found in services_api_ideas.txt. I’m eager to hear suggestions on how to tackle this problem.

djng is very much an experiment at the moment—I wouldn’t suggest building anything against it unless you’re willing to maintain your own fork. That said, the code is all on GitHub partly because I want people to fork it and experiment with their own API concepts as much as possible.

If you’re interested in exploring these concepts with me, please join me on the brand new djng mailing list.

This is djng—a Django powered microframework by Simon Willison, posted on 19th May 2009.

Tagged , , , , , , , , , , ,

View blog reactions

Next: Facebook Usernames and OpenID

Previous: rev=canonical bookmarklet and designing shorter URLs

24 comments

  1. Two obligatory bits of pushback :)

    1. Thanks to the joy of backwards-compatibility policies, I'd suspect most of this is stuff that'd have to wait for a major version bump in Django (e.g., 1.x -> 2.x). Fortunately that gives plenty of time to get the design right.

    2. I'm not entirely sold on the idea of callables all the way down, mostly because of what I see as limitations in WSGI's attempt to do this. It feels like that approach leads to a very different sort of contract -- one where the callable you're handed is, or is meant to be treated as, a black box that you don't get to peek into or mess with. This comes with its own set of trade-offs and limitations (some minor, some not), which I'm not sure I'm ready to accept.

    James Bennett - 19th May 2009 01:38 - #

  2. James: completely agree about a major version bump - that's why I'm doing this experimentation as an entirely separate project, with no promises and hopefully no confusion as to whether this is the "future of Django" or anything like that. It's a playground for ideas, nothing more.

    I'd like to hear more about your resistance to callables all the way down - the only thing I've had trouble getting my head around is URL reversing, but I have a hunch that we can get an OK 80/29 solution by just punting that off to a "URL reversing service", which can then subvert the callable stack as much as it likes.

    Simon Willison - 19th May 2009 01:43 - #

  3. Oh, and on the subject of "black boxes that you don't get to mess around with", have you seen my TemplateResponse class yet? The idea with that is that a view can hand back a TemplateResponse which includes both a template and its context, but is only actually rendered at the last possible moment (similar to how QuerySets are lazily evaluated). This means that callables higher up the chain can alter the context before it gets rendered back to the browser.

    Simon Willison - 19th May 2009 01:47 - #

  4. Well, let me take an example I brought up when discussing this with Mark Ramm a while back: suppose I have a WSGI application which uses SQLAlchemy to talk to a database, and I want to have a WSGI middleware I can enable to get query logging. How do I do that?

    The answer, as far as I can tell from talking to both WSGI and SA people, is that you can't do this using just WSGI -- you have to invent some sort of side channel for the middleware and the application to talk to each other outside of the WSGI API contract.

    Partly I think this is because WSGI (and Rack in the Ruby world, and others) is following a sort of corollary of Zawinski's law (which I formulate as "every gateway interface evolves until it looks sort of like a framework API"), but ultimately I think it's just something you have to live with in that model. Even with a rich, robust API (e.g., not what WSGI gives us), it seems as if commitment to the "X goes in, Y comes out" approach will inevitably create this sort of situation.

    All of which is why I'm still on the fence about this; I suspect there needs to be a level at which you can work with more than just black-box request/response callables. Which gets back, unfortunately, to some sort of global configuration. Without the ability to ask high-level questions like "what applications are in use here" it's much harder to write, say, an administrative application that discovers all your data models (to take one salient example).

    James Bennett - 19th May 2009 02:26 - #

  5. Two thoughts. The first is that in the example of SQLAlchemy query logging, your middleware would simply push a “logging SQLAlchemy backend” over the already-configured “SQLAlchemy backend” database service.

    The second thought is that “X in, Y out, black box” sounds a lot like purely functional programming, and passing mutable context around with clean rollback sounds a lot like the sort of messiness that the FP folks clean up with monads. It might be worth bouncing ideas off of someone who has really grokked monads.

    Zellyn Hunter - 19th May 2009 02:56 - #

  6. One of the problems I see you running into here is that you're doing 2 things at once. 1) is a micro framework, which I am totally non-convinced about (is there any project you spend more than a day on for which a microframework is useful?), and 2) the services/turtles ideas, which I think are interesting, and probably useful (even if I'm not totally convinced :) ).

    Alex Gaynor - 19th May 2009 03:15 - #

  7. Zellyn: how -- using only WSGI -- do you "push a logging backend"? And how do you get the data back out of it? No matter what you have to have some sort of side-channel or additional API you can access.

    The black-box approach is just that: you aren't supposed to know what's in it, and it's typically designed to keep or discourage you from trying to do that. So maybe SQLAlchemy's going to be running in there and maybe it'll respond to an argument or an environment variable, maybe it'll leave a log file you can read once it's done, but according to the API contract you don't get to know that.

    James Bennett - 19th May 2009 04:14 - #

  8. I don't dig the name ;), you guys know about Dyango?

    http://en.wikipedia.org/wiki/Dyango

    Igor - 19th May 2009 04:38 - #

  9. Great to see this continue to evolve.

    The services bit of this could reasonably be done in a backwards-compatible way. It's sort of a generic approach to the latest thinking on multi-db support -- maintain a list of named e-mail servers, template loaders/directories, etc., just as the multi-db world maintains a list of named connections.

    See you on the mailing list! :-)

    Adrian Holovaty - 19th May 2009 04:49 - #

  10. James: Sorry, I misread your comment. With WSGI, you're right. With some version of callables all the way down, I think Simon's stackable services are a pretty workable side-channel abstraction.

    I'm going to sleep on the “discovering all database models” problem, though!

    Zellyn Hunter - 19th May 2009 05:25 - #

  11. Services? Pluggability (by purpose, interface) and a stack (registry) to draw on? This sounds familiar; actually, it sounds like you need a component architecture.

    Sean Upton - 19th May 2009 09:12 - #

  12. James: completely agree with you, and that's why djng doesn't "just use WSGI" - the services concept is the side-channel you describe. I guess djng is "turtles all the way down" but with a side channel - the stack mechanism means that the side channel still relates to the turtles in that it is consistently pushed and popped as the chain of callables is executed.

    I've been mentally comparing the services stack to the way memory management works in Apache - create one or more memory pools during the request cycle, then free them up by the time the request cycle completes. That way you get the convenience of pseudo-global variables without the risk of state accidentally leaking from one request to the next.

    Simon Willison - 19th May 2009 09:20 - #

  13. If you are considering anything WSGI like in the internal stack in anyway, please considering using your effort to push changes to WSGI to make it easier to use. Some of the stuff suggested for WSGI 2.0 may make it easier, but if you have other suggestions then please bring them up. Unless some major initiative gives impetus to changing of WSGI, I'd say at this point that WSGI 2.0 is a dead duck and we will be stuck with WSGI 1.0 as is forever. :-(

    Graham Dumpleton - 19th May 2009 09:26 - #

  14. From what I remember from my past with using Paste, paste.registry.StackedObjectProxy should be similar in purpose to werkzeug.LocalProxy (although it was far harder for me to grok at that time). If that assumption is right, you should take a look at that code, too, for more inspiration.

    Personally, I'm still not convinced of Django itself (and, as apposed to you, I like neither the default templating nor the custom ORM, so the services idea sounds very appealing), but I think your request-in-response-out and somehow-everything-is-a-callable ideas head in the right direction (and are similar to what I do in my apps by now) and continue to make me feel much better about Django.

    Keep up the good will and work regarding that.

    Jochen Kupperschmidt - 19th May 2009 09:35 - #

  15. Graham: I don't think fixing WSGI is in scope for this project, I'm afraid. The core abstraction in djng is still the Django request/response objects - I'd like to make them interchangeable with WSGI at various points, but I don't expect that part to be much more complicated than the existing WSGIHandler adapter and something based on the django_to_wsgi module: http://github.com/rfk/django/blob/f96eb6068552e228 f705beda9c8181db2079a797/django/wsgi/wsgi_to_djang o.py

    Simon Willison - 19th May 2009 11:47 - #

  16. Services sound great. Django would have access to different services at different times. You could mash around with caching or db components on your live application.

    Julian - 19th May 2009 11:59 - #

  17. Much like my continued passive nodding at said brainstorming idea, I do like this idea; as I code more and more python, it has the tendency to separate into neat components that communicate using queues, and while I can see a few potential problems in getting reasonable service interfaces for some things - say, the ORM (specifically other apps' models, etc) - it does open the door to some cool things (drop-in versioned ORM? I can dream.)

    Andrew Godwin - 19th May 2009 13:00 - #

  18. So Ian Bicking is right about everything. That's what I'm hearing.

    Unfortunately it's proven wrong by the fact that he's moving to Minneapolis.

    Brantley Harris - 19th May 2009 17:50 - #

  19. Very interesting.

    If djng came around first I would suppose something like Django would just be a "best practice" of djng.

    I wonder if this sort of micro architecture would allow for or encourage separation of layers so that ORMs or Templating languages could be swapped out easier

    (disclaimer: I'm new to django)

    Eric Frederich - 19th May 2009 23:26 - #

  20. Agree, as far as the need to improve/replace settings.py. Your example of wanting request-dependent TEMPLATE_DIRS is one requirement i've come up against before, without finding a nice solution.

    Dan R - 14th June 2009 21:02 - #

  21. >Microframeworks let you build an entire web application in a single file, usually with only one import statement.
    >They are becoming increasingly popular for building small, self-contained applications that perform only one task

    PHP seems to me exactly the microframework that you are trying do build in python, if you want a very simple/flexible model to follow, look closely to it.

    in my mind, sinatra DSL would be great as a format to configure Apache

    devsmt - 15th June 2009 12:17 - #

  22. I don't see reason on your use cases for changing settings at runtime.

    Some are just plain wrong (why changing template dirs at runtime? just point to a different root template for mobile devices). Others, don't need a setting change at all (different servers for registered users? just subclass your mailer and make it use other servers. simple).

    You can freely subclass things from Django, no need to use everything as-is and hacking settings at runtime to make it adapt to your needs. That's plain ugly.

    Henrique - 11th July 2009 01:19 - #

  23. An App Engine JSONP web service for retrieving the time in different timezones and a proxy that adds jQuery-powered annotations to the proxied site, configured by a CSS selector.

    çelik konstrüksiyon - 15th July 2009 12:02 - #

  24. One of the problems I see you running into here is that you're doing 2 things at once. 1) is a micro framework, which I am totally non-convinced about (is there any project you spend more than a day on for which a microframework is useful?), and 2) the services/turtles ideas, which I think are interesting, and probably useful (even if I'm not totally convinced :) ).

    Bedava film izle - 21st December 2009 15:32 - #

Sign in with OpenID

Auto-HTML: Line breaks are preserved; URLs will be converted in to links.

Manual XHTML: Enter your own, valid XHTML. Allowed tags are a, p, blockquote, ul, ol, li, dl, dt, dd, em, strong, dfn, code, q, samp, kbd, var, cite, abbr, acronym, sub, sup, br, pre

A django site