Extending ccPublisher
ccPublisher 2 has been designed for extensibility and customization by 3rd party developers. This document covers extending ccPublisher 2 and other P6-based applications through the use of extensions.
Much of this page is currently theoretical; most of the infrastructure is in place, but until ccPublisher 2 ships, things may be in flux.
Contents
Overview
ccPublisher 2 is built on a framework dubbed P6. P6 is a loosely coupled system written in Python where software components communicate with one another via message passing. The P6 extension model allows applications to declare ways in which they may be extended, and allows 3rd party developers to provide additional functionality to the applications. Extensions have the following lifecycle: registration, event response, clean up. This document provides instructions on creating basic extensions, as well as details about common extension patterns. All instructions assume a Linux-based development environment.
Creating An Extension
Extensions are developed as Python module or packages, and a piece of ZCML (a "slug") which the P6 framework uses to complete the registration process. We are currently working on finalizing the installation location for extensions. When that is finalized, we will provide a script or template for creating a distribution package for extensions. The final distribution model will likely involve packaging extensions as Python Eggs.
To begin creating an extension, first check out a ccPublisher sandbox. Create a subdirectory in the checkout for your extension.
Registration
Extension registration and configuration is done using a ZCML slug.
A sample registration ZCML would look something like this:
<configure xmlns="http://namespaces.zope.org/zope" xmlns:i18n="http://namespaces.zope.org/i18n" i18n_domain="org_cc_p6">
<!-- P6 Sample Extension Configuration (c) 2005-2006, Nathan R. Yergler, Creative Commons licensed under the GNU GPL 2 -->
<extension id="org.cc.p6.sample.blog_ping" name="Blog Ping Example" config_factory="blogping.config" > <subscriber for="p6.storage.events.IStored" handler="blogping.handlers.afterStored" /> </extension> </configure>
In this example we first declare a <configure>
object, which all of our configuration will take place within. The i18n_domain attribute will be used for transparent internationalization.
The remainder of the configuration takes place within an <extension>
element. The extension element defines an id and name (both required), and an optional config_factory. The value of config_factory is a Python callable which will display the extensions configuration UI when called. Note that Python must be able to import the package path (the value, less the final element); in this case, Python will attempt to import the blogping
module. Note that this implies that the code must be on the application's Python path. P6 places all subdirectories of the extensions
directory into the Python before loading extensions. See Installing Extensions for details on platform-specific extension locations.
Finally, we define a <subscriber>
which registers a callable (blogping.handlers.afterStored) which will be called after all storage providers have processed the submission. This callable should be a standard Zope3 event-handler; that is, it will be passed a single parameter. That parameter will be an event object which implements p6.storage.events.IStored
.
Responding to Events
Contributing to the User Interface
Extension Patterns
Metadata Providers
The Metadata Provider pattern is used to extract additional metadata from files or retrieve it from outside sources. For example, ccPublisher uses this pattern to extract the title and artist from an MP3 using ID3 tags. There are two steps to implementing this pattern: listening for ItemSelected events and, if appropriate, publishing UpdateMetadata events.
Because a particular P6 application will have a specific metadata grouping, we use canonical URIs to specify fields outside of the group-field structure. For example, ccPublisher uses the following declaration in app.zcml to declare the Work Title field:
<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" />
The canonical
property specifies the URI to use, regardless of grouping. In general, metadata providers included with P6 will map attributes to Dublin Core elements whenever possible. See Canonical IDs for more information.
To examine the details of a metadata provider we'll examine some relevant bits of the ID3 provider included with P6. The source for the provider is located at p6/storage/providers/id3.py. The provider function is registered in configure.zcml
as a subscriber:
<!-- Metadata provider registrations --> <subscriber for="p6.storage.events.IItemSelected" handler=".providers.id3.itemSelected" />
Listening for the IItemSelected events will notify the provider function, id3.itemSelected in this case, when the user selects a file. The actual provider function has the following signature:
def itemSelected(event): """IItemSelected event subscriber which provides ID3 metadata for MP3 files."""
The value passed in for event will be an object implementing IItemSelected. The ID3 provider then checks to make sure we're dealing with an actual file (as opposed to a stream, URI, etc) since that's what it knows how to handle:
# make sure a FileItem was selected if (p6.storage.interfaces.IFileItem in zope.interface.implementedBy(event.item.__class__)):
The IFileItem interface states that the .getIdentifier()
method will return the filename. We use this to retrieve the ID3 information. Once we have the metadata extracted, we publish an UpdateMetadataEvent for each field we found:
updateEvent = p6.metadata.events.UpdateMetadataEvent( event.item, 'http://purl.org/dc/elements/1.1/title', id3.getTitle() ) zope.component.handle(updateEvent)
Note that we specify event.item
as the item we're updating metadata for, and that we use the canonical URI for the field.
Post-Upload activities
Storage Providers
Future Directions
The Extension API is relatively new and so still under development. See P6 Extension Ideas for possible extension ideas and API suggestions.'