Reputation: 2710
So I'm working on an application that, upon import of certain records, requires some fields to be recalculated. To prevent a database read for each check, there is a caching decorator so the database read is only preformed once every n seconds during import. The trouble comes with building test cases. The following does work, but it has an ugly sleep in it.
# The decorator I need to patch
@cache_function_call(2.0)
def _latest_term_modified():
return PrimaryTerm.objects.latest('object_modified').object_modified
# The 2.0 sets the TTL of the decorator. So I need to switch out
# self.ttl for this decorated function before
# this test. Right now I'm just using a sleep, which works
@mock.patch.object(models.Student, 'update_anniversary')
def test_import_on_term_update(self, mock_update):
self._import_student()
latest_term = self._latest_term_mod()
latest_term.save()
time.sleep(3)
self._import_student()
self.assertEqual(mock_update.call_count, 2)
The decorator itself looks like the following:
class cache_function_call(object):
"""Cache an argument-less function call for 'ttl' seconds."""
def __init__(self, ttl):
self.cached_result = None
self.timestamp = 0
self.ttl = ttl
def __call__(self, func):
@wraps(func)
def inner():
now = time.time()
if now > self.timestamp + self.ttl:
self.cached_result = func()
self.timestamp = now
return self.cached_result
return inner
I have attempted to set the decorator before the import of the models:
decorators.cache_function_call = lambda x : x
import models
But even at the top of the file, django still initializes the models before running my tests.py
and the function still gets decorated with the caching decorator instead of my lambda/noop one.
What's the best way to go about writing this test so I don't have a sleep. Can I set the ttl of the decorator before running my import somehow?
Upvotes: 1
Views: 1101
Reputation: 882721
You can change the decorator class just a little bit.
At module level in decorators.py
set the global
BAILOUT = False
and in your decorator class, change:
def __call__(self, func):
@wraps(func)
def inner():
now = time.time()
if BAILOUT or now > self.timestamp + self.ttl:
self.cached_result = func()
self.timestamp = now
return self.cached_result
return inner
Then in your tests set decorators.BAILOUT = True
, and, hey presto!-)
Upvotes: 1