Skip to content

Instantly share code, notes, and snippets.

@tgs
Created August 5, 2013 21:44
Show Gist options
  • Save tgs/6159884 to your computer and use it in GitHub Desktop.
Save tgs/6159884 to your computer and use it in GitHub Desktop.
Patch to Google Course Builder v1.4.1, adding caching of the bytecode of Jinja2 templates. Change to the root dir of your course builder installation and use patch -p2 < (this file)
diff --git a/coursebuilder/common/jinja_filters.py b/coursebuilder/common/jinja_filters.py
index 2bb6d17..a8a6261 100644
--- a/coursebuilder/common/jinja_filters.py
+++ b/coursebuilder/common/jinja_filters.py
@@ -17,8 +17,10 @@
__author__ = 'John Orr (jorr@google.com)'
import jinja2
+from jinja2.bccache import BytecodeCache
import safe_dom
import tags
+from models.models import MemcacheManager
def finalize(x):
@@ -52,3 +54,59 @@ def gcb_tags(data):
return jinja2.utils.Markup(tags.html_to_safe_dom(data))
else:
return jinja2.utils.Markup(data)
+
+
+class ClearableMemcachedBytecodeCache(BytecodeCache):
+ """Requires a fancy memcache client, like Google App Engine's,
+ that supports namespaces. Requires get, set, add, and incr.
+
+ When you call .clear(), the entries previously stored through
+ this object become inaccessible through it, although they
+ are not guaranteed to be evicted from the underlying memcached
+ immediately.
+ """
+ __author__ = 'Thomas Grenfell Smith (thomathom@gmail.com)'
+
+ def __init__(self, client, namespace=None,
+ prefix='jinja2:bytecode:', timeout=None,
+ ignore_memcache_errors=True):
+ self.namespace = namespace
+ self.client = client
+ self.prefix = prefix
+ self.timeout = timeout
+ self.ignore_memcache_errors = ignore_memcache_errors
+
+ def _get_key(self, bucket):
+ generation_key = self.prefix + '__generation__'
+ generation = '1'
+ if not self.client.add(generation_key, generation, namespace=self.namespace):
+ generation = self.client.get(generation_key,
+ namespace=self.namespace)
+
+ return '%s:%s:%s' % (self.prefix, generation, bucket.key)
+
+ def clear(self):
+ generation_key = self.prefix + '__generation__'
+ if not self.client.incr(generation_key, 1, namespace=self.namespace):
+ self.client.set(generation_key, '1', namespace=self.namespace)
+
+ def load_bytecode(self, bucket):
+ try:
+ code = self.client.get(self._get_key(bucket),
+ namespace=self.namespace)
+ except Exception:
+ if not self.ignore_memcache_errors:
+ raise
+ code = None
+ if code is not None:
+ bucket.bytecode_from_string(code)
+
+ def dump_bytecode(self, bucket):
+ args = (self._get_key(bucket), bucket.bytecode_to_string())
+ if self.timeout is not None:
+ args += (self.timeout,)
+ try:
+ self.client.set(*args, namespace=self.namespace)
+ except Exception:
+ if not self.ignore_memcache_errors:
+ raise
diff --git a/coursebuilder/models/models.py b/coursebuilder/models/models.py
index 74a794f..36efb68 100644
--- a/coursebuilder/models/models.py
+++ b/coursebuilder/models/models.py
@@ -96,7 +96,17 @@ class MemcacheManager(object):
if CAN_USE_MEMCACHE.value:
if not namespace:
namespace = appengine_config.DEFAULT_NAMESPACE_NAME
- memcache.incr(key, delta, namespace=namespace, initial_value=0)
+ return memcache.incr(key, delta, namespace=namespace, initial_value=0)
+
+ @classmethod
+ def add(cls, key, value, ttl=DEFAULT_CACHE_TTL_SECS, namespace=None):
+ """Adds an item in memcache if memcache is enabled."""
+ if CAN_USE_MEMCACHE.value:
+ CACHE_PUT.inc()
+ if not namespace:
+ namespace = appengine_config.DEFAULT_NAMESPACE_NAME
+ return memcache.add(
+ key, value, ttl, namespace=namespace)
@classmethod
def delete(cls, key, namespace=None):
diff --git a/coursebuilder/models/vfs.py b/coursebuilder/models/vfs.py
index 2477e94..6fa8473 100644
--- a/coursebuilder/models/vfs.py
+++ b/coursebuilder/models/vfs.py
@@ -156,6 +156,8 @@ class LocalReadOnlyFileSystem(object):
jinja_environment = jinja2.Environment(
autoescape=True, finalize=jinja_filters.finalize,
extensions=['jinja2.ext.i18n'],
+ bytecode_cache=jinja_filters.ClearableMemcachedBytecodeCache(
+ MemcacheManager),
loader=jinja2.FileSystemLoader(physical_dir_names))
jinja_environment.filters['js_string'] = jinja_filters.js_string
@@ -511,6 +513,8 @@ class DatastoreBackedFileSystem(object):
jinja_environment = jinja2.Environment(
autoescape=True, finalize=jinja_filters.finalize,
extensions=['jinja2.ext.i18n'],
+ bytecode_cache=jinja_filters.ClearableMemcachedBytecodeCache(
+ MemcacheManager, namespace=self._ns),
loader=VirtualFileSystemTemplateLoader(
self, self._logical_home_folder, dir_names))
jinja_environment.filters['js_string'] = jinja_filters.js_string
diff --git a/coursebuilder/modules/admin/admin.py b/coursebuilder/modules/admin/admin.py
index 3cdaa87..708546e 100644
--- a/coursebuilder/modules/admin/admin.py
+++ b/coursebuilder/modules/admin/admin.py
@@ -34,6 +34,7 @@ from models import config
from models import counters
from models import custom_modules
from models import roles
+from models.models import MemcacheManager
from models.config import ConfigProperty
import modules.admin.config
from modules.admin.config import ConfigPropertyEditor
@@ -130,6 +131,8 @@ class AdminHandler(
"""Sets up an environment and Gets jinja template."""
jinja_environment = jinja2.Environment(
autoescape=True, finalize=jinja_filters.finalize,
+ bytecode_cache=jinja_filters.ClearableMemcachedBytecodeCache(
+ MemcacheManager),
loader=jinja2.FileSystemLoader(dirs + [os.path.dirname(__file__)]))
jinja_environment.filters['js_string'] = jinja_filters.js_string
return jinja_environment.get_template(template_name)
diff --git a/coursebuilder/modules/dashboard/dashboard.py b/coursebuilder/modules/dashboard/dashboard.py
index d738e1e..043d6cb 100644
--- a/coursebuilder/modules/dashboard/dashboard.py
+++ b/coursebuilder/modules/dashboard/dashboard.py
@@ -35,7 +35,7 @@ from models import roles
from models import transforms
from models import utils
from models import vfs
-from models.models import Student
+from models.models import Student, MemcacheManager
from course_settings import CourseSettingsHandler
from course_settings import CourseSettingsRESTHandler
import filer
@@ -120,6 +120,9 @@ class DashboardHandler(
jinja_environment = jinja2.Environment(
autoescape=True, finalize=jinja_filters.finalize,
+ bytecode_cache=jinja_filters.ClearableMemcachedBytecodeCache(
+ MemcacheManager,
+ namespace=self.app_context.get_namespace_name()),
loader=jinja2.FileSystemLoader(dirs + [os.path.dirname(__file__)]))
jinja_environment.filters['js_string'] = jinja_filters.js_string
diff --git a/coursebuilder/modules/oeditor/oeditor.py b/coursebuilder/modules/oeditor/oeditor.py
index d629b72..17709a1 100644
--- a/coursebuilder/modules/oeditor/oeditor.py
+++ b/coursebuilder/modules/oeditor/oeditor.py
@@ -26,6 +26,7 @@ from controllers import utils
import jinja2
from models import custom_modules
from models import transforms
+from models.models import MemcacheManager
import webapp2
# a set of YUI and inputex modules required by the editor
@@ -151,6 +152,9 @@ class PopupHandler(webapp2.RequestHandler, utils.ReflectiveRequestHandler):
jinja_environment = jinja2.Environment(
autoescape=True, finalize=jinja_filters.finalize,
+ bytecode_cache=jinja_filters.ClearableMemcachedBytecodeCache(
+ MemcacheManager,
+ namespace=self.app_context.get_namespace_name()),
loader=jinja2.FileSystemLoader(dirs + [os.path.dirname(__file__)]))
jinja_environment.filters['js_string'] = jinja_filters.js_string
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment