1# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3# (c) 2005 Clark C. Evans 4# This module is part of the Python Paste Project and is released under 5# the MIT License: http://www.opensource.org/licenses/mit-license.php 6""" 7Middleware related to transactions and database connections. 8 9At this time it is very basic; but will eventually sprout all that 10two-phase commit goodness that I don't need. 11 12.. note:: 13 14 This is experimental, and will change in the future. 15""" 16from paste.httpexceptions import HTTPException 17from wsgilib import catch_errors 18 19class TransactionManagerMiddleware(object): 20 21 def __init__(self, application): 22 self.application = application 23 24 def __call__(self, environ, start_response): 25 environ['paste.transaction_manager'] = manager = Manager() 26 # This makes sure nothing else traps unexpected exceptions: 27 environ['paste.throw_errors'] = True 28 return catch_errors(self.application, environ, start_response, 29 error_callback=manager.error, 30 ok_callback=manager.finish) 31 32class Manager(object): 33 34 def __init__(self): 35 self.aborted = False 36 self.transactions = [] 37 38 def abort(self): 39 self.aborted = True 40 41 def error(self, exc_info): 42 self.aborted = True 43 self.finish() 44 45 def finish(self): 46 for trans in self.transactions: 47 if self.aborted: 48 trans.rollback() 49 else: 50 trans.commit() 51 52 53class ConnectionFactory(object): 54 """ 55 Provides a callable interface for connecting to ADBAPI databases in 56 a WSGI style (using the environment). More advanced connection 57 factories might use the REMOTE_USER and/or other environment 58 variables to make the connection returned depend upon the request. 59 """ 60 def __init__(self, module, *args, **kwargs): 61 #assert getattr(module,'threadsaftey',0) > 0 62 self.module = module 63 self.args = args 64 self.kwargs = kwargs 65 66 # deal with database string quoting issues 67 self.quote = lambda s: "'%s'" % s.replace("'","''") 68 if hasattr(self.module,'PgQuoteString'): 69 self.quote = self.module.PgQuoteString 70 71 def __call__(self, environ=None): 72 conn = self.module.connect(*self.args, **self.kwargs) 73 conn.__dict__['module'] = self.module 74 conn.__dict__['quote'] = self.quote 75 return conn 76 77def BasicTransactionHandler(application, factory): 78 """ 79 Provides a simple mechanism for starting a transaction based on the 80 factory; and for either committing or rolling back the transaction 81 depending on the result. It checks for the response's current 82 status code either through the latest call to start_response; or 83 through a HTTPException's code. If it is a 100, 200, or 300; the 84 transaction is committed; otherwise it is rolled back. 85 """ 86 def basic_transaction(environ, start_response): 87 conn = factory(environ) 88 environ['paste.connection'] = conn 89 should_commit = [500] 90 def finalizer(exc_info=None): 91 if exc_info: 92 if isinstance(exc_info[1], HTTPException): 93 should_commit.append(exc_info[1].code) 94 if should_commit.pop() < 400: 95 conn.commit() 96 else: 97 try: 98 conn.rollback() 99 except: 100 # TODO: check if rollback has already happened 101 return 102 conn.close() 103 def basictrans_start_response(status, headers, exc_info = None): 104 should_commit.append(int(status.split(" ")[0])) 105 return start_response(status, headers, exc_info) 106 return catch_errors(application, environ, basictrans_start_response, 107 finalizer, finalizer) 108 return basic_transaction 109 110__all__ = ['ConnectionFactory', 'BasicTransactionHandler'] 111 112if '__main__' == __name__ and False: 113 from pyPgSQL import PgSQL 114 factory = ConnectionFactory(PgSQL, database="testing") 115 conn = factory() 116 curr = conn.cursor() 117 curr.execute("SELECT now(), %s" % conn.quote("B'n\\'gles")) 118 (time, bing) = curr.fetchone() 119 print(bing, time) 120 121