From 896bc1c8595705e06c7ceba10cf2bd7e6b6c63b7 Mon Sep 17 00:00:00 2001 From: stephensanchez Date: Thu, 22 Jan 2015 17:05:57 +0000 Subject: [PATCH] Adding caching to the course details endpoint for the Enrollment API. --- common/djangoapps/enrollment/api.py | 27 ++++++++++++++++++- .../djangoapps/enrollment/tests/test_api.py | 19 ++++++++++++- lms/envs/aws.py | 3 +++ lms/envs/common.py | 3 +++ 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/common/djangoapps/enrollment/api.py b/common/djangoapps/enrollment/api.py index 1290992663..7fd5599cac 100644 --- a/common/djangoapps/enrollment/api.py +++ b/common/djangoapps/enrollment/api.py @@ -5,6 +5,7 @@ course level, such as available course modes. """ from django.utils import importlib import logging +from django.core.cache import cache from django.conf import settings from enrollment import errors @@ -250,7 +251,31 @@ def get_course_enrollment_details(course_id): } """ - return _data_api().get_course_enrollment_info(course_id) + cache_key = u"enrollment.course.details.{course_id}".format(course_id=course_id) + + cached_enrollment_data = None + try: + cached_enrollment_data = cache.get(cache_key) + except Exception: + # The cache backend could raise an exception (for example, memcache keys that contain spaces) + log.exception(u"Error occurred while retrieving course enrollment details from the cache") + + if cached_enrollment_data: + log.info(u"Get enrollment data for course %s (cached)", course_id) + return cached_enrollment_data + + course_enrollment_details = _data_api().get_course_enrollment_info(course_id) + + try: + cache_time_out = getattr(settings, 'ENROLLMENT_COURSE_DETAILS_CACHE_TIMEOUT', 60) + cache.set(cache_key, course_enrollment_details, cache_time_out) + except Exception: + # Catch any unexpected errors during caching. + log.exception(u"Error occurred while caching course enrollment details for course %s", course_id) + raise errors.CourseEnrollmentError(u"An unexpected error occurred while retrieving course enrollment details.") + + log.info(u"Get enrollment data for course %s", course_id) + return course_enrollment_details def _validate_course_mode(course_id, mode): diff --git a/common/djangoapps/enrollment/tests/test_api.py b/common/djangoapps/enrollment/tests/test_api.py index f6b80994b6..c165a5d627 100644 --- a/common/djangoapps/enrollment/tests/test_api.py +++ b/common/djangoapps/enrollment/tests/test_api.py @@ -2,6 +2,7 @@ Tests for student enrollment. """ import ddt +from django.core.cache import cache from nose.tools import raises import unittest from django.test import TestCase @@ -24,6 +25,7 @@ class EnrollmentTest(TestCase): def setUp(self): fake_data_api.reset() + cache.clear() @ddt.data( # Default (no course modes in the database) @@ -125,7 +127,7 @@ class EnrollmentTest(TestCase): ) def test_update_enrollment(self): - # Add a fake course enrollment information to the fake data API + # Add fake course enrollment information to the fake data API fake_data_api.add_course(self.COURSE_ID, course_modes=['honor', 'verified', 'audit']) # Enroll in the course and verify the URL we get sent to result = api.add_enrollment(self.USERNAME, self.COURSE_ID, mode='audit') @@ -150,3 +152,18 @@ class EnrollmentTest(TestCase): def test_data_api_config_error(self): # Enroll in the course and verify the URL we get sent to api.add_enrollment(self.USERNAME, self.COURSE_ID, mode='audit') + + def test_caching(self): + # Add fake course enrollment information to the fake data API + fake_data_api.add_course(self.COURSE_ID, course_modes=['honor', 'verified', 'audit']) + + # Hit the fake data API. + details = api.get_course_enrollment_details(self.COURSE_ID) + + # Reset the fake data API, should rely on the cache. + fake_data_api.reset() + cached_details = api.get_course_enrollment_details(self.COURSE_ID) + + # The data matches + self.assertEqual(len(details['course_modes']), 3) + self.assertEqual(details, cached_details) diff --git a/lms/envs/aws.py b/lms/envs/aws.py index a33359ad29..e0f117246d 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -484,6 +484,9 @@ INVOICE_PAYMENT_INSTRUCTIONS = ENV_TOKENS.get('INVOICE_PAYMENT_INSTRUCTIONS', IN API_DATE_FORMAT = '%Y-%m-%d' API_DATE_FORMAT = ENV_TOKENS.get('API_DATE_FORMAT', API_DATE_FORMAT) +# Enrollment API Cache Timeout +ENROLLMENT_COURSE_DETAILS_CACHE_TIMEOUT = ENV_TOKENS.get('ENROLLMENT_COURSE_DETAILS_CACHE_TIMEOUT', 60) + # PDF RECEIPT/INVOICE OVERRIDES PDF_RECEIPT_TAX_ID = ENV_TOKENS.get('PDF_RECEIPT_TAX_ID', PDF_RECEIPT_TAX_ID) PDF_RECEIPT_FOOTER_TEXT = ENV_TOKENS.get('PDF_RECEIPT_FOOTER_TEXT', PDF_RECEIPT_FOOTER_TEXT) diff --git a/lms/envs/common.py b/lms/envs/common.py index 1b015000db..f609758535 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -1990,6 +1990,9 @@ COURSE_ABOUT_VISIBILITY_PERMISSION = 'see_exists' #date format the api will be formatting the datetime values API_DATE_FORMAT = '%Y-%m-%d' +# Enrollment API Cache Timeout +ENROLLMENT_COURSE_DETAILS_CACHE_TIMEOUT = 60 + # for Student Notes we would like to avoid too frequent token refreshes (default is 30 seconds) if FEATURES['ENABLE_EDXNOTES']: OAUTH_ID_TOKEN_EXPIRATION = 60 * 60