Home | History | Annotate | Download | only in paste
      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 """
      7 Middleware related to transactions and database connections.
      8 
      9 At this time it is very basic; but will eventually sprout all that
     10 two-phase commit goodness that I don't need.
     11 
     12 .. note::
     13 
     14    This is experimental, and will change in the future.
     15 """
     16 from paste.httpexceptions import HTTPException
     17 from wsgilib import catch_errors
     18 
     19 class 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 
     32 class 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 
     53 class 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 
     77 def 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 
    112 if '__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