Thu, 07 Dec 2006
Architecture Overview
PyBlosxom is implemented as a pipeline of plugins. The input to the pipeline is an HTTP request and the output is an HTTP response (most commonly containing HTML or RSS/Atom). There are abstractions to represent the HTTP request and response, the configuration information, the entries and other data stored in PyBlosxom.
In PyBlosxom, a plugin is a Python module. You define functions in the module, and PyBlosxom will call functions with a specific name at a well-defined point in the execution of the PyBlosxom pipeline. For example, a callback function named cb_start is called before most PyBlosxom processing begins. If your plugin module provides a function called cb_start, then it will be called.
PyBlosxom allows you to install many plugins, so a natural question is how do all those plugins interact. The py['load_plugins'] list serve two purposes. It tells PyBlosxom which plugins in the various plugin directories will actually get used. It also defines a sequential order in which plugins will be called. If you don't provide a value for py['load_plugins'] (you must still provide a value for py['plugin_dirs'] if you want to use any plugins), then PyBlosxom will load all the modules in all the plugin directories specified in py['plugin_dirs']. The plugins will be sorted by alphanumeric sorting on their names, and that will be the order in which they are called.
At a given callback point, every module function which provides a correctly named function will be called. So if you load five plugin modules and each module provides a cb_start function, then all five functions will be called when PyBlosxom reaches the start callback point. The order in which those five functions are called will be determined by the rules described in the previous paragraph. This allows multiple plugins to get access to data at the proper point during PyBlosxom's execution. It also allows plugins to exchange information if they know their relative ordering (it's best to use py['load_plugins'] in this case.)
For advanced usage, it is possible to change the way that chains of callback functions are processed. So instead of calling every callback function in the chain, it's possible to arrange for a particular callback chain to work differently. The cb_handle callback chain gives each callback function in the chain a chance to try processing data. If the callback is successful then none of the remaining callback functions in the chain will execute.
When a callback function is called it is passed a dictionary that contains the information in the HTTP request, the information from the configuration file, and any data that may exist in PyBlosxom at that point in time (usually a list of weblog entries). I'll be referring to this dictionary as the "args dictionary' in the rest of this document.
Here's a skeleton of a start callback function -- remember that all callbacks are prefixed with cb_.
def cb_start(args):
request = args['request'] # get the argument dictionary
config = request.getConfiguration() # config file information
http = request.getHttp() # HTTP header information
cgi_form = http['form'] # including any CGI form
data = request.getData() # the rest of the data
[23:19] |
[] |
#-permalink-#
Basic Pipeline
PyBlosxom as a pipleline of plugins
Now we're ready to look at the various stages of the PyBlosxom
pipeline. We're going to drill down through the various layers.
At the simplest level the execution of PyBlosxom is broken up into an
initialization section and a "main" section. We'll skip over the
initialization for the moment, since it is really only needed for
entryparser usage, which we'll cover later on.
(In the diagrams I've omitted the cb_ prefix on the names of all
the callbacks.)
The main section calls the cb_start callback, which is where you
would perform any global initialization in your plugin if you needed
that initialization to happen before any real work got done in the
pipeline.
Next PyBlosxom will call any cb_handle callbacks. If a
cb_handle callback is successful, PyBlosxom will jump to the end
of its processing, calling any cb_end callbacks before it returns
an HTTP response. (Note that the HTTP request is processed by the
cb_handle callback in this case). If the cb_handle callback
fails or there are no cb_handle callbacks registered, then
processing passes to the blosxom handler, which does its job and then
exits via cb_end.
The cb_handle callback provides a way to bypass the Blosxom
handler in case you need to do a different kind of processing.
Currently this is used to implement XML-RPC handling in PyBlosxom,
since XML-RPC handling consumes and produces completely different
input and output from regular PyBlosxom processing.
[23:18] |
[] |
#-permalink-#
Blosxom handling
The blosxom_handler is a combination of a series of setup callbacks
followed by a renderer that is actually responsible for producing the
output.
The first of the setup callbacks, cb_renderer, is used to
select a renderer to be used. At the moment, the core distribution
contains a a Blosxom compatible renderer and a debugging renderer.
People have expressed interest in building renderers based on other
templating engines, such as Cheetah.
Now we come to the first callback function that actually does
something with the HTTP request. The cb_pathinfo callback is
charged with looking at the information in the request and modifying
the data part of the plugin args dictionary with information about the
type of entry being requested:
- 'bl_type' - the type ('dir' or 'file')
- 'pi_bl' - the HTTP PATH_INFO header
- 'pi_yr' - the yar
- 'pi_mo' - the month, either as a 2 digit number or 3 letter string
- 'pi_da' - the day as a 2 digit number
- 'root_datadir' - the full path to the entry folder in the filesystem
- 'flavour - the flavour of output requested
After cb_pathinfo has identified the entry being requested (and
its type), the cb_filelist callback is used to generate a list of
entries. The entries will all be instances of
pyblosxom.entries.base.EntryBase, and will be stored in the data
dictionary under the key 'entry_list'. In the default PyBlosxom
configuration, these entries are actually instances of
pyblosxom.entries.fileentry.FileEntry that represent weblog
entries stored in the filesystem according to the Blosxom family
rules. In particular, instances of FileEntry have the mtime
(modified time) of the file for each entry, which is used to sort the
entries in reverse chronological order for a typical weblog
presentation. By providing your own cb_filelist callback, you
could provide entries using some other storage mechanism, at least
in theory. Currently PyBlosxom doesn't support storing entries in
other storage mechanisms, though it's something we are looking into
fixing in the future.
After the list of entries has been generated, it will be sent to
the renderer to be formatted for output. Before this happens, there
are two callbacks that can be used to alter that data that will be
rendered.
The cb_prepare callback is a general-purpose callback that should
be used to modify the data before the rendering phase. This can
include injecting or filtering out entries, or modifying the data in
specific entries. Another common thing to do in cb_prepare is to
create new variables that can be accessed from the renderer, in order
to provide some additional information. You can even use
cb_prepare to handle HTTP POST requests, which is what the
comments plugin does.
Just before rendering, the cb_logrequest callback is called. This
is mostly used to log information to a file. To that end, it expects
an entry in the argument dictionary whose key is filename. It
also expects an argument entry keyed by return_code. This is why
there is a separate callback (you ought to be able to do lots of
logging type functionality using cb_prepare). The internal
pathway through the PyBlosxom core is slightly different from that of
cb_prepare, in order to obtain the return_code for cb_logrequest.
[23:17] |
[] |
#-permalink-#
Rendering
By default, PyBlosxom uses a renderer that is based on the Blosxom
template language. The renderer allows a template writer to insert
variables into their weblog templates. At rendering time, the
renderer replaces the variables with their actual values -- that's the
meaning of 'render' in the context of PyBlosxom.
The Blosxoms combine several distinct templates in order to create a
page in a weblog:
- head - the content of this template is inserted before any entries
are rendered.
- date_head - the content of this templete in inserted at the start of
a new day (the output is sorted chronologically).
- story - the content of this template is inserted once for each entry
to be rendered.
- date_foot - the content of this templete in inserted at the end of
a day (the output is sorted chronologically).
- foot - the content of this template is appended after all entries
have been rendered.
The Blosxom renderer also has callback functions that you can
provide in order to override behavior. The callbacks are named after
the templates they are associated. They are all executed right before
the template is rendered. There is also a callback that isn't
associated with a template, cb_story_end that is called after
the store template is rendered. Also, these callbacks are passed
different arguments. They are passed a dictionary that contains all
the variables that the renderer will use for template rendering.
- cb_head - before the head template is rendered.
- cb_date_head - before the date_head template is rendered.
- cb_story - before the story template is rendered.
- cb_story_end - the story template is rendered.
- cb_date_foot - before the date_foot template is rendered.
- cb_foot - before the foot template is rendered.
[23:16] |
[] |
#-permalink-#
Entry Parsers
During PyBlosxom initialization there a call to the cb_entryparser
callback. The cb_entryparser callback returns a dict whose keys
are filename extensions and whose entries are functions uses to parse
entry files whose extension is the key. This allows the user to use
different formatting languages to write weblog posts. PyBlosxom
includes entryparsers that can deal with ReStructuredText and Textile
formats, as well as Python source code.
The blosxom_entry_parser (the default) parses entry files with
extensions of '.txt'. The first line of a file will be the title of
the entry, and the rest of the file will be the body of the entry.
The body will be substituted directly in to the blosxom templates, and
can contain HTML.
Entry parsers also support their own plugin mechanism. There are two
callbacks, cb_preformat and cb_postformat that can be called
before and after the entry parser has done whatever parsing it does.
cb_preformat and cb_postformat must return a dict that
contains entries for the 'title' and story keys. The dict can
have more entries that just these two. PyBlosxom includes
preformatters for allowing wiki words and replacing linebreaks with
<br /> or <p>.
[23:15] |
[] |
#-permalink-#
XML-RPC Subsystem
The XML-RPC subsystem is the last of the major PyBlosxom subsystems
that we need to cover. It is interesting because it is implemented
entirely via the plugin system - in fact it's contained entirely in the
contributed plugins distribution. What we want is a way for PyBlosxom
to handle requests made via XML-RPC. Initially, this is because we
want to support some of the popular XML-RPC based blog authoring
API's, such as the Blogger and Metaweblog API's. But we'd also like
to make it possible for plugins (like the pingback handler for the
comments plugin) to offer some of their services via XML-RPC, which
means that we cannot hardwire the set of functions that are processed
by the XML-RPC system.
The XML-RPC system consists of a PyBlosxom plugin that acts as a
dispatcher for all XML-RPC services. This plugin, named xmlrpc,
is wired in via PyBlosxom's cb_handle callback, since we want it
to process any HTTP request that contains an XML-RPC request. Recall
that cb_handle bypasses the regular blosxom_handler flow
through the PyBlosxom engine. xmlrpc also takes care of
authenticating XML-RPC requests. The plugin takes care of dispatching
XML-RPC requests by using a dispatch table stored in the plugin's args
dictionary. The table is a Python dictionary that is accessed via the
'methods' key.
Every plugin that wants to offer its services via XML-RPC needs to
provide a cb_xml_rpc_register function. This function must update
the 'methods' dictionary with entries the map the XML-RPC method
names to functions in the plugin's module. XML-RPC plugins may use
any of the other PyBlosxom callback methods as well.
[23:14] |
[] |
#-permalink-#