9th
TiddlyWeb Plugin Tutorial Part 1
Updated 20130122 Latest version is on TiddySpace
Update 20100831: The version of this tutorial kept on the TiddlyWeb documentation site is now the most up to date.
Update 20100405: Yet another version of this document is now being kept in the TiddlyWeb documentation site. See: PluginTutorial.
Update 20091113: This document is fairly popular according to google, but had some wrong information in it. I have updated it to reflect the current state of the art in command line TiddlyWeb shtuff.
Several days ago I was asked by some members of the Osmosoft crew to give a little demo or tutorial of creating a server-side plugin for TiddlyWeb. That went pretty well, resulting in some code. It’s since been suggested that writing something here explaining how it fits together would be useful. So here it is.
TiddlyWeb plugins are, at their most basic, Python modules which are imported into the TiddlyWeb process when it starts up. They are imported when the web server starts or the twanager command line tool is used. A TiddlyWeb instance is made (explicitly) aware of available plugins by having their names added to the tiddlywebconfig.py in the root of the instance. system_plugins are used by the web server, twanager_plugins by (as you might have guessed) twanager.
We’re going to create a plugin called jinx.py that starts out as a simple hello world, and incrementally adds support for a variety of features.
Please follow along at home if you are inclined.
Install TiddlyWeb
The first thing you need to do is install TiddlyWeb or make sure you have the latest version (at the time of this writing the latest version is 0.9.12). Make sure you have Python 2.4, 2.5 or 2.6. Get Python’s setuptools (for easy_install, your OS may already have it, or have a package from which you can install it) and then:
sudo easy_install -U tiddlywebwiki
This should install TiddlyWeb and all the extra modules that it needs. If you are on a legacy operating system, you may not have sudo, in which case you need to be able to write to your disk in whatever way is required for that. If you do not want to install TiddlyWeb in its default location, you have several options which you can explore for yourself. Tools like Python’s virtualenv and pip may be useful.
Create an Instance
TiddlyWeb comes packaged with its own web server, so you can run it without needing to configure Apache or something similar. It will work other servers, but it doesn’t have to. Any single TiddlyWeb data store and server combination is called an instance. Find a suitable location on your filesystem for an instance and create it with the twinstance tool:
twinstance jinx
This creates a directory named jinx, and puts within it a basic tiddlywebconfig.py file and a store directory. Change into the jinx directory, we’ll do the rest of our work from there:
cd jinx
Confirm the Server
Let’s confirm the server is working:
twanager server
This should start up a server running at http://0.0.0.0:8080/ . If there are errors it may be that using 0.0.0.0 does not work on your system or port 8080 is in use. Try instead:
twanager server 127.0.0.1 8888
which will start a server at http://127.0.0.1:8888/
Go to the URL using your browser, and see links to recipes and bags. Click around, explore. When you are convinced that things are working, type Ctrl-C in your terminal window to kill the server. Because the web server is multi-threaded it make take some time and multiple taps of Ctrl-C to get things to shut down.
Hello World
When a plugin is imported by TiddlyWeb the controlling code will call a function called init(config) in the plugin module. config is the current value of tiddlyweb.config, which has a bunch of useful information in it that your plugin may need or want. A plugin does not have to pay attention to config but it does need to define the method. Let’s create a file called jinx.py in the current directory (the TiddlyWeb instance directory). In that file put:
def init(config):
pass
Edit tiddlywebconfig.py so that it looks something like this:
config = {
'secret': 'bf88b86XXXXXXXXXX01952a1c6639140912f28b6',
'system_plugins': ['tiddlywebwiki', 'jinx'],
'debug_level': 'DEBUG',
'css_uri': 'http://peermore.com/tiddlyweb.css,
}
Setting debug_level to DEBUG will cause some verbose logging to tiddlyweb.log, which will help us know if we are doing things correctly. The css_uri setting makes the output from the server a bit more pretty than the default. If you don’t like me knowing that you are using that CSS file, just copy it to a location of your own. Start the server, stop the server, and look in tiddlyweb.log:
twanager server
Ctrl-C
cat tiddlyweb.log
Somewhere in there you should see:
DEBUG attempt to import system plugin jinx
Huzzah! You’ve created your first plugin! Hmmm, it seems to do nothing, let’s fix that.
A common plugin need is to add a new URL to the HTTP interface that TiddlyWeb presents. We’re going to add one at /jinx. Add to jinx.py so it now looks like this:
def jinx(environ, start_response):
start_response('200', [])
return ['hello world']
def init(config):
config['selector'].add('/jinx', GET=jinx)
If you start the server (twanager server) and go to http://0.0.0.0:8080/jinx you should see hello world as plain text. If not, review the steps up to here. It’s important to get this part right, or none of the rest of this will work.
There’s a lot going on in this small amount of code, so we’ll pause here to cover what’s going on.
TiddlyWeb is a WSGI application, or more correctly a collection of WSGI applications. WSGI is a specification for making portable and interoperable web tools. It defines a simple contract for how the tools get and return information that makes it possible to stack them up and achieve a great deal of flexibility while keeping code separated and concise.
Selector is a WSGI application used by TiddlyWeb to dispatch urls patterns to code. The line
config['selector'].add('/jinx', GET=jinx)
adds the /jinx URL to the existing dispatch rules, pointing to the method jinx when there is a GET request. Selector expects the things it dispatches to also be WSGI applications. A WSGI application is a callable (a function or class that can be called) with a specific signature. We see that signature in the definition of the jinx method:
def jinx(environ, start_response):
environ is a dictionary containing HTTP request headers, other request information and anything upstream WSGI applications have chosen to inject into the dictionary. start_response is how we set response codes and headers:
start_response('200', [])
A WSGI application must return a list or generator of strings:
return ['hello world']
So what we’ve done is pass the HTTP environ into the jinx function, done nothing with it, set a 200 response code but no headers, and returned hello world. Let’s make it so we are sending HTML instead. Change two lines:
start_response('200', [('Content-Type', 'text/html; charset=UTF-8')])
return ['<html><body><h1>hello world</h1></body></html>']
Start the server back up and have a look at http://0.0.0.0:8080/jinx.
