Skip to content

A Python component architecture

November 16, 2007

When building large frameworks you often want to make everything easily pluggable and extensible. Objects need to be able to interact with each other without needing to be told about each other beforehand. To create this kind of pluggability is not generally very hard, especially not in dynamic languages like Python, but if you have to create the framework for pluggability each time you want one, you are most likely not going to do it unless you really have to.

Therefore, it’s nice to have a standard way for how objects interact with each other and how they can know of each other that you can use every time you want it. In short, you want a component architecture. Luckily, there is one for Python already, that is well-written, well-proved and has been in use for years. And it’s called the Zope Component Architecture.

Oh, yes, I can hear you go “Euugh! Not Zope… That’s Zope specific, unpythonic, web only and full of XML. Ick!” But, you’d be wrong. The Zope Component Architectures only unpythonic webby part is that all the modules are called zope.something. And it’s called that because it’s written by Zope Corp, not because it’s web only or Zope only. And yes, Zope uses XML for it’s aspect oriented configuration language. You see, in aspect orientation you are supposed to have a separate configuration language to connect the components together, so Zope has one, ZCML. But the component architecture doesn’t require ZCML in any way. It’s totaly python-based (except for some parts written in c for speed, but they have alternative Python implementations as well).

Lets take a really stupid example of how to use the component architecture. Lets create a component that can do integer maths. Yes, that’s stupid, because there are already standard python ways of doing that, but it’s the best idea I had. First we define up how the math component should look, it’s interface:

from zope import interface, component
class IIntegerMaths(interface.Interface):

    def add(a, b):
        """Adds two integers"""

    def subtract(a, b):
        """Subtracts two integers"""

    def multiply(a, b):
        """Multiplies two integers"""

    def divide(a, b):
        """Divides two integers"""

It’s a convention to call interfaces ISomething, so that’s why it’s called IIntegerMaths. It’s not a missspelling. ;-)
Now when we have an interface, we create a component that implements this interface, and instantiate it:

class Calculator:

    interface.implements(IIntegerMaths)

    def add(self, a, b):
        return a + b

    def subtract(self, a, b):
        return a - b

    def multiply(self, a, b):
        return a * b

    def divide(self, a, b):
        return a//b

maths = Calculator()

We can now divide with the calculator directly by just doing maths.divide(23, 5), but that’s not very componentish. No, to use the component architecture, we should register the component:

component.provideUtility(Calculator())

Now anybody who needs the math functions, can look it up and use it:

maths = component.getUtility(IIntegerMaths)
print "Division:", maths.divide(23, 5)

Note that you don’t have to know about the component. Just about the interface. This means that anybody can implement the component and you can use it. If course, when it comes to something as simple as adding and dividing there is no use for that, but you can for example create an interface for components that talk to SQL-databases, and then plug in one utility per type of SQL-database. You can have any amount of components all implementing the same interface if you just register them with different names, and then you can select in your applications configuration which of the registered components to actually use. You get pluggable components, with total ease.

Also, the ZCA has provisions for extending other components. Lets say that we also want to be able to do modulo of the integers. We define up how a component that would calculate the modulo should look. We could let it extend the previous IIntegerMaths interface, by just subclassing IIntegerMaths, but that would mean that to get Modulo functionality, we need to sublass every implementation of IIntegerMaths. But since since modulo can be calculated by using division, we can implement it as an adapter. First the new interface for modulo maths:

class IModuloMaths(interface.Interface):

    def modulo(a, b):
        "returns the modulo"

Create an adapter implementation and register it:

class DividerModuloAdapter:

    interface.implements(IModuloMaths)
    component.adapts(IIntegerMaths)

    def __init__(self, context):
        self.context = context

    def modulo(self, a , b):
        return a - (b*self.context.divide(a, b))

component.provideAdapter(DividerModuloAdapter)

Note that we this time didn’t instantiate the class, instead adapters get instantiated when you adapt something.

Now we can as usual look up my IIntegerMaths component:

maths = component.getUtility(IIntegerMaths)

and now I can also adapt it to a IModuloMaths component and use it:

mod = IModuloMaths(maths)
print "Modulo:", mod.modulo(23, 5)

Note that it will work with any IIntegerMath component. All that’s needed is that it can divide. This way I extend ALL IIntegerMath components at the same time. I don’t need to extend every IIntegerMath implementation, instead with the adaptation I give any implementation that exists the module functionality.

It really is that simple? If you don’t believe me, do easy_install zope.component, and copy all the above code into a file, and run it with python. It really works. All you need to get the coolest component architecture on the planet is to do from zope import interface, component, create an interface for your plugins and start looking them up with getUtility.

Now, there are of course many more things and functionalities and various neat things you can do with the component architecture. There is a new ZCA documentation project started by Baiju Muthukadan, his ZCA Book should give you more details.

About these ads

From → python, zope, zope3

9 Comments
  1. Hi Lennart,

    a nice and important introduction to the ZCA. I believe the ZCA is one of zope’s strong points and at the same time one of its ‘bits’ that are immensely useful on its own (i.e. outside a ‘webby’ context) and we need to start getting the word out.

    One thing irked me, though, when reading your example. It has been custom from the early days to begin interface names with a capital `I`, shouldn’t we keep this convention, when introducing the ZCA ‘into the wild’? (i.e. `IModuloMaths` and `I IntegerMaths `).

    Just a thought,

    Tom

  2. Lennart Regebro permalink

    I didn’t do that because I didn’t want to confuse people. On the other hand, you are right that we shouldn’t teach people bad things… I’ll change it.

  3. Thanks for this introduction. I think this is useful and plan to share this with colleagues getting started with using zope components in Python applications.

    One question: DividerModuloAdapter doesn’t implement IIntegerMaths (its context does), but IModuloMaths subclasses IIntegerMaths. Would it make more sense to just have IModuloMaths subclass zope.interface.Interface instead (since DividerModuloAdapter doesn’t proxy add, subtract, multiply, divide to self.context)?

  4. Lennart Regebro permalink

    Yes, you are right. I feel victim to my own refactoring. :-)

  5. A very clear introduction, thanks Lennart.

  6. At the very bottom of your post you give an challenge to do:

    I get this:
    In [1]: from zope import interfaces, component
    —————————————————————————
    ImportError Traceback (most recent call last)

    /home/ril3y/ in ()

    ImportError: cannot import name interfaces

    Did you mean interface? If I use interface it works.

    Ril3y

Trackbacks & Pingbacks

  1. Would you go to a Zope conference? « Lennart Regebro: Plone consulting
  2. The plugin architecture bashout: Grok! « Lennart Regebro: Plone consulting

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 1,339 other followers

%d bloggers like this: