moved caching from loaders to environment and added environment overlays
authorArmin Ronacher <armin.ronacher@active-4.com>
Wed, 30 Apr 2008 11:03:59 +0000 (13:03 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Wed, 30 Apr 2008 11:03:59 +0000 (13:03 +0200)
--HG--
branch : trunk

13 files changed:
.hgignore
docs/api.rst
docs/conf.py
docs/jinjaext.py
docs/templates.rst
jinja2/__init__.py
jinja2/defaults.py
jinja2/environment.py
jinja2/ext.py
jinja2/loaders.py
jinja2/runtime.py
jinja2/utils.py
tests/conftest.py

index bfd0c2ae0838bb77c51a074f43115e0a3e21d7bf..55d124f4f4d080403e7ed70f1dada66bf67089a7 100644 (file)
--- a/.hgignore
+++ b/.hgignore
@@ -1,6 +1,7 @@
 ^instance$
 ^instance/
-^jinja/.*\.so$
-^(build|dist|Jinja\.egg-info)/
+^jinja2/.*\.so$
+^docs/_.*
+^(build|dist|Jinja2\.egg-info)/
 \.py[co]$
 \.DS_Store$
index 18ef9a21b2bb13e58dc3d066dfe6f6d71471cd8b..95622639e62259456b8d406a45c4d9d70d8d43c8 100644 (file)
@@ -48,7 +48,7 @@ High Level API
 --------------
 
 .. autoclass:: jinja2.environment.Environment
-    :members: from_string, get_template, join_path
+    :members: from_string, get_template, join_path, overlay
 
     .. attribute:: shared
 
@@ -67,24 +67,34 @@ High Level API
     .. attribute:: filters
 
         A dict of filters for this environment.  As long as no template was
-        loaded it's safe to add new filters or remove old.
+        loaded it's safe to add new filters or remove old.  For custom filters
+        see :ref:`writing-filters`.
 
     .. attribute:: tests
 
         A dict of test funcitons for this environment.  As long as no
-        template way loaded it's safe to modify this dict.
+        template way loaded it's safe to modify this dict.  For custom tests
+        see :ref:`writing-tests`.
 
     .. attribute:: globals
 
         A dict of global variables.  These variables are always available
         in a template and (if the optimizer is enabled) may not be
         override by templates.  As long as no template was loaded it's safe
-        to modify this dict.
+        to modify this dict.  For more details see :ref:`global-namespace`.
 
 
 .. autoclass:: jinja2.Template
     :members: render, stream, generate, module
 
+    .. attribute:: globals
+
+        foo
+
+    .. attribute:: name
+
+        foo
+
 
 .. autoclass:: jinja2.environment.TemplateStream
     :members: disable_buffering, enable_buffering
@@ -110,29 +120,56 @@ disallows all operations beside testing if it's an undefined object.
 .. autoclass:: jinja2.runtime.StrictUndefined
 
 
+The Context
+-----------
+
+.. autoclass:: jinja2.runtime.TemplateContext
+    :members: super, get, get_exported, get_all
+
+    .. attribute:: parent
+
+        A dict of read only, global variables the template looks up.  These
+        can either come from another :class:`TemplateContext`, from the
+        :attr:`Environment.globals` or :attr:`Template.globals`.  It must not
+        be altered.
+
+    .. attribute:: vars
+
+        The template local variables.  This list contains environment and
+        context functions from the :attr:`parent` scope as well as local
+        modifications and exported variables from the template.  The template
+        will modify this dict during template evaluation but filters and
+        context functions are not allowed to modify it.
+
+    .. attribute:: environment
+
+        The environment that loaded the template.
+
+    .. attribute:: exported_vars
+
+        This set contains all the names the template exports.  The values for
+        the names are in the :attr:`vars` dict.  In order to get a copy of the
+        exported variables as dict, :meth:`get_exported` can be used.
+
+    .. attribute:: name
+
+        The load name of the template owning this context.
+
+    .. attribute:: blocks
+
+        A dict with the current mapping of blocks in the template.  The keys
+        in this dict are the names of the blocks, and the values a list of
+        blocks registered.  The last item in each list is the current active
+        block (latest in the inheritance chain).
+
+
 Loaders
 -------
 
 Loaders are responsible for loading templates from a resource such as the
-file system and for keeping the compiled modules in memory.  These work like
-Python's `sys.modules` which keeps the imported templates in memory.  Unlike
-`sys.modules` however this cache is limited in size by default and templates
-are automatically reloaded.  Each loader that extends :class:`BaseLoader`
-supports this caching and accepts two parameters to configure it:
-
-`cache_size`
-    The size of the cache.  Per default this is ``50`` which means that if
-    more than 50 templates are loaded the loader will clean out the least
-    recently used template.  If the cache size is set to ``0`` templates are
-    recompiled all the time, if the cache size is ``-1`` the cache will not
-    be cleaned.
-
-`auto_reload`
-    Some loaders load templates from locations where the template sources
-    may change (ie: file system or database).  If `auto_reload` is set to
-    `True` (default) every time a template is requested the loader checks
-    if the source changed and if yes, it will reload the template.  For
-    higher performance it's possible to disable that.
+file system.  The environment will keep the compiled modules in memory like
+Python's `sys.modules`.  Unlike `sys.modules` however this cache is limited in
+size by default and templates are automatically reloaded.
 
 .. autoclass:: jinja2.loaders.FileSystemLoader
 
@@ -189,3 +226,109 @@ Exceptions
 .. autoclass:: jinja2.exceptions.TemplateSyntaxError
 
 .. autoclass:: jinja2.exceptions.TemplateAssertionError
+
+
+.. _writing-filters:
+
+Custom Filters
+--------------
+
+Custom filters are just regular Python functions that take the left side of
+the filter as first argument and the the arguments passed to the filter as
+extra arguments or keyword arguments.
+
+For example in the filter ``{{ 42|myfilter(23) }}`` the function would be
+called with ``myfilter(42, 23)``.  Here for example a simple filter that can
+be applied to datetime objects to format them::
+
+    def datetimeformat(value, format='%H:%M / %d-%m-%Y'):
+        return value.strftime(format)
+
+You can register it on the template environment by updating the
+:attr:`~Environment.filters` dict on the environment::
+
+    environment.filters['datetimeformat'] = datetimeformat
+
+Inside the template it can then be used as follows:
+
+.. sourcecode:: jinja
+
+    written on: {{ article.pub_date|datetimeformat }}
+    publication date: {{ article.pub_date|datetimeformat('%d-%m-%Y') }}
+
+Filters can also be passed the current template context or environment.  This
+is useful if a filters wants to return an undefined value or check the current
+:attr:`~Environment.autoescape` setting.  For this purpose two decorators
+exist: :func:`environmentfilter` and :func:`contextfilter`.
+
+Here a small example filter that breaks a text into HTML line breaks and
+paragraphs and marks the return value as safe HTML string if autoescaping is
+enabled::
+
+    import re
+    from jinja2 import environmentfilter, Markup, escape
+
+    _paragraph_re = re.compile(r'(?:\r\n|\r|\n){2,}')
+
+    @environmentfilter
+    def nl2br(environment, value):
+        result = u'\n\n'.join(u'<p>%s</p>' % p.replace('\n', '<br>\n')
+                              for p in _paragraph_re.split(escape(value)))
+        if environment.autoescape:
+            result = Markup(result)
+        return result
+
+Context filters work the same just that the first argument is the current
+active :class:`TemplateContext` rather then the environment.
+
+
+.. _writing-tests:
+
+Custom Tests
+------------
+
+Tests work like filters just that there is no way for a filter to get access
+to the environment or context and that they can't be chained.  The return
+value of a filter should be `True` or `False`.  The purpose of a filter is to
+give the template designers the possibility to perform type and conformability
+checks.
+
+Here a simple filter that checks if a variable is a prime number::
+
+    import math
+
+    def is_prime(n):
+        if n == 2:
+            return True
+        for i in xrange(2, int(math.ceil(math.sqrt(n))) + 1):
+            if n % i == 0:
+                return False
+        return True
+        
+
+You can register it on the template environment by updating the
+:attr:`~Environment.tests` dict on the environment::
+
+    environment.tests['prime'] = is_prime
+
+A template designer can then use the test like this:
+
+.. sourcecode:: jinja
+
+    {% if 42 is prime %}
+        42 is a prime number
+    {% else %}
+        42 is not a prime number
+    {% endif %}
+
+
+.. _global-namespace:
+
+The Global Namespace
+--------------------
+
+Variables stored in the :attr:`Environment.globals` or :attr:`Template.globals`
+dicts are special as they are available for imported templates too and will be
+used by the optimizer in future releases to evaluates parts of the template at
+compile time.  This is the place where you can put variables and functions
+that should be available all the time.
index 237b61b82c5a37b26c7325eabe0b99b97bbb4988..54a1ad40c93a58898003b2763421a1d96ec66494 100644 (file)
@@ -137,7 +137,7 @@ latex_preamble = '''
 \definecolor{TitleColor}{rgb}{0.7,0,0}
 \definecolor{InnerLinkColor}{rgb}{0.7,0,0}
 \definecolor{OuterLinkColor}{rgb}{0.8,0,0}
-\definecolor{VerbatimColor}{rgb}{0.98,0.98,0.98}
+\definecolor{VerbatimColor}{rgb}{0.985,0.985,0.985}
 \definecolor{VerbatimBorderColor}{rgb}{0.8,0.8,0.8}
 '''
 
index 8485a62ecbf829d7c75b01e15e26a49733aa3292..57c70973d48a87b34c5e2b5f513d5583a0979bd3 100644 (file)
@@ -43,7 +43,7 @@ class JinjaStyle(Style):
         Name.Tag:                   'bold #686868',
         Name.Decorator:             '#686868',
 
-        String:                     '#BE9B5D',
+        String:                     '#AA891C',
         Number:                     '#444444',
 
         Generic.Heading:            'bold #000080',
index 384d27ec0a05906abc8924dedf7e643714cf66dd..85171d69211dd19437ba41afaa96fad81e6d57b9 100644 (file)
@@ -782,3 +782,24 @@ List of Builtin Tests
 ---------------------
 
 .. jinjatests::
+
+
+List of Global Functions
+------------------------
+
+The following functions are available in the global scope by default:
+
+.. function:: range([start,] stop[, step])
+
+    Return a list containing an arithmetic progression of integers.
+    range(i, j) returns [i, i+1, i+2, ..., j-1]; start (!) defaults to 0.
+    When step is given, it specifies the increment (or decrement).
+    For example, range(4) returns [0, 1, 2, 3].  The end point is omitted!
+    These are exactly the valid indices for a list of 4 elements.
+
+.. function:: lipsum(n=5, html=True, min=20, max=100)
+
+    Generates some lorem ipsum for the template.  Per default five paragraphs
+    with HTML are generated each paragraph between 20 and 100 words.  If html
+    is disabled regular text is returned.  This is useful to generate simple
+    contents for layout testing.
index 4310d34e01916c4ea4abfa2cfcd71643e28753bf..0ab742351effb804450453e89ade7c6aad656642 100644 (file)
@@ -50,3 +50,13 @@ from jinja2.exceptions import TemplateError, UndefinedError, \
 # decorators and public utilities
 from jinja2.filters import environmentfilter, contextfilter
 from jinja2.utils import Markup, escape, environmentfunction, contextfunction
+
+__all__ = [
+    'Environment', 'Template', 'BaseLoader', 'FileSystemLoader',
+    'PackageLoader', 'DictLoader', 'FunctionLoader', 'PrefixLoader',
+    'ChoiceLoader', 'Undefined', 'DebugUndefined', 'StrictUndefined',
+    'TemplateError', 'UndefinedError', 'TemplateNotFound',
+    'TemplateSyntaxError', 'TemplateAssertionError', 'environmentfilter',
+    'contextfilter', 'Markup', 'escape', 'environmentfunction',
+    'contextfunction'
+]
index 773ad80c241f24a4a3ada2ab4e1e2d92f86b1550..848cb8f0e7067c98b34669fc8b072819fdff205d 100644 (file)
@@ -13,7 +13,20 @@ from jinja2.tests import TESTS as DEFAULT_TESTS
 from jinja2.utils import generate_lorem_ipsum
 
 
+BLOCK_START_STRING = '{%'
+BLOCK_END_STRING = '%}'
+VARIABLE_START_STRING = '{{'
+VARIABLE_END_STRING = '}}'
+COMMENT_START_STRING = '{#'
+COMMENT_END_STRING = '#}'
+LINE_STATEMENT_PREFIX = None
+
+
 DEFAULT_NAMESPACE = {
     'range':        xrange,
     'lipsum':       generate_lorem_ipsum
 }
+
+
+# export all constants
+__all__ = tuple(x for x in locals() if x.isupper())
index 919ee9aaa9fed841411de8d5ddd06d0df1a7f31f..09a0ae27ed15a1621a2d87600d855bb738d01501 100644 (file)
@@ -9,14 +9,14 @@
     :license: BSD, see LICENSE for more details.
 """
 import sys
+from jinja2.defaults import *
 from jinja2.lexer import Lexer
 from jinja2.parser import Parser
 from jinja2.optimizer import optimize
 from jinja2.compiler import generate
 from jinja2.runtime import Undefined, TemplateContext, concat
 from jinja2.debug import translate_exception
-from jinja2.utils import import_string, LRUCache, Markup
-from jinja2.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE
+from jinja2.utils import import_string, LRUCache, Markup, missing
 
 
 # for direct template usage we have up to ten living environments
@@ -39,29 +39,35 @@ def get_spontaneous_environment(*args):
     return env
 
 
-def template_from_code(environment, code, globals, uptodate=None,
-                       template_class=None):
-    """Generate a new template object from code.  It's used in the
-    template constructor and the loader `load` implementation.
+def create_cache(size):
+    """Return the cache class for the given size."""
+    if size == 0:
+        return None
+    if size < 0:
+        return {}
+    return LRUCache(size)
+
+
+def load_extensions(environment, extensions):
+    """Load the extensions from the list and bind it to the environment.
+    Returns a new list of instanciated environments.
     """
-    t = object.__new__(template_class or environment.template_class)
-    namespace = {
-        'environment':          environment,
-        '__jinja_template__':   t
-    }
-    exec code in namespace
-    t.environment = environment
-    t.name = namespace['name']
-    t.filename = code.co_filename
-    t.root_render_func = namespace['root']
-    t.blocks = namespace['blocks']
-    t.globals = globals
-
-    # debug and loader helpers
-    t._debug_info = namespace['debug_info']
-    t._uptodate = uptodate
-
-    return t
+    result = []
+    for extension in extensions:
+        if isinstance(extension, basestring):
+            extension = import_string(extension)
+        result.append(extension(environment))
+    return result
+
+
+def _environment_sanity_check(environment):
+    """Perform a sanity check on the environment."""
+    assert issubclass(environment.undefined, Undefined), 'undefined must ' \
+           'be a subclass of undefined because filters depend on it.'
+    assert environment.block_start_string != \
+           environment.variable_start_string != \
+           environment.comment_start_string, 'block, variable and comment ' \
+           'start strings must be different'
 
 
 class Environment(object):
@@ -118,6 +124,20 @@ class Environment(object):
 
     `loader`
         The template loader for this environment.
+
+    `cache_size`
+        The size of the cache.  Per default this is ``50`` which means that if
+        more than 50 templates are loaded the loader will clean out the least
+        recently used template.  If the cache size is set to ``0`` templates are
+        recompiled all the time, if the cache size is ``-1`` the cache will not
+        be cleaned.
+
+    `auto_reload`
+        Some loaders load templates from locations where the template sources
+        may change (ie: file system or database).  If `auto_reload` is set to
+        `True` (default) every time a template is requested the loader checks
+        if the source changed and if yes, it will reload the template.  For
+        higher performance it's possible to disable that.
     """
 
     #: if this environment is sandboxed.  Modifying this variable won't make
@@ -125,25 +145,30 @@ class Environment(object):
     #: have a look at jinja2.sandbox
     sandboxed = False
 
+    #: True if the environment is just an overlay
+    overlay = False
+
     #: shared environments have this set to `True`.  A shared environment
     #: must not be modified
     shared = False
 
     def __init__(self,
-                 block_start_string='{%',
-                 block_end_string='%}',
-                 variable_start_string='{{',
-                 variable_end_string='}}',
-                 comment_start_string='{#',
-                 comment_end_string='#}',
-                 line_statement_prefix=None,
+                 block_start_string=BLOCK_START_STRING,
+                 block_end_string=BLOCK_END_STRING,
+                 variable_start_string=VARIABLE_START_STRING,
+                 variable_end_string=VARIABLE_END_STRING,
+                 comment_start_string=COMMENT_START_STRING,
+                 comment_end_string=COMMENT_END_STRING,
+                 line_statement_prefix=LINE_STATEMENT_PREFIX,
                  trim_blocks=False,
                  extensions=(),
                  optimized=True,
                  undefined=Undefined,
                  finalize=None,
                  autoescape=False,
-                 loader=None):
+                 loader=None,
+                 cache_size=50,
+                 auto_reload=True):
         # !!Important notice!!
         #   The constructor accepts quite a few arguments that should be
         #   passed by keyword rather than position.  However it's important to
@@ -153,14 +178,7 @@ class Environment(object):
         #       -   unittests
         #   If parameter changes are required only add parameters at the end
         #   and don't change the arguments (or the defaults!) of the arguments
-        #   up to (but excluding) loader.
-
-        # santity checks
-        assert issubclass(undefined, Undefined), 'undefined must be ' \
-               'a subclass of undefined because filters depend on it.'
-        assert block_start_string != variable_start_string != \
-               comment_start_string, 'block, variable and comment ' \
-               'start strings must be different'
+        #   existing already.
 
         # lexer / parser information
         self.block_start_string = block_start_string
@@ -185,16 +203,60 @@ class Environment(object):
 
         # set the loader provided
         self.loader = loader
-
-        # create lexer
-        self.lexer = Lexer(self)
+        self.cache = create_cache(cache_size)
+        self.auto_reload = auto_reload
 
         # load extensions
-        self.extensions = []
-        for extension in extensions:
-            if isinstance(extension, basestring):
-                extension = import_string(extension)
-            self.extensions.append(extension(self))
+        self.extensions = load_extensions(self, extensions)
+
+        _environment_sanity_check(self)
+
+    def overlay(self, block_start_string=missing, block_end_string=missing,
+                variable_start_string=missing, variable_end_string=missing,
+                comment_start_string=missing, comment_end_string=missing,
+                line_statement_prefix=missing, trim_blocks=missing,
+                extensions=missing, optimized=missing, undefined=missing,
+                finalize=missing, autoescape=missing, loader=missing,
+                cache_size=missing, auto_reload=missing):
+        """Create a new overlay environment that shares all the data with the
+        current environment except of cache and the overriden attributes.
+        Extensions cannot be removed for a overlayed environment.  A overlayed
+        environment automatically gets all the extensions of the environment it
+        is linked to plus optional extra extensions.
+
+        Creating overlays should happen after the initial environment was set
+        up completely.  Not all attributes are truly linked, some are just
+        copied over so modifications on the original environment may not shine
+        through.
+        """
+        args = dict(locals())
+        del args['self'], args['cache_size'], args['extensions']
+
+        rv = object.__new__(self.__class__)
+        rv.__dict__.update(self.__dict__)
+        rv.overlay = True
+        rv.linked_to = self
+
+        for key, value in args.iteritems():
+            if value is not missing:
+                setattr(rv, key, value)
+
+        if cache_size is not missing:
+            rv.cache = create_cache(cache_size)
+
+        rv.extensions = []
+        for extension in self.extensions:
+            rv.extensions.append(extension.bind(self))
+        if extensions is not missing:
+            rv.extensions.extend(load_extensions(extensions))
+
+        _environment_sanity_check(rv)
+        return rv
+
+    @property
+    def lexer(self):
+        """Return a fresh lexer for the environment."""
+        return Lexer(self)
 
     def subscribe(self, obj, argument):
         """Get an item or attribute of an object."""
@@ -279,15 +341,26 @@ class Environment(object):
             raise TypeError('no loader for this environment specified')
         if parent is not None:
             name = self.join_path(name, parent)
-        return self.loader.load(self, name, self.make_globals(globals))
+
+        if self.cache is not None:
+            template = self.cache.get(name)
+            if template is not None and (not self.auto_reload or \
+                                         template.is_up_to_date):
+                return template
+
+        template = self.loader.load(self, name, self.make_globals(globals))
+        if self.cache is not None:
+            self.cache[name] = template
+        return template
 
     def from_string(self, source, globals=None, template_class=None):
         """Load a template from a string.  This parses the source given and
         returns a :class:`Template` object.
         """
         globals = self.make_globals(globals)
-        return template_from_code(self, self.compile(source, globals=globals),
-                                  globals, None, template_class)
+        cls = template_class or self.template_class
+        return cls.from_code(self, self.compile(source, globals=globals),
+                             globals, None)
 
     def make_globals(self, d):
         """Return a dict for the globals."""
@@ -345,9 +418,33 @@ class Template(object):
             block_start_string, block_end_string, variable_start_string,
             variable_end_string, comment_start_string, comment_end_string,
             line_statement_prefix, trim_blocks, tuple(extensions), optimized,
-            undefined, finalize, autoescape)
+            undefined, finalize, autoescape, None, 0, False)
         return env.from_string(source, template_class=cls)
 
+    @classmethod
+    def from_code(cls, environment, code, globals, uptodate=None):
+        """Creates a template object from compiled code and the globals.  This
+        is used by the loaders and environment to create a template object.
+        """
+        t = object.__new__(cls)
+        namespace = {
+            'environment':          environment,
+            '__jinja_template__':   t
+        }
+        exec code in namespace
+        t.environment = environment
+        t.name = namespace['name']
+        t.filename = code.co_filename
+        t.root_render_func = namespace['root']
+        t.blocks = namespace['blocks']
+        t.globals = globals
+
+        # debug and loader helpers
+        t._debug_info = namespace['debug_info']
+        t._uptodate = uptodate
+
+        return t
+
     def render(self, *args, **kwargs):
         """This method accepts the same arguments as the `dict` constructor:
         A dict, a dict subclass or some keyword arguments.  If no arguments
index bc04a6972baab9e5d072a142172b8232a7d7e55b..84d656d18a196984669f8300686755c07288570a 100644 (file)
@@ -26,7 +26,13 @@ GETTEXT_FUNCTIONS = ('_', 'gettext', 'ngettext')
 
 
 class Extension(object):
-    """Instances of this class store parser extensions."""
+    """Extensions can be used to add extra functionality to the Jinja template
+    system at the parser level.  This is a supported but currently
+    undocumented interface.  Custom extensions are bound to an environment but
+    may not store environment specific data on `self`.  The reason for this is
+    that an extension can be bound to another environment (for overlays) by
+    creating a copy and reassigning the `environment` attribute.
+    """
 
     #: if this extension parses this is the list of tags it's listening to.
     tags = set()
@@ -34,6 +40,13 @@ class Extension(object):
     def __init__(self, environment):
         self.environment = environment
 
+    def bind(self, environment):
+        """Create a copy of this extension bound to another environment."""
+        rv = object.__new__(self.__class__)
+        rv.__dict__.update(self.__dict__)
+        rv.environment = environment
+        return rv
+
     def parse(self, parser):
         """Called if one of the tags matched."""
 
@@ -289,8 +302,10 @@ def babel_extract(fileobj, keywords, comment_tags, options):
         options.get('trim_blocks', '').lower() in ('1', 'on', 'yes', 'true'),
         tuple(extensions),
         # fill with defaults so that environments are shared
-        # with other spontaneus environments.
-        True, Undefined, None, False
+        # with other spontaneus environments.  The rest of the
+        # arguments are optimizer, undefined, finalize, autoescape,
+        # loader, cache size and auto reloading setting
+        True, Undefined, None, False, None, 0, False
     )
 
     node = environment.parse(fileobj.read().decode(encoding))
index 9744ef4d146bfce966575f0da2ef3df6d3fcdd1e..cdda94abab6c6755d0dc2ad488e4191e9480ad60 100644 (file)
@@ -5,12 +5,15 @@
 
     Jinja loader classes.
 
+    XXX: move caching from the loaders to environment.get_template and add
+    environment overlays that allow to redefine escaping and other things but
+    shared the globals and filter mappings.
+
     :copyright: 2008 by Armin Ronacher.
     :license: BSD, see LICENSE for more details.
 """
 from os import path
 from jinja2.exceptions import TemplateNotFound
-from jinja2.environment import template_from_code
 from jinja2.utils import LRUCache
 
 
@@ -57,15 +60,6 @@ class BaseLoader(object):
                 return source, path, lambda: mtime != getmtime(path)
     """
 
-    def __init__(self, cache_size=50, auto_reload=True):
-        if cache_size == 0:
-            self.cache = None
-        elif cache_size < 0:
-            self.cache = {}
-        else:
-            self.cache = LRUCache(cache_size)
-        self.auto_reload = auto_reload
-
     def get_source(self, environment, template):
         """Get the template source, filename and reload helper for a template.
         It's passed the environment and template name and has to return a
@@ -95,19 +89,10 @@ class BaseLoader(object):
         """
         if globals is None:
             globals = {}
