From 0ed39e35076248d4aa2bb4eebdf81eedceec060a Mon Sep 17 00:00:00 2001 From: Jay O'Conor Date: Tue, 21 Jun 2016 15:21:27 -0700 Subject: [PATCH 1/4] Create a new context manager / decorator: use_slave that mirrors the use_master context manager / decorator. This is needed to override the default behavior in cases where the behavior would be to pin to master (e.g., if request method is a POST) but the decorated method is known not to do db writes. --- multidb/pinning.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/multidb/pinning.py b/multidb/pinning.py index 6daec5f..5f843fd 100644 --- a/multidb/pinning.py +++ b/multidb/pinning.py @@ -95,6 +95,30 @@ def __exit__(self, type, value, tb): use_master = UseMaster() +class UseSlave(object): + """A contextmanager/decorator to use the slave database.""" + "Use this in cases where the usual behavior would be to pin to master," + "such as when the request method is POST, but you know you're not doing any writing." + old = False + + def __call__(self, func): + @wraps(func) + def decorator(*args, **kw): + with self: + return func(*args, **kw) + return decorator + + def __enter__(self): + self.old = this_thread_has_db_write_set() + unset_db_write_for_this_thread() + + def __exit__(self, type, value, tb): + if self.old: + set_db_write_for_this_thread() + +use_slave = UseSlave() + + def mark_as_write(response): """Mark a response as having done a DB write.""" response._db_write = True From b51319ce47d0f1c415c2e5f4c504278c75906a0c Mon Sep 17 00:00:00 2001 From: Jay O'Conor Date: Wed, 22 Jun 2016 11:37:51 -0700 Subject: [PATCH 2/4] UseSlave needs to change 'pinned' state, not 'db_write' state. --- multidb/pinning.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/multidb/pinning.py b/multidb/pinning.py index 5f843fd..56e242c 100644 --- a/multidb/pinning.py +++ b/multidb/pinning.py @@ -6,7 +6,7 @@ __all__ = ['this_thread_is_pinned', 'pin_this_thread', 'unpin_this_thread', 'use_master', 'db_write', 'set_db_write_for_this_thread', - 'unset_db_write_for_this_thread', + 'use_slave', 'unset_db_write_for_this_thread', 'this_thread_has_db_write_set', 'set_db_write_for_this_thread_if_needed'] @@ -109,12 +109,12 @@ def decorator(*args, **kw): return decorator def __enter__(self): - self.old = this_thread_has_db_write_set() - unset_db_write_for_this_thread() + self.old = this_thread_is_pinned() + unpin_this_thread() def __exit__(self, type, value, tb): if self.old: - set_db_write_for_this_thread() + pin_this_thread() use_slave = UseSlave() From 2aa4b7fb743974df9d3d4fcaca8ea61ee60a626b Mon Sep 17 00:00:00 2001 From: Jay O'Conor Date: Wed, 22 Jun 2016 14:47:31 -0700 Subject: [PATCH 3/4] Tests for use_slave decorator and context manager --- multidb/tests/test_all.py | 47 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/multidb/tests/test_all.py b/multidb/tests/test_all.py index 78e6317..91fe0a2 100644 --- a/multidb/tests/test_all.py +++ b/multidb/tests/test_all.py @@ -11,7 +11,7 @@ from multidb.conf import settings from multidb.middleware import PinningRouterMiddleware from multidb.pinning import (this_thread_is_pinned, pin_this_thread, - unpin_this_thread, use_master, db_write) + unpin_this_thread, use_master, use_slave, db_write) def expire_cookies(cookies): @@ -327,3 +327,48 @@ def test_context_manager_exception(self): self.assertTrue(this_thread_is_pinned()) raise ValueError self.assertFalse(this_thread_is_pinned()) + + def test_slave_decorator(self): + + @use_slave + def check(): + self.assertFalse(this_thread_is_pinned()) + + pin_this_thread() + self.assertTrue(this_thread_is_pinned()) + check() + self.assertTrue(this_thread_is_pinned()) + + def test_slave_decorator_resets(self): + + @use_slave + def check(): + self.assertFalse(this_thread_is_pinned()) + + unpin_this_thread() + self.assertFalse(this_thread_is_pinned()) + check() + self.assertFalse(this_thread_is_pinned()) + + def test_slave_context_manager(self): + pin_this_thread() + self.assertTrue(this_thread_is_pinned()) + with use_slave: + self.assertFalse(this_thread_is_pinned()) + self.assertTrue(this_thread_is_pinned()) + + def test_slave_context_manager_resets(self): + unpin_this_thread() + self.assertFalse(this_thread_is_pinned()) + with use_slave: + self.assertFalse(this_thread_is_pinned()) + self.assertFalse(this_thread_is_pinned()) + + def test_slave_context_manager_exception(self): + pin_this_thread() + self.assertTrue(this_thread_is_pinned()) + with self.assertRaises(ValueError): + with use_slave: + self.assertFalse(this_thread_is_pinned()) + raise ValueError + self.assertTrue(this_thread_is_pinned()) From 0c33abb5b4f846238eb07b30621d1b4c787168c5 Mon Sep 17 00:00:00 2001 From: Jay O'Conor Date: Thu, 23 Jun 2016 11:01:13 -0700 Subject: [PATCH 4/4] Small refactor to avoid duplicate __call__ methods in UseMaster & UseSlave --- multidb/pinning.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/multidb/pinning.py b/multidb/pinning.py index 56e242c..b74bcd4 100644 --- a/multidb/pinning.py +++ b/multidb/pinning.py @@ -95,19 +95,12 @@ def __exit__(self, type, value, tb): use_master = UseMaster() -class UseSlave(object): +class UseSlave(UseMaster): """A contextmanager/decorator to use the slave database.""" "Use this in cases where the usual behavior would be to pin to master," "such as when the request method is POST, but you know you're not doing any writing." old = False - def __call__(self, func): - @wraps(func) - def decorator(*args, **kw): - with self: - return func(*args, **kw) - return decorator - def __enter__(self): self.old = this_thread_is_pinned() unpin_this_thread()