[Repoze-checkins] r614 - www/trunk/blog.repoze.org/entries
Tres Seaver
tseaver at palladion.com
Tue Dec 18 22:33:29 UTC 2007
Author: Tres Seaver <tseaver at palladion.com>
Date: Tue Dec 18 22:33:29 2007
New Revision: 614
Log:
Blog about Pylons - repoze.tm integration.
Added:
www/trunk/blog.repoze.org/entries/repoze.tm_with_pylons-20071218.txt
Added: www/trunk/blog.repoze.org/entries/repoze.tm_with_pylons-20071218.txt
==============================================================================
--- (empty file)
+++ www/trunk/blog.repoze.org/entries/repoze.tm_with_pylons-20071218.txt Tue Dec 18 22:33:29 2007
@@ -0,0 +1,163 @@
+Adding repoze.tm to a Pylons Application
+#label general
+
+<p> After sprinting with the <a href="http://pyatl.org/">PyATL</a>
+ 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.
+</p>
+
+<p> I worked through the
+ <a href="http://wiki.pylonshq.com/display/pylonscookbook/Making+a+Pylons+Blog"
+ >How to write a basic blog with Pylons</a> tutorial, using
+ the <code>sqlite</code> 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::</p>
+
+<pre>
+ 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")
+</pre>
+
+<h3>Using the <code>transaction</code> Framework in the Application</h3>
+
+I decided to knock together a simple "data manager" for this application,
+following Chris' <a href="http://repoze.org/tmdemo.html">Transactions
+in WSGI</a> tutorial. The class implements the <code>IDataManager</code>
+API, using the ORM session to do the real work::</p>
+
+<pre>
+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'
+</pre>
+
+<p> 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
+ <code>repoze.tm</code>::</p>
+
+<pre>
+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")
+</pre>
+
+<h3>Configuring the Application</h3>
+
+<p>First, I needed to install <code>repoze.tm</code> and its
+ dependencies:</p>
+
+<pre>
+ $ ../bin/easy_install -i http://dist.repoze.org/simple repoze.tm
+</pre>
+
+<p>Then, I needed to wire the <code>repoze.tm</code> middleware into
+ the <code>PasteDeploy</code> configuration. In order to add
+ middleware, I renamed the <code>[app:main]<code> section::</p>
+
+<pre>
+[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
+</pre>
+
+<p>Note that I turned off the <code>full_stack</code> option, because
+ I want errors to propagate out to the middleware, so that it can
+ abort the transaction.</p>
+
+<p>I then defined a <code>[pipeline:main]</code> section, adding both
+ the transaction middleware and some error handling::</p>
+
+<pre>
+[pipeline:main]
+pipeline =
+ egg:Paste#cgitb
+ egg:Paste#httpexceptions
+ egg:repoze.tm#tm
+ myblog
+</pre>
+
+<p>At this point, the application works as desired:</p>
+
+<ul>
+ <li> "Normal" posts get added to the table
+ <li> "Invalid" posts (those whose subject starts with "Abort"),
+ are blocked.</li>
+</ul>
More information about the Repoze-checkins
mailing list