Skip to content

Commit

Permalink
Only cache precompilers specified in COMPRESS_CACHEABLE_PRECOMPILERS …
Browse files Browse the repository at this point in the history
…setting, and refactor caching logic into new CachedCompilerFilter class
  • Loading branch information
pindia committed Dec 14, 2012
1 parent d6742ae commit 64d17a9
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 17 deletions.
6 changes: 3 additions & 3 deletions compressor/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from compressor.conf import settings
from compressor.exceptions import (CompressorError, UncompressableFileError,
FilterDoesNotExist)
from compressor.filters import CompilerFilter
from compressor.filters import CachedCompilerFilter
from compressor.storage import default_storage, compressor_file_storage
from compressor.signals import post_compress
from compressor.utils import get_class, get_mod_func, staticfiles
Expand Down Expand Up @@ -212,8 +212,8 @@ def precompile(self, content, kind=None, elem=None, filename=None, **kwargs):
try:
mod = import_module(mod_name)
except ImportError:
return True, CompilerFilter(content, filter_type=self.type,
command=filter_or_command, filename=filename).input(
return True, CachedCompilerFilter(content=content, filter_type=self.type,
command=filter_or_command, filename=filename, mimetype=mimetype).input(
**kwargs)
try:
precompiler_class = getattr(mod, cls_name)
Expand Down
4 changes: 4 additions & 0 deletions compressor/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ def get_hashed_content(filename, length=12):
return get_hexdigest(content, length)


def get_precompiler_cachekey(command, contents):
return hashlib.sha1('precompiler.%s.%s' % (command, contents)).hexdigest()


def cache_get(key):
packed_val = cache.get(key)
if packed_val is None:
Expand Down
1 change: 1 addition & 0 deletions compressor/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class CompressorConf(AppConf):
# ('text/stylus', 'stylus < {infile} > {outfile}'),
# ('text/x-scss', 'sass --scss {infile} {outfile}'),
)
CACHEABLE_PRECOMPILERS = ()
CLOSURE_COMPILER_BINARY = 'java -jar compiler.jar'
CLOSURE_COMPILER_ARGUMENTS = ''
CSSTIDY_BINARY = 'csstidy'
Expand Down
2 changes: 1 addition & 1 deletion compressor/filters/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# flake8: noqa
from compressor.filters.base import (FilterBase, CallbackOutputFilter,
CompilerFilter, FilterError)
CompilerFilter, CachedCompilerFilter, FilterError)
32 changes: 23 additions & 9 deletions compressor/filters/base.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
from __future__ import absolute_import
import os
import hashlib
import logging
import subprocess

from django.core.cache import get_cache
from django.core.exceptions import ImproperlyConfigured
from django.core.files.temp import NamedTemporaryFile
from django.utils.importlib import import_module

from compressor.cache import cache, get_precompiler_cachekey
from compressor.conf import settings
from compressor.exceptions import FilterError
from compressor.utils import get_mod_func
from compressor.utils.stringformat import FormattableString as fstr

logger = logging.getLogger("compressor.filters")

cache = get_cache(settings.COMPRESS_CACHE_BACKEND)


class FilterBase(object):

Expand Down Expand Up @@ -99,10 +96,6 @@ def __init__(self, content, command=None, *args, **kwargs):
self.infile, self.outfile = None, None

def input(self, **kwargs):
content_hash = hashlib.sha1(self.command + self.content.encode('utf8')).hexdigest()
data = cache.get(content_hash)
if data:
return data
options = dict(self.options)
if self.infile is None:
if "{infile}" in self.command:
Expand Down Expand Up @@ -150,5 +143,26 @@ def input(self, **kwargs):
if self.outfile is not None:
filtered = self.outfile.read()
self.outfile.close()
cache.set(content_hash, filtered, settings.COMPRESS_REBUILD_TIMEOUT)
return filtered


class CachedCompilerFilter(CompilerFilter):

def __init__(self, mimetype, **kwargs):
self.mimetype = mimetype
super(CachedCompilerFilter, self).__init__(**kwargs)

def input(self, **kwargs):
if self.mimetype in settings.COMPRESS_CACHEABLE_PRECOMPILERS:
key = self.get_cache_key()
data = cache.get(key)
if data:
return data
filtered = super(CachedCompilerFilter, self).input(**kwargs)
cache.set(key, filtered, settings.COMPRESS_REBUILD_TIMEOUT)
return filtered
else:
return super(CachedCompilerFilter, self).input(**kwargs)

def get_cache_key(self):
return get_precompiler_cachekey(self.command, self.content.encode('utf8'))
19 changes: 15 additions & 4 deletions compressor/tests/test_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from compressor.conf import settings
from compressor.css import CssCompressor
from compressor.utils import find_command
from compressor.filters.base import CompilerFilter
from compressor.filters.base import CompilerFilter, CachedCompilerFilter
from compressor.filters.cssmin import CSSMinFilter
from compressor.filters.css_default import CssAbsoluteFilter
from compressor.filters.template import TemplateFilter
Expand Down Expand Up @@ -41,6 +41,7 @@ def setUp(self):
with open(self.filename) as f:
self.content = f.read()
self.test_precompiler = os.path.join(test_dir, 'precompiler.py')
settings.COMPRESS_CACHEABLE_PRECOMPILERS = ('text/css',)

def test_precompiler_infile_outfile(self):
command = '%s %s -f {infile} -o {outfile}' % (sys.executable, self.test_precompiler)
Expand Down Expand Up @@ -69,18 +70,28 @@ def test_precompiler_stdin_stdout_filename(self):

def test_precompiler_cache(self):
command = '%s %s -f {infile} -o {outfile}' % (sys.executable, self.test_precompiler)
compiler = CompilerFilter(content=self.content, filename=self.filename, command=command)
compiler = CachedCompilerFilter(content=self.content, filename=self.filename, command=command, mimetype='text/css')
self.assertEqual(u"body { color:#990; }", compiler.input())
# We tell whether the precompiler actually ran by inspecting compiler.infile. If not None, the compiler had to
# write the input out to the file for the external command. If None, it was in the cache and thus skipped.
self.assertIsNotNone(compiler.infile) # Not cached

compiler = CompilerFilter(content=self.content, filename=self.filename, command=command)
compiler = CachedCompilerFilter(content=self.content, filename=self.filename, command=command, mimetype='text/css')
self.assertEqual(u"body { color:#990; }", compiler.input())
self.assertIsNone(compiler.infile) # Cached

self.content += ' ' # Invalidate cache by slightly changing content
compiler = CompilerFilter(content=self.content, filename=self.filename, command=command)
compiler = CachedCompilerFilter(content=self.content, filename=self.filename, command=command, mimetype='text/css')
self.assertEqual(u"body { color:#990; }", compiler.input())
self.assertIsNotNone(compiler.infile) # Not cached

def test_precompiler_not_cacheable(self):
command = '%s %s -f {infile} -o {outfile}' % (sys.executable, self.test_precompiler)
compiler = CachedCompilerFilter(content=self.content, filename=self.filename, command=command, mimetype='text/different')
self.assertEqual(u"body { color:#990; }", compiler.input())
self.assertIsNotNone(compiler.infile) # Not cached

compiler = CachedCompilerFilter(content=self.content, filename=self.filename, command=command, mimetype='text/different')
self.assertEqual(u"body { color:#990; }", compiler.input())
self.assertIsNotNone(compiler.infile) # Not cached

Expand Down

0 comments on commit 64d17a9

Please sign in to comment.