-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
177 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
language: python | ||
python: | ||
- 3.8 | ||
- 3.6 | ||
- 2.7 | ||
install: pip install tox-travis | ||
script: tox |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
from contextlib import contextmanager | ||
|
||
from django.db.backends import utils | ||
|
||
from readonly.cursor import ( | ||
PatchedCursorWrapper, | ||
PatchedCursorDebugWrapper, | ||
) | ||
|
||
_orig_CursorWrapper = utils.CursorWrapper | ||
_orig_CursorDebugWrapper = utils.CursorDebugWrapper | ||
|
||
|
||
class ForcedPatchedCursorWrapper(PatchedCursorWrapper): | ||
def __init__(self, cursor, db): | ||
super(ForcedPatchedCursorWrapper, self).__init__(cursor, db, read_only=True) | ||
|
||
|
||
class ForcedPatchedCursorDebugWrapper(PatchedCursorDebugWrapper): | ||
def __init__(self, cursor, db): | ||
super(ForcedPatchedCursorDebugWrapper, self).__init__( | ||
cursor, db, read_only=True | ||
) | ||
|
||
|
||
@contextmanager | ||
def readonly(): | ||
old_CursorWrapper = utils.CursorWrapper | ||
old_CursorDebugWrapper = utils.CursorDebugWrapper | ||
utils.CursorWrapper = ForcedPatchedCursorWrapper | ||
utils.CursorDebugWrapper = ForcedPatchedCursorDebugWrapper | ||
try: | ||
yield | ||
finally: | ||
utils.CursorWrapper = old_CursorWrapper | ||
utils.CursorDebugWrapper = old_CursorDebugWrapper | ||
|
||
|
||
@contextmanager | ||
def write_enabled(): | ||
old_CursorWrapper = utils.CursorWrapper | ||
old_CursorDebugWrapper = utils.CursorDebugWrapper | ||
utils.CursorWrapper = _orig_CursorWrapper | ||
utils.CursorDebugWrapper = _orig_CursorDebugWrapper | ||
try: | ||
yield | ||
finally: | ||
utils.CursorWrapper = old_CursorWrapper | ||
utils.CursorDebugWrapper = old_CursorDebugWrapper |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from django.db import models | ||
|
||
|
||
class Widget(models.Model): | ||
name = models.CharField(max_length=100) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
from django.db import transaction | ||
from django.db.transaction import TransactionManagementError | ||
|
||
from django.test import TestCase | ||
|
||
from readonly.decorators import readonly, write_enabled | ||
from readonly.exceptions import DatabaseWriteDenied | ||
|
||
from tests.models import Widget | ||
|
||
|
||
class ContextManagerTestCase(TestCase): | ||
def _create_obj(self): | ||
with transaction.atomic(): | ||
obj = Widget.objects.create() | ||
obj.save() | ||
|
||
def test_normal(self): | ||
Widget.objects.count() | ||
obj = Widget.objects.create() | ||
obj.save() | ||
|
||
def test_readonly_transaction(self): | ||
before = Widget.objects.count() | ||
|
||
with readonly(): | ||
with self.assertRaises(DatabaseWriteDenied): | ||
with transaction.atomic(): | ||
obj = Widget.objects.create() | ||
obj.save() | ||
|
||
after = Widget.objects.count() | ||
assert after == before | ||
|
||
obj = Widget.objects.create() | ||
obj.save() | ||
|
||
after = Widget.objects.count() | ||
assert after == before + 1 | ||
|
||
def test_readonly(self): | ||
Widget.objects.count() | ||
|
||
with readonly(): | ||
with self.assertRaises(DatabaseWriteDenied): | ||
obj = Widget.objects.create() | ||
obj.save() | ||
|
||
# TODO: Automatic cancellation of the transaction would simplify | ||
# developer use of readonly & DatabaseWriteDenied with foreign code | ||
with self.assertRaises(TransactionManagementError): | ||
Widget.objects.count() | ||
|
||
def test_nested_readonly_disabled(self): | ||
with readonly(): | ||
with self.assertRaises(DatabaseWriteDenied): | ||
self._create_obj() | ||
with readonly(): | ||
with self.assertRaises(DatabaseWriteDenied): | ||
self._create_obj() | ||
with readonly(): | ||
with self.assertRaises(DatabaseWriteDenied): | ||
self._create_obj() | ||
|
||
Widget.objects.create() | ||
|
||
def test_readonly_enabled(self): | ||
with readonly(): | ||
with write_enabled(): | ||
self._create_obj() | ||
|
||
def test_nested_readonly_enabled(self): | ||
with readonly(): | ||
with readonly(): | ||
with write_enabled(): | ||
with readonly(): | ||
with write_enabled(): | ||
with readonly(): | ||
with self.assertRaises(DatabaseWriteDenied): | ||
self._create_obj() | ||
|
||
with self.assertRaises(DatabaseWriteDenied): | ||
self._create_obj() | ||
|
||
with self.assertRaises(DatabaseWriteDenied): | ||
self._create_obj() | ||
|
||
self._create_obj() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
[tox] | ||
envlist = py27-django{18,19,110,111}, py{36,37,38}-django{111,20,21,22,30,31} | ||
skip_missing_interpreters = True | ||
|
||
[testenv] | ||
commands = python -m django test {posargs} | ||
setenv = | ||
DJANGO_SETTINGS_MODULE = tests.settings | ||
deps = | ||
django18: Django>=1.8,<1.9 | ||
django19: Django>=1.9,<1.10 | ||
django110: Django>=1.10,<1.11 | ||
django111: Django>=1.11,<1.12 | ||
django20: Django>=2.0,<2.1 | ||
django21: Django>=2.1,<2.2 | ||
django22: Django>=2.2,<2.3 | ||
django30: Django>=3.0,<3.1 | ||
django31: Django>=3.1,<3.2 |