121 lines
4.3 KiB
Python
121 lines
4.3 KiB
Python
# (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)
|
|
|