Using Storm with Pylons and Repoze.tm2

This example shows how to integrate Storm with Repoze.tm2 in a Pylons application. Repoze.tm2 is a transaction manager which uses Zope's transaction package.

The example project will just have a simple controller with two actions. The first action will add a person to a sqldb database and the second action will list all persons in the database.

You need to have the following packages:

The following dependencies are required when using Storm 0.12 or lower. They are optional when using the following patch or until the require-less-zope branch is merged.

Create the project using paster:

$ paster create -t pylons RepozeStorm

As explained in this blog you need to add repoze.tm2 to the pipeline of your Pylons application. This will enable every request to be a transaction.

You should add the following pipeline and rename the app:main section to app:repozestorm in development.ini.

use = egg:RepozeStorm
full_stack = false
storm.database.persons.uri = sqlite:///%(here)s/repozestorm/data/persons.db

pipeline = egg:Paste#cgitb

Next define the model for persons repozestorm/model/

   1 from storm.locals import *
   3 class Person(object):
   4     __storm_table__ = "Persons"
   5     id = Int(primary=True)
   6     firstname = Unicode()
   7     lastname = Unicode()

Person will be stored in a sql table called persons. Create that table by using setup-app. But for that to work make repozestorm/

   1 """Setup the RepozeStorm application"""
   2 import logging
   4 from paste.deploy import appconfig
   5 from pylons import config
   7 from storm.zope.zstorm import global_zstorm
   9 from repozestorm.config.environment import load_environment
  11 log = logging.getLogger(__name__)
  13 def setup_config(command, filename, section, vars):
  14     """Place any commands to setup repozestorm here"""
  15     conf = appconfig('config:' + filename, name=section.split(':')[1])
  16     load_environment(conf.global_conf, conf.local_conf)
  18     store = global_zstorm.get('persondb', config['storm.database.persons.uri'])
  19     try:
  20         store.execute("create table persons "
  21                       "(id integer primary key, "
  22                       "firstname varchar, lastname varchar)")
  23         store.commit()
  24     finally:
  25         store.close()

Create directory to hold the database and run setup-app:

$ mkdir -p repozestorm/data
$ paster setup-app development.ini#repozestorm
Running setup_config() from repozestorm.websetup

Create a controller for the application repozestorm/controllers/

   1 import logging
   3 from pylons import config
   5 from storm.zope.zstorm import global_zstorm
   7 from repozestorm.lib.base import *
   8 from repozestorm.model.person import Person
  10 log = logging.getLogger(__name__)
  12 class PersonController(BaseController):
  14     def index(self):
  15         # Return a rendered template
  16         #   return render('/some/template.mako')
  17         # or, Return a response
  18         return 'Hello World'
  20     def new(self):
  21         store = global_zstorm.get('persondb', config['storm.database.persons.uri'])
  22         p = Person()
  23         p.firstname = u"Boo"
  24         p.lastname = u"Foo"
  25         store.add(p)
  26         return "Done"
  27         #raise Exception('Haha')
  29     def list(self):
  30         store = global_zstorm.get('persondb', config['storm.database.persons.uri'])
  31         c.persons = list(store.find(Person))
  32         return render('/list_persons.mako')

global_zstorm contains all Storm stores by key. In this example the uri associated with that key is stored in development.ini. You can also set a default uri on global_zstorm.

Because the application runs inside a Repoze.tm2 pipeline, there is no need to call store.commit(). Every request is a transaction and global_zstorm will register every store to the current transaction. If an exception is raised the transaction is rolled back or otherwise committed.

The list controller action calls a mako template repozestorm/templates/list_persons.mako to display a list of persons:

# -*- coding: utf-8 -*-
    <title>List Persons</title>
    <div class="content">
      <h1 class="main">List of persons</h1>
        <ul id="persons">
        % for person in c.persons:
          <li>${} ${person.firstname} ${person.lastname}</li>
        % endfor

And finally run the example:

$ paster serve --reload development.ini
Starting subprocess with file monitor
Starting server in PID 1234.
serving on view at

Start off by showing an (initially) empty list of persons by going to http://localhost:5000/person/list. Add a person by visiting http://localhost:5000/person/new and check the result on http://localhost:5000/person/list again.

Abort on GET, commit on POST

Repoze.tm2 has commit_veto support which can be specified in development.ini (this requires repoze.tm2 >= 1.0a3).

use = egg:repoze.tm2#tm
commit_veto = repozestorm.lib.commit_veto:veto_on_get

pipeline = egg:Paste#cgitb

And define veto_on_get in repozestorm/lib/ as follows:

   1 def veto_on_get(environ, status, headers):
   2     if environ['REQUEST_METHOD'] == 'GET':
   3         return True
   4     return False

Different policies might include checking for a logged in user, or whatever policy you want by examining the environment, status or headers.

You can test the policy by visiting http://localhost:5000/person/new. The new user should not be added, check on http://localhost:5000/person/list. Commits will only happen on POST requests, which means the new controller should use a form.

PylonsRepoze.tm2 (last edited 2008-08-03 22:04:15 by olaf-conradi)