mod_deco
========
This is a system for mod_python which uses decorators to create a pipeline of
handlers. The inner function, which is registered as the PythonHandler in
.htaccess of some other Apache configuration file, usually reads from a file,
the outer handler writes to the response, and the ones in between transform
content, add stuff to the string, dict, tree, or whatever state the content is
in at that point, or perform checks and override the contents based on certain
criteria.
Because different handlers produce different results, it is important that
handlers are decorated in an order so that each decorator can handle the
data of the previous one. The first decorator in the list (the outer one) must
always write the response.
Example PythonHandler
---------------------
An example of a typical generic handler: the core function reads contents into
a string (for larger file downloads this is obviously not suitable), if it's
HTML the string will be converted to a tree and layout will be added to it
before it the tree gets converted back to a string and is sent to the client.
Other decorators in the chain check whether the requested file really exists
(presenting different content if not), checks for errors (presenting an error
page if there are any), and check whether the requested file is a directory
(returning the contents of the index document, or a file listing, instead of
reading the file). Other possible decorators could cache the contents, add
other elements, present alternative layouts or content, edit pages, etc.
The example::
import py
from decorators import *
from mimetypes import guess_type
@preparesite()
@responsewriter()
@treetostring()
@layout('/my/app/templates/macro.html')
@checknotfound()
@checkerrors()
@treetodict(body='//body/*', head='//head/*')
@htmltotree()
@directories('/my/app/templates/dirlist.html')
def handler(request):
"""read a file into a string and present it using decorators"""
fpath = py.path.local(request.filename)
ct = guess_type(fpath.basename)[0]
if ct is None:
return apache.DECLINED # not sure what to do with this
request.content_type = ct
return fpath.read()
As you can see most of the work is done in the decorators. Since the top-level
handler is written by you, it is easy to 'plug in' new ones.
Available decorators
--------------------
A list of available decorators. For the examples to run, you'll have to import
the decorators list::
>>> from mod_deco.decorators import *
Also we import some stuff especially for the tests, to replace things Apache
and mod_python would normally provide, and some additional modules::
>>> from mod_deco.dummy import FakeRequest, apache
>>> import mod_deco
>>> import os
>>> from lxml import etree
Currently the following decorators are available:
* ``responsewriter``
Write the response to request.write, expects the handler it decorates to
return a string with the complete response body. This should be the top
decorator for a pipeline (if you decide to use it).
Usage::
>>> @responsewriter()
... def handler(request):
... return 'foo'
...
>>> r = FakeRequest('/foo')
>>> handler(r) == apache.OK
True
>>> r.written()
'foo'
* ``treetostring``
Expects the decorated handler to return an etree.Element node and converts
it into a string.
Usage::
define a test handler
>>> @treetostring()
... def handler(request):
... from lxml import etree
... return etree.Element('foo')
...
it should return the etree.Element returned by the previous handler, but
converted to a string
>>> handler('no request here')
''
* ``layout(templatedir)``
Expects a dict from the handler, keys depending on the macro in
/macro.html, uses `Templess`_ to add the dict values to
the macro, returns an lxml.etree Element node (the rendered html).
Usage::
define a handler
>>> @layout('/mywebapp',
... ('%s/testdata/macro.html' %
... os.path.dirname(mod_deco.__file__,)))
... def handler(request):
... # html = ('foo'
... # 'Foo!
')
... # return etree.fromstring(html)
... title = etree.Element('title')
... title.text = 'foo'
... d = {
... 'head': title,
... 'body': 'body contents',
... 'sidebar': 'sidebar contents',
... }
... return d
call it
>>> request = FakeRequest('/foo')
>>> response = handler(request)
it should have returned a tree
>>> type(response)
with our contents
>>> response.xpath('//title')[0].text
'foo'
* ``addsidebar``
Expects a dict from the handler, returns the same dict with a new key,
'sidebar', added that contains a simple sidebar (list of etree.Element
items).
* ``checknotfound``
Checks if request.filename is found, returns a dict with keys 'head'
and 'body' if not, and the return value of the wrapped handler (which
is only called if the file is found!) if it is.
Usage::
write a handler
>>> @checknotfound()
... def handler(request):
... return 'foo'
if request.filename exists, the contents are returned
>>> r = FakeRequest(mod_deco.__file__)
>>> handler(r)
'foo'
if not, a dict with keys 'head' and 'body' is returned
>>> r = FakeRequest('/hope/this/doesnt/exist')
>>> ret = handler(r)
>>> ret['head'][0].text
'Not Found!'
* ``treetodict(**kwargs)``
Convert an lxml.etree to a dictionary, uses the keys of kwargs as the
keys of the return dict, the values to perform XPath queries against the
tree, the results of which are used as the values for the return dict.
* ``htmltotree``
Expects a string with HTML data, converts it to an etree.Element node.
* ``resttohtml``
Expects a string with ReStructured Text data, converts to HTML. This
caches data.
* ``thumbnail(size)``
Expects a string with image data, creates a thumbnail of that image. If
request.content_type doesn't start with 'image/', does nothing.
Expects a string with the image data, converts to thumbnail of which
width and height are maximum pixels. Does nothing if the handler
return value is not a string. This caches thumbnails in-memory.
.. _`Templess`: http://templess.johnnydebris.net