Chapter 2. Sulfur Plug-Ins

Table of Contents
Overview
Plug-In Objects
Plug-In Development

A plug-in is a self-contained software part, designed to be interchangeable with others of its kind at runtime. Plug-ins are used to make an application flexible, by allowing functionality to be added later without changes to the core of the program. For example, many graphics applications use plug-ins to implement the multitude of filters and rendering tools designers use, because new plug-ins can be added to the same core application at any time.

The Sulfur plug-in architecture is an extension to the usual Python package/module/object model, adding a layer of metadata and standard behavior useful in managing plug-ins. It is certainly possible for a Python program to have plug-in-like behavior using nothing more than standard Python programming techniques, but using Sulfur makes a plug-in oriented style of programming more convenient.

Overview

At its core, a Sulfur plug-in is simply an ordinary Python object, with certain common attributes and methods inherited from the Sulfur.Plugin class. However, the term "plug-in" can also be applied to classes and modules; hopefully the following explanation will make clear how plug-ins actually work.

Plug-In Collections

Plug-ins are organized into collections, based on what they do. For example, Pyrite has collections called App (for plug-ins that support specific palmtop applications), Conduit (for plug-ins used during the synchronization process), and so on. All of the plug-ins in a collection are expected to be interchangeable, so that any program using one plug-in in the collection can use another. (This does not mean that individual plug-ins can't have behavior which others in the collection do not share, only that there is some common interface shared by all plug-ins in the collection.)

A plug-in collection is similar to a standard Python package. In fact, in the current implementation of Sulfur, a collection is a standard Python package... almost. The Sulfur plug-in loader allows collections to span multiple packages, so that Sulfur applications can draw related plug-ins from several places in the Python package hierarchy.

The name of a collection is simply the name of the package containing it. For example, if a package named Foo contains plug-ins, your program can use the collection name Foo to refer to it. If there are multiple packages with the same name on the Sulfur plug-in search path (which will be described later, in Chapter 4), then they will all be combined into a single plug-in collection; that is, when your program requests a certain plug-in, Sulfur will search all of those packages until it is found.

Plug-In Loading

A Sulfur plug-in is just an ordinary Python object, which means that it must have a class definition somewhere. That class definition is found inside a module, which is found inside a standard Python package (as described above). When your application requests a plug-in, Sulfur searches in the specified collection for a module with the name you requested.

Important: This has nothing at all to do with the plug-in's name attribute, or with the name of its class. The only thing that matters is the name of the module.

After locating and importing the specified module, Sulfur searches inside it for a class that defines a plug-in, and uses the first one (and only the first one) that it finds. It then instantiates an object of that class, which is the actual plug-in that will be returned to your application.

For example, let's say your application asks for the Backup plug-in from the Conduit collection. Sulfur will look in each location specified by the plug-in search path, and if there is a package called Conduit there, it will attempt to import a module called Backup from that package. After importing that module, it will look for a class object that defines a plug-in (i.e. is derived from the Sulfur.Plugin class), create an object of that class, and return it.

Options and Properties

An option is any value which is meant to be configurable by the user, either permanently or at runtime. (For example, a plug-in might have an option called 'debug', which controls whether it prints extra output as it runs.) Sulfur provides a common interface to options; it will be described in more detail later, in INSERT REFERENCE HERE.

Each option has associated with it a name as well as a type, description, and other information. Options may be validated, so that your application cannot set them to a value outside a particular range or set of values. At runtime, the current values of a conduit's options are accessible using the has_option, set_option, and get_option functions. Also, as a convenience, options are readable (only) as if they were attributes of the plug-in object, provided of course that their names are valid Python identifiers.

Each plug-in also has a set of properties, which can be used by the developer to distinguish between small differences in plug-ins in the same collection. For example, a collection of format-conversion plug-ins might have properties called 'read', 'write', 'compress', and others describing the various operations each plug-in might be capable of doing. The set of potentially valid properties is application-dependent.