Skip to content

Commit

Permalink
Support Django cached loader. Closes #23.
Browse files Browse the repository at this point in the history
  • Loading branch information
miracle2k committed Feb 8, 2012
1 parent 3edda75 commit bf1fc6a
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 21 deletions.
25 changes: 18 additions & 7 deletions coffin/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,29 @@ def _get_loaders(self):
loaders = []

from coffin.template.loaders import jinja_loader_from_django_loader
from jinja2.loaders import BaseLoader as JinjaLoader

from django.conf import settings
_loaders = getattr(settings, 'JINJA2_TEMPLATE_LOADERS', settings.TEMPLATE_LOADERS)
for loader in _loaders:
if isinstance(loader, basestring):
loader_obj = jinja_loader_from_django_loader(loader)
if loader_obj:
loaders.append(loader_obj)
else:
warnings.warn('Cannot translate loader: %s' % loader)
else: # It's assumed to be a Jinja2 loader instance.
if isinstance(loader, JinjaLoader):
loaders.append(loader)
else:
loader_name = args = None
if isinstance(loader, basestring):
loader_name = loader
args = []
elif isinstance(loader, (tuple, list)):
loader_name = loader[0]
args = loader[1]

if loader_name:
loader_obj = jinja_loader_from_django_loader(loader_name, args)
if loader_obj:
loaders.append(loader_obj)
continue

warnings.warn('Cannot translate loader: %s' % loader)
return loaders


Expand Down
50 changes: 48 additions & 2 deletions coffin/template/loaders.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from jinja2 import loaders


def jinja_loader_from_django_loader(django_loader):
def jinja_loader_from_django_loader(django_loader, args=None):
"""Attempts to make a conversion from the given Django loader to an
similarly-behaving Jinja loader.
Expand All @@ -13,7 +13,7 @@ def jinja_loader_from_django_loader(django_loader):
return None
for substr, func in _JINJA_LOADER_BY_DJANGO_SUBSTR.iteritems():
if substr in django_loader:
return func()
return func(*(args or []))
return None


Expand All @@ -33,8 +33,54 @@ def _make_jinja_filesystem_loader():
return loaders.FileSystemLoader(settings.TEMPLATE_DIRS)


def _make_jinja_cached_loader(*loaders):
"""Makes a loader for Jinja which acts like
:mod:`django.template.loaders.cached`.
"""
return JinjaCachedLoader(
[jinja_loader_from_django_loader(l) for l in loaders])


# Determine loaders from Django's conf.
_JINJA_LOADER_BY_DJANGO_SUBSTR = { # {substr: callable, ...}
'app_directories': _make_jinja_app_loader,
'filesystem': _make_jinja_filesystem_loader,
'cached': _make_jinja_cached_loader,
}


class JinjaCachedLoader(loaders.BaseLoader):
"""A "sort of" port of of Django's "cached" template loader
to Jinja 2. It exists primarily to support Django's full
TEMPLATE_LOADERS syntax.
However, note that it does not behave exactly like Django's cached
loader: Rather than caching the compiled template, it only caches
the template source, and recompiles the template every time. This is
due to the way the Jinja2/Coffin loader setup works: The ChoiceLoader,
which Coffin uses at the root to select from any of the configured
loaders, calls the ``get_source`` method of each loader directly,
bypassing ``load``. Our loader can therefore only hook into the process
BEFORE template compilation.
Caching the compiled templates by implementing ``load`` would only
work if this loader instance were the root loader. See also the comments
in Jinja2's BaseLoader class.
Note that Jinja2 has an environment-wide bytecode cache (i.e. it caches
compiled templates), that can function alongside with this class.
Note further that Jinja2 has an environment-wide template cache (via the
``auto_reload`` environment option), which duplicate the functionality
of this class entirely, and should be preferred when possible.
"""

def __init__(self, subloaders):
self.loader = loaders.ChoiceLoader(subloaders)
self.template_cache = {}

def get_source(self, environment, template):
key = (environment, template)
if key not in self.template_cache:
result = self.loader.get_source(environment, template)
self.template_cache[key] = result
return self.template_cache[key]
45 changes: 33 additions & 12 deletions tests/test_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,36 @@ def test_i18n():
assert get_env().from_string('{{ _("test") }}').render() == 'test'


def test_django_loader_replace():
from coffin.template.loaders import jinja_loader_from_django_loader
from jinja2 import loaders

# Test replacement of filesystem loader
l = jinja_loader_from_django_loader('django.template.loaders.filesystem.Loader')
assert isinstance(l, loaders.FileSystemLoader)

# Since we don't do exact matches for the loader string, make sure we
# are not replacing loaders that are outside the Django namespace.
l = jinja_loader_from_django_loader('djangoaddon.template.loaders.filesystem.Loader')
assert not isinstance(l, loaders.FileSystemLoader)
class TestLoaders:

def test_django_loader_replace(self):
from coffin.template.loaders import jinja_loader_from_django_loader
from jinja2 import loaders

# Test replacement of filesystem loader
l = jinja_loader_from_django_loader('django.template.loaders.filesystem.Loader')
assert isinstance(l, loaders.FileSystemLoader)

# Since we don't do exact matches for the loader string, make sure we
# are not replacing loaders that are outside the Django namespace.
l = jinja_loader_from_django_loader('djangoaddon.template.loaders.filesystem.Loader')
assert not isinstance(l, loaders.FileSystemLoader)

def test_cached_loader(self):
from jinja2 import loaders

with override_settings(TEMPLATE_LOADERS=[
('django.template.loaders.cached.Loader', (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
)),]):
env = get_env()
assert len(env.loader.loaders) == 1
cached_loader = get_env().loader.loaders[0]
assert hasattr(cached_loader, 'template_cache')
assert len(cached_loader.loader.loaders) == 2
assert isinstance(cached_loader.loader.loaders[0], loaders.FileSystemLoader)

# the cached loader can find a template too.
assert env.loader.load(env, 'render-x.html').render({'x': 'foo'}) == 'foo'

0 comments on commit bf1fc6a

Please sign in to comment.