Building Extensible Desktop Applications

Nathan R. Yergler
Software Engineer,
Creative Commons

Agenda

  • ccPublisher
  • Zope 3
  • smarter software
  • dumber people

In the beginning...

There was ccPublisher

images/ccpublisher.png

and it was good.

Seeing ccPublisher was all alone...

"Yea, verily, I shalt take the code and make derivative works."

images/ourmedia.gif images/jamloader_logo.png

and it was a pain in the ass.

Problems

  • Customization is hard
  • People want features
    • not necessarily hard to implement
    • just not appropriate for the core

What We Wanted to Solve

  • uploading was easy...
  • ...customization should be easy

Zope 3

  • redesign of Zope 2
  • implements a "component architecture"
  • separates application server from support libraries

First Attempt

  • Minimal externals -- zope.interface
  • "Java-style" interfaces
  • Customize it? Subclass it!
  • and it was really crap

Current Effort

  • Everything is a component
  • Some interfaces you conform to
  • Only subclass to get functionality "for free"
  • Using the Zope 3 component model and event dispatch

Event & Component Model

  • Application functionality is broken into components
  • Components communicate via events
  • Which lets people be stupid

For example...

if field.persist:
    event = p6.metadata.events.LoadMetadataEvent(
               self.metagroup.appliesTo,
               self.metagroup, field,
            )
    zope.component.handle(event)

...and another

zope.component.handle(
  p6.metadata.events.UpdateMetadataEvent(
        self.metagroup.appliesTo,
        field,
        widget.GetValue()
        ))

Events (1)

class ILoadMetadataEvent(zope.interface.Interface):

   item = zope.interface.Attribute(
       "The item or interface the field applies to.")

   group = zope.interface.Attribute(
       "The metadata group this field belongs to.")
   field = zope.interface.Attribute(
       "The metadata field being updated.")

   value = zope.interface.Attribute(
       "The new value of the metadata field.")

Events (2)

class LoadMetadataEvent(object):
   zope.interface.implements(ILoadMetadataEvent)

   def __init__(self, item, group, field):
      ...

Configuration

  • components encourage cohesive code
  • but don't handle the extension use case
  • and we still need to instantiate everything

ZCML

  • ZCML is a markup language
  • uses configuration directives to trigger actions
  • Zope 3 uses it for registering adapters, etc
  • we use it for that, along with some custom directives

CC + ZCML

we declare

  • what backend(s) the application will use
  • what metadata fields we want to collect
  • how the user interface will be put together

any extension can add to any of these areas

Handler Registration

<subscriber
      for=".events.ILoadMetadataEvent"
      handler=".adapters.loadMetadata"
      />

Metadata Fields

<field id="title"
       label="Title of Work"
       type="p6.metadata.types.ITextField"
       validator="ccpublisher.validators.validateTitle"
       canonical="http://purl.org/dc/elements/1.1/title"
       />

Extensions

  • Configuration happens at run time
  • So we can add functionality at run time
  • Extensions use same model as core
  • Framework just knows where to look

Extension Preferences

...
<preferences
  id="blogping"
  label="Blog Integration" >

  <field id="xml_url"
         type="str"
         label="XML-RPC URL" />

  <field id="username"
         type="str"
         label="Username" />
...

Extension Registration

<subscriber
      for="p6.storage.events.IStored"
      handler=".blogping.AfterStored"
      />

Extension Implementation

def AfterStored(event):
   """Subscriber for WorkStored (IStored) events -- prompts the user
   to publish to their blog.
   """

   result = wx.MessageDialog(
       None, "Blog this submission?", "Blogping", wx.YES_NO).ShowModal()

   if result == wx.ID_YES:
      ...

Packaging

  • used distutils, py2exe, py2app for earlier release
  • unfortunately py2exe doesn't play well with ZCML
  • solution:
    • duplicate the ZCML
    • patch the ZCML loader

Future

  • eggs
  • use decorators instead of ZCML for some registrations
  • oh yeah, actual derivatives

Conclusion

  • Zope 3 components have allowed us
    • to clean up our own code
    • to create tasks that people can jump in and complete
    • to make derivative works easy, er, easier

Thank you

Questions?

http://yergler.net/yiki/PyCon2006