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.

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

[pipeline:main]
pipeline = egg:Paste#cgitb
           egg:Paste#httpexceptions
           egg:repoze.tm2#tm
           repozestorm

Next define the model for persons repozestorm/model/person.py.

   1 from storm.locals import *
   2 
   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/websetup.py:

   1 """Setup the RepozeStorm application"""
   2 import logging
   3 
   4 from paste.deploy import appconfig
   5 from pylons import config
   6 
   7 from storm.zope.zstorm import global_zstorm
   8 
   9 from repozestorm.config.environment import load_environment
  10 
  11 log = logging.getLogger(__name__)
  12 
  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)
  17 
  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/person.py:

   1 import logging
   2 
   3 from pylons import config
   4 
   5 from storm.zope.zstorm import global_zstorm
   6 
   7 from repozestorm.lib.base import *
   8 from repozestorm.model.person import Person
   9 
  10 log = logging.getLogger(__name__)
  11 
  12 class PersonController(BaseController):
  13 
  14     def index(self):
  15         # Return a rendered template
  16         #   return render('/some/template.mako')
  17         # or, Return a response
  18         return 'Hello World'
  19 
  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')
  28 
  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 -*-
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html>
  <head>
    <title>List Persons</title>
  </head>
  <body>
    <div class="content">
      <h1 class="main">List of persons</h1>
        <ul id="persons">
        % for person in c.persons:
          <li>${person.id} ${person.firstname} ${person.lastname}</li>
        % endfor
        </ul>
    </div>
  </body>
</html>

And finally run the example:

$ paster serve --reload development.ini
Starting subprocess with file monitor
Starting server in PID 1234.
serving on 0.0.0.0:5000 view at http://127.0.0.1:5000

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).

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

[pipeline:main]
pipeline = egg:Paste#cgitb
           egg:Paste#httpexceptions
           tm2
           repozestorm

And define veto_on_get in repozestorm/lib/commit_veto.py 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)