Posts by author crumble

Use Redis as a session backend

Recently I used Redis in one of our projects, while looking for documentation about it I stumbled upon this article about configuring Redis as a LRU cache. I decided to try and write a little session manager for Nagare using Redis as backend.

Requirements

Nagare Installation

Many Nagare mechanisms can be extended through the use of entry points. The Nagare entry point I used is nagare.sessions:

...
[nagare.sessions]
redis = nagare.sessions.redis:Sessions
...

nagare.sessions.redis:Sessions is the path to the Session manager implementation class.

Session Manager API

Redis Session manager has to inherit from nagare.sessions.common.Sessions

from nagare.sessions import common
class Sessions(common.Sessions):
    ...

The following methods must be implemented:

  • Save an application state to your backend:
def serialize(self, data):
    """Serialize application state."""
def _set(self, session_id, state_id, secure_id, use_same_state, session_data, state_data):
    """Store the serialized state and sessions state into our backend"""
  • Retrieve and restore application state from your backend
def deserialize(self, session_data, state_data):
    """Deserialize application serialized state and session"""
def _get(self, session_id, state_id, use_same_state):
    """Retrieve serialized state from our backend"""
  • Create and delete state
def _create(self, session_id, secure_id):
    """Create a new session in our backend"""
def _delete(self, session_id):
    """Delete a session"

The whole code, with handling of locks and other low level stuff can be viewed here.

Use Redis Session Manager

Now, to use the newly created session manager, you have to create a publisher configuration file. The sessions section is configured as this:

...
[sessions]
type = redis
...

Finally I manage to replace seamlessly the standard Nagare session managers by the Redis one I wrote. Hopefully the same technique can be used to try out any other session backend for Nagare.

Edit:

As pointed out by expo on Nagare mailing list version linked in this post does not work with Nagare latest version. The working version he submitted is available.

Project Internationalization with Nagare (message extraction)

Goals

Nagare doesn't have tight integration with gettext or Babel, here we're going to see a way to use those tools in a nagare project.

In the first part, we will integrate Babel into the project, in order to manage catalogs and extract messages. In the second part, we will set up effective message translation inside the project.

Requirements

We need a working Nagare Installation.

We will use Babel to extract the template file and manage our catalog files, so let's install it:

$easy_install Babel
...

Then, we will create and register the example application this way

$ nagare-admin create-app example_i18n
Application 'example_i18n' created.
...
$ cd example_i18n
$ python setup.py develop
...
Finished processing dependencies for example-i18n==0.0.1

Set some defaults for Babel commands

Create a setup.cfg in your newly created app folder containing the following

[extract_messages]
keywords = _ , ugettext , lazy_ugettext:1 , N_:1,2 , ungettext:1,2 , lazy_ungettext:1,2
output_file = data/locale/messages.pot
[init_catalog]
input_file = data/locale/messages.pot
output_dir = data/locale
domain = messages
[update_catalog]
input_file = data/locale/messages.pot
output_dir = data/locale
domain = messages
[compile_catalog]
directory = data/locale
domain = messages

Here, we decide to put translation files into data/locale, but this folder is not created by create-app so we have to create it manually:

$ mkdir data/locale

And finally, we add message extractors information into out setup.py file:

VERSION = '0.0.1'
from setuptools import setup, find_packages
setup(
      name = 'example_i18n',
      version = VERSION,
      author = '',
      author_email = '',
      description = '',
      license = '',
      keywords = '',
      url = '',
      packages = find_packages(),
      include_package_data = True,
      package_data = {'' : ['*.cfg']},
      zip_safe = False,
      install_requires = ('nagare', 'Babel'),
      entry_points = """
      [nagare.applications]
      example_i18n = example_i18n.app:app
      """,
      message_extractors = {'example_i18n': [
                ('**.py', 'python', None)]}
     )
Note:
Babel has been added as a dependency for the project, this way typing 'python setup.py develop' should install Babel if it is missing.

Prepare application for internationalization

Edit example_i18n/app.py, put every strings between _() and define a fake _ function:

from __future__ import with_statement
import os
from nagare import presentation
_ = lambda x: x

class Example_i18n(object):
    pass
@presentation.render_for(Example_i18n)
def render(self, h, *args):
    this_file = __file__
    if this_file.endswith('.pyc'):
        this_file = __file__[:-1]
    models_file = os.path.join(os.path.dirname(__file__), 'models.py')
    h.head.css_url('/static/nagare/application.css')
    h.head << h.head.title(_('Up and Running !'))
    with h.div(class_='mybody'):
        with h.div(id='myheader'):
            h << h.a(h.img(src='/static/nagare/img/logo.gif'), id='logo', href='http://www.nagare.org/', title='Nagare home')
            h << h.span(_('Congratulations !'), id='title')
        with h.div(id='main'):
            h << h.h1(_('Your application is running'))
            with h.p:
                h << _('You can now:')
                with h.ul:
                    h << h.li(_('If your application uses a database, add your database entities into '), h.i(models_file))
                    h << h.li(_('Add your application components into '), h.i(this_file), _(' or create new files'))
            h << h.p(_('To learn more, go to the '), h.a(_('official website'), href='http://www.nagare.org/'))
            h << _("Have fun !")
    h << h.div(class_='footer')
    return h.root
# ---------------------------------------------------------------
app = Example_i18n

Message extraction and catalog manipulation

Messages can be extracted:

$ python setup.py extract_messages
running extract_messages
extracting messages from example_i18n/__init__.py
extracting messages from example_i18n/app.py
extracting messages from example_i18n/models.py
writing PO template file to data/locale/messages.pot

A messages.pot file is now created into data/locale

For the next step, we have to initialize a catalog for your preferred locale (fr in this example):

$ python setup.py init_catalog -l fr
running init_catalog
creating catalog 'data/locale/fr/LC_MESSAGES/messages.po' based on 'data/locale/messages.pot'

The catalog is now added and is ready to be edited.

Once edited and filled in with the right translations, we can compile our catalog:

$ python setup.py compile_catalog
running compile_catalog
compiling catalog 'data/locale/fr/LC_MESSAGES/messages.po' to 'data/locale/fr/LC_MESSAGES/messages.mo'

This way we can prepare as many catalogs as locales needed.

Note:
when changing messages in a project we have to do a extract_messages followed by a update_catalog, and add missing translation in every modified catalog.

Next Step

We are using a fake _ function, so nothing is translated at all, the next step is to plug gettext functions into example_i18n application, in order to get effective message translation.

A simple tabbed navigation with Nagare

Tabs are a widely used mean to navigate between categories or sections of a website. There are lots of howtos and tutorials to create clean tab based navigation with HTML and CSS.

Let's see how to handle such a navigation with Nagare. We are going to create a tabbed navigation between a TicTacToe and a Counter.

Requirements

We use TicTacToe and Counter examples from our Demonstration Portal. Hence nagare examples have to be installed with following command:

easy_install nagare.examples

To run our small example we use serve-module:

nagare-admin serve-module ./menu_example.py:TabNav menu_example

Create Menu component

The Menu has to meet the following needs:

  1. store the selected entry
  2. display the list of clickable entries
  3. notify when an entry is clicked

A fairly simple implementation in Nagare could be:

from nagare import presentation, var
class Menu:
    def __init__(self, menu):
        self.menu = menu
        self.selected = var.Var(0)
    def display(self, i, comp):
        comp.answer(i)

@presentation.render_for(Menu)
def render(self, h, comp, *args):
    with h.ul(class_='menu'):
        for index, entry in enumerate(self.menu):
            h << h.li(h.a(entry).action(lambda index=index: self.display(index, comp)),
                      class_='selected-entry' if self.selected() == index else '')
    return h.root

Notification of the clicked entry is done through the use of answer() method of the Menu component.

Put it together in TabNav component

TabNav is composed of two parts, menu and content. TabNav is expected to work the following way:

  1. When an entry is clicked in menu, select_tab is called
  2. select_tab change the displayed content and then sets the selected entry in menu
from nagare.examples import counter, tictactoe
from nagare import presentation, component
class TabNav:
    tab_factories = [('Counter', counter.Counter1),
                     ('TicTacToe', tictactoe.Task)]
    def __init__(self):
        self.menu = component.Component(Menu([label for label, factory in self.tab_factories]))
        self.menu.on_answer(self.select_tab)

        self.content = component.Component(None)
        self.select_tab(0)
    def select_tab(self, index):
        self.content.becomes(self.tab_factories[index][1]())
        self.menu().selected(index)
@presentation.render_for(TabNav)
def render(self, h, *args):
    h << self.menu
    with h.div(class_='content'):
        h << self.content
    return h.root

on_answer() method of the Menu component let us bind answer() method of the Menu component with select_tab() method of TabNav component. This way select_tab is called with the index of the clicked entry in the menu as parameter.

Some styling

Finally a bit of CSS can be added to get the tabbed rendering effect.

@presentation.render_for(TabNav)
def render(self, h, *args):
    css = """
