mirror of
https://git.anonymousland.org/anonymousland/synapse.git
synced 2025-08-09 17:02:13 -04:00
Allow for ignoring some arguments when caching. (#12189)
* `@cached` can now take an `uncached_args` which is an iterable of names to not use in the cache key. * Requires `@cached`, @cachedList` and `@lru_cache` to use keyword arguments for clarity. * Asserts that keyword-only arguments in cached functions are not accepted. (I tested this briefly and I don't believe this works properly.)
This commit is contained in:
parent
032688854b
commit
690cb4f3b3
4 changed files with 142 additions and 21 deletions
|
@ -141,6 +141,84 @@ class DescriptorTestCase(unittest.TestCase):
|
|||
self.assertEqual(r, "chips")
|
||||
obj.mock.assert_not_called()
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_cache_uncached_args(self):
|
||||
"""
|
||||
Only the arguments not named in uncached_args should matter to the cache
|
||||
|
||||
Note that this is identical to test_cache_num_args, but provides the
|
||||
arguments differently.
|
||||
"""
|
||||
|
||||
class Cls:
|
||||
# Note that it is important that this is not the last argument to
|
||||
# test behaviour of skipping arguments properly.
|
||||
@descriptors.cached(uncached_args=("arg2",))
|
||||
def fn(self, arg1, arg2, arg3):
|
||||
return self.mock(arg1, arg2, arg3)
|
||||
|
||||
def __init__(self):
|
||||
self.mock = mock.Mock()
|
||||
|
||||
obj = Cls()
|
||||
obj.mock.return_value = "fish"
|
||||
r = yield obj.fn(1, 2, 3)
|
||||
self.assertEqual(r, "fish")
|
||||
obj.mock.assert_called_once_with(1, 2, 3)
|
||||
obj.mock.reset_mock()
|
||||
|
||||
# a call with different params should call the mock again
|
||||
obj.mock.return_value = "chips"
|
||||
r = yield obj.fn(2, 3, 4)
|
||||
self.assertEqual(r, "chips")
|
||||
obj.mock.assert_called_once_with(2, 3, 4)
|
||||
obj.mock.reset_mock()
|
||||
|
||||
# the two values should now be cached; we should be able to vary
|
||||
# the second argument and still get the cached result.
|
||||
r = yield obj.fn(1, 4, 3)
|
||||
self.assertEqual(r, "fish")
|
||||
r = yield obj.fn(2, 5, 4)
|
||||
self.assertEqual(r, "chips")
|
||||
obj.mock.assert_not_called()
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_cache_kwargs(self):
|
||||
"""Test that keyword arguments are treated properly"""
|
||||
|
||||
class Cls:
|
||||
def __init__(self):
|
||||
self.mock = mock.Mock()
|
||||
|
||||
@descriptors.cached()
|
||||
def fn(self, arg1, kwarg1=2):
|
||||
return self.mock(arg1, kwarg1=kwarg1)
|
||||
|
||||
obj = Cls()
|
||||
obj.mock.return_value = "fish"
|
||||
r = yield obj.fn(1, kwarg1=2)
|
||||
self.assertEqual(r, "fish")
|
||||
obj.mock.assert_called_once_with(1, kwarg1=2)
|
||||
obj.mock.reset_mock()
|
||||
|
||||
# a call with different params should call the mock again
|
||||
obj.mock.return_value = "chips"
|
||||
r = yield obj.fn(1, kwarg1=3)
|
||||
self.assertEqual(r, "chips")
|
||||
obj.mock.assert_called_once_with(1, kwarg1=3)
|
||||
obj.mock.reset_mock()
|
||||
|
||||
# the values should now be cached.
|
||||
r = yield obj.fn(1, kwarg1=2)
|
||||
self.assertEqual(r, "fish")
|
||||
# We should be able to not provide kwarg1 and get the cached value back.
|
||||
r = yield obj.fn(1)
|
||||
self.assertEqual(r, "fish")
|
||||
# Keyword arguments can be in any order.
|
||||
r = yield obj.fn(kwarg1=2, arg1=1)
|
||||
self.assertEqual(r, "fish")
|
||||
obj.mock.assert_not_called()
|
||||
|
||||
def test_cache_with_sync_exception(self):
|
||||
"""If the wrapped function throws synchronously, things should continue to work"""
|
||||
|
||||
|
@ -656,7 +734,7 @@ class CachedListDescriptorTestCase(unittest.TestCase):
|
|||
def fn(self, arg1, arg2):
|
||||
pass
|
||||
|
||||
@descriptors.cachedList("fn", "args1")
|
||||
@descriptors.cachedList(cached_method_name="fn", list_name="args1")
|
||||
async def list_fn(self, args1, arg2):
|
||||
assert current_context().name == "c1"
|
||||
# we want this to behave like an asynchronous function
|
||||
|
@ -715,7 +793,7 @@ class CachedListDescriptorTestCase(unittest.TestCase):
|
|||
def fn(self, arg1):
|
||||
pass
|
||||
|
||||
@descriptors.cachedList("fn", "args1")
|
||||
@descriptors.cachedList(cached_method_name="fn", list_name="args1")
|
||||
def list_fn(self, args1) -> "Deferred[dict]":
|
||||
return self.mock(args1)
|
||||
|
||||
|
@ -758,7 +836,7 @@ class CachedListDescriptorTestCase(unittest.TestCase):
|
|||
def fn(self, arg1, arg2):
|
||||
pass
|
||||
|
||||
@descriptors.cachedList("fn", "args1")
|
||||
@descriptors.cachedList(cached_method_name="fn", list_name="args1")
|
||||
async def list_fn(self, args1, arg2):
|
||||
# we want this to behave like an asynchronous function
|
||||
await run_on_reactor()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue