Skip to content.

plope

Personal tools
You are here: Home » Members » chrism's Home » Plugin-Izing an Application
 
 

Plugin-Izing an Application

I just tried to make an application pluggable as per André Roberge's recent specification using the Zope Component Architecture.

I just tried to create a plugin system as per André Roberge's specification using the Zope Component Architecture. The program which is being made pluggable is a program written by the effbot. I'm sure the effbot will be thrilled. ;-)

This is not exactly the kind of problem domain where you'd expect to need plugins. It's a parser and needs to operate very quickly, and most plugin architectures (including the ZCA) are based on indirections that aren't really optimized to be inside the inner loop of programs that require very high speed. But that said, I suppose it's as good an application as any to introduce plugins into as a demonstration, as long as you're not judging on speed difference between the pluggable version and the original. I didn't measure speed, as I'd never use the component architecture "for real" in this particular program.

After completing pluginizing the application, a few things strike me. First of all, in order to get the ZCA to parse ZCML, it needs a lot of dependencies. We knew this, of course, but it's pretty striking exactly how many are required when you're dealing with this simple of a problem. Here they are:

  pytz-2008i-py2.4.egg
  zope.component-3.4.0-py2.4.egg
  zope.configuration-3.4.0-py2.4.egg
  zope.deferredimport-3.4.0-py2.4.egg
  zope.deprecation-3.4.0-py2.4.egg
  zope.event-3.4.0-py2.4.egg
  zope.exceptions-3.5.2-py2.4.egg
  zope.i18n-3.6.0-py2.4.egg
  zope.i18nmessageid-3.4.3-py2.4-macosx-10.5-i386.egg
  zope.interface-3.4.1-py2.4-macosx-10.5-i386.egg
  zope.location-3.4.0-py2.4.egg
  zope.proxy-3.4.1-py2.4-macosx-10.5-i386.egg
  zope.proxy-3.4.2-py2.4-macosx-10.5-i386.egg
  zope.publisher-3.5.4-py2.4.egg
  zope.schema-3.4.0-py2.4.egg
  zope.security-3.5.2-py2.4-macosx-10.5-i386.egg
  zope.testing-3.5.1-py2.4.egg
  zope.traversing-3.5.0a4-py2.4.egg

That's just absurd. The publisher? zope.security? zope.location? We really need to detangle these dependencies this at some point to make it reasonable for very small applications to use zope.configuration (ZCML). Most of these dependencies are actually dependencies of ZCML (zope.configuration), rather than the ZCA "proper" (zope.component).

In any case, on to the actual plugin-ization. To no one's surprise, the resulting plugin-ized version of the application is much more complex.

To actually plugin-ize the app, I made each operator into a named utility using the ZCA, configured via ZCML. The name of the utility is the operator itself. Each utility is registered via ZCML, ala:

  <utility
    provides=".interfaces.IOperator"
    component=".operators.operator_add_token"
    name="+"
  />

One utility is registered for each operator required. Accordingly, the application's tokenize() function now looks up each operator via a utility lookup:

  def tokenize(program):
      for number, operator in re.findall("\s*(?:(\d+)|(\*\*|.))", program):
          if number:
              yield literal_token(int(number))
          elif operator:
              utility = queryUtility(IOperator, name=operator)
              if utility is None:
                  raise SyntaxError("unknown operator: %r" % operator)
              yield utility()
          else:
              raise SyntaxError("unknown operator: %r" % operator)
      yield end_token()

When an operator is encountered, queryUtility is run; it will try to find a named utility (or it won't, and will raise a syntax error). The utilities themselves are classes. For example, the operator_add_token utility is defined as:

    class operator_add_token(object):
        """ plugin """
        lbp = 10
        def nud(self, context):
            return context.expression(100)

        def led(self, context, left):
            return left + context.expression(10)

Note that I changed the application to use a "context" object rather than module-scope globals to find the token and expression callable, so this definition isn't exactly like the one defined by the original application, but it still does the same thing.

In any case, all of the operators are defined in the same file (calc.operators). This is just for convenience; they could be defined all over hell and gone if you liked (you'd just change the ZCML to refer to a utility component at a different dotted name). And of course if you included more ZCML (which can cross files too), you'd could add another operator or override the implementation of an existing operator. I don't have very much imagination, so I did neither. You get the point, hopefully.

I suppose this is about the simplest possible example of using the Zope Component Architecture to create a pluggable application. You can also define your own ZCML directives (e.g. I could have made the ZCML read something like <registerOperator name="+" implementation=".operators.operator_add">. You also don't really need to use ZCML, it's just a shell around the actual component architecture that makes clear the difference between code and configuration.

The result of my toying around exists at http://plope.com/static/misc/calc-0.2.tar.gz . Run "setup.py install" (in a virtualenv, to prevent polluting your system Python with the above libraries) to install it. To run it subsequently, run "bin/calctest" (a setuptools console script).

Created by chrism
Last modified 2008-12-18 11:09 PM

And now, Grok!

I thought I could avoid many of the dependencies by using grokcore.component and avoiding ZCML. Funnily enough, I couldn't avoid ZCML, but the dependency list is still much shorter! I'm a bit surprised.

grokcore.component-1.5.1-py2.4.egg
martian-0.11-py2.4.egg
zope.component-3.5.1-py2.4.egg
zope.configuration-3.4.1-py2.4.egg
zope.deferredimport-3.4.0-py2.4.egg
zope.deprecation-3.4.0-py2.4.egg
zope.event-3.4.0-py2.4.egg
zope.i18nmessageid-3.4.3-py2.4-linux-i686.egg
zope.interface-3.5.0-py2.4-linux-i686.egg
zope.proxy-3.4.2-py2.4-linux-i686.egg
zope.schema-3.5.0a2-py2.4.egg
zope.testing-3.7.1-py2.4.egg

http://regebro.wordpress.com/2008/12/19/the-plugin-architecture-bashout-grok/