Hey everyone,
I've been working on a patch for Django that would allow you to optionally cache templates after they've been lexed and parsed (compiled) by the template engine. I've got things far enough along that I have a working implementation, so I thought I'd share here and see if anyone had any thoughts / comments. I've attached my diff to ticket #6262, so visit http://code.djangoproject.com/ticket/6262 to check it out. I'd like to add template caching as a feature to Django 1.2, and am willing to do whatever is necessary to make this happen. If you'd rather read prose, here's a bit of an explanation / justification for the changes: Why? At the moment, Django is reading templates from disk, lexing them, parsing them, and then rendering them with the current context _every time_ a template is rendered. If your blog.html template loops through an array of 15 blog posts and {% includes %} post.html for each of them, post.html is read from disk (or disk cache), lexed, parsed, and rendered 15 times. All but the render step is avoidable with a bit of caching. I've done some rather crude benchmarking and, by my measurements, a template takes about 1ms to lex and parse. That doesn't seem like much, but it adds up. I'm working on a project that renders ~400 templates for the index page (you need to do lots of extends and includes if you're making a reusable app that you want to let people skin) and with this patch enabled we're seeing template rendering time decrease by about 350ms. Big win. How? The only thing that's absolutely necessary to make cached templates possible in Django is to add a branch in ``django.template.loader.get_template()`` that checks whether the returned template "source" is actually a compiled template, in which case it is returned directly and the compilation step is skipped. Once this is done, template caching can be implemented with a custom template loader. By simply checking that the template has a ``render`` method in ``get_template``, we get the added benefit of allowing users to write loaders that return custom Template instances, or Template instances that use an alternative template language like Jinja or Mako. Template-Tag State: In order to make cached templates usable in practice (and backwards compatible) some changes need to be made in the template tags as well. In particular, the block, extends, and cycle template tags store state on instances. Since template tags are instantiated when the template is parsed, this state sticks around between template renders. This also means templates are not thread-safe (a separate, but related problem) -- if two threads share the same instantiated template and both try to render it problems can arise. To make template tags safe for cached templates (and thread-safe) all state should be stored in the template context. But if we just stick state in the context dictionary we're polluting the template namespace with irrelevant stuff that probably shouldn't be exposed there. Therefore, I propose adding an attribute to Context instances, ``parser_context`` that can be used to store parser state. Unlike the Context object, I believe parser context should be statically scoped per Template render. That is, if the dictionary at the top of the "stack" doesn't contain the requested key, a KeyError should be raised immediately rather than walking the stack looking for the key in dictionaries further up the stack. I think this makes sense since parser state is generally associated with a single Template, and Templates are rendered recursively (because of extends and include tags). My implementation pushes() the parser_context stack at the beginning of each template render, and pops it after rendering is complete. Unfortunately, since there are numerous template tags that exist "in the wild," some work will probably have to be done to port existing Django projects over to use cached templates. But since template caching will be implemented using a custom loader, and off by default, users can choose to enable it or not, so this shouldn't be a problem. Refactoring Loaders: At the moment, template loaders are implemented as module-level functions. This makes them difficult to extend. I suggest refactoring the built-in template loaders to be classes. By implementing __call__ in the ``BaseLoader`` and instantiating a module-level instance of each loader we can maintain backwards compatibility. Cached Template Loader Options: Once all this groundwork is done, we need to decide how to implement a caching template loader. There are several options: 1. Don't include one at all. Let users write their own and implement it however they want. 2. Implement a generic caching loader that can be instantiated with a list of loaders that it should try to use to load templates, caching the results. This requires a bit of a change to ``django.template.loader.find_template_source()`` since the current implementation assumes you're passing in a string containing the module path. 3. Implement a caching version of each existing template loader. I've implemented option 2, and I think it's probably the best alternative. Looking forward to hearing your comments, suggestions, ridicule, etc, - Mike P.S. Thanks to Alex Gaynor and Marty Alchin who have helped review some of the stuff I've done so far. Respect and whatnot. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Django developers" group. To post to this group, send email to django-developers@googlegroups.com To unsubscribe from this group, send email to django-developers+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/django-developers?hl=en -~----------~----~----~----~------~----~------~--~---