Using Declarative Syntax, Part 2

by Marty Alchin on November 11, 2007 about Django

Today, continuing the series, get ready for some code. The first thing we’ll need to do is lay out some files to work with.

I previously described the syntax in three separate parts:

  1. A single module namespace to import
  2. A base class to specify in subclases
  3. A set of attribute classes to instantiate

Thankfully, those can map very nicely to three different files. These files will all be placed in a widgets directory somewhere on the PYTHONPATH, representing the entire framework.

The first thing to do is set up a base class to extend. This may look fairly complicated, and it will encompass the rest of this post, but it’s really not that bad. The real trick here is that we’ll need to use a metaclass. A full explanation of metaclasses is being the scope of this article, and maybe someday I’ll do it justice, but for now, just know that it’s a necessary part of the process. The best way to sum up what you’re missing is with a quote from Tim Peters:

Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why).

What it basically boils down to is that we need to set up two different classes just to make one work properly. First, we’ll start with the metaclass, which we’ll call WidgetBase, keeping with Django’s naming scheme. The following code is pretty much copied out of Django itself, changing Model to Widget:

class WidgetBase(type):
    def __new__(cls, name, bases, attrs):
        # If this isn't a subclass of Widget, don't do anything special.
        try:
            if not filter(lambda b: issubclass(b, Widget), bases):
                return super(WidgetBase, cls).__new__(cls, name, bases, attrs)
        except NameError:
            # 'Widget' isn't defined yet, meaning we're looking at our own
            # Widget class, defined below.
            return super(WidgetBase, cls).__new__(cls, name, bases, attrs)

There’s a lot going on here, and I apologize for not explaining it more fully, but here are the important bits. This class will be called any time a Widget class definition is processed, including Widget itself. The code above makes sure that Widget and any classes that don’t subclass Widget just get processed like any other class. Any additional code in the __new__ method can then safely assume that it’s processing a subclass of Widget, which is exactly what we want. For now, we’ll just return the the new class, as it was defined, with no additional processing:

        return type.__new__(cls, name, bases, attrs)

When working with the rest of the metaclass code, it’s important to know what arguments you’re dealing with. As you can see from the code above, there are four arguments passed to __new__:

Now, to create the foundation of the base Widget class, we’ll create just a basic class and tell it to use WidgetBase as its metaclass:

class Widget(object):
    __metaclass__ = WidgetBase

While this doesn’t yet do anything special, it gets some of the most compliated stuff out of the way, leaving us free to explore more useful code in future articles.