Ticket #10771: django-transactions.2.diff
File django-transactions.2.diff, 13.4 KB (added by , 14 years ago) |
---|
-
django/db/transaction.py
diff --git a/django/db/transaction.py b/django/db/transaction.py index af42fd5..20dbb91 100644
a b called, a commit is made. 11 11 Managed transactions don't do those commits, but will need some kind of manual 12 12 or implicit commits or rollbacks. 13 13 """ 14 import sys 14 15 15 16 try: 16 17 import thread … … try: 20 21 from functools import wraps 21 22 except ImportError: 22 23 from django.utils.functional import wraps # Python 2.4 fallback. 23 from django.db import connections, DEFAULT_DB_ALIAS 24 24 25 from django.conf import settings 26 from django.db import connections, DEFAULT_DB_ALIAS 25 27 26 28 class TransactionManagementError(Exception): 27 29 """ … … def savepoint_commit(sid, using=None): 257 259 # DECORATORS # 258 260 ############## 259 261 260 def autocommit(using=None): 261 """ 262 Decorator that activates commit on save. This is Django's default behavior; 263 this decorator is useful if you globally activated transaction management in 264 your settings file and want the default behavior in some view functions. 265 """ 266 def inner_autocommit(func, db=None): 267 def _autocommit(*args, **kw): 262 class Transaction(object): 263 def __init__(self, entering, exiting, using): 264 self.entering = entering 265 self.exiting = exiting 266 self.using = using 267 268 def __enter__(self): 269 self.entering(self.using) 270 271 def __exit__(self, exc_type, exc_value, traceback): 272 self.exiting(exc_value, self.using) 273 274 def __call__(self, func): 275 @wraps(func) 276 def inner(*args, **kwargs): 277 # Once we drop support for Python 2.4 this block should become: 278 # with self: 279 # func(*args, **kwargs) 280 self.__enter__() 268 281 try: 269 enter_transaction_management(managed=False, using=db) 270 managed(False, using=db) 271 return func(*args, **kw) 272 finally: 273 leave_transaction_management(using=db) 274 return wraps(func)(_autocommit) 282 res = func(*args, **kwargs) 283 except: 284 self.__exit__(*sys.exc_info()) 285 raise 286 else: 287 self.__exit__(None, None, None) 288 return res 289 return inner 275 290 291 def _transaction_func(entering, exiting, using): 276 292 # Note that although the first argument is *called* `using`, it 277 293 # may actually be a function; @autocommit and @autocommit('foo') 278 294 # are both allowed forms. 279 295 if using is None: 280 296 using = DEFAULT_DB_ALIAS 281 297 if callable(using): 282 return inner_autocommit(using, DEFAULT_DB_ALIAS) 283 return lambda func: inner_autocommit(func, using) 298 return Transaction(entering, exiting, DEFAULT_DB_ALIAS)(using) 299 return Transaction(entering, exiting, using) 300 301 302 def autocommit(using=None): 303 """ 304 Decorator that activates commit on save. This is Django's default behavior; 305 this decorator is useful if you globally activated transaction management in 306 your settings file and want the default behavior in some view functions. 307 """ 308 def entering(using): 309 enter_transaction_management(managed=False, using=using) 310 managed(False, using=using) 311 312 def exiting(exc_value, using): 313 leave_transaction_management(using=using) 284 314 315 return _transaction_func(entering, exiting, using) 285 316 286 317 def commit_on_success(using=None): 287 318 """ … … def commit_on_success(using=None): 290 321 a rollback is made. This is one of the most common ways to do transaction 291 322 control in Web apps. 292 323 """ 293 def inner_commit_on_success(func, db=None): 294 def _commit_on_success(*args, **kw): 295 try: 296 enter_transaction_management(using=db) 297 managed(True, using=db) 324 def entering(using): 325 enter_transaction_management(using=using) 326 managed(True, using=using) 327 328 def exiting(exc_value, using): 329 if exc_value is not None: 330 if is_dirty(using=using): 331 rollback(using=using) 332 else: 333 if is_dirty(using=using): 298 334 try: 299 res = func(*args, **kw)335 commit(using=using) 300 336 except: 301 # All exceptions must be handled here (even string ones). 302 if is_dirty(using=db): 303 rollback(using=db) 337 rollback(using=using) 304 338 raise 305 else:306 if is_dirty(using=db):307 try:308 commit(using=db)309 except:310 rollback(using=db)311 raise312 return res313 finally:314 leave_transaction_management(using=db)315 return wraps(func)(_commit_on_success)316 339 317 # Note that although the first argument is *called* `using`, it 318 # may actually be a function; @autocommit and @autocommit('foo') 319 # are both allowed forms. 320 if using is None: 321 using = DEFAULT_DB_ALIAS 322 if callable(using): 323 return inner_commit_on_success(using, DEFAULT_DB_ALIAS) 324 return lambda func: inner_commit_on_success(func, using) 340 return _transaction_func(entering, exiting, using) 325 341 326 342 def commit_manually(using=None): 327 343 """ … … def commit_manually(using=None): 330 346 own -- it's up to the user to call the commit and rollback functions 331 347 themselves. 332 348 """ 333 def inner_commit_manually(func, db=None): 334 def _commit_manually(*args, **kw): 335 try: 336 enter_transaction_management(using=db) 337 managed(True, using=db) 338 return func(*args, **kw) 339 finally: 340 leave_transaction_management(using=db) 349 def entering(using): 350 enter_transaction_management(using=using) 351 managed(True, using=using) 341 352 342 return wraps(func)(_commit_manually) 353 def exiting(exc_value, using): 354 leave_transaction_management(using=using) 343 355 344 # Note that although the first argument is *called* `using`, it 345 # may actually be a function; @autocommit and @autocommit('foo') 346 # are both allowed forms. 347 if using is None: 348 using = DEFAULT_DB_ALIAS 349 if callable(using): 350 return inner_commit_manually(using, DEFAULT_DB_ALIAS) 351 return lambda func: inner_commit_manually(func, using) 356 return _transaction_func(entering, exiting, using) -
docs/releases/1.3.txt
diff --git a/docs/releases/1.3.txt b/docs/releases/1.3.txt index d681c4d..d5d7eac 100644
a b you just won't get any of the nice new unittest2 features. 56 56 57 57 .. _unittest2: http://pypi.python.org/pypi/unittest2 58 58 59 Transactions context managers 60 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 61 62 Transactions functions can now be used as a context manager, with Python 2.5 63 and above. For example: 64 65 with transaction.autocommit(): 66 # ... 67 68 For more information see :doc:`the transactions documentation 69 </topic/db/transactions>`. 70 71 59 72 Everything else 60 73 ~~~~~~~~~~~~~~~ 61 74 -
docs/topics/db/transactions.txt
diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt index 2d99c17..4fdbb3c 100644
a b parameter which should be the alias for a database connection for which the 61 61 behavior applies to. If no alias is specified then the ``"default"`` database 62 62 is used. 63 63 64 In addition, all of these decorators can be used as a context manager with 65 Python 2.5 and above. 66 64 67 .. note:: 65 68 66 69 Although the examples below use view functions as examples, these -
tests/modeltests/transactions/tests.py
diff --git a/tests/modeltests/transactions/tests.py b/tests/modeltests/transactions/tests.py index be95005..9deb183 100644
a b 1 import sys 2 1 3 from django.db import connection, transaction, IntegrityError, DEFAULT_DB_ALIAS 2 4 from django.conf import settings 3 5 from django.test import TransactionTestCase, skipUnlessDBFeature … … from django.test import TransactionTestCase, skipUnlessDBFeature 5 7 from models import Reporter 6 8 7 9 10 if sys.version_info >= (2, 5): 11 from tests_25 import TransactionContextManagerTests 12 13 8 14 class TransactionTests(TransactionTestCase): 9 15 def create_a_reporter_then_fail(self, first, last): 10 16 a = Reporter(first_name=first, last_name=last) -
new file tests/modeltests/transactions/tests_25.py
diff --git a/tests/modeltests/transactions/tests_25.py b/tests/modeltests/transactions/tests_25.py new file mode 100644 index 0000000..5c191f2
- + 1 from __future__ import with_statement 2 3 from django.db import connection, transaction, IntegrityError 4 from django.test import TransactionTestCase, skipUnlessDBFeature 5 6 from models import Reporter 7 8 9 class TransactionContextManagerTests(TransactionTestCase): 10 def create_reporter_and_fail(self): 11 Reporter.objects.create(first_name="Bob", last_name="Holtzman") 12 raise Exception 13 14 @skipUnlessDBFeature('supports_transactions') 15 def test_autocommit(self): 16 """ 17 The default behavior is to autocommit after each save() action. 18 """ 19 with self.assertRaises(Exception): 20 self.create_reporter_and_fail() 21 # The object created before the exception still exists 22 self.assertEqual(Reporter.objects.count(), 1) 23 24 @skipUnlessDBFeature('supports_transactions') 25 def test_autocommit_context_manager(self): 26 """ 27 The autocommit context manager works exactly the same as the default 28 behavior. 29 """ 30 with self.assertRaises(Exception): 31 with transaction.autocommit(): 32 self.create_reporter_and_fail() 33 34 self.assertEqual(Reporter.objects.count(), 1) 35 36 @skipUnlessDBFeature('supports_transactions') 37 def test_autocommit_decorator_with_using(self): 38 """ 39 The autocommit context manager also works with a using argument. 40 """ 41 with self.assertRaises(Exception): 42 with transaction.autocommit(): 43 self.create_reporter_and_fail() 44 45 self.assertEqual(Reporter.objects.count(), 1) 46 47 @skipUnlessDBFeature('supports_transactions') 48 def test_commit_on_success(self): 49 """ 50 With the commit_on_success context manager, the transaction is only 51 committed if the block doesn't throw an exception. 52 """ 53 with self.assertRaises(Exception): 54 with transaction.commit_on_success(): 55 self.create_reporter_and_fail() 56 57 self.assertEqual(Reporter.objects.count(), 0) 58 59 @skipUnlessDBFeature('supports_transactions') 60 def test_commit_on_success_with_using(self): 61 """ 62 The commit_on_success context manager also works with a using argument. 63 """ 64 with self.assertRaises(Exception): 65 with transaction.commit_on_success(using="default"): 66 self.create_reporter_and_fail() 67 68 self.assertEqual(Reporter.objects.count(), 0) 69 70 @skipUnlessDBFeature('supports_transactions') 71 def test_commit_on_success_succeed(self): 72 """ 73 If there aren't any exceptions, the data will get saved. 74 """ 75 Reporter.objects.create(first_name="Alice", last_name="Smith") 76 with transaction.commit_on_success(): 77 Reporter.objects.filter(first_name="Alice").delete() 78 79 self.assertQuerysetEqual(Reporter.objects.all(), []) 80 81 @skipUnlessDBFeature('supports_transactions') 82 def test_manually_managed(self): 83 """ 84 You can manually manage transactions if you really want to, but you 85 have to remember to commit/rollback. 86 """ 87 with transaction.commit_manually(): 88 Reporter.objects.create(first_name="Libby", last_name="Holtzman") 89 transaction.commit() 90 self.assertEqual(Reporter.objects.count(), 1) 91 92 @skipUnlessDBFeature('supports_transactions') 93 def test_manually_managed_mistake(self): 94 """ 95 If you forget, you'll get bad errors. 96 """ 97 with self.assertRaises(transaction.TransactionManagementError): 98 with transaction.commit_manually(): 99 Reporter.objects.create(first_name="Scott", last_name="Browning") 100 101 @skipUnlessDBFeature('supports_transactions') 102 def test_manually_managed_with_using(self): 103 """ 104 The commit_manually function also works with a using argument. 105 """ 106 with self.assertRaises(transaction.TransactionManagementError): 107 with transaction.commit_manually(using="default"): 108 Reporter.objects.create(first_name="Walter", last_name="Cronkite") 109 110 @skipUnlessDBFeature('requires_rollback_on_dirty_transaction') 111 def test_bad_sql(self): 112 """ 113 Regression for #11900: If a block wrapped by commit_on_success 114 writes a transaction that can't be committed, that transaction should 115 be rolled back. The bug is only visible using the psycopg2 backend, 116 though the fix is generally a good idea. 117 """ 118 with self.assertRaises(IntegrityError): 119 with transaction.commit_on_success(): 120 cursor = connection.cursor() 121 cursor.execute("INSERT INTO transactions_reporter (first_name, last_name) VALUES ('Douglas', 'Adams');") 122 transaction.set_dirty() 123 transaction.rollback()