[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