Quantcast
Viewing latest article 1
Browse Latest Browse All 3

A Python component architecture

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.


Image may be NSFW.
Clik here to view.
Image may be NSFW.
Clik here to view.
Image may be NSFW.
Clik here to view.
Image may be NSFW.
Clik here to view.

Viewing latest article 1
Browse Latest Browse All 3

Trending Articles