Skinning with pyquery and deliverance

 

I'm using deliverance since a few month now to skin this blog and afpy.org. I also contribute to pyquery since i like the idea of manipulating xml in python like with jQuery. So the next step is to use a kind of pyquery rule in deliverance.

After looking at the deliverance code it seems that adding new rules is easy. You just need to register it in the deliverance rules. So here is the result:

from deliverance import rules
from pyquery import PyQuery as pq

class PyQuery(rules.AbstractAction):
    """PyQuery rule for deliverance"""
    name = 'pyquery'
    def __init__(self, source_location, callback=None):
        self.source_location = source_location
        self.callback = callback

    def apply(self, content_doc, theme_doc, resource_fetcher, log):
        """apply the rule"""
        self.callback(pq([content_doc]), pq([theme_doc]), resource_fetcher, log)

    @classmethod
    def from_xml(cls, tag, source_location):
        """Parses and instantiates the class from an element"""
        use = tag.attrib['use']
        modname, funcname = use.split(':')
        mod = __import__(modname, globals(), locals(), [''])
        callback = getattr(mod, funcname)
        return cls(source_location, callback)

# register the new rule
rules._actions['pyquery'] = PyQuery

Now i'm able to use my rule. I just need to import the above module before any deliverance one:

from myproject import pyquery_rule
from deliverance.middleware import DeliveranceMiddleware
# initialize your middleware

Then here is the rules:

<ruleset>
  <theme href="/theme.html" />
  <rule class="default">
      <pyquery use="myproject.rules:default" />
  </rule>
</ruleset>

Where myproject/rules.py look like this:

def default(content, theme, resource_fetcher, log):
    """rule used for testing"""
    content('title').text('My site - '+content('title').text())
    content('#footer').remove()
    theme('body').append(content('body'))

content and theme are pyquery objects. Feel free to do whatever you want with them. Enjoy !!

Notice that this work only with deliverance's trunk (or >=0.3).

Combine zc.buildout and pip benefits

 

I've just released a new zc.buildout recipe which allow to install packages with pip.

I am getting really excited about it because it has many advantages taken from both components.

  1. The recipe adds a virtualenv in the parts/ directory of your buildout then use this binary to generate executable python scripts. So you have a clean sandbox.
  2. The recipe is based on zc.recipe.egg#scripts so you can share your eggs between buildouts as usual.
  3. Of course, you can install some .pybundle files.
  4. You can build package from svn with the editables option.
  1. Each line found in the install option is the last part of a pip command. This allow you to build eggs with dependencies. For example to install lxml in a pure sandbox without libxml2 and libxslt installed you need Cython installed and this command line python setup.py install --static-deps to install lxml. This is easy with the recipe. Here is a sample configuration file for this case:
[buildout]
# the cache dir is used by buildout & pip
download-cache = download
parts = eggs

[eggs]
recipe = gp.recipe.pip

# eggs installed by pip (also add the Deliverance bundle)
install =
    Cython
    --install-option=--static-deps lxml==2.2alpha1
    http://deliverance.openplans.org/dist/Deliverance-snapshot-latest.pybundle

# eggs installed by zc.recipe.egg
eggs =
    Paste
    pyquery

That's all !! This works perfectly on my Mac OSX and should works on most system.

In fact zc.recipe.egg will work in most cases but when you need a clean sandbox and some extra options, gp.recipe.pip is a good alternative.

Get thumbnails the WSGI way

 

A few month ago I've created a middleware to generate and cache images thumbnails. I need this to evaluate the image size from the browser's window on the client side and then get the thumbnail at an url based on this size.

The result is iw.thumbs. The package provide a highly configurable middleware to serve images thumbnails.

The principle is to map an uri to a file system directory. Then if an uri match the specified regexp, a thumbnail is served by the middleware.

For example, with the default configuration, the url http://localhost/thumbs/100x100/image.png will render a thumbnail of /var/mapped_dir/image.png.

There is also a "size" uri parser to use named size and prevent potential DoS attacks.

That what i'm using here, for my blog. I have two size: blog (490x490) and large (750x750). Here the Paste configuration:

[app:thumbs]
use = egg:iw.thumbs
url_regexp = ^/(?P<size>%s)(?P<path>/.+)
url_parser = iw.thumbs.url:size_parser
image_dir = %(here)s/var/rst/images
cache_dir = %(here)s/data/thumbs
sizes =
    blog = 490x490
    large = 750x750

As you can see, the package also provide an application factory in case of you just want to math a specific prefix.

Then I just have to map it in my url mapper section:

[app:main]
use = egg:Paste#urlmap
/thumbs = thumbs
/ = pylons # my pylons application

Another middleware exist to generate thumbnail with a different approach. repoze.bitblt allow you to generate thumbnail from the size specified in your image tag. It also add a secure part to the image url to prevent DoS attacks. I think both package have their place.

What I like with iw.thumbs is that I only need to change a configuration parameter instead of all image tags if the width of my blog column change in the future. The other good point is that you can generate image tags on the client side wich seems not really possible with repoze.bitblt.

But well, both middleware are another good reason to use WSGI applications.

And as an example, here is Alain, the AFPy mascot render with the blog size. If you click on it, you'll get the large size in a popup.

/thumbs/blog/alain_at_rennes.jpg

Wonderful world of WSGI

 

This time it's really winter. When it's cold the only thing i like is geeking at home. So I decide to rewrite my website with a set of WSGI applications.

The first thing done was to initialize a Pylons project for my blog. I got something working after a few hours. Pylons really speed developments.

Then I need a skin. I decide to give a try to Deliverance. Skinning with Deliverance is quite easy. After reading the docs, I've create a layout based on my old html code and a set of rules. Add this to my configuration file:

[filter:deliverance]
use = egg:Deliverance
theme_uri = file:///%(here)s/themes/layout.html
rule_uri = file:///%(here)s/themes/rules.xml

That's it. My Pylons app looks good without any style sheet as you can see ;)

Another part of my website is my projects documentations which are generated by Sphinx and served by a static app. Those pages are now skinned with Deliverance and you can't really know that you browse another application.

I also use Trac to browse my svn repository so I've try to put it in my WSGI stack. Trac now support WSGI except that it do something strange with the output and/or stderr so you'd better not using an error middleware with it else you get a beautiful blank page. You can create a WSGI application with a few lines of code:

# -*- coding: utf-8 -*-
import os
from trac.web.main import dispatch_request
def make_trac(conf, trac_env):
    def application(environ, start_request):
        environ['trac.env_path'] = trac_env
        return dispatch_request(environ, start_request)
    return application

and skin it with Deliverance. Notice that in my case, Trac seems inserting different encoding in the same page so I need to hack it a bit.

The last thing done is to use a ProxyApp to serve and skin my buildbot. Of course, this part is also skinned with Deliverance.

I now have only one process to serve all my python apps so I can use repoze.who to manage authentication for all apps. I have a ldap server, so I've write a small plugin for it. This work perfect, and I'm now able to log in Pylons and Trac.

I'm using Deliverance as a WSGI middleware and it seems a bit different than when you use it as a proxy. I encounter a few bugs. I have to only use XPath to find nodes in the DOM. Other expressions just don't work. You also can't remove nodes. But, well, this seems promising.