Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added some assertions to enforce the atomicity of atomic.

  • Loading branch information...
commit 7c46c8d5f27fe305507359588ca0635b6d87c59a 1 parent d7bc4fb
@aaugustin aaugustin authored
View
1  django/db/__init__.py
@@ -70,6 +70,7 @@ def reset_queries(**kwargs):
# their lifetime. NB: abort() doesn't do anything outside of a transaction.
def close_old_connections(**kwargs):
for conn in connections.all():
+ # Remove this when the legacy transaction management goes away.
try:
conn.abort()
except DatabaseError:
View
15 django/db/backends/__init__.py
@@ -157,6 +157,7 @@ def commit(self):
Commits a transaction and resets the dirty flag.
"""
self.validate_thread_sharing()
+ self.validate_no_atomic_block()
self._commit()
self.set_clean()
@@ -165,6 +166,7 @@ def rollback(self):
Rolls back a transaction and resets the dirty flag.
"""
self.validate_thread_sharing()
+ self.validate_no_atomic_block()
self._rollback()
self.set_clean()
@@ -265,6 +267,8 @@ def enter_transaction_management(self, managed=True, forced=False):
If you switch off transaction management and there is a pending
commit/rollback, the data will be commited, unless "forced" is True.
"""
+ self.validate_no_atomic_block()
+
self.transaction_state.append(managed)
if not managed and self.is_dirty() and not forced:
@@ -280,6 +284,8 @@ def leave_transaction_management(self):
over to the surrounding block, as a commit will commit all changes, even
those from outside. (Commits are on connection level.)
"""
+ self.validate_no_atomic_block()
+
if self.transaction_state:
del self.transaction_state[-1]
else:
@@ -305,10 +311,19 @@ def set_autocommit(self, autocommit=True):
"""
Enable or disable autocommit.
"""
+ self.validate_no_atomic_block()
self.ensure_connection()
self._set_autocommit(autocommit)
self.autocommit = autocommit
+ def validate_no_atomic_block(self):
+ """
+ Raise an error if an atomic block is active.
+ """
+ if self.in_atomic_block:
+ raise TransactionManagementError(
+ "This is forbidden when an 'atomic' block is active.")
+
def abort(self):
"""
Roll back any ongoing transaction and clean the transaction state
View
18 django/db/transaction.py
@@ -367,6 +367,9 @@ def autocommit(using=None):
this decorator is useful if you globally activated transaction management in
your settings file and want the default behavior in some view functions.
"""
+ warnings.warn("autocommit is deprecated in favor of set_autocommit.",
+ PendingDeprecationWarning, stacklevel=2)
+
def entering(using):
enter_transaction_management(managed=False, using=using)
@@ -382,6 +385,9 @@ 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.
"""
+ warnings.warn("commit_on_success is deprecated in favor of atomic.",
+ PendingDeprecationWarning, stacklevel=2)
+
def entering(using):
enter_transaction_management(using=using)
@@ -409,6 +415,9 @@ def commit_manually(using=None):
own -- it's up to the user to call the commit and rollback functions
themselves.
"""
+ warnings.warn("commit_manually is deprecated in favor of set_autocommit.",
+ PendingDeprecationWarning, stacklevel=2)
+
def entering(using):
enter_transaction_management(using=using)
@@ -420,10 +429,15 @@ def exiting(exc_value, using):
def commit_on_success_unless_managed(using=None):
"""
Transitory API to preserve backwards-compatibility while refactoring.
+
+ Once the legacy transaction management is fully deprecated, this should
+ simply be replaced by atomic. Until then, it's necessary to avoid making a
+ commit where Django didn't use to, since entering atomic in managed mode
+ triggers a commmit.
"""
connection = get_connection(using)
- if connection.autocommit and not connection.in_atomic_block:
- return commit_on_success(using)
+ if connection.autocommit or connection.in_atomic_block:
+ return atomic(using)
else:
def entering(using):
pass
View
4 docs/internals/deprecation.txt
@@ -329,6 +329,10 @@ these changes.
1.8
---
+* The decorators and context managers ``django.db.transaction.autocommit``,
+ ``commit_on_success`` and ``commit_manually`` will be removed. See
+ :ref:`transactions-upgrading-from-1.5`.
+
* The :ttag:`cycle` and :ttag:`firstof` template tags will auto-escape their
arguments. In 1.6 and 1.7, this behavior is provided by the version of these
tags in the ``future`` template tag library.
View
6 docs/releases/1.3-alpha-1.txt
@@ -105,16 +105,14 @@ you just won't get any of the nice new unittest2 features.
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::
+Users of Python 2.5 and above may now use 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`.
-
Configurable delete-cascade
~~~~~~~~~~~~~~~~~~~~~~~~~~~
View
6 docs/releases/1.3.txt
@@ -148,16 +148,14 @@ you just won't get any of the nice new unittest2 features.
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::
+Users of Python 2.5 and above may now use 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`.
-
Configurable delete-cascade
~~~~~~~~~~~~~~~~~~~~~~~~~~~
View
17 docs/releases/1.6.txt
@@ -39,7 +39,7 @@ should improve performance. The existing APIs were deprecated, and new APIs
were introduced, as described in :doc:`/topics/db/transactions`.
Please review carefully the list of :ref:`known backwards-incompatibilities
-<transactions-changes-from-1.5>` to determine if you need to make changes in
+<transactions-upgrading-from-1.5>` to determine if you need to make changes in
your code.
Persistent database connections
@@ -163,7 +163,7 @@ Backwards incompatible changes in 1.6
* Database-level autocommit is enabled by default in Django 1.6. While this
doesn't change the general spirit of Django's transaction management, there
are a few known backwards-incompatibities, described in the :ref:`transaction
- management docs <transactions-changes-from-1.5>`. You should review your code
+ management docs <transactions-upgrading-from-1.5>`. You should review your code
to determine if you're affected.
* In previous versions, database-level autocommit was only an option for
@@ -256,6 +256,19 @@ Backwards incompatible changes in 1.6
Features deprecated in 1.6
==========================
+Transaction management APIs
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Transaction management was completely overhauled in Django 1.6, and the
+current APIs are deprecated:
+
+- :func:`django.db.transaction.autocommit`
+- :func:`django.db.transaction.commit_on_success`
+- :func:`django.db.transaction.commit_manually`
+
+The reasons for this change and the upgrade path are described in the
+:ref:`transactions documentation <transactions-upgrading-from-1.5>`.
+
Changes to :ttag:`cycle` and :ttag:`firstof`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
View
439 docs/topics/db/transactions.txt
@@ -24,7 +24,7 @@ immediately committed to the database. :ref:`See below for details
.. versionchanged:: 1.6
Previous version of Django featured :ref:`a more complicated default
- behavior <transactions-changes-from-1.5>`.
+ behavior <transactions-upgrading-from-1.5>`.
Tying transactions to HTTP requests
-----------------------------------
@@ -89,7 +89,7 @@ Django provides a single API to control database transactions.
database. If this argument isn't provided, Django uses the ``"default"``
database.
- ``atomic`` is usable both as a decorator::
+ ``atomic`` is usable both as a `decorator`_::
from django.db import transaction
@@ -98,7 +98,7 @@ Django provides a single API to control database transactions.
# This code executes inside a transaction.
do_stuff()
- and as a context manager::
+ and as a `context manager`_::
from django.db import transaction
@@ -110,6 +110,9 @@ Django provides a single API to control database transactions.
# This code executes inside a transaction.
do_more_stuff()
+ .. _decorator: http://docs.python.org/glossary.html#term-decorator
+ .. _context manager: http://docs.python.org/glossary.html#term-context-manager
+
Wrapping ``atomic`` in a try/except block allows for natural handling of
integrity errors::
@@ -145,158 +148,116 @@ Django provides a single API to control database transactions.
- releases or rolls back to the savepoint when exiting an inner block;
- commits or rolls back the transaction when exiting the outermost block.
-.. _transaction-management-functions:
-
-Controlling transaction management in views
-===========================================
-
-For most people, implicit request-based transactions work wonderfully. However,
-if you need more fine-grained control over how transactions are managed, you can
-use a set of functions in ``django.db.transaction`` to control transactions on a
-per-function or per-code-block basis.
-
-These functions, described in detail below, can be used in two different ways:
-
-* As a decorator_ on a particular function. For example::
-
- from django.db import transaction
-
- @transaction.commit_on_success
- def viewfunc(request):
- # ...
- # this code executes inside a transaction
- # ...
-
-* As a `context manager`_ around a particular block of code::
-
- from django.db import transaction
-
- def viewfunc(request):
- # ...
- # this code executes using default transaction management
- # ...
-
- with transaction.commit_on_success():
- # ...
- # this code executes inside a transaction
- # ...
-
-Both techniques work with all supported version of Python.
+.. _topics-db-transactions-savepoints:
-.. _decorator: http://docs.python.org/glossary.html#term-decorator
-.. _context manager: http://docs.python.org/glossary.html#term-context-manager
+Savepoints
+==========
-For maximum compatibility, all of the examples below show transactions using the
-decorator syntax, but all of the follow functions may be used as context
-managers, too.
+A savepoint is a marker within a transaction that enables you to roll back
+part of a transaction, rather than the full transaction. Savepoints are
+available with the SQLite (≥ 3.6.8), PostgreSQL, Oracle and MySQL (when using
+the InnoDB storage engine) backends. Other backends provide the savepoint
+functions, but they're empty operations -- they don't actually do anything.
-.. note::
+Savepoints aren't especially useful if you are using autocommit, the default
+behavior of Django. However, once you open a transaction with :func:`atomic`,
+you build up a series of database operations awaiting a commit or rollback. If
+you issue a rollback, the entire transaction is rolled back. Savepoints
+provide the ability to perform a fine-grained rollback, rather than the full
+rollback that would be performed by ``transaction.rollback()``.
- Although the examples below use view functions as examples, these
- decorators and context managers can be used anywhere in your code
- that you need to deal with transactions.
+.. versionchanged:: 1.6
-.. _topics-db-transactions-autocommit:
+When the :func:`atomic` decorator is nested, it creates a savepoint to allow
+partial commit or rollback. You're strongly encouraged to use :func:`atomic`
+rather than the functions described below, but they're still part of the
+public API, and there's no plan to deprecate them.
-.. function:: autocommit
+Each of these functions takes a ``using`` argument which should be the name of
+a database for which the behavior applies. If no ``using`` argument is
+provided then the ``"default"`` database is used.
- Use the ``autocommit`` decorator to switch a view function to Django's
- default commit behavior.
+Savepoints are controlled by three methods on the transaction object:
- Example::
+.. method:: transaction.savepoint(using=None)
- from django.db import transaction
+ Creates a new savepoint. This marks a point in the transaction that
+ is known to be in a "good" state.
- @transaction.autocommit
- def viewfunc(request):
- ....
+ Returns the savepoint ID (sid).
- @transaction.autocommit(using="my_other_database")
- def viewfunc2(request):
- ....
+.. method:: transaction.savepoint_commit(sid, using=None)
- Within ``viewfunc()``, transactions will be committed as soon as you call
- ``model.save()``, ``model.delete()``, or any other function that writes to
- the database. ``viewfunc2()`` will have this same behavior, but for the
- ``"my_other_database"`` connection.
+ Updates the savepoint to include any operations that have been performed
+ since the savepoint was created, or since the last commit.
-.. function:: commit_on_success
+.. method:: transaction.savepoint_rollback(sid, using=None)
- Use the ``commit_on_success`` decorator to use a single transaction for all
- the work done in a function::
+ Rolls the transaction back to the last point at which the savepoint was
+ committed.
- from django.db import transaction
+The following example demonstrates the use of savepoints::
- @transaction.commit_on_success
- def viewfunc(request):
- ....
+ from django.db import transaction
- @transaction.commit_on_success(using="my_other_database")
- def viewfunc2(request):
- ....
+ # open a transaction
+ @transaction.atomic
+ def viewfunc(request):
- If the function returns successfully, then Django will commit all work done
- within the function at that point. If the function raises an exception,
- though, Django will roll back the transaction.
+ a.save()
+ # transaction now contains a.save()
-.. function:: commit_manually
+ sid = transaction.savepoint()
- Use the ``commit_manually`` decorator if you need full control over
- transactions. It tells Django you'll be managing the transaction on your
- own.
+ b.save()
+ # transaction now contains a.save() and b.save()
- Whether you are writing or simply reading from the database, you must
- ``commit()`` or ``rollback()`` explicitly or Django will raise a
- :exc:`TransactionManagementError` exception. This is required when reading
- from the database because ``SELECT`` statements may call functions which
- modify tables, and thus it is impossible to know if any data has been
- modified.
+ if want_to_keep_b:
+ transaction.savepoint_commit(sid)
+ # open transaction still contains a.save() and b.save()
+ else:
+ transaction.savepoint_rollback(sid)
+ # open transaction now contains only a.save()
- Manual transaction management looks like this::
+Autocommit
+==========
- from django.db import transaction
+.. _autocommit-details:
- @transaction.commit_manually
- def viewfunc(request):
- ...
- # You can commit/rollback however and whenever you want
- transaction.commit()
- ...
+Why Django uses autocommit
+--------------------------
- # But you've got to remember to do it yourself!
- try:
- ...
- except:
- transaction.rollback()
- else:
- transaction.commit()
+In the SQL standards, each SQL query starts a transaction, unless one is
+already in progress. Such transactions must then be committed or rolled back.
- @transaction.commit_manually(using="my_other_database")
- def viewfunc2(request):
- ....
+This isn't always convenient for application developers. To alleviate this
+problem, most databases provide an autocommit mode. When autocommit is turned
+on, each SQL query is wrapped in its own transaction. In other words, the
+transaction is not only automatically started, but also automatically
+committed.
-.. _topics-db-transactions-requirements:
+:pep:`249`, the Python Database API Specification v2.0, requires autocommit to
+be initially turned off. Django overrides this default and turns autocommit
+on.
-Requirements for transaction handling
-=====================================
+To avoid this, you can :ref:`deactivate the transaction management
+<deactivate-transaction-management>`, but it isn't recommended.
-Django requires that every transaction that is opened is closed before the
-completion of a request.
+.. versionchanged:: 1.6
+ Before Django 1.6, autocommit was turned off, and it was emulated by
+ forcing a commit after write operations in the ORM.
-If you are using :func:`autocommit` (the default commit mode) or
-:func:`commit_on_success`, this will be done for you automatically. However,
-if you are manually managing transactions (using the :func:`commit_manually`
-decorator), you must ensure that the transaction is either committed or rolled
-back before a request is completed.
+.. warning::
-This applies to all database operations, not just write operations. Even
-if your transaction only reads from the database, the transaction must
-be committed or rolled back before you complete a request.
+ If you're using the database API directly — for instance, you're running
+ SQL queries with ``cursor.execute()`` — be aware that autocommit is on,
+ and consider wrapping your operations in a transaction, with
+ :func:`atomic`, to ensure consistency.
.. _managing-autocommit:
Managing autocommit
-===================
+-------------------
.. versionadded:: 1.6
@@ -310,10 +271,17 @@ database connection, if you need to.
These functions take a ``using`` argument which should be the name of a
database. If it isn't provided, Django uses the ``"default"`` database.
+Autocommit is initially turned on. If you turn it off, it's your
+responsibility to restore it.
+
+:func:`atomic` requires autocommit to be turned on; it will raise an exception
+if autocommit is off. Django will also refuse to turn autocommit off when an
+:func:`atomic` block is active, because that would break atomicity.
+
.. _deactivate-transaction-management:
-How to globally deactivate transaction management
-=================================================
+Deactivating transaction management
+-----------------------------------
Control freaks can totally disable all transaction management by setting
:setting:`TRANSACTIONS_MANAGED` to ``True`` in the Django settings file. If
@@ -328,71 +296,6 @@ something really strange.
In almost all situations, you'll be better off using the default behavior, or
the transaction middleware, and only modify selected functions as needed.
-.. _topics-db-transactions-savepoints:
-
-Savepoints
-==========
-
-A savepoint is a marker within a transaction that enables you to roll back
-part of a transaction, rather than the full transaction. Savepoints are
-available with the SQLite (≥ 3.6.8), PostgreSQL, Oracle and MySQL (when using
-the InnoDB storage engine) backends. Other backends provide the savepoint
-functions, but they're empty operations -- they don't actually do anything.
-
-Savepoints aren't especially useful if you are using the default
-``autocommit`` behavior of Django. However, if you are using
-``commit_on_success`` or ``commit_manually``, each open transaction will build
-up a series of database operations, awaiting a commit or rollback. If you
-issue a rollback, the entire transaction is rolled back. Savepoints provide
-the ability to perform a fine-grained rollback, rather than the full rollback
-that would be performed by ``transaction.rollback()``.
-
-Each of these functions takes a ``using`` argument which should be the name of
-a database for which the behavior applies. If no ``using`` argument is
-provided then the ``"default"`` database is used.
-
-Savepoints are controlled by three methods on the transaction object:
-
-.. method:: transaction.savepoint(using=None)
-
- Creates a new savepoint. This marks a point in the transaction that
- is known to be in a "good" state.
-
- Returns the savepoint ID (sid).
-
-.. method:: transaction.savepoint_commit(sid, using=None)
-
- Updates the savepoint to include any operations that have been performed
- since the savepoint was created, or since the last commit.
-
-.. method:: transaction.savepoint_rollback(sid, using=None)
-
- Rolls the transaction back to the last point at which the savepoint was
- committed.
-
-The following example demonstrates the use of savepoints::
-
- from django.db import transaction
-
- @transaction.commit_manually
- def viewfunc(request):
-
- a.save()
- # open transaction now contains a.save()
- sid = transaction.savepoint()
-
- b.save()
- # open transaction now contains a.save() and b.save()
-
- if want_to_keep_b:
- transaction.savepoint_commit(sid)
- # open transaction still contains a.save() and b.save()
- else:
- transaction.savepoint_rollback(sid)
- # open transaction now contains only a.save()
-
- transaction.commit()
-
Database-specific notes
=======================
@@ -477,45 +380,57 @@ transaction. For example::
In this example, ``a.save()`` will not be undone in the case where
``b.save()`` raises an exception.
-Under the hood
-==============
+.. _transactions-upgrading-from-1.5:
-.. _autocommit-details:
+Changes from Django 1.5 and earlier
+===================================
-Details on autocommit
----------------------
+The features described below were deprecated in Django 1.6 and will be removed
+in Django 1.8. They're documented in order to ease the migration to the new
+transaction management APIs.
-In the SQL standards, each SQL query starts a transaction, unless one is
-already in progress. Such transactions must then be committed or rolled back.
+Legacy APIs
+-----------
-This isn't always convenient for application developers. To alleviate this
-problem, most databases provide an autocommit mode. When autocommit is turned
-on, each SQL query is wrapped in its own transaction. In other words, the
-transaction is not only automatically started, but also automatically
-committed.
+The following functions, defined in ``django.db.transaction``, provided a way
+to control transactions on a per-function or per-code-block basis. They could
+be used as decorators or as context managers, and they accepted a ``using``
+argument, exactly like :func:`atomic`.
-:pep:`249`, the Python Database API Specification v2.0, requires autocommit to
-be initially turned off. Django overrides this default and turns autocommit
-on.
+.. function:: autocommit
-To avoid this, you can :ref:`deactivate the transaction management
-<deactivate-transaction-management>`, but it isn't recommended.
+ Enable Django's default autocommit behavior.
-.. versionchanged:: 1.6
- Before Django 1.6, autocommit was turned off, and it was emulated by
- forcing a commit after write operations in the ORM.
+ Transactions will be committed as soon as you call ``model.save()``,
+ ``model.delete()``, or any other function that writes to the database.
-.. warning::
+.. function:: commit_on_success
- If you're using the database API directly — for instance, you're running
- SQL queries with ``cursor.execute()`` — be aware that autocommit is on,
- and consider wrapping your operations in a transaction to ensure
- consistency.
+ Use a single transaction for all the work done in a function.
+
+ If the function returns successfully, then Django will commit all work done
+ within the function at that point. If the function raises an exception,
+ though, Django will roll back the transaction.
+
+.. function:: commit_manually
+
+ Tells Django you'll be managing the transaction on your own.
+
+ Whether you are writing or simply reading from the database, you must
+ ``commit()`` or ``rollback()`` explicitly or Django will raise a
+ :exc:`TransactionManagementError` exception. This is required when reading
+ from the database because ``SELECT`` statements may call functions which
+ modify tables, and thus it is impossible to know if any data has been
+ modified.
.. _transaction-states:
-Transaction management states
------------------------------
+Transaction states
+------------------
+
+The three functions described above relied on a concept called "transaction
+states". This mechanisme was deprecated in Django 1.6, but it's still
+available until Django 1.8..
At any time, each database connection is in one of these two states:
@@ -529,35 +444,80 @@ Django starts in auto mode. ``TransactionMiddleware``,
Internally, Django keeps a stack of states. Activations and deactivations must
be balanced.
-For example, at the beginning of each HTTP request, ``TransactionMiddleware``
-switches to managed mode; at the end of the request, it commits or rollbacks,
+For example, ``commit_on_success`` switches to managed mode when entering the
+block of code it controls; when exiting the block, it commits or rollbacks,
and switches back to auto mode.
-.. admonition:: Nesting decorators / context managers
+So :func:`commit_on_success` really has two effects: it changes the
+transaction state and it defines an transaction block. Nesting will give the
+expected results in terms of transaction state, but not in terms of
+transaction semantics. Most often, the inner block will commit, breaking the
+atomicity of the outer block.
- :func:`commit_on_success` has two effects: it changes the transaction
- state, and defines an atomic transaction block.
+:func:`autocommit` and :func:`commit_manually` have similar limitations.
- Nesting with :func:`autocommit` and :func:`commit_manually` will give the
- expected results in terms of transaction state, but not in terms of
- transaction semantics. Most often, the inner block will commit, breaking
- the atomicity of the outer block.
+API changes
+-----------
-Django currently doesn't provide any APIs to create transactions in auto mode.
+Managing transactions
+~~~~~~~~~~~~~~~~~~~~~
-.. _transactions-changes-from-1.5:
+Starting with Django 1.6, :func:`atomic` is the only supported API for
+defining a transaction. Unlike the deprecated APIs, it's nestable and always
+guarantees atomicity.
-Changes from Django 1.5 and earlier
-===================================
+In most cases, it will be a drop-in replacement for :func:`commit_on_success`.
-Since version 1.6, Django uses database-level autocommit in auto mode.
+During the deprecation period, it's possible to use :func:`atomic` within
+:func:`autocommit`, :func:`commit_on_success` or :func:`commit_manually`.
+However, the reverse is forbidden, because nesting the old decorators /
+context managers breaks atomicity.
+
+If you enter :func:`atomic` while you're in managed mode, it will trigger a
+commit to start from a clean slate.
+
+Managing autocommit
+~~~~~~~~~~~~~~~~~~~
+
+Django 1.6 introduces an explicit :ref:`API for mananging autocommit
+<managing-autocommit>`.
+
+To disable autocommit temporarily, instead of::
+ with transaction.commit_manually():
+ # do stuff
+
+you should now use::
+
+ transaction.set_autocommit(autocommit=False)
+ try:
+ # do stuff
+ finally:
+ transaction.set_autocommit(autocommit=True)
+
+To enable autocommit temporarily, instead of::
+
+ with transaction.autocommit():
+ # do stuff
+
+you should now use::
+
+ transaction.set_autocommit(autocommit=True)
+ try:
+ # do stuff
+ finally:
+ transaction.set_autocommit(autocommit=False)
+
+Backwards incompatibilities
+---------------------------
+
+Since version 1.6, Django uses database-level autocommit in auto mode.
Previously, it implemented application-level autocommit by triggering a commit
after each ORM write.
-As a consequence, each database query (for instance, an
-ORM read) started a transaction that lasted until the next ORM write. Such
-"automatic transactions" no longer exist in Django 1.6.
+As a consequence, each database query (for instance, an ORM read) started a
+transaction that lasted until the next ORM write. Such "automatic
+transactions" no longer exist in Django 1.6.
There are four known scenarios where this is backwards-incompatible.
@@ -565,7 +525,7 @@ Note that managed mode isn't affected at all. This section assumes auto mode.
See the :ref:`description of modes <transaction-states>` above.
Sequences of custom SQL queries
--------------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you're executing several :ref:`custom SQL queries <executing-custom-sql>`
in a row, each one now runs in its own transaction, instead of sharing the
@@ -577,20 +537,20 @@ usually followed by a call to ``transaction.commit_unless_managed``, which
isn't necessary any more and should be removed.
Select for update
------------------
+~~~~~~~~~~~~~~~~~
If you were relying on "automatic transactions" to provide locking between
:meth:`~django.db.models.query.QuerySet.select_for_update` and a subsequent
write operation — an extremely fragile design, but nonetheless possible — you
-must wrap the relevant code in :func:`commit_on_success`.
+must wrap the relevant code in :func:`atomic`.
Using a high isolation level
-----------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you were using the "repeatable read" isolation level or higher, and if you
relied on "automatic transactions" to guarantee consistency between successive
-reads, the new behavior is backwards-incompatible. To maintain consistency,
-you must wrap such sequences in :func:`commit_on_success`.
+reads, the new behavior might be backwards-incompatible. To enforce
+consistency, you must wrap such sequences in :func:`atomic`.
MySQL defaults to "repeatable read" and SQLite to "serializable"; they may be
affected by this problem.
@@ -602,10 +562,9 @@ PostgreSQL and Oracle default to "read committed" and aren't affected, unless
you changed the isolation level.
Using unsupported database features
------------------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
With triggers, views, or functions, it's possible to make ORM reads result in
database modifications. Django 1.5 and earlier doesn't deal with this case and
it's theoretically possible to observe a different behavior after upgrading to
-Django 1.6 or later. In doubt, use :func:`commit_on_success` to enforce
-integrity.
+Django 1.6 or later. In doubt, use :func:`atomic` to enforce integrity.
View
15 tests/backends/tests.py
@@ -522,7 +522,8 @@ def test_disable_constraint_checks_manually(self):
"""
When constraint checks are disabled, should be able to write bad data without IntegrityErrors.
"""
- with transaction.commit_manually():
+ transaction.set_autocommit(autocommit=False)
+ try:
# Create an Article.
models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r)
# Retrive it from the DB
@@ -536,12 +537,15 @@ def test_disable_constraint_checks_manually(self):
self.fail("IntegrityError should not have occurred.")
finally:
transaction.rollback()
+ finally:
+ transaction.set_autocommit(autocommit=True)
def test_disable_constraint_checks_context_manager(self):
"""
When constraint checks are disabled (using context manager), should be able to write bad data without IntegrityErrors.
"""
- with transaction.commit_manually():
+ transaction.set_autocommit(autocommit=False)
+ try:
# Create an Article.
models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r)
# Retrive it from the DB
@@ -554,12 +558,15 @@ def test_disable_constraint_checks_context_manager(self):
self.fail("IntegrityError should not have occurred.")
finally:
transaction.rollback()
+ finally:
+ transaction.set_autocommit(autocommit=True)
def test_check_constraints(self):
"""
Constraint checks should raise an IntegrityError when bad data is in the DB.
"""
- with transaction.commit_manually():
+ try:
+ transaction.set_autocommit(autocommit=False)
# Create an Article.
models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r)
# Retrive it from the DB
@@ -572,6 +579,8 @@ def test_check_constraints(self):
connection.check_constraints()
finally:
transaction.rollback()
+ finally:
+ transaction.set_autocommit(autocommit=True)
class ThreadTests(TestCase):
View
11 tests/fixtures_model_package/tests.py
@@ -25,7 +25,8 @@ def testClassFixtures(self):
class TestNoInitialDataLoading(TransactionTestCase):
def test_syncdb(self):
- with transaction.commit_manually():
+ transaction.set_autocommit(autocommit=False)
+ try:
Book.objects.all().delete()
management.call_command(
@@ -35,6 +36,9 @@ def test_syncdb(self):
)
self.assertQuerysetEqual(Book.objects.all(), [])
transaction.rollback()
+ finally:
+ transaction.set_autocommit(autocommit=True)
+
def test_flush(self):
# Test presence of fixture (flush called by TransactionTestCase)
@@ -45,7 +49,8 @@ def test_flush(self):
lambda a: a.name
)
- with transaction.commit_manually():
+ transaction.set_autocommit(autocommit=False)
+ try:
management.call_command(
'flush',
verbosity=0,
@@ -55,6 +60,8 @@ def test_flush(self):
)
self.assertQuerysetEqual(Book.objects.all(), [])
transaction.rollback()
+ finally:
+ transaction.set_autocommit(autocommit=True)
class FixtureTestCase(TestCase):
View
7 tests/fixtures_regress/tests.py
@@ -684,5 +684,8 @@ def ticket_11101(self):
@skipUnlessDBFeature('supports_transactions')
def test_ticket_11101(self):
"""Test that fixtures can be rolled back (ticket #11101)."""
- ticket_11101 = transaction.commit_manually(self.ticket_11101)
- ticket_11101()
+ transaction.set_autocommit(autocommit=False)
+ try:
+ self.ticket_11101()
+ finally:
+ transaction.set_autocommit(autocommit=True)
View
6 tests/middleware/tests.py
@@ -24,6 +24,8 @@
from django.utils.six.moves import xrange
from django.utils.unittest import expectedFailure
+from transactions.tests import IgnorePendingDeprecationWarningsMixin
+
from .models import Band
@@ -670,11 +672,12 @@ def test_compress_response(self):
self.assertNotEqual(gzip_etag, nogzip_etag)
-class TransactionMiddlewareTest(TransactionTestCase):
+class TransactionMiddlewareTest(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
"""
Test the transaction middleware.
"""
def setUp(self):
+ super(TransactionMiddlewareTest, self).setUp()
self.request = HttpRequest()
self.request.META = {
'SERVER_NAME': 'testserver',
@@ -686,6 +689,7 @@ def setUp(self):
def tearDown(self):
transaction.abort()
+ super(TransactionMiddlewareTest, self).tearDown()
def test_request(self):
TransactionMiddleware().process_request(self.request)
View
71 tests/transactions/tests.py
@@ -1,9 +1,10 @@
from __future__ import absolute_import
import sys
+import warnings
from django.db import connection, transaction, IntegrityError
-from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature
+from django.test import TransactionTestCase, skipUnlessDBFeature
from django.utils import six
from django.utils.unittest import skipUnless
@@ -158,7 +159,69 @@ def tearDown(self):
self.atomic.__exit__(*sys.exc_info())
-class TransactionTests(TransactionTestCase):
+class AtomicInsideLegacyTransactionManagementTests(AtomicTests):
+
+ def setUp(self):
+ transaction.enter_transaction_management()
+
+ def tearDown(self):
+ # The tests access the database after exercising 'atomic', making the
+ # connection dirty; a rollback is required to make it clean.
+ transaction.rollback()
+ transaction.leave_transaction_management()
+
+
+@skipUnless(connection.features.uses_savepoints,
+ "'atomic' requires transactions and savepoints.")
+class AtomicErrorsTests(TransactionTestCase):
+
+ def test_atomic_requires_autocommit(self):
+ transaction.set_autocommit(autocommit=False)
+ try:
+ with self.assertRaises(transaction.TransactionManagementError):
+ with transaction.atomic():
+ pass
+ finally:
+ transaction.set_autocommit(autocommit=True)
+
+ def test_atomic_prevents_disabling_autocommit(self):
+ autocommit = transaction.get_autocommit()
+ with transaction.atomic():
+ with self.assertRaises(transaction.TransactionManagementError):
+ transaction.set_autocommit(autocommit=not autocommit)
+ # Make sure autocommit wasn't changed.
+ self.assertEqual(connection.autocommit, autocommit)
+
+ def test_atomic_prevents_calling_transaction_methods(self):
+ with transaction.atomic():
+ with self.assertRaises(transaction.TransactionManagementError):
+ transaction.commit()
+ with self.assertRaises(transaction.TransactionManagementError):
+ transaction.rollback()
+
+ def test_atomic_prevents_calling_transaction_management_methods(self):
+ with transaction.atomic():
+ with self.assertRaises(transaction.TransactionManagementError):
+ transaction.enter_transaction_management()
+ with self.assertRaises(transaction.TransactionManagementError):
+ transaction.leave_transaction_management()
+
+
+class IgnorePendingDeprecationWarningsMixin(object):
+
+ def setUp(self):
+ super(IgnorePendingDeprecationWarningsMixin, self).setUp()
+ self.catch_warnings = warnings.catch_warnings()
+ self.catch_warnings.__enter__()
+ warnings.filterwarnings("ignore", category=PendingDeprecationWarning)
+
+ def tearDown(self):
+ self.catch_warnings.__exit__(*sys.exc_info())
+ super(IgnorePendingDeprecationWarningsMixin, self).tearDown()
+
+
+class TransactionTests(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
+
def create_a_reporter_then_fail(self, first, last):
a = Reporter(first_name=first, last_name=last)
a.save()
@@ -313,7 +376,7 @@ def test_manually_managed_with_using(self):
)
-class TransactionRollbackTests(TransactionTestCase):
+class TransactionRollbackTests(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
def execute_bad_sql(self):
cursor = connection.cursor()
cursor.execute("INSERT INTO transactions_reporter (first_name, last_name) VALUES ('Douglas', 'Adams');")
@@ -330,7 +393,7 @@ def test_bad_sql(self):
self.assertRaises(IntegrityError, execute_bad_sql)
transaction.rollback()
-class TransactionContextManagerTests(TransactionTestCase):
+class TransactionContextManagerTests(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
def create_reporter_and_fail(self):
Reporter.objects.create(first_name="Bob", last_name="Holtzman")
raise Exception
View
12 tests/transactions_regress/tests.py
@@ -6,10 +6,12 @@
from django.test.utils import override_settings
from django.utils.unittest import skipIf, skipUnless, expectedFailure
+from transactions.tests import IgnorePendingDeprecationWarningsMixin
+
from .models import Mod, M2mA, M2mB
-class TestTransactionClosing(TransactionTestCase):
+class TestTransactionClosing(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
"""
Tests to make sure that transactions are properly closed
when they should be, and aren't left pending after operations
@@ -166,7 +168,7 @@ def test_failing_query_transaction_closed_debug(self):
(connection.settings_dict['NAME'] == ':memory:' or
not connection.settings_dict['NAME']),
'Test uses multiple connections, but in-memory sqlite does not support this')
-class TestNewConnection(TransactionTestCase):
+class TestNewConnection(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
"""
Check that new connections don't have special behaviour.
"""
@@ -211,7 +213,7 @@ def test_enter_exit_management(self):
@skipUnless(connection.vendor == 'postgresql',
"This test only valid for PostgreSQL")
-class TestPostgresAutocommitAndIsolation(TransactionTestCase):
+class TestPostgresAutocommitAndIsolation(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
"""
Tests to make sure psycopg2's autocommit mode and isolation level
is restored after entering and leaving transaction management.
@@ -292,7 +294,7 @@ def test_enter_autocommit(self):
self.assertTrue(connection.autocommit)
-class TestManyToManyAddTransaction(TransactionTestCase):
+class TestManyToManyAddTransaction(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
def test_manyrelated_add_commit(self):
"Test for https://code.djangoproject.com/ticket/16818"
a = M2mA.objects.create()
@@ -307,7 +309,7 @@ def test_manyrelated_add_commit(self):
self.assertEqual(a.others.count(), 1)
-class SavepointTest(TransactionTestCase):
+class SavepointTest(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
@skipIf(connection.vendor == 'sqlite',
"SQLite doesn't support savepoints in managed mode")
Please sign in to comment.
Something went wrong with that request. Please try again.