updated jinja docs
[jinja2.git] / jinja2 / loaders.py
1 # -*- coding: utf-8 -*-
2 """
3     jinja2.loaders
4     ~~~~~~~~~~~~~~
5
6     Jinja loader classes.
7
8     :copyright: 2008 by Armin Ronacher.
9     :license: BSD, see LICENSE for more details.
10 """
11 from os import path
12 from jinja2.exceptions import TemplateNotFound
13 from jinja2.environment import template_from_code
14 from jinja2.utils import LRUCache
15
16
17 def split_template_path(template):
18     """Split a path into segments and perform a sanity check.  If it detects
19     '..' in the path it will raise a `TemplateNotFound` error.
20     """
21     pieces = []
22     for piece in template.split('/'):
23         if path.sep in piece \
24            or (path.altsep and path.altsep in piece) or \
25            piece == path.pardir:
26             raise TemplateNotFound(template)
27         elif piece != '.':
28             pieces.append(piece)
29     return pieces
30
31
32 class BaseLoader(object):
33     """Baseclass for all loaders.  Subclass this and override `get_source` to
34     implement a custom loading mechanism.  The environment provides a
35     `get_template` method that calls the loader's `load` method to get the
36     :class:`Template` object.
37
38     A very basic example for a loader that looks up templates on the file
39     system could look like this::
40
41         from jinja2 import BaseLoader, TemplateNotFound
42         from os.path import join, exists, getmtime
43
44         class MyLoader(BaseLoader):
45
46             def __init__(self, path, cache_size=50, auto_reload=True):
47                 BaseLoader.__init__(self, cache_size, auto_reload)
48                 self.path = path
49
50             def get_source(self, environment, template):
51                 path = join(self.path, template)
52                 if not exists(path):
53                     raise TemplateNotFound(template)
54                 mtime = getmtime(path)
55                 with file(path) as f:
56                     source = f.read().decode('utf-8')
57                 return source, path, lambda: mtime != getmtime(path)
58     """
59
60     def __init__(self, cache_size=50, auto_reload=True):
61         if cache_size == 0:
62             self.cache = None
63         elif cache_size < 0:
64             self.cache = {}
65         else:
66             self.cache = LRUCache(cache_size)
67         self.auto_reload = auto_reload
68
69     def get_source(self, environment, template):
70         """Get the template source, filename and reload helper for a template.
71         It's passed the environment and template name and has to return a
72         tuple in the form ``(source, filename, uptodate)`` or raise a
73         `TemplateNotFound` error if it can't locate the template.
74
75         The source part of the returned tuple must be the source of the
76         template as unicode string or a ASCII bytestring.  The filename should
77         be the name of the file on the filesystem if it was loaded from there,
78         otherwise `None`.  The filename is used by python for the tracebacks
79         if no loader extension is used.
80
81         The last item in the tuple is the `uptodate` function.  If auto
82         reloading is enabled it's always called to check if the template
83         changed.  No arguments are passed so the function must store the
84         old state somewhere (for example in a closure).  If it returns `False`
85         the template will be reloaded.
86         """
87         raise TemplateNotFound(template)
88
89     def load(self, environment, name, globals=None):
90         """Loads a template.  This method looks up the template in the cache
91         or loads one by calling :meth:`get_source`.  Subclasses should not
92         override this method as loaders working on collections of other
93         loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
94         will not call this method but `get_source` directly.
95         """
96         if globals is None:
97             globals = {}
98
99         if self.cache is not None:
100             template = self.cache.get(name)
101             if template is not None and (not self.auto_reload or \
102                                          template.is_up_to_date):
103                 return template
104
105         source, filename, uptodate = self.get_source(environment, name)
106         code = environment.compile(source, name, filename, globals)
107         template = template_from_code(environment, code, globals, uptodate)
108         if self.cache is not None:
109             self.cache[name] = template
110         return template
111
112
113 class FileSystemLoader(BaseLoader):
114     """Loads templates from the file system.  This loader can find templates
115     in folders on the file system and is the preferred way to load them.
116
117     The loader takes the path to the templates as string, or if multiple
118     locations are wanted a list of them which is then looked up in the
119     given order:
120
121     >>> loader = FileSystemLoader('/path/to/templates')
122     >>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
123
124     Per default the template encoding is ``'utf-8'`` which can be changed
125     by setting the `encoding` parameter to something else.
126     """
127
128     def __init__(self, searchpath, encoding='utf-8', cache_size=50,
129                  auto_reload=True):
130         BaseLoader.__init__(self, cache_size, auto_reload)
131         if isinstance(searchpath, basestring):
132             searchpath = [searchpath]
133         self.searchpath = list(searchpath)
134         self.encoding = encoding
135
136     def get_source(self, environment, template):
137         pieces = split_template_path(template)
138         for searchpath in self.searchpath:
139             filename = path.join(searchpath, *pieces)
140             if not path.isfile(filename):
141                 continue
142             f = file(filename)
143             try:
144                 contents = f.read().decode(self.encoding)
145             finally:
146                 f.close()
147             old = path.getmtime(filename)
148             return contents, filename, lambda: path.getmtime(filename) != old
149         raise TemplateNotFound(template)
150
151
152 class PackageLoader(BaseLoader):
153     """Load templates from python eggs or packages.  It is constructed with
154     the name of the python package and the path to the templates in that
155     package:
156
157     >>> loader = PackageLoader('mypackage', 'views')
158
159     If the package path is not given, ``'templates'`` is assumed.
160
161     Per default the template encoding is ``'utf-8'`` which can be changed
162     by setting the `encoding` parameter to something else.  Due to the nature
163     of eggs it's only possible to reload templates if the package was loaded
164     from the file system and not a zip file.
165     """
166
167     def __init__(self, package_name, package_path='templates',
168                  encoding='utf-8', cache_size=50, auto_reload=True):
169         BaseLoader.__init__(self, cache_size, auto_reload)
170         from pkg_resources import DefaultProvider, ResourceManager, get_provider
171         provider = get_provider(package_name)
172         self.encoding = encoding
173         self.manager = ResourceManager()
174         self.filesystem_bound = isinstance(provider, DefaultProvider)
175         self.provider = provider
176         self.package_path = package_path
177
178     def get_source(self, environment, template):
179         pieces = split_template_path(template)
180         p = '/'.join((self.package_path,) + tuple(pieces))
181         if not self.provider.has_resource(p):
182             raise TemplateNotFound(template)
183
184         filename = uptodate = None
185         if self.filesystem_bound:
186             filename = self.provider.get_resource_filename(self.manager, p)
187             mtime = path.getmtime(filename)
188             def uptodate():
189                 return path.getmtime(filename) != mtime
190
191         source = self.provider.get_resource_string(self.manager, p)
192         return source.decode(self.encoding), filename, uptodate
193
194
195 class DictLoader(BaseLoader):
196     """Loads a template from a python dict.  It's passed a dict of unicode
197     strings bound to template names.  This loader is useful for unittesting:
198
199     >>> loader = DictLoader({'index.html': 'source here'})
200
201     Because auto reloading is rarely useful this is disabled per default.
202     """
203
204     def __init__(self, mapping, cache_size=50, auto_reload=False):
205         BaseLoader.__init__(self, cache_size, auto_reload)
206         self.mapping = mapping
207
208     def get_source(self, environment, template):
209         if template in self.mapping:
210             source = self.mapping[template]
211             return source, None, lambda: source != self.mapping[template]
212         raise TemplateNotFound(template)
213
214
215 class FunctionLoader(BaseLoader):
216     """A loader that is passed a function which does the loading.  The
217     function becomes the name of the template passed and has to return either
218     an unicode string with the template source, a tuple in the form ``(source,
219     filename, uptodatefunc)`` or `None` if the template does not exist.
220
221     >>> def load_template(name):
222     ...     if name == 'index.html'
223     ...         return '...'
224     ...
225     >>> loader = FunctionLoader(load_template)
226
227     The `uptodatefunc` is a function that is called if autoreload is enabled
228     and has to return `True` if the template is still up to date.  For more
229     details have a look at :meth:`BaseLoader.get_source` which has the same
230     return value.
231     """
232
233     def __init__(self, load_func, cache_size=50, auto_reload=True):
234         BaseLoader.__init__(self, cache_size, auto_reload)
235         self.load_func = load_func
236
237     def get_source(self, environment, template):
238         rv = self.load_func(template)
239         if rv is None:
240             raise TemplateNotFound(template)
241         elif isinstance(rv, basestring):
242             return rv, None, None
243         return rv
244
245
246 class PrefixLoader(BaseLoader):
247     """A loader that is passed a dict of loaders where each loader is bound
248     to a prefix.  The caching is independent of the actual loaders so the
249     per loader cache settings are ignored.  The prefix is delimited from the
250     template by a slash:
251
252     >>> loader = PrefixLoader({
253     ...     'app1':     PackageLoader('mypackage.app1'),
254     ...     'app2':     PackageLoader('mypackage.app2')
255     ... })
256
257     By loading ``'app1/index.html'`` the file from the app1 package is loaded,
258     by loading ``'app2/index.html'`` the file from the second.
259     """
260
261     def __init__(self, mapping, delimiter='/', cache_size=50,
262                  auto_reload=True):
263         BaseLoader.__init__(self, cache_size, auto_reload)
264         self.mapping = mapping
265         self.delimiter = delimiter
266
267     def get_source(self, environment, template):
268         try:
269             prefix, template = template.split(self.delimiter, 1)
270             loader = self.mapping[prefix]
271         except (ValueError, KeyError):
272             raise TemplateNotFound(template)
273         return loader.get_source(environment, template)
274
275
276 class ChoiceLoader(BaseLoader):
277     """This loader works like the `PrefixLoader` just that no prefix is
278     specified.  If a template could not be found by one loader the next one
279     is tried.  Like for the `PrefixLoader` the cache settings of the actual
280     loaders don't matter as the choice loader does the caching.
281
282     >>> loader = ChoiceLoader([
283     ...     FileSystemLoader('/path/to/user/templates'),
284     ...     PackageLoader('myapplication')
285     ])
286
287     This is useful if you want to allow users to override builtin templates
288     from a different location.
289     """
290
291     def __init__(self, loaders, cache_size=50, auto_reload=True):
292         BaseLoader.__init__(self, cache_size, auto_reload)
293         self.loaders = loaders
294
295     def get_source(self, environment, template):
296         for loader in self.loaders:
297             try:
298                 return loader.get_source(environment, template)
299             except TemplateNotFound:
300                 pass
301         raise TemplateNotFound(template)