body {
    font-family: verdana,'Bitstream Vera Sans',helvetica,arial,sans-serif;
}
ul.menu {
    height: 2em;
    list-style: none;
    margin: 0;
    padding: 0;
}
ul.menu li {
    float: left;
    margin: 0 1px 0 0;
    padding-left: 10px;
    border: 1px solid #555555;
    border-bottom: none;
    color: #555555;
}
ul.menu a {
    color: #555555;
    display: block;
    float: left;
    height: 2em;
    line-height: 2em;
    padding-right: 10px;
    text-decoration: none;
}
ul.menu li.selected-entry {
    background-color: #555555;
}
ul.menu li.selected-entry a {
    color: #FFFFFF;
    font-weight: bold;
}
div.content {
    border: #555555 solid 2px;
    clear: left;
    padding: 1em;
}
"""
    h << h.style(css, type="text/css")
    h << self.menu
    with h.div(class_='content'):
        h << self.content
    return h.root

This renders as following:

/trac/raw-attachment/blog/tabbed-navigation-with-nagare/tabbed_navigation.2.png
Rq:
As we use serve-module, CSS has to be embedded into HTML but it is usually put in the static folder of the application skeleton.

Use Nagare namespaces to create an rss feed

In Nagare HTML and XML generation is done through Renderers. Thus, to generate RSS feeds it is possible to use Nagare XML Renderer, which accept any tag or attribute. Another way is to create a RSS Renderer for Nagare which matches RSS namespace.

To do so we are extending Nagare XmlRender, which enforces possible xml tags and attributes (e.g. namespace).

Simple RSSRenderer

RSS specification, leads to the following namespace definition in Nagare:

class RssRenderer(xml.XmlRenderer):
    """RSS renderer"""
    # RSS tags
    # ------------
    rss = TagProp('rss', set(('version', )))
    channel = TagProp('channel', set())
    title = TagProp('title', set())
    link = TagProp('link', set())
    description = TagProp('description', set())
    language = TagProp('language', set())
    copyright = TagProp('copyright', set())
    managingEditor = TagProp('managingEditor', set())
    webMaster = TagProp('webMaster', set())
    pubDate = TagProp('pubDate', set())
    lastBuildDate = TagProp('lastBuildDate', set())
    category = TagProp('category', set())
    generator = TagProp('generator', set())
    docs = TagProp('docs', set())
    cloud = TagProp('cloud', set(('domain', 'port',
        'path', 'registerProcedure', 'protocol')))
    ttl = TagProp('ttl', set())
    image = TagProp('image', set())
    url = TagProp('url', set())
    width = TagProp('width', set())
    height = TagProp('height', set())
    rating = TagProp('rating', set())
    textInput = TagProp('textInput', set())
    name = TagProp('name', set())
    skipHours = TagProp('skipHours', set())
    skipDays = TagProp('skipDays', set())
    item = TagProp('item', set())
    title = TagProp('title', set())
    link = TagProp('link', set())
    description = TagProp('description', set())
    author = TagProp('author', set())
    category = TagProp('category', set(('domain', )))
    comments = TagProp('comments', set())
    enclosure = TagProp('enclosure', set(('url', 'length', 'type')))
    guid = TagProp('guid', set(('isPermaLink', )))
    pubDate = TagProp('pubDate', set())
    source = TagProp('source', set(('url', )))

Note

Notes:

  • RssRenderer checks if tags and attibutes exist in namespace
  • RssRenderer does not validate against any DTD or schema

Let's look at the following example:

<?xml version="1.0"?>
<rss version="2.0">
    <channel>
        <title>Some Blog Title</title>
        <link>http://someblog.example.com</link>
        <description>Description</description>
        <language>en-us</language>
        <pubDate>Tue, 25 Jan 2010 14:00:00 GMT</pubDate>
        <lastBuildDate>Tue, 25 Jan 2010 14:00:00 GMT</lastBuildDate>
        <docs>http://blogs.law.harvard.edu/tech/rss</docs>
        <generator>Some Generator</generator>
        <managingEditor>editor@example.com</managingEditor>
        <webMaster>webmaster@example.com</webMaster>
        <item>
            <title>Some Post Title</title>
            <link>http://someblog.example.com/post1</link>
            <description>Some Post Description</description>
            <pubDate>Tue, 25 Jan 2010 14:00:00 GMT</pubDate>
            <guid>Some Global Unique Identifier</guid>
        </item>
    </channel>
</rss>

With RssRenderer, this feed is generated this way:

r = rss.RssRenderer()
with r.rss(version="2.0"):
    with r.channel:
        r << r.title('Some Blog Title')
        r << r.link('http://someblog.example.com')
        r << r.description('Description')
        r << r.language('en-us')
        r << r.pubDate('Tue, 25 Jan 2010 14:00:00 GMT')
        r << r.lastBuildDate('Tue, 25 Jan 2010 14:00:00 GMT')
        r << r.docs('http://blogs.law.harvard.edu/tech/rss')
        r << r.generator('Some Generator')
        r << r.managingEditor('editor@example.com')
        r << r.webMaster('webmaster@example.com')
        with r.item:
            r << r.title('Some Post Title')
            r << r.link('http://someblog.example.com/post1')
            r << r.description('Some Post Description')
            r << r.pubDate('Tue, 25 Jan 2010 14:00:00 GMT')
            r << r.guid('Some Global Unique Identifier')

Now RSSRenderer can be used to generate any RSS feed.

Mixing multiple namespaces

There's several extensions to RSS, actually extensions are just XML namespaces added in the feed. One simple extension is blogChannel namespace.

First create a BlogChannelRenderer:

class BlogChannelRenderer(xml.XmlRenderer):
    blogRoll = TagProp('blogRoll', set())
    mySubscriptions = TagProp('mySubscriptions', set())
    blink = TagProp('blink', set())
    changes = TagProp('changes', set())

Now we can mix RssRenderer and BlogChannelRenderer to create one feed:

r = rss.RssRenderer()
r.namespaces = {'blogChannel': 'http://backend.userland.com/blogChannelModule'}
b = rss.BlogChannelRenderer(r)
b.default_namespace = 'blogChannel'
with r.rss(version="2.0"):
    with r.channel:
        r << r.title('Some Blog Title')
        r << r.link('http://someblog.example.com')
        r << r.description('Description')
        r << r.language('en-us')
        r << b.blogRoll('http://someblog.example.com/blogroll')
        r << b.mySubscriptions('http://someblog.example.com/subscriptions')
        r << b.blink('http://someblog.example.com/backlink')
        r << b.changes('http://someblog.example.com/changes')
        r << r.pubDate('Tue, 25 Jan 2010 14:00:00 GMT')
        r << r.lastBuildDate('Tue, 25 Jan 2010 14:00:00 GMT')
        r << r.docs('http://blogs.law.harvard.edu/tech/rss')
        r << r.generator('Some Generator')
        r << r.managingEditor('editor@example.com')
        r << r.webMaster('webmaster@example.com')
        with r.item:
            r << r.title('Some Post Title')
            r << r.link('http://someblog.example.com/post1')
            r << r.description('Some Post Description')
            r << r.pubDate('Tue, 25 Jan 2010 14:00:00 GMT')
            r << r.guid('Some Global Unique Identifier')

This produces the following RSS feed:

<rss xmlns:blogChannel="http://backend.userland.com/blogChannelModule" version="2.0">
  <channel>
    <title>Some Blog Title</title>
    <link>http://someblog.example.com</link>
    <description>Description</description>
    <language>en-us</language>
    <blogChannel:blogRoll>http://someblog.example.com/blogroll</blogChannel:blogRoll>
    <blogChannel:mySubscriptions>http://someblog.example.com/subscriptions</blogChannel:mySubscriptions>
    <blogChannel:blink>http://someblog.example.com/backlink</blogChannel:blink>
    <blogChannel:changes>http://someblog.example.com/changes</blogChannel:changes>
    <pubDate>Tue, 25 Jan 2010 14:00:00 GMT</pubDate>
    <lastBuildDate>Tue, 25 Jan 2010 14:00:00 GMT</lastBuildDate>
    <docs>http://blogs.law.harvard.edu/tech/rss</docs>
    <generator>Some Generator</generator>
    <managingEditor>editor@example.com</managingEditor>
    <webMaster>webmaster@example.com</webMaster>
    <item>
      <title>Some Post Title</title>
      <link>http://someblog.example.com/post1</link>
      <description>Some Post Description</description>
      <pubDate>Tue, 25 Jan 2010 14:00:00 GMT</pubDate>
      <guid>Some Global Unique Identifier</guid>
    </item>
  </channel>
</rss>

With this process any XML namespace can be added to Nagare. For example, Nagare XHTML namespace is implemented as a Renderer.

Nagare installation on Mac OS X (Snow Leopard)

Snow Leopard has been released this summer.

The 64-bits Mac OS X version is installed by default on new Mac. Currently, the use of Nagare with the 64-bits stackless Mac OS X version leads to a Bus Error .

To get around this, we need to install a 32-bits stackless python, thus changing slightly the install procedure for Mac OS X described previously .

Install MacPorts

Install instructions here.

Configure MacPorts

MacPorts has to use universal variant (64 + 32 bits compilation).

In order to do so, you add +universal into variants.conf file, i.e. for default macports installation:

echo +universal | sudo tee -a /opt/local/etc/macports/variants.conf

Install Stackless

Now we have to run configure for 32-bits architecture:

cd /tmp
wget http://www.stackless.com/binaries/stackless-264-export.tar.bz2
tar jxvf stackless-264-export.tar.bz2
cd stackless-2.6.4*
CC="gcc -arch i386" ./configure --enable-stacklessfewerregisters --prefix=<STACKLESS_HOME>
make all
sudo make install

Once Stackless Python installed, Nagare installation goes on as usual.

Nagare installation on Mac OS X (Leopard)

As Mac OS X is a unix based system, the Linux section of NagareInstallation#linux-installation applies, but a few tweaks are needed to setup a fully working nagare environment.

We'll need the following external tools:

Install Xcode

Xcode should be available from your Applications Install DVD or can be downloaded from here.

Install MacPorts

Install instructions can be found here.

Install Stackless

Here we have to give some specific options to configure:

cd /tmp
wget http://www.stackless.com/binaries/stackless-252-export.tar.bz2
tar jxvf stackless-252-export.tar.bz2
cd stackless-2.5.2*
./configure --enable-stacklessfewerregisters --enable-universalsdk --prefix=<STACKLESS_HOME>
make all
sudo make install

--enable-stacklessfewerregisters and --enable-universalsdk are the key options here. Also note how Stackless Python is installed in its own <STACKLESS_HOME> directory, in order not to override the Python system.

Install libxml2 and libxslt

As the libxml2 version given with Xcode prevents lxml from installing, we'll use MacPorts to install the right one:

sudo port install libxslt

Finish nagare installation

From now on, you can follow the vanilla install guide, starting with section 2.