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