[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