summaryrefslogtreecommitdiff
path: root/paste/transaction.py
diff options
context:
space:
mode:
Diffstat (limited to 'paste/transaction.py')
-rw-r--r--paste/transaction.py120
1 files changed, 120 insertions, 0 deletions
diff --git a/paste/transaction.py b/paste/transaction.py
new file mode 100644
index 0000000..1347acd
--- /dev/null
+++ b/paste/transaction.py
@@ -0,0 +1,120 @@
+# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
+# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+# (c) 2005 Clark C. Evans
+# This module is part of the Python Paste Project and is released under
+# the MIT License: http://www.opensource.org/licenses/mit-license.php
+"""
+Middleware related to transactions and database connections.
+
+At this time it is very basic; but will eventually sprout all that
+two-phase commit goodness that I don't need.
+
+.. note::
+
+ This is experimental, and will change in the future.
+"""
+from paste.httpexceptions import HTTPException
+from wsgilib import catch_errors
+
+class TransactionManagerMiddleware(object):
+
+ def __init__(self, application):
+ self.application = application
+
+ def __call__(self, environ, start_response):
+ environ['paste.transaction_manager'] = manager = Manager()
+ # This makes sure nothing else traps unexpected exceptions:
+ environ['paste.throw_errors'] = True
+ return catch_errors(self.application, environ, start_response,
+ error_callback=manager.error,
+ ok_callback=manager.finish)
+
+class Manager(object):
+
+ def __init__(self):
+ self.aborted = False
+ self.transactions = []
+
+ def abort(self):
+ self.aborted = True
+
+ def error(self, exc_info):
+ self.aborted = True
+ self.finish()
+
+ def finish(self):
+ for trans in self.transactions:
+ if self.aborted:
+ trans.rollback()
+ else:
+ trans.commit()
+
+
+class ConnectionFactory(object):
+ """
+ Provides a callable interface for connecting to ADBAPI databases in
+ a WSGI style (using the environment). More advanced connection
+ factories might use the REMOTE_USER and/or other environment
+ variables to make the connection returned depend upon the request.
+ """
+ def __init__(self, module, *args, **kwargs):
+ #assert getattr(module,'threadsaftey',0) > 0
+ self.module = module
+ self.args = args
+ self.kwargs = kwargs
+
+ # deal with database string quoting issues
+ self.quote = lambda s: "'%s'" % s.replace("'","''")
+ if hasattr(self.module,'PgQuoteString'):
+ self.quote = self.module.PgQuoteString
+
+ def __call__(self, environ=None):
+ conn = self.module.connect(*self.args, **self.kwargs)
+ conn.__dict__['module'] = self.module
+ conn.__dict__['quote'] = self.quote
+ return conn
+
+def BasicTransactionHandler(application, factory):
+ """
+ Provides a simple mechanism for starting a transaction based on the
+ factory; and for either committing or rolling back the transaction
+ depending on the result. It checks for the response's current
+ status code either through the latest call to start_response; or
+ through a HTTPException's code. If it is a 100, 200, or 300; the
+ transaction is committed; otherwise it is rolled back.
+ """
+ def basic_transaction(environ, start_response):
+ conn = factory(environ)
+ environ['paste.connection'] = conn
+ should_commit = [500]
+ def finalizer(exc_info=None):
+ if exc_info:
+ if isinstance(exc_info[1], HTTPException):
+ should_commit.append(exc_info[1].code)
+ if should_commit.pop() < 400:
+ conn.commit()
+ else:
+ try:
+ conn.rollback()
+ except:
+ # TODO: check if rollback has already happened
+ return
+ conn.close()
+ def basictrans_start_response(status, headers, exc_info = None):
+ should_commit.append(int(status.split(" ")[0]))
+ return start_response(status, headers, exc_info)
+ return catch_errors(application, environ, basictrans_start_response,
+ finalizer, finalizer)
+ return basic_transaction
+
+__all__ = ['ConnectionFactory', 'BasicTransactionHandler']
+
+if '__main__' == __name__ and False:
+ from pyPgSQL import PgSQL
+ factory = ConnectionFactory(PgSQL, database="testing")
+ conn = factory()
+ curr = conn.cursor()
+ curr.execute("SELECT now(), %s" % conn.quote("B'n\\'gles"))
+ (time, bing) = curr.fetchone()
+ print(bing, time)
+