From e6883234ccab6662dae464e8610754f9ba768b52 Mon Sep 17 00:00:00 2001 From: Chuck Harmston Date: Wed, 6 Apr 2016 11:49:34 -0600 Subject: [PATCH] Adds cache TTLs: * Cache-Control headers on 200 responses. * Memcached TTLs. * Environment variables to configure each. Closes #85. --- recommendation/conf.py | 3 +++ recommendation/memorize.py | 3 ++- recommendation/tasks/task_recommend.py | 3 ++- recommendation/views/main.py | 13 ++++++++++++- recommendation/views/tests/test_main.py | 24 ++++++++++++++++++++---- 5 files changed, 39 insertions(+), 7 deletions(-) diff --git a/recommendation/conf.py b/recommendation/conf.py index fcf47ad..900acbe 100644 --- a/recommendation/conf.py +++ b/recommendation/conf.py @@ -4,6 +4,9 @@ DEBUG = env.get('RECOMMENDATION_ENV', 'development') == 'development' KEY_PREFIX = env.get('RECOMMENDATION_KEY_PREFIX', 'query_') +CACHE_TTL = env.get('RECOMMENDATION_CACHE_TTL', 7 * 24 * 60 * 60) +MEMCACHED_TTL = env.get('RECOMMENDATION_MEMCACHED_TTL', CACHE_TTL) + BING_ACCOUNT_KEY = env.get('BING_ACCOUNT_KEY', '') EMBEDLY_API_KEY = env.get('EMBEDLY_API_KEY', '') YAHOO_OAUTH_KEY = env.get('YAHOO_OAUTH_KEY', '') diff --git a/recommendation/memorize.py b/recommendation/memorize.py index 36285c4..8b04e06 100644 --- a/recommendation/memorize.py +++ b/recommendation/memorize.py @@ -5,6 +5,7 @@ from wrapt import ObjectProxy +from recommendation.conf import MEMCACHED_TTL from recommendation.memcached import memcached @@ -85,7 +86,7 @@ def attr(self, key): author.from_cache # False author.cache_key # 'memorize_cd2db0e4dc383ea0c5643ce6478612a3' """ - def __init__(self, prefix='memorized', ttl=0): + def __init__(self, prefix='memorized', ttl=MEMCACHED_TTL): self.prefix = prefix self.ttl = ttl diff --git a/recommendation/tasks/task_recommend.py b/recommendation/tasks/task_recommend.py index 545954d..492c624 100644 --- a/recommendation/tasks/task_recommend.py +++ b/recommendation/tasks/task_recommend.py @@ -1,3 +1,4 @@ +from recommendation.conf import MEMCACHED_TTL from recommendation.factory import create_queue from recommendation.memcached import memcached from recommendation.search.recommendation import SearchRecommendation @@ -9,5 +10,5 @@ @queue.task(name='main.recommend') def recommend(q, key): recommendation = SearchRecommendation(q).do_search(q) - memcached.set(key, recommendation) + memcached.set(key, recommendation, time=MEMCACHED_TTL) return recommendation diff --git a/recommendation/views/main.py b/recommendation/views/main.py index 363e2ea..971a11f 100644 --- a/recommendation/views/main.py +++ b/recommendation/views/main.py @@ -1,6 +1,7 @@ import hashlib -from flask import abort, current_app, Blueprint, jsonify, request +from flask import (abort, after_this_request, current_app, Blueprint, jsonify, + request) from recommendation import conf from recommendation.memcached import memcached @@ -11,6 +12,15 @@ @main.route('/') def view(): + + @after_this_request + def cache_control_headers(response): + if response.status_code == 200: + response.headers['Cache-Control'] = 'max-age=%d' % conf.CACHE_TTL + else: + response.headers['Cache-Control'] = 'no-cache, must-revalidate' + return response + from recommendation.tasks.task_recommend import recommend query = request.args.get('q') if not query: @@ -19,6 +29,7 @@ def view(): conf.KEY_PREFIX, hashlib.md5(str(query).encode('utf-8')).hexdigest() ]) + try: response = memcached.get(key) except Exception as e: diff --git a/recommendation/views/tests/test_main.py b/recommendation/views/tests/test_main.py index 9f1b4e5..1c650a5 100644 --- a/recommendation/views/tests/test_main.py +++ b/recommendation/views/tests/test_main.py @@ -3,6 +3,7 @@ from mock import patch from nose.tools import eq_, ok_ +from recommendation.conf import CACHE_TTL from recommendation.cors import cors_headers from recommendation.tests.memcached import mock_memcached from recommendation.tests.util import AppTestCase @@ -33,6 +34,13 @@ def _query(self, query): }) return self._get(url) + def _cached(self, response): + ok_('max-age=%d' % CACHE_TTL in response.headers['Cache-Control']) + + def _not_cached(self, response): + ok_('no-cache' in response.headers['Cache-Control']) + ok_('must-revalidate' in response.headers['Cache-Control']) + def test_cors(self): headers = [ 'Access-Control-Allow-Headers', @@ -46,7 +54,9 @@ def test_cors(self): for header in headers])) def test_no_query(self): - eq_(self._get('/').status_code, 400) + response = self._get('/') + eq_(response.status_code, 400) + self._not_cached(response) @patch('recommendation.tasks.task_recommend.memcached.get') def test_exception(self, mock_get): @@ -54,19 +64,24 @@ def test_exception(self, mock_get): response = self._query(QUERY) eq_(response.status_code, 500) eq_(response.json, {}) + self._not_cached(response) @patch('recommendation.tasks.task_recommend.memcached.get') def test_cache_hit(self, mock_get): mock_get.return_value = RESULTS - eq_(self._query(QUERY).status_code, 200) - eq_(self._query(QUERY).json, RESULTS) + response = self._query(QUERY) + eq_(response.status_code, 200) + eq_(response.json, RESULTS) + self._cached(response) @patch('recommendation.tasks.task_recommend.memcached.get') @patch('recommendation.tasks.task_recommend.recommend.delay') def test_cache_miss(self, mock_delay, mock_get): mock_get.return_value = None - eq_(self._query(QUERY).status_code, 202) + response = self._query(QUERY) + eq_(response.status_code, 202) eq_(mock_delay.call_count, 1) + self._not_cached(response) class TestMainDebug(TestMain): @@ -79,5 +94,6 @@ def test_exception(self, mock_delay, mock_get): response = self._query(QUERY) exception_name = list(response.json.keys())[0] eq_(response.status_code, 500) + self._not_cached(response) eq_(exception_name, EXCEPTION.__class__.__name__) eq_(response.json[exception_name], EXCEPTION_ARGS)