[Python-modules-commits] [django-celery-transactions] 01/02: Imported Upstream version 0.3.2

Michael Fladischer fladi at moszumanska.debian.org
Tue Jul 7 14:35:06 UTC 2015


This is an automated email from the git hooks/post-receive script.

fladi pushed a commit to branch master
in repository django-celery-transactions.

commit f4da5edfe3f41737eca3bdecb6ca92432b03950b
Author: Michael Fladischer <FladischerMichael at fladi.at>
Date:   Tue Jul 7 16:02:23 2015 +0200

    Imported Upstream version 0.3.2
---
 .gitignore                                   |   5 +
 .travis.yml                                  |  37 ++++
 AUTHORS                                      |  12 ++
 CHANGELOG                                    | 277 +++++++++++++++++++++++++++
 LICENSE                                      |  26 +++
 MANIFEST.in                                  |   3 +
 README                                       |   1 +
 README.md                                    | 111 +++++++++++
 djcelery_transactions/__init__.py            | 155 +++++++++++++++
 djcelery_transactions/transaction_signals.py | 155 +++++++++++++++
 runtests-djcelery.py                         |  57 ++++++
 runtests.py                                  |  61 ++++++
 setup.py                                     |  30 +++
 tests.settings                               |   0
 tests/__init__.py                            |   0
 tests/test/__init__.py                       |   0
 tests/test/models.py                         |  10 +
 tests/test/tests.py                          | 126 ++++++++++++
 tests/test/views.py                          |  14 ++
 tests/urls.py                                |   6 +
 20 files changed, 1086 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4758409
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+build
+dist
+*.egg-info
+*.pyc
+*~
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..1435174
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,37 @@
+language: python
+
+python:
+  - "2.7"
+  - "3.3"
+  - "3.4"
+
+services: mysql
+
+env:
+  - DJANGO="django==1.8.2" DB="mysql"
+  - DJANGO="django==1.7.8" DB="mysql"
+  - DJANGO="django==1.6.10" DB="mysql"
+
+install:
+  - pip install $DJANGO
+  - pip install celery==3.1.18
+  - pip install django-celery==3.1.16
+  - pip install coveralls
+  - pip install mysqlclient
+
+before_script:
+  - mysql -e 'create database testdb;'
+
+script:
+  - coverage run --source=djcelery_transactions runtests.py
+  - coverage run --source=djcelery_transactions runtests-djcelery.py
+
+after_success:
+  coveralls
+
+notifications:
+  email:
+    recipients:
+      - nicolas.grasset at gmail.com
+    on_success: change
+    on_failure: change
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..40d4962
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,12 @@
+Bradley Ayers <bradley.ayers at gmail.com>
+Nicolas Delaby
+Chris Doble <chris at chrisdoble.com>
+Bryce Drennan
+Michael Fladischer
+Nicolas Grasset <nicolas.grasset at gmail.com>
+Nils Lundquist
+Marcin Nowak
+Jakub Paczkowski
+Tom Playford
+Nic Pottier
+Peter Sheats
diff --git a/CHANGELOG b/CHANGELOG
new file mode 100644
index 0000000..a81ad57
--- /dev/null
+++ b/CHANGELOG
@@ -0,0 +1,277 @@
+Changelog
+=========
+
+0.3.2 (unreleased)
+------------------------
+
+- Drop support for Django 1.5. [Nicolas Grasset]
+
+- Travis test with MySQL backend. Fixes #7. [Nicolas Grasset]
+
+  #7 is not “fixed”, but if the tests pass, I’ll need more details about
+  the issue
+
+
+- Pkg 0.3.2 change log. [Nicolas Grasset]
+
+- Pkg: Drop test support for Django 1.5. [Nicolas Grasset]
+
+- Trigger tasks in inner savepoint commits. [Nicolas Grasset]
+
+  This is a rollback of
+  https://github.com/fellowshipofone/django-celery-transactions/commit/c3d
+  2b704101ed90b0d6eebec0f0b8acc9ff1b617 by @sheats but I think it is
+  cleaner this way, and it is required for new Django 1.8 tests since
+  they are nested in atomic blocks
+
+
+- Django 1.8 extra not compatible with TransactionTestCase. [Nicolas
+  Grasset]
+
+- Make Django 1.8 code update compatible with 1.5. [Nicolas Grasset]
+
+- Pkg: Update from Django 1.8 source. [Nicolas Grasset]
+
+- Pkg: Cleanup test structure for extra 1.8 test. [Nicolas Grasset]
+
+- [tests] Check Django 1.8 logic. [Nicolas Grasset]
+
+0.3.1 (2015-04-18)
+------------------
+
+Changes
+~~~~~~~
+
+- Removed support for Django <= 1.3. [Nicolas Grasset]
+
+Other
+~~~~~
+
+- 0.3.1 PyPi upload issues. Fixes #9. [Nicolas Grasset]
+
+- Pkg: Fix PyPi file. [Nicolas Grasset]
+
+0.3.0 (2015-03-14)
+------------------
+
+- [travis] Target specific Django releases. [Nicolas Grasset]
+
+- 0.3.0 Release for Python 3. Fixes #8. [Nicolas Grasset]
+
+- [travis-ci] Test newest minor releases of Django. [Nicolas Grasset]
+
+- [travis-ci] Test Python 3.3 and 3.4. [Nicolas Grasset]
+
+- [python 3] Fixed issues on Python 3. [Nicolas Grasset]
+
+- [settings] Add MIDDLEWARE_CLASSES for Django 1.7 default. [Nicolas
+  Grasset]
+
+- Added coveralls support. [Nicolas Grasset]
+
+- Update Travis environment matrix for Django 1.7.1. [Nicolas Grasset]
+
+0.2.0 (2014-11-09)
+------------------
+
+- Merge pull request #5 from fellowshipofone/unify-releases. [Nicolas
+  Grasset]
+
+  Unify releases
+
+- Small readability change for README. [Nicolas Grasset]
+
+- Update AUTHORS after merge. [Nicolas Grasset]
+
+- Merge branch 'brycedrennan-master' into unify-releases. [Nicolas
+  Grasset]
+
+- Merge branch 'master' of github.com:brycedrennan/django-celery-
+  transactions into brycedrennan-master. [Nicolas Grasset]
+
+  Conflicts:
+  	README.md
+  	djcelery_transactions/__init__.py
+  	tests/tests.py
+
+
+- Merge remote-tracking branch 'fellowshipofone/master' [Bryce]
+
+  Conflicts:
+  	djcelery_transactions/__init__.py
+
+
+- Merge remote-tracking branch '10to8/master' [Bryce]
+
+  Conflicts:
+  	djcelery_transactions/__init__.py
+
+
+- Merge remote-tracking branch 'fladi/asyncresult-not-returned' [U
+  -CircleUp-Alpha\Bryce]
+
+- Heads up that task.delay() returns None, not AsyncResult. [Michael
+  Fladischer]
+
+- Merge remote-tracking branch 'fladi/celery-3.x' [U-CircleUp-
+  Alpha\Bryce]
+
+- Use celery.app package instead of deprecated celery.task. [Michael
+  Fladischer]
+
+- Merge remote-tracking branch 'ticosax/fix-regression' [U-CircleUp-
+  Alpha\Bryce]
+
+- Fix regression for django.db.transaction.managed. [Nicolas Delaby]
+
+- Add syntax highlighting. [Corey Farwell]
+
+- Fix eager_transaction. [Nicolas Grasset]
+
+- Update AUTHORS. [Nicolas Grasset]
+
+- New setting CELERY_EAGER_TRANSACTION. [Nicolas Grasset]
+
+- Merge branch 'clearcare-master' into unify-releases. [Nicolas Grasset]
+
+- Merge branch 'master' of github.com:clearcare/django-celery-
+  transactions into clearcare-master. [Nicolas Grasset]
+
+  Conflicts:
+  	djcelery_transactions/__init__.py
+
+
+- Looks like you only need djcelery to run tests, so install it if
+  you're going to run tests.  But I'd rather not have it installed if we
+  don't need it. [Peter Sheats]
+
+- Need to honor CELERY_ALWAYS_EAGER here too. [Peter Sheats]
+
+- We don't want tasks to get fired off during inner savepoint commits --
+  only when the parent transaction is committed. [Peter Sheats]
+
+- Update setup file for version 0.2.0. [Nicolas Grasset]
+
+- Update README file. [Nicolas Grasset]
+
+- Django 1.7, use default caches. [Nicolas Grasset]
+
+- Use django cache instead of  global variable to fix issue from Django
+  1.6. [Nicolas Grasset]
+
+- TestRunner, use DiscoverRunner from Django 1.6. [Nicolas Grasset]
+
+- Make both test settings more similar. [Nicolas Grasset]
+
+- Only test Django 2.7 for now. [Nicolas Grasset]
+
+- Update email, copied from django-redis earlier. [Nicolas Grasset]
+
+- Travis-ci. [Nicolas Grasset]
+
+- Fix broken tests. Works on Django < 1.6. [Nicolas Grasset]
+
+- Merge pull request #3 from fellowshipofone/django-1.6. [Nicolas
+  Grasset]
+
+  Django 1.6 support
+
+- Fixed the tests for 1.6, CELERY_EAGER now working differently.
+  [Nicolas Grasset]
+
+- Merge github.com:nlundquist/django-celery-transactions into
+  django-1.6. [Nicolas Grasset]
+
+  Conflicts:
+  	.gitignore
+  	djcelery_transactions/__init__.py
+  	djcelery_transactions/transaction_signals.py
+
+
+- Adding Batches subclass. [Nils Lundquist]
+
+- Celery 3.1 compatibility. [Jakub Paczkowski]
+
+- Bring django celery transactions to 1.6.5 version. [Nic Pottier]
+
+- Only queue tasks for later if we are in an atomic block. [Nic Pottier]
+
+- Modifications for django 1.6, serious overhaul. [Nic Pottier]
+
+- Deal with case when there are no args. [Nic Pottier]
+
+- Do not queue celeryt asks if always eager is on. [Nic Pottier]
+
+- Simpler CELERY_ALWAYS_EAGER support. [Nicolas Grasset]
+
+- Change CELERY_ALWAYS_EAGER fix. [Nicolas Grasset]
+
+- Ignore .idea files. [Nicolas Grasset]
+
+- Merge pull request #1 from 10to8/master. [Nicolas Grasset]
+
+  Fix for CELERY_ALWAYS_EAGER setting
+
+- Update __init__.py. [Tom Playford]
+
+- After_transaction support. [Tom Playford]
+
+- Fix for CELERY_ALWAYS_EAGER setting. [Tom Playford]
+
+- Merge pull request #5 from nicolas-DH/add-test-and-fix-bug. [Bradley
+  Ayers]
+
+  Add test and fix bug
+
+- Ignore build directory. [Nicolas Delaby]
+
+- Add minimal test suite to check any regression and prove bug fixing.
+  [Nicolas Delaby]
+
+- BUG Fix. [Nicolas Delaby]
+
+  If transactions is not mark as dirty in case of rollback, celery queue is still consumed
+
+
+- Get rid of extra argument after_transaction passed to apply_async.
+  [Nicolas Delaby]
+
+- Add support for prior versions of Django 1.3. [Nicolas Delaby]
+
+- Reorganise import for pep8 compliancy. [Nicolas Delaby]
+
+- Be function signature agnostic. [Nicolas Delaby]
+
+  Make sure we can follow any changes in transaction API
+
+
+- Assign and return in same time. [Nicolas Delaby]
+
+- Push task to broker in the same order they have appeared. [Nicolas
+  Delaby]
+
+- Alphabetise AUTHORS to standardise it for future contributors.
+  [Bradley Ayers]
+
+0.1.3 (2012-10-15)
+------------------
+
+- Reduce version requirements, bump to v0.1.3. [Chris Doble]
+
+- Merge pull request #2 from marcinn/master. [Chris Doble]
+
+  Python 2.5 compatibility.
+
+- Compatibility with python 2.5. [Marcin Nowak]
+
+- Improve README. [Chris Doble]
+
+- Fix task queue creation. [Chris Doble]
+
+- Use transaction signals instead of request signals. [Chris Doble]
+
+- Use signal handlers instead of middleware. [Chris Doble]
+
+- Initial commit. [Chris Doble]
+
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..2d0cb9d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,26 @@
+Copyright (c) 2012, Chris Doble
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The views and conclusions contained in the software and documentation are those
+of the authors and should not be interpreted as representing official policies,
+either expressed or implied, of the FreeBSD Project.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..a659298
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,3 @@
+include AUTHORS
+include README.md
+include LICENSE
diff --git a/README b/README
new file mode 100644
index 0000000..95053db
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+See README.md.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5ee6a7b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,111 @@
+# django-celery-transactions
+
+
+[![Travis](https://img.shields.io/travis/fellowshipofone/django-celery-transactions.svg?style=flat)][2]
+[![Version](https://img.shields.io/pypi/v/django-celery-transactions.svg?style=flat)][3]
+[![Downloads](https://img.shields.io/pypi/dm/django-celery-transactions.svg?style=flat)][4]
+[![Coverage Status](https://coveralls.io/repos/fellowshipofone/django-celery-transactions/badge.png?branch=master)][5]
+
+django-celery-transactions holds on to Celery tasks until the current database
+transaction is committed, avoiding potential race conditions as described in
+Celery's [user guide][1]. Send tasks from signal handlers without fear!
+
+## Features
+
+* If the transaction is rolled back, the tasks are discarded. Django's
+  transaction middleware does this if an exception is raised.
+* If transactions aren't being managed, tasks are sent as normal. This means
+  that sending tasks from within Django's shell will work as expected, as will
+  the various transaction decorators `commit_manually`, `commit_on_success`, etc.
+
+## Installation & Use
+
+1. Install django-celery-transactions from PyPI:
+
+```sh
+$ pip install django-celery-transactions
+```
+
+2. Use the patched decorator to create your tasks:
+
+```python
+from djcelery_transactions import task
+from models import Model
+
+
+ at task
+def print_model(model_pk):
+    print Model.objects.get(pk=model_pk)
+```
+
+3. Then use them as normal:
+
+```python
+from django.db import transaction
+from models import Model
+from tasks import print_model
+
+
+# This task will be sent after the transaction is committed. This works
+# from anywhere in the managed transaction block (e.g. signal handlers).
+def view(request):
+    model = Model.objects.create(...)
+    print_model.delay(model.pk)
+
+
+# This task will not be sent (it is discarded) as the transaction
+# middleware rolls the transaction back when it catches the exception.
+def failing_view(request, model_pk):
+    print_model.delay(model_pk)
+    raise Exception()
+
+
+# This task will be sent immediately as transactions aren't being
+# managed and it is assumed that the author knows what they're doing.
+ at transaction.commit_manually
+def manual_view(request, model_pk):
+    print_model.delay(model_pk)
+    transaction.commit()
+```
+
+## Caveats
+
+Due to the task being sent after the current transaction has been commited, the
+`PostTransactionTask` provided in this package does not return an
+`celery.result.AsyncResult` as the original celery `Task` does.
+
+Thus, `print_model.delay(model_pk)` simply returns `None`. In order to track
+the task later on, the `task_id` can be predefined in the `apply_async` method:
+
+        from celery.utils import uuid
+
+        u = uuid()
+        print_model.apply_async((model_pk), {}, task_id=u)
+        
+## Compatibility with CELERY_ALWAYS_EAGER
+
+There are 2 main reasons for `CELERY_ALWAYS_EAGER`:
+
+   1. Running task synchronously and returning `EagerResult`, Celery's 
+   [user guide][1]
+   
+   2. Being able to run code (often tests) without a celery broker.
+   
+For this second reason, the intended behavior will often conflict with 
+transactions handling, which is why you should then also use 
+`CELERY_EAGER_TRANSACTION`
+
+        CELERY_ALWAYS_EAGER = True
+        CELERY_EAGER_TRANSACTION = True
+
+## Run test suite
+
+```sh
+$ python setup.py test
+```
+
+[1]: http://celery.readthedocs.org/en/latest/userguide/tasks.html#database-transactions
+[2]: https://travis-ci.org/fellowshipofone/django-celery-transactions
+[3]: https://pypi.python.org/pypi/django-celery-transactions
+[4]: https://pypi.python.org/pypi/django-celery-transactions
+[5]: https://coveralls.io/r/fellowshipofone/django-celery-transactions?branch=master
diff --git a/djcelery_transactions/__init__.py b/djcelery_transactions/__init__.py
new file mode 100644
index 0000000..90b1af9
--- /dev/null
+++ b/djcelery_transactions/__init__.py
@@ -0,0 +1,155 @@
+# coding=utf-8
+from functools import partial
+import threading
+from celery import current_app
+
+from celery import task as base_task, current_app, Task
+from celery.contrib.batches import Batches
+import django
+from django.conf import settings
+from django.db import transaction
+
+from django.db.transaction import get_connection, atomic
+
+import djcelery_transactions.transaction_signals
+
+# Thread-local data (task queue).
+_thread_data = threading.local()
+
+def _get_task_queue():
+    """Returns the calling thread's task queue."""
+    return _thread_data.__dict__.setdefault("task_queue", [])
+
+
+class PostTransactionTask(Task):
+    """A task whose execution is delayed until after the current transaction.
+
+    The task's fate depends on the outcome of the current transaction. If it's
+    committed or no changes are made in the transaction block, the task is sent
+    as normal. If it's rolled back, the task is discarded.
+
+    If transactions aren't being managed when ``apply_asyc()`` is called (if
+    you're in the Django shell, for example) or the ``after_transaction``
+    keyword argument is ``False``, the task will
+    A replacement decorator is provided:
+
+    .. code-block:: python
+
+        from djcelery_transactions import task
+
+        @task
+        def example(pk):
+            print "Hooray, the transaction has been committed!"
+    """
+
+    abstract = True
+
+    def original_apply_async(self, *args, **kwargs):
+        """Shortcut method to reach real implementation
+        of celery.Task.apply_sync
+        """
+        return super(PostTransactionTask, self).apply_async(*args, **kwargs)
+
+    def apply_async(self, *args, **kwargs):
+        # Delay the task unless the client requested otherwise or transactions
+        # aren't being managed (i.e. the signal handlers won't send the task).
+
+        celery_eager = _get_celery_settings('CELERY_ALWAYS_EAGER')
+
+        # New setting to run eager task post transaction
+        # defaults to `not CELERY_ALWAYS_EAGER`
+        eager_transaction = _get_celery_settings('CELERY_EAGER_TRANSACTION',
+                                                 not celery_eager)
+
+        if django.VERSION < (1, 6):
+
+            if transaction.is_managed() and eager_transaction:
+                if not transaction.is_dirty():
+                    # Always mark the transaction as dirty
+                    # because we push task in queue that must be fired or discarded
+                    if 'using' in kwargs:
+                        transaction.set_dirty(using=kwargs['using'])
+                    else:
+                        transaction.set_dirty()
+                _get_task_queue().append((self, args, kwargs))
+            else:
+                apply_async_orig = super(PostTransactionTask, self).apply_async
+                return apply_async_orig(*args, **kwargs)
+
+        else:
+
+            connection = get_connection()
+            if connection.in_atomic_block and eager_transaction:
+                _get_task_queue().append((self, args, kwargs))
+            else:
+                return self.original_apply_async(*args, **kwargs)
+
+
+class PostTransactionBatches(Batches):
+    """A batch of tasks whose queuing is delayed until after the current
+        transaction.
+    """
+
+    abstract = True
+
+    def original_apply_async(self, *args, **kwargs):
+        """Shortcut method to reach real implementation
+        of celery.Task.apply_sync
+        """
+        return super(PostTransactionBatches, self).apply_async(*args, **kwargs)
+
+    def apply_async(self, *args, **kwargs):
+        # Delay the task unless the client requested otherwise or transactions
+        # aren't being managed (i.e. the signal handlers won't send the task).
+
+        celery_eager = _get_celery_settings('CELERY_ALWAYS_EAGER')
+
+        # New setting to run eager task post transaction
+        # defaults to `not CELERY_ALWAYS_EAGER`
+        eager_transaction = _get_celery_settings('CELERY_EAGER_TRANSACTION',
+                                                 not celery_eager)
+
+        connection = get_connection()
+        if connection.in_atomic_block and eager_transaction:
+            _get_task_queue().append((self, args, kwargs))
+        else:
+            return self.original_apply_async(*args, **kwargs)
+
+def _discard_tasks(**kwargs):
+    """Discards all delayed Celery tasks.
+
+    Called after a transaction is rolled back."""
+    _get_task_queue()[:] = []
+
+
+def _send_tasks(**kwargs):
+    """Sends all delayed Celery tasks.
+
+    Called after a transaction is committed or we leave a transaction
+    management block in which no changes were made (effectively a commit).
+    """
+
+    celery_eager = _get_celery_settings('CELERY_ALWAYS_EAGER')
+
+
+    queue = _get_task_queue()
+    while queue:
+        tsk, args, kwargs = queue.pop(0)
+        tsk.original_apply_async(*args, **kwargs)
+
+
+# A replacement decorator.
+task = partial(base_task, base=PostTransactionTask)
+
+# Hook the signal handlers up.
+transaction.signals.post_commit.connect(_send_tasks)
+transaction.signals.post_rollback.connect(_discard_tasks)
+
+def _get_celery_settings(setting, default=False):
+    """ Returns CELERY setting
+    :param setting:
+    :param default:
+    :return:
+    """
+    return any(getattr(obj, setting, default)
+               for obj in (current_app.conf, settings))
diff --git a/djcelery_transactions/transaction_signals.py b/djcelery_transactions/transaction_signals.py
new file mode 100644
index 0000000..149c5ef
--- /dev/null
+++ b/djcelery_transactions/transaction_signals.py
@@ -0,0 +1,155 @@
+# coding=utf-8
+"""Adds signals to ``django.db.transaction``.
+
+Signals are monkey patched into ``django.db.transaction``:
+
+* ``post_commit``: sent after a transaction is committed. If no changes were
+  made in the transaction block, nothing is committed and this won't be sent.
+* ``post_rollback``: sent after a transaction is rolled back.
+* ``post_transaction_management``: sent after leaving transaction management.
+  This signal isn't  posted if a ``TransactionManagementError`` is raised.
+
+.. code-block:: python
+
+    import djcelery_transactions.transaction_signals
+
+
+    def _post_commit(**kwargs):
+        print "The transaction has been committed!"
+
+
+    django.db.transaction.signals.post_commit.connect(_post_commit)
+
+This code was inspired by Grégoire Cachet's implementation of similar
+functionality, which can be found on GitHub: https://gist.github.com/247844
+
+.. warning::
+
+    This module must be imported before you attempt to use the signals.
+"""
+from functools import partial
+import django
+
+from django.db import (
+    DEFAULT_DB_ALIAS, DatabaseError, ProgrammingError, Error, connections,
+)
+
+from django.dispatch import Signal
+from django.db.transaction import get_connection
+
+from django.db import transaction
+
+class TransactionSignals(object):
+    """A container for the transaction signals."""
+
+    def __init__(self):
+        self.post_commit = Signal()
+        self.post_rollback = Signal()
+
+# Access as django.db.transaction.signals.
+transaction.signals = TransactionSignals()
+
+
+__original__exit__ = transaction.Atomic.__exit__
+
+def __patched__exit__(self, exc_type, exc_value, traceback):
+    connection = get_connection(self.using)
+
+    if connection.savepoint_ids:
+        sid = connection.savepoint_ids.pop()
+    else:
+        # Prematurely unset this flag to allow using commit or rollback.
+        connection.in_atomic_block = False
+
+    try:
+        if connection.closed_in_transaction:
+            # The database will perform a rollback by itself.
+            # Wait until we exit the outermost block.
+            pass
+
+        elif exc_type is None and not connection.needs_rollback:
+            if connection.in_atomic_block:
+                # Release savepoint if there is one
+                if sid is not None:
+                    try:
+                        connection.savepoint_commit(sid)
+                        transaction.signals.post_commit.send(None)
+                    except DatabaseError:
+                        try:
+                            connection.savepoint_rollback(sid)
+                            transaction.signals.post_rollback.send(None)
+                            # The savepoint won't be reused. Release it to
+                            # minimize overhead for the database server.
+                            connection.savepoint_commit(sid)
+                            transaction.signals.post_commit.send(None)
+                        except Error:
+                            # If rolling back to a savepoint fails, mark for
+                            # rollback at a higher level and avoid shadowing
+                            # the original exception.
+                            connection.needs_rollback = True
+                        raise
+            else:
+                # Commit transaction
+                try:
+                    connection.commit()
+                    transaction.signals.post_commit.send(None)
+                except DatabaseError:
+                    try:
+                        connection.rollback()
+                        transaction.signals.post_rollback.send(None)
+                    except Error:
+                        # An error during rollback means that something
+                        # went wrong with the connection. Drop it.
+                        connection.close()
+                    raise
+        else:
+            # This flag will be set to True again if there isn't a savepoint
+            # allowing to perform the rollback at this level.
+            connection.needs_rollback = False
+            if connection.in_atomic_block:
+                # Roll back to savepoint if there is one, mark for rollback
+                # otherwise.
+                if sid is None:
+                    connection.needs_rollback = True
+                else:
+                    try:
+                        connection.savepoint_rollback(sid)
+                        transaction.signals.post_rollback.send(None)
+                        # The savepoint won't be reused. Release it to
+                        # minimize overhead for the database server.
+                        connection.savepoint_commit(sid)
+                        transaction.signals.post_commit.send(None)
+                    except Error:
+                        # If rolling back to a savepoint fails, mark for
+                        # rollback at a higher level and avoid shadowing
+                        # the original exception.
+                        connection.needs_rollback = True
+            else:
+                # Roll back transaction
+                try:
+                    connection.rollback()
+                    transaction.signals.post_rollback.send(None)
+                except Error:
+                    # An error during rollback means that something
+                    # went wrong with the connection. Drop it.
+                    connection.close()
+
+    finally:
+        # Outermost block exit when autocommit was enabled.
+        if not connection.in_atomic_block:
+            if connection.closed_in_transaction:
+                connection.connection = None
+            elif connection.features.autocommits_when_autocommit_is_off:
+                connection.autocommit = True
+            else:
+                connection.set_autocommit(True)
+        # Outermost block exit when autocommit was disabled.
+        elif not connection.savepoint_ids and not connection.commit_on_exit:
+            if connection.closed_in_transaction:
+                connection.connection = None
+            else:
+                connection.in_atomic_block = False
+
+
+# Monkey patch that shit
+transaction.Atomic.__exit__ = __patched__exit__
\ No newline at end of file
diff --git a/runtests-djcelery.py b/runtests-djcelery.py
new file mode 100644
index 0000000..f880878
--- /dev/null
+++ b/runtests-djcelery.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+import django
+from django.conf import settings
+from django.core.management import call_command
+
+import os, sys
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
+sys.path.insert(0, 'tests')
+
+
+def runtests():
+    if not settings.configured:
+        # Choose database for settings
+        DATABASES = {
+            'default': {
+                'ENGINE': 'django.db.backends.sqlite3',
+                'NAME': ':memory:'
+            }
+        }
+        test_db = os.environ.get('DB', 'sqlite')
+        if test_db == 'mysql':
+            DATABASES['default'].update({
+                'ENGINE': 'django.db.backends.mysql',
+                'NAME': 'testdb',
+                'USER': 'root',
+            })
+
+        # Configure test environment
+        settings.configure(
+            DATABASES=DATABASES,
+            INSTALLED_APPS=(
+                'djcelery_transactions',
+                'tests.test',
+            ),
+            ROOT_URLCONF='tests.urls',
+            LANGUAGES=(
+                ('en', 'English'),
+            ),
+            MIDDLEWARE_CLASSES=(),
+            CELERY_EAGER_TRANSACTION = True,
+            TEST_RUNNER = 'djcelery.contrib.test_runner.CeleryTestSuiteRunner'
+        )
+
+        from celery import current_app
+        current_app.conf.CELERY_ALWAYS_EAGER = True
+        current_app.conf.CELERY_EAGER_PROPAGATES_EXCEPTIONS = True
+
+    if django.VERSION >= (1, 7):
+        django.setup()
+    failures = call_command(
+        'test', 'tests', interactive=False, failfast=False, verbosity=1)
+
+    sys.exit(bool(failures))
+
+
+if __name__ == '__main__':
+    runtests()
diff --git a/runtests.py b/runtests.py
new file mode 100644
index 0000000..fd39e78
--- /dev/null
+++ b/runtests.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+import django
+from django.conf import settings
+from django.core.management import call_command
+
+import os, sys
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
+sys.path.insert(0, 'tests')
+
+
+def runtests():
+    if not settings.configured:
+        # Choose database for settings
+        DATABASES = {
+            'default': {
+                'ENGINE': 'django.db.backends.sqlite3',
+                'NAME': ':memory:'
+            }
+        }
+        test_db = os.environ.get('DB', 'sqlite')
+        if test_db == 'mysql':
+            DATABASES['default'].update({
+                'ENGINE': 'django.db.backends.mysql',
+                'NAME': 'testdb',
+                'USER': 'root',
+            })
+
+        TEST_RUNNER = 'django.test.runner.DiscoverRunner'
+
+        # Configure test environment
+        settings.configure(
+            DATABASES=DATABASES,
+            INSTALLED_APPS=(
+                'djcelery_transactions',
+                'tests.test',
+            ),
+            ROOT_URLCONF='tests.urls',
+            LANGUAGES=(
+                ('en', 'English'),
+            ),
+            MIDDLEWARE_CLASSES=(),
+            CELERY_ALWAYS_EAGER = True,
+            CELERY_EAGER_PROPAGATES_EXCEPTIONS = True,
+            CELERY_EAGER_TRANSACTION = True,
+            TEST_RUNNER=TEST_RUNNER
+        )
+
+        from celery import current_app
+        current_app.conf.CELERY_ALWAYS_EAGER = True
+        current_app.conf.CELERY_EAGER_PROPAGATES_EXCEPTIONS = True
+
+    if django.VERSION >= (1, 7):
+        django.setup()
+    failures = call_command(
+        'test', 'tests', interactive=False, failfast=False, verbosity=1)
+
+    sys.exit(bool(failures))
+
+
... 229 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/django-celery-transactions.git



More information about the Python-modules-commits mailing list