Permalink
Browse files

Fixed #10771 -- added support for using the transaction management fu…

…nctions as context managers in Python 2.5 and above. Thanks to Jacob for help with the docs.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14288 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
1 parent cfbba28 commit 27db9378cfba804f532442d9c3fc48f8249e0d46 @alex alex committed Oct 19, 2010
@@ -11,6 +11,7 @@
Managed transactions don't do those commits, but will need some kind of manual
or implicit commits or rollbacks.
"""
+import sys
try:
import thread
@@ -20,8 +21,9 @@
from functools import wraps
except ImportError:
from django.utils.functional import wraps # Python 2.4 fallback.
-from django.db import connections, DEFAULT_DB_ALIAS
+
from django.conf import settings
+from django.db import connections, DEFAULT_DB_ALIAS
class TransactionManagementError(Exception):
"""
@@ -257,31 +259,79 @@ def savepoint_commit(sid, using=None):
# DECORATORS #
##############
-def autocommit(using=None):
+class Transaction(object):
"""
- Decorator that activates commit on save. This is Django's default behavior;
- this decorator is useful if you globally activated transaction management in
- your settings file and want the default behavior in some view functions.
+ Acts as either a decorator, or a context manager. If it's a decorator it
+ takes a function and returns a wrapped function. If it's a contextmanager
+ it's used with the ``with`` statement. In either event entering/exiting
+ are called before and after, respectively, the function/block is executed.
+
+ autocommit, commit_on_success, and commit_manually contain the
+ implementations of entering and exiting.
"""
- def inner_autocommit(func, db=None):
- def _autocommit(*args, **kw):
+ def __init__(self, entering, exiting, using):
+ self.entering = entering
+ self.exiting = exiting
+ self.using = using
+
+ def __enter__(self):
+ self.entering(self.using)
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.exiting(exc_value, self.using)
+
+ def __call__(self, func):
+ @wraps(func)
+ def inner(*args, **kwargs):
+ # Once we drop support for Python 2.4 this block should become:
+ # with self:
+ # func(*args, **kwargs)
+ self.__enter__()
try:
- enter_transaction_management(managed=False, using=db)
- managed(False, using=db)
- return func(*args, **kw)
- finally:
- leave_transaction_management(using=db)
- return wraps(func)(_autocommit)
+ res = func(*args, **kwargs)
+ except:
+ self.__exit__(*sys.exc_info())
+ raise
+ else:
+ self.__exit__(None, None, None)
+ return res
+ return inner
+def _transaction_func(entering, exiting, using):
+ """
+ Takes 3 things, an entering function (what to do to start this block of
+ transaction management), an exiting function (what to do to end it, on both
+ success and failure, and using which can be: None, indiciating using is
+ DEFAULT_DB_ALIAS, a callable, indicating that using is DEFAULT_DB_ALIAS and
+ to return the function already wrapped.
+
+ Returns either a Transaction objects, which is both a decorator and a
+ context manager, or a wrapped function, if using is a callable.
+ """
# Note that although the first argument is *called* `using`, it
# may actually be a function; @autocommit and @autocommit('foo')
# are both allowed forms.
if using is None:
using = DEFAULT_DB_ALIAS
if callable(using):
- return inner_autocommit(using, DEFAULT_DB_ALIAS)
- return lambda func: inner_autocommit(func, using)
+ return Transaction(entering, exiting, DEFAULT_DB_ALIAS)(using)
+ return Transaction(entering, exiting, using)
+
+
+def autocommit(using=None):
+ """
+ Decorator that activates commit on save. This is Django's default behavior;
+ this decorator is useful if you globally activated transaction management in
+ your settings file and want the default behavior in some view functions.
+ """
+ def entering(using):
+ enter_transaction_management(managed=False, using=using)
+ managed(False, using=using)
+ def exiting(exc_value, using):
+ leave_transaction_management(using=using)
+
+ return _transaction_func(entering, exiting, using)
def commit_on_success(using=None):
"""
@@ -290,38 +340,23 @@ def commit_on_success(using=None):
a rollback is made. This is one of the most common ways to do transaction
control in Web apps.
"""
- def inner_commit_on_success(func, db=None):
- def _commit_on_success(*args, **kw):
- try:
- enter_transaction_management(using=db)
- managed(True, using=db)
+ def entering(using):
+ enter_transaction_management(using=using)
+ managed(True, using=using)
+
+ def exiting(exc_value, using):
+ if exc_value is not None:
+ if is_dirty(using=using):
+ rollback(using=using)
+ else:
+ if is_dirty(using=using):
try:
- res = func(*args, **kw)
+ commit(using=using)
except:
- # All exceptions must be handled here (even string ones).
- if is_dirty(using=db):
- rollback(using=db)
+ rollback(using=using)
raise
- else:
- if is_dirty(using=db):
- try:
- commit(using=db)
- except:
- rollback(using=db)
- raise
- return res
- finally:
- leave_transaction_management(using=db)
- return wraps(func)(_commit_on_success)
- # Note that although the first argument is *called* `using`, it
- # may actually be a function; @autocommit and @autocommit('foo')
- # are both allowed forms.
- if using is None:
- using = DEFAULT_DB_ALIAS
- if callable(using):
- return inner_commit_on_success(using, DEFAULT_DB_ALIAS)
- return lambda func: inner_commit_on_success(func, using)
+ return _transaction_func(entering, exiting, using)
def commit_manually(using=None):
"""
@@ -330,22 +365,11 @@ def commit_manually(using=None):
own -- it's up to the user to call the commit and rollback functions
themselves.
"""
- def inner_commit_manually(func, db=None):
- def _commit_manually(*args, **kw):
- try:
- enter_transaction_management(using=db)
- managed(True, using=db)
- return func(*args, **kw)
- finally:
- leave_transaction_management(using=db)
+ def entering(using):
+ enter_transaction_management(using=using)
+ managed(True, using=using)
- return wraps(func)(_commit_manually)
+ def exiting(exc_value, using):
+ leave_transaction_management(using=using)
- # Note that although the first argument is *called* `using`, it
- # may actually be a function; @autocommit and @autocommit('foo')
- # are both allowed forms.
- if using is None:
- using = DEFAULT_DB_ALIAS
- if callable(using):
- return inner_commit_manually(using, DEFAULT_DB_ALIAS)
- return lambda func: inner_commit_manually(func, using)
+ return _transaction_func(entering, exiting, using)
View
@@ -73,6 +73,19 @@ you just won't get any of the nice new unittest2 features.
.. _unittest2: http://pypi.python.org/pypi/unittest2
+Transaction context managers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Users of Python 2.5 and above may now use :ref:`transaction management functions
+<transaction-management-functions>` as `context managers`_. For example::
+
+ with transaction.autocommit():
+ # ...
+
+.. _context managers: http://docs.python.org/glossary.html#term-context-manager
+
+For more information, see :ref:`transaction-management-functions`.
+
Everything else
~~~~~~~~~~~~~~~
Oops, something went wrong.

0 comments on commit 27db937

Please sign in to comment.