-
-        if self.cache is not None:
-            template = self.cache.get(name)
-            if template is not None and (not self.auto_reload or \
-                                         template.is_up_to_date):
-                return template
-
         source, filename, uptodate = self.get_source(environment, name)
         code = environment.compile(source, name, filename, globals)
-        template = template_from_code(environment, code, globals, uptodate)
-        if self.cache is not None:
-            self.cache[name] = template
-        return template
+        return environment.template_class.from_code(environment, code,
+                                                    globals, uptodate)
 
 
 class FileSystemLoader(BaseLoader):
@@ -125,9 +110,7 @@ class FileSystemLoader(BaseLoader):
     by setting the `encoding` parameter to something else.
     """
 
-    def __init__(self, searchpath, encoding='utf-8', cache_size=50,
-                 auto_reload=True):
-        BaseLoader.__init__(self, cache_size, auto_reload)
+    def __init__(self, searchpath, encoding='utf-8'):
         if isinstance(searchpath, basestring):
             searchpath = [searchpath]
         self.searchpath = list(searchpath)
@@ -165,8 +148,7 @@ class PackageLoader(BaseLoader):
     """
 
     def __init__(self, package_name, package_path='templates',
-                 encoding='utf-8', cache_size=50, auto_reload=True):
-        BaseLoader.__init__(self, cache_size, auto_reload)
+                 encoding='utf-8'):
         from pkg_resources import DefaultProvider, ResourceManager, get_provider
         provider = get_provider(package_name)
         self.encoding = encoding
@@ -201,8 +183,7 @@ class DictLoader(BaseLoader):
     Because auto reloading is rarely useful this is disabled per default.
     """
 
-    def __init__(self, mapping, cache_size=50, auto_reload=False):
-        BaseLoader.__init__(self, cache_size, auto_reload)
+    def __init__(self, mapping):
         self.mapping = mapping
 
     def get_source(self, environment, template):
@@ -230,8 +211,7 @@ class FunctionLoader(BaseLoader):
     return value.
     """
 
-    def __init__(self, load_func, cache_size=50, auto_reload=True):
-        BaseLoader.__init__(self, cache_size, auto_reload)
+    def __init__(self, load_func):
         self.load_func = load_func
 
     def get_source(self, environment, template):
@@ -245,9 +225,9 @@ class FunctionLoader(BaseLoader):
 
 class PrefixLoader(BaseLoader):
     """A loader that is passed a dict of loaders where each loader is bound
-    to a prefix.  The caching is independent of the actual loaders so the
-    per loader cache settings are ignored.  The prefix is delimited from the
-    template by a slash:
+    to a prefix.  The prefix is delimited from the template by a slash per
+    default, which can be changed by setting the `delimiter` argument to
+    something else.
 
     >>> loader = PrefixLoader({
     ...     'app1':     PackageLoader('mypackage.app1'),
@@ -258,9 +238,7 @@ class PrefixLoader(BaseLoader):
     by loading ``'app2/index.html'`` the file from the second.
     """
 
-    def __init__(self, mapping, delimiter='/', cache_size=50,
-                 auto_reload=True):
-        BaseLoader.__init__(self, cache_size, auto_reload)
+    def __init__(self, mapping, delimiter='/'):
         self.mapping = mapping
         self.delimiter = delimiter
 
@@ -276,8 +254,7 @@ class PrefixLoader(BaseLoader):
 class ChoiceLoader(BaseLoader):
     """This loader works like the `PrefixLoader` just that no prefix is
     specified.  If a template could not be found by one loader the next one
-    is tried.  Like for the `PrefixLoader` the cache settings of the actual
-    loaders don't matter as the choice loader does the caching.
+    is tried.
 
     >>> loader = ChoiceLoader([
     ...     FileSystemLoader('/path/to/user/templates'),
@@ -288,8 +265,7 @@ class ChoiceLoader(BaseLoader):
     from a different location.
     """
 
-    def __init__(self, loaders, cache_size=50, auto_reload=True):
-        BaseLoader.__init__(self, cache_size, auto_reload)
+    def __init__(self, loaders):
         self.loaders = loaders
 
     def get_source(self, environment, template):
index f4a2f1aceb19aa34f637fab3052c7cf254fc6045..5233210f5bf41cc409e87539929f5f3f1c3322eb 100644 (file)
@@ -11,7 +11,7 @@
 import sys
 from types import FunctionType
 from itertools import chain, imap
-from jinja2.utils import Markup, partial, soft_unicode, escape
+from jinja2.utils import Markup, partial, soft_unicode, escape, missing
 from jinja2.exceptions import UndefinedError, TemplateRuntimeError
 
 
@@ -21,10 +21,6 @@ __all__ = ['LoopContext', 'TemplateContext', 'TemplateReference', 'Macro',
            'markup_join', 'unicode_join']
 
 
-# special singleton representing missing values for the runtime
-missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
-
-
 # concatenate a list of strings and convert them to unicode.
 # unfortunately there is a bug in python 2.4 and lower that causes
 # unicode.join trash the traceback.
@@ -33,8 +29,8 @@ try:
         raise TypeError(_test_gen_bug)
         yield None
     u''.join(_test_gen_bug())
-except TypeError, e:
-    if e.args and e.args[0] is _test_gen_bug:
+except TypeError, _error:
+    if _error.args and _error.args[0] is _test_gen_bug:
         concat = u''.join
     else:
         def concat(gen):
@@ -43,7 +39,7 @@ except TypeError, e:
             except:
                 exc_type, exc_value, tb = sys.exc_info()
                 raise exc_type, exc_value, tb.tb_next
-del _test_gen_bug
+    del _test_gen_bug, _error
 
 
 def markup_join(*args):
@@ -63,15 +59,21 @@ def unicode_join(*args):
 
 
 class TemplateContext(object):
-    """Holds the variables of the local template or of the global one.  It's
-    not save to use this class outside of the compiled code.  For example
-    update and other methods will not work as they seem (they don't update
-    the exported variables for example).
-
-    The context is immutable.  Modifications on `parent` must not happen and
-    modifications on `vars` are allowed from generated template code.  However
-    functions that are passed the template context may not modify the context
-    in any way.
+    """The template context holds the variables of a template.  It stores the
+    values passed to the template and also the names the template exports.
+    Creating instances is neither supported nor useful as it's created
+    automatically at various stages of the template evaluation and should not
+    be created by hand.
+
+    The context is immutable.  Modifications on :attr:`parent` **must not**
+    happen and modifications on :attr:`vars` are allowed from generated
+    template code only.  Template filters and global functions marked as
+    :func:`contextfunction`\s get the active context passed as first argument
+    and are allowed to access the context read-only.
+
+    The template context supports read only dict operations (`get`,
+    `__getitem__`, `__contains__`) however `__getitem__` doesn't fail with
+    a `KeyError` but returns an :attr:`Undefined` object.
     """
 
     def __init__(self, environment, parent, name, blocks):
@@ -110,7 +112,9 @@ class TemplateContext(object):
         return render
 
     def get(self, key, default=None):
-        """For dict compatibility"""
+        """Returns an item from the template context, if it doesn't exist
+        `default` is returned.
+        """
         if key in self.vars:
             return self.vars[key]
         if key in self.parent:
@@ -121,19 +125,12 @@ class TemplateContext(object):
         """Get a new dict with the exported variables."""
         return dict((k, self.vars[k]) for k in self.exported_vars)
 
-    def get_root(self):
-        """Return a new dict with all the non local variables."""
-        return dict(self.parent)
-
     def get_all(self):
-        """Return a copy of the complete context as dict."""
+        """Return a copy of the complete context as dict including the
+        global variables.
+        """
         return dict(self.parent, **self.vars)
 
-    def clone(self):
-        """Return a copy of the context without the locals."""
-        return self.__class__(self.environment, self.parent,
-                              self.name, self.blocks)
-
     def __contains__(self, name):
         return name in self.vars or name in self.parent
 
index 5d8ca483d3fbb12e102cc575d30d6276df4ab5b8..6a5c9a07c5bc126f198ea8b2e4cc8f7c4869f41d 100644 (file)
@@ -25,6 +25,10 @@ _punctuation_re = re.compile(
 _simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
 
 
+# special singleton representing missing values for the runtime
+missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
+
+
 def contextfunction(f):
     """This decorator can be used to mark a callable as context callable.  A
     context callable is passed the active context as first argument if it
index e38299f2553a5ed70bfccd758640bc32f1958873..8a24673c627deb27c59cdd6ec0faab0c7cd0dd9a 100644 (file)
@@ -63,8 +63,8 @@ class GlobalLoader(BaseLoader):
             raise TemplateNotFound(name)
 
 
-loader = GlobalLoader(cache_size=0)
-simple_env = Environment(trim_blocks=True, loader=loader)
+loader = GlobalLoader()
+simple_env = Environment(trim_blocks=True, loader=loader, cache_size=0)
 
 
 class Module(py.test.collect.Module):