Commit 4f464b0a by Robert Raposa Committed by Christopher Lee

Introduce CACHE_MISS utility object.

LEARNER-5001
parent 90b984a5
"""
Caching Utilities
The following caching utilities help make it simpler to properly handle caching.
Safe Cache Misses:
An object to be used to represent a CACHE_MISS. It should only be
used as follows:
value = safe_cache_get(key)
if value is CACHE_MISS:
value = None # or any appropriate default
...
The purpose of this code is to ensure that None is not used as the cache miss
in places where None was also meant to sometimes be a cache hit.
"""
from django.core.cache import cache
class CacheMissError(Exception):
"""
An error used when the CACHE_MISS object is misused in any context other
than checking if it is the CACHE_MISS object.
"""
USAGE_MESSAGE = 'Proper Usage: "if value is CACHE_MISS: value = DEFAULT; ...".'
def __init__(self, message=USAGE_MESSAGE):
super(CacheMissError, self).__init__(message)
class _CacheMiss(object):
"""
Private class representing cache misses. This is not meant to be used
outside of the singleton declaration of CACHE_MISS.
Meant to be a noisy object if used for any other purpose other than:
if value is CACHE_MISS:
"""
def __repr__(self):
return 'CACHE_MISS'
def __nonzero__(self):
raise CacheMissError()
def __bool__(self):
raise CacheMissError()
def __index__(self):
raise CacheMissError()
def __getattr__(self, name):
raise CacheMissError()
def __setattr__(self, name, val):
raise CacheMissError()
def __getitem__(self, key):
raise CacheMissError()
def __setitem__(self, key, val):
raise CacheMissError()
def __iter__(self):
raise CacheMissError()
def __contains__(self, value):
raise CacheMissError()
# Singleton CacheMiss to be used everywhere.
CACHE_MISS = _CacheMiss()
def safe_cache_get(key):
"""
Safely retrieves a cached value or returns the CACHE_MISS object.
The CACHE_MISS object helps avoid the problem where a cache miss is
represented by None, but some intended cache hits were also None, and
instead get treated as a cache miss by mistake.
Usage:
value = safe_cache_get(key)
if value is CACHE_MISS:
value = None # or any appropriate default
...
Args:
key (string): The key for which to retrieve a value from the cache.
Returns: The value associated with key, or the CACHE_MISS object.
"""
return cache.get(key, CACHE_MISS)
from ecommerce.core.cache_utils import CACHE_MISS, CacheMissError
from ecommerce.tests.testcases import TestCase
class CacheUtilityTests(TestCase):
def test_miss_cache_valid_use(self):
""" Test the valid uses of CACHE_MISS. """
self.assertTrue(CACHE_MISS is CACHE_MISS)
def test_miss_cache_invalid_use(self):
""" Test invalid uses of CACHE_MISS. """
with self.assertRaises(CacheMissError):
bool(CACHE_MISS)
with self.assertRaises(CacheMissError):
# For Python 3
CACHE_MISS.__bool__()
with self.assertRaises(CacheMissError):
CACHE_MISS.get('x')
with self.assertRaises(CacheMissError):
CACHE_MISS.x = None
with self.assertRaises(CacheMissError):
CACHE_MISS['key'] # pylint: disable=pointless-statement
with self.assertRaises(CacheMissError):
CACHE_MISS['key'] = None
with self.assertRaises(CacheMissError):
[0, 1][CACHE_MISS] # pylint: disable=expression-not-assigned, pointless-statement
with self.assertRaises(CacheMissError):
'x' in CACHE_MISS # pylint: disable=pointless-statement
with self.assertRaises(CacheMissError):
for x in CACHE_MISS: # pylint: disable=unused-variable
pass
......@@ -11,6 +11,7 @@ from oscar.core.loading import get_model
from requests.exceptions import ConnectionError, Timeout
from slumber.exceptions import HttpNotFoundError, SlumberBaseException
from ecommerce.core.cache_utils import CACHE_MISS, safe_cache_get
from ecommerce.core.utils import get_cache_key, traverse_pagination
from ecommerce.extensions.offer.decorators import check_condition_applicability
from ecommerce.extensions.offer.mixins import SingleItemConsumptionConditionMixin
......@@ -20,9 +21,6 @@ Condition = get_model('offer', 'Condition')
logger = logging.getLogger(__name__)
CACHE_MISS = object()
class ProgramCourseRunSeatsCondition(SingleItemConsumptionConditionMixin, Condition):
class Meta(object):
app_label = 'programs'
......@@ -55,7 +53,7 @@ class ProgramCourseRunSeatsCondition(SingleItemConsumptionConditionMixin, Condit
resource=resource_name,
username=basket.owner.username,
)
data_list = cache.get(cache_key, CACHE_MISS)
data_list = safe_cache_get(cache_key)
if data_list is CACHE_MISS:
data_list = None
user = basket.owner.username
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment