transaction.py 4.3 KB

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