Okay, for starters, I don't think I've ever written anything so trollish before. Apologies in advance.

I had heard the criticism that Django had too much "magic" before I started working with Pylons. I thought to myself, "I have no idea how the ORM works," and I agreed. I thought to myself, "I have no idea how the request dispatch works," and I agreed.

So then I started working with Pylons. I learned two more things:

  1. I have no idea how ORMappers work, period.
  2. I have no idea how request dispatch works, period.

The problem wasn't that Django was magical; the problem was that I just didn't have the expertise and experience.

Anyway, after working heavily with Pylons for a few months, I got the opportunity to build a Django app for someone, and it was a breath of fresh air. I love the Pythonic way things work in Django, and the way I can express my model so well, the way I can put my program logic into views and model methods in pure Python code, without worrying about implementation details.

But all that is just icing on the cake. What I enjoyed most was how little magic there was.

Magic: import *

Explicit is better than implicit. By and large, Python is explicit. There are a couple of exceptions, and one of them are * imports. * imports should be avoided, and in public interfaces that other programmers have to use - like, say, a Pylons controller - are downright Considered Harmful.

Yet what is at the top of every Pylons controller? Take a guess.

  1. from projectname.lib.base import *

A little promiscuous with the global variables, no? Jeez, have some decency, cover up your namespace. How about...

  1. from django.http import HttpResponse, HttpResponseRedirect

Aaah. And then I can render a string with whatever templating engine I want, and write it to your response, because it's a file-like object. Thank goodness for the Pythonic duck typing!

  1. def index(request):
  2. response = HttpResponse()
  3. rendered_template = mako_render("filename.mako")
  4. response.write(rendered_template)
  5. return response

Heck, I could have guessed that.

Sadistic Magic

But hey, maybe I'm just being lazy. What I really want is to go look in lib/base.py to see exactly what's in __all__, right? Sounds good to me. "Surely", I think to myself, "that'll clear everything up for me; I can't judge Pylons too quickly."

  1. ## inside lib/base.py:
  2. __all__ = [__name for __name in locals().keys() if not __name.startswith('_') \
  3. or __name == '_']

My eyes! The Goggles, they do nothing!

To say the least, I don't understand the "Django magic" complaints anymore. * imports are magical. Dynamically generating __all___ is magic, not to mention sadistic and a crime against humanity. But mapping a url to match a regexp? Not magic. A model description language that doesn't require knowledge of SQL? Impressive, but not magic.

Sadistic, Useless Magic

What frustrates me the most about the incredible amounts of magic in Pylons is how little the programmer gains from it. The Pylons programmer can use the 'c' global variables to include context in a template - nevermind that this can be done much more intuitively by passing context as an argument to a render function, which also makes it trivial to dynamically generate said context. The Pylons programmer can use 'h' global object to do things that your models should really have as methods anyway. The Pylons programmer can use the 'g' global variable to - well, actually, no one knows.

The Pylons programmer can use the shortsightedly named1 'session' global variable to access the user's session. Gee, where else could session data be put, if not in a global variable? Well, hmm, maybe in an attribute of the request object. Nah, we'll use a global variable.

And if you actually want to access the request object? Well, instead of passing it to the function handling the request (intuitive) or maybe as an attribute of the controller object (OOPy), lets make it a global variable. Without a .session attribute, of course, because that's gonna be another global variable.

What Loose Coupling Is Not

Why not just have the session be an attribute of the request object? For one, it would couple the request to the session middleware! Oh, the horror - users wouldn't be able to transparently swap out Beaker for... that other session component people use with Pylons!

Pylons, as a framework, seems to be confused about what loose coupling is. Loose coupling between layers is not creating a new global variable for each layer.

What Loose Coupling Is

So what is loose coupling? Loose coupling is using native data structures, conventions, and idioms. Once you do that, programmers can predict how to swap out whatever they want.

Quick example: let's suppose we have controllers that are normal functions instead of methods of a specially-derived base class. Now let's suppose you want to do something like require authentication for a view. How would you easily apply some sort of authentication-wrapping to a function?

Easy. A normal, Python decorator.

And if you're using Pylons? Well, you have to look through the dismal documentation2 until, by chance, you find the Authkit Cookbook 3 which mentions the __befor\__ function. Funny, I don't remember seeing __before__ anywhere else in Python. And it's applied via some process not visible to the programmer... it's like magic!

Let's compare code for a second:

  1. @auth_required
  2. def index(request):
  3. # Actual controller body, auth_required applied as you'd expect.

Versus:4

  1. class BrittleNamingController(BaseController):
  2. def __before__(self):
  3. # auth_required code
  4. def index(self):
  5. # __before__ magically applied.

I hope I'm not the only one who sees how simplicity and loose coupling are one and the same. To change template rendering with Django, all you have to do is change:

  1. from django.template import Template
  2. def index(request)
  3. t = Template("String to {{ render }}")
  4. return HttpResponse(t.render())

to:

  1. from alternate_renderer import render
  2. def index(request):
  3. s = render("String to ${ render }")
  4. return HttpResponse(s)

There are no config files to edit, no eggs to install. You just do it, because you can predict how to do it.

  • Loose coupling is using native Python data structures

  • Loose coupling is giving us a file-like object as a request, not a global variable that is manipulated by a class with magical, arbitrary, undocumented methods.

Verdict

Django is by no means perfect, but they are so much closer than Pylons. You know, even if Django did have NIH, they "Invented" very useful components, so, Pylons devs, Steal from Django, PLEASE!

Okay, I think I got it out of my system. Except now I'm going to go work with Pylons for a few more hours. It's okay. At least I have my Django-powered, pinnacle-of-simplicity blog to post on.


  1. Why badly named? Because SQLAlchemy uses session objects a lot as well. Perhaps, if Pylons were dimly aware of the fact that web applications use databases, it'd be more careful with it's naming. But no! We must loosely couple the layers, by giving each layer a global variable! Yeah, that sounds good. 

  2. I've seen some people online actually saying that the Pylons documentation was good. No one contests that the Django documentation is better. Listen: if you think the Pylons documentation is "good, just not as good as Django's," you're given them orders of magnitude too much credit. The documentation is, in many cases, worse than useless, as we'll see with AuthKit in just a second. 

  3. At this point in time, the Authkit Cookbook openly admits that it's out of date with the actual, public AuthKit module. And, of course, it's been out of date since this April, which is the last time it was touched. Brilliant! What do we do in the meantime? 

  4. Am I the only one who's scared that Pylons needs your class to be named (controllername)Controller? What's going on the background to predict the controller name? You know what's magic is how Routes is able to refer the request to the right controller, by going from strings to class instances. That doesn't scare anyone? With Django URLs, you can actually pass objects, not just strings, to the url mappers, which means the name of it in the module doesn't matter - just give it a variable pointing to the right view. That's how actual Python works. A Brittle-y named class name, on the other hand, positively reeks of implementation details.