Skip to content

Commit

Permalink
Merge pull request #1 from tobiasmcnulty/django-upgrades
Browse files Browse the repository at this point in the history
try to simplify Django 1.8/Django 1.11 compatibility
  • Loading branch information
vkurup committed Oct 13, 2017
2 parents 4f1fb8e + 52d4ea6 commit c220d61
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 55 deletions.
81 changes: 30 additions & 51 deletions caching/base.py
Expand Up @@ -13,11 +13,17 @@
from caching.invalidation import invalidator, flush_key, make_key, byid, cache

try:
# ModelIterable is defined in Django 1.9+, and if it's present, we use it
# iterate over our results.
from django.db.models.query import ModelIterable
except ImportError:
# ModelIterable is defined in Django 1.9+, and if it's present, we
# use it iterate over our results. If not, we fall back to a Django 1.8 compatible way.
ModelIterable = None
# If not, define a Django 1.8-compatible stub we can use instead.
class ModelIterable(object):
def __init__(self, queryset):
self.queryset = queryset

def __iter__(self):
return super(CachingQuerySet, self.queryset).iterator()

log = logging.getLogger('caching')

Expand Down Expand Up @@ -57,11 +63,20 @@ def no_cache(self):
return self.cache(config.NO_CACHE)


class CacheInternalCommonMixin(object):
class CachingModelIterable(ModelIterable):
"""
A set of methods common to our Django 1.8 and Django 1.9+ iterators.
Handles all the cache management for a QuerySet.
Takes the string representation of a query and a function that can be
called to get an iterator over some database results.
"""

def __init__(self, queryset, *args, **kwargs):
self.iter_function = kwargs.pop('iter_function', None)
self.timeout = kwargs.pop('timeout', queryset.timeout)
self.db = kwargs.pop('db', queryset.db)
super(CachingModelIterable, self).__init__(queryset, *args, **kwargs)

def query_key(self):
"""
Generate the cache key for this query.
Expand All @@ -83,14 +98,13 @@ def cache_objects(self, objects, query_key):
invalidator.cache_objects(self.queryset.model, objects, query_key, query_flush)

def __iter__(self):
if hasattr(super(CacheInternalCommonMixin, self), '__iter__'):
# This is the Django 1.9+ class, so we'll use super().__iter__
# which is a ModelIterable iterator.
iterator = super(CacheInternalCommonMixin, self).__iter__
else:
# This is Django 1.8. Use the function passed into the class
# constructor.
if self.iter_function is not None:
# This a RawQuerySet. Use the function passed into
# the class constructor.
iterator = self.iter_function
else:
# Otherwise, use super().__iter__.
iterator = super(CachingModelIterable, self).__iter__

if self.timeout == config.NO_CACHE:
# no cache, just iterate and return the results
Expand All @@ -102,7 +116,7 @@ def __iter__(self):
try:
query_key = self.query_key()
except query.EmptyResultSet:
raise StopIteration
return

cached = cache.get(query_key)
if cached is not None:
Expand All @@ -127,44 +141,14 @@ def __iter__(self):
self.cache_objects(to_cache, query_key)


class CacheMachine(CacheInternalCommonMixin):
"""
Handles all the cache management for a QuerySet.
Takes the string representation of a query and a function that can be
called to get an iterator over some database results.
"""

def __init__(self, queryset, iter_function=None, timeout=DEFAULT_TIMEOUT, db='default'):
self.queryset = queryset
self.iter_function = iter_function
self.timeout = timeout
self.db = db


if ModelIterable:
class CachingModelIterable(CacheInternalCommonMixin, ModelIterable):
"""
A version of Django's ModelIterable that first tries to get results from the cache.
"""

def __init__(self, *args, **kwargs):
super(CachingModelIterable, self).__init__(*args, **kwargs)
# copy timeout and db from queryset to allow CacheInternalCommonMixin to be DRYer
self.timeout = self.queryset.timeout
self.db = self.queryset.db


class CachingQuerySet(models.query.QuerySet):

_default_timeout_pickle_key = '__DEFAULT_TIMEOUT__'

def __init__(self, *args, **kw):
super(CachingQuerySet, self).__init__(*args, **kw)
self.timeout = DEFAULT_TIMEOUT
if ModelIterable:
# Django 1.9+
self._iterable_class = CachingModelIterable
self._iterable_class = CachingModelIterable

def __getstate__(self):
"""
Expand Down Expand Up @@ -193,11 +177,7 @@ def query_key(self):
return sql % params

def iterator(self):
if ModelIterable:
# Django 1.9+
return self._iterable_class(self)
iterator = super(CachingQuerySet, self).iterator
return iter(CacheMachine(self, iterator, self.timeout, db=self.db))
return self._iterable_class(self)

def fetch_by_id(self):
"""
Expand Down Expand Up @@ -332,9 +312,8 @@ def __iter__(self):
while True:
yield next(iterator)
else:
for obj in CacheMachine(self, iterator, timeout=self.timeout):
for obj in CachingModelIterable(self, iter_function=iterator, timeout=self.timeout):
yield obj
raise StopIteration

def query_key(self):
return self.raw_query % tuple(self.params)
Expand Down
2 changes: 1 addition & 1 deletion run_tests.py
Expand Up @@ -47,7 +47,7 @@ def main():
test_cmd = ['coverage', 'run']
else:
test_cmd = []
test_cmd += [django_admin, 'test']
test_cmd += [django_admin, 'test', '--keepdb']
results.append(call(test_cmd))
if args.with_coverage:
results.append(call(['coverage', 'report', '-m', '--fail-under', '70']))
Expand Down
6 changes: 3 additions & 3 deletions tests/test_cache.py
Expand Up @@ -165,15 +165,15 @@ def test_raw_cache_params(self):
raw2 = list(Addon.objects.raw(sql, [2]))[0]
self.assertEqual(raw2.id, 2)

@mock.patch('caching.base.CacheMachine')
def test_raw_nocache(self, CacheMachine):
@mock.patch('caching.base.CachingModelIterable')
def test_raw_nocache(self, CachingModelIterable):
base.TIMEOUT = 60
sql = 'SELECT * FROM %s WHERE id = 1' % Addon._meta.db_table
raw = list(Addon.objects.raw(sql, timeout=config.NO_CACHE))
self.assertEqual(len(raw), 1)
raw_addon = raw[0]
self.assertFalse(hasattr(raw_addon, 'from_cache'))
self.assertFalse(CacheMachine.called)
self.assertFalse(CachingModelIterable.called)

@mock.patch('caching.base.cache')
def test_count_cache(self, cache_mock):
Expand Down

0 comments on commit c220d61

Please sign in to comment.