Repoze Notes
Tue, 18 Dec 2007
Adding repoze.tm to a Pylons Application
After sprinting with the PyATL folks in Atlanta last week, Chris and I decided to dive into the "share Zope stuff with Python web developers" story a little more. We wanted to show that various bits of Zopeish middleware might be useful in the context of the other WSGI-enabled web frameworks.
I worked through the
How to write a basic blog with Pylons tutorial, using
the sqlite backend for simplicity. Once done,
I looked at the code in the application which did manual / explicit
transaction handline in the controller method for the blog
add form POST:
def blog_add_process(self):
# Create a new Blog object and populate it.
newpost = model.Blog()
newpost.date = datetime.datetime.now()
newpost.content = request.params['content']
newpost.author = request.params['author']
newpost.subject = request.params['subject']
# I didn't set ID because it will get an autoincrement value.
# Attach the object to the session.
model.Session.save(newpost)
# Commit the transaction.
# (This sends the SQL INSERT command due to autoflushing.)
model.Session.commit()
redirect_to("/blog")
Using the transaction Framework in the Application
I decided to knock together a simple "data manager" for this application,
following Chris' Transactions
in WSGI tutorial. The class implements the IDataManager
API, using the ORM session to do the real work:
class DataManager(object):
transaction_manager = None
def __init__(self, post):
self.post = post
def commit(self, transaction):
""" See IDataManager.
"""
model.Session.save(self.post)
def abort(self, transaction):
""" See IDataManager.
"""
model.Session.rollback()
def tpc_begin(self, transaction):
""" See IDataManager.
"""
def tpc_vote(self, transaction):
""" See IDataManager.
"""
def tpc_finish(self, transaction):
""" See IDataManager.
"""
model.Session.commit()
def tpc_abort(self, transaction):
""" See IDataManager.
"""
model.Session.rollback()
def sortKey(self):
""" See IDataManager.
"""
return 'myblog-sql'
I then modified the controller method such that it registers
an instance of the DataManager class with the transaction. Note
the addition of pseudo-validation logic, which triggers an exception
in order to demonstrate the "auto-rollback" feature of
repoze.tm:
import transaction
...
def blog_add_process(self):
# Create a new Blog object and populate it.
newpost = model.Blog()
newpost.date = datetime.datetime.now()
newpost.content = request.params['content']
newpost.author = request.params['author']
newpost.subject = request.params['subject']
# Register with the global two-phase transaction manager
dm = DataManager(newpost)
transaction.get().join(dm)
# Trigger an error on "invalid" data, to trigger the abort.
if newpost.subject.startswith('Abort'):
raise ValueError('Invalid data')
redirect_to("/blog")
Configuring the Application
First, I needed to install repoze.tm and its
dependencies:
$ ../bin/easy_install -i http://dist.repoze.org/simple repoze.tm
Then, I needed to wire the repoze.tm middleware into
the PasteDeploy configuration. In order to add
middleware, I renamed the [app:main] section::
[app:myblog] use = egg:MyBlog # Let pipeline handle errors full_stack = false cache_dir = %(here)s/data beaker.session.key = myblog beaker.session.secret = somesecret sqlalchemy.url = sqlite:///%(here)s/db.sqlite sqlalchemy.convert_unicode = true
Note that I turned off the full_stack option, because
I want errors to propagate out to the middleware, so that it can
abort the transaction.
I then defined a [pipeline:main] section, adding both
the transaction middleware and some error handling::
[pipeline:main]
pipeline =
egg:Paste#cgitb
egg:Paste#httpexceptions
egg:repoze.tm#tm
myblog
At this point, the application works as desired:
- "Normal" posts get added to the table
- "Invalid" posts (those whose subject starts with "Abort"), are blocked.
posted at: 17:36 | permanent link to this entry
