Ticket #10771: django-transactions.2.diff

File django-transactions.2.diff, 13.4 KB (added by Alex Gaynor, 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.  
    1111Managed transactions don't do those commits, but will need some kind of manual
    1212or implicit commits or rollbacks.
    1313"""
     14import sys
    1415
    1516try:
    1617    import thread
    try:  
    2021    from functools import wraps
    2122except ImportError:
    2223    from django.utils.functional import wraps  # Python 2.4 fallback.
    23 from django.db import connections, DEFAULT_DB_ALIAS
     24
    2425from django.conf import settings
     26from django.db import connections, DEFAULT_DB_ALIAS
    2527
    2628class TransactionManagementError(Exception):
    2729    """
    def savepoint_commit(sid, using=None):  
    257259# DECORATORS #
    258260##############
    259261
    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):
     262class 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__()
    268281            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
    275290
     291def _transaction_func(entering, exiting, using):
    276292    # Note that although the first argument is *called* `using`, it
    277293    # may actually be a function; @autocommit and @autocommit('foo')
    278294    # are both allowed forms.
    279295    if using is None:
    280296        using = DEFAULT_DB_ALIAS
    281297    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
     302def 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)
    284314
     315    return _transaction_func(entering, exiting, using)
    285316
    286317def commit_on_success(using=None):
    287318    """
    def commit_on_success(using=None):  
    290321    a rollback is made. This is one of the most common ways to do transaction
    291322    control in Web apps.
    292323    """
    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):
    298334                try:
    299                     res = func(*args, **kw)
     335                    commit(using=using)
    300336                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)
    304338                    raise
    305                 else:
    306                     if is_dirty(using=db):
    307                         try:
    308                             commit(using=db)
    309                         except:
    310                             rollback(using=db)
    311                             raise
    312                 return res
    313             finally:
    314                 leave_transaction_management(using=db)
    315         return wraps(func)(_commit_on_success)
    316339
    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)
    325341
    326342def commit_manually(using=None):
    327343    """
    def commit_manually(using=None):  
    330346    own -- it's up to the user to call the commit and rollback functions
    331347    themselves.
    332348    """
    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)
    341352
    342         return wraps(func)(_commit_manually)
     353    def exiting(exc_value, using):
     354        leave_transaction_management(using=using)
    343355
    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.  
    5656
    5757.. _unittest2: http://pypi.python.org/pypi/unittest2
    5858
     59Transactions context managers
     60~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     61
     62Transactions functions can now be used as a context manager, with Python 2.5
     63and above.  For example:
     64
     65    with transaction.autocommit():
     66        # ...
     67
     68For more information see :doc:`the transactions documentation
     69</topic/db/transactions>`.
     70
     71
    5972Everything else
    6073~~~~~~~~~~~~~~~
    6174
  • 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  
    6161behavior applies to.  If no alias is specified then the ``"default"`` database
    6262is used.
    6363
     64In addition, all of these decorators can be used as a context manager with
     65Python 2.5 and above.
     66
    6467.. note::
    6568
    6669    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  
     1import sys
     2
    13from django.db import connection, transaction, IntegrityError, DEFAULT_DB_ALIAS
    24from django.conf import settings
    35from django.test import TransactionTestCase, skipUnlessDBFeature
    from django.test import TransactionTestCase, skipUnlessDBFeature  
    57from models import Reporter
    68
    79
     10if sys.version_info >= (2, 5):
     11    from tests_25 import TransactionContextManagerTests
     12
     13
    814class TransactionTests(TransactionTestCase):
    915    def create_a_reporter_then_fail(self, first, last):
    1016        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
    - +  
     1from __future__ import with_statement
     2
     3from django.db import connection, transaction, IntegrityError
     4from django.test import TransactionTestCase, skipUnlessDBFeature
     5
     6from models import Reporter
     7
     8
     9class 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()
Back to Top