1 # -*- coding: utf-8 -*- 2 """ 3 jinja2.loaders 4 ~~~~~~~~~~~~~~ 5 6 Jinja loader classes. 7 8 :copyright: (c) 2010 by the Jinja Team. 9 :license: BSD, see LICENSE for more details. 10 """ 11 import os 12 import sys 13 import weakref 14 from types import ModuleType 15 from os import path 16 try: 17 from hashlib import sha1 18 except ImportError: 19 from sha import new as sha1 20 from jinja2.exceptions import TemplateNotFound 21 from jinja2.utils import LRUCache, open_if_exists, internalcode 22 23 24 def split_template_path(template): 25 """Split a path into segments and perform a sanity check. If it detects 26 '..' in the path it will raise a `TemplateNotFound` error. 27 """ 28 pieces = [] 29 for piece in template.split('/'): 30 if path.sep in piece \ 31 or (path.altsep and path.altsep in piece) or \ 32 piece == path.pardir: 33 raise TemplateNotFound(template) 34 elif piece and piece != '.': 35 pieces.append(piece) 36 return pieces 37 38 39 class BaseLoader(object): 40 """Baseclass for all loaders. Subclass this and override `get_source` to 41 implement a custom loading mechanism. The environment provides a 42 `get_template` method that calls the loader's `load` method to get the 43 :class:`Template` object. 44 45 A very basic example for a loader that looks up templates on the file 46 system could look like this:: 47 48 from jinja2 import BaseLoader, TemplateNotFound 49 from os.path import join, exists, getmtime 50 51 class MyLoader(BaseLoader): 52 53 def __init__(self, path): 54 self.path = path 55 56 def get_source(self, environment, template): 57 path = join(self.path, template) 58 if not exists(path): 59 raise TemplateNotFound(template) 60 mtime = getmtime(path) 61 with file(path) as f: 62 source = f.read().decode('utf-8') 63 return source, path, lambda: mtime == getmtime(path) 64 """ 65 66 #: if set to `False` it indicates that the loader cannot provide access 67 #: to the source of templates. 68 #: 69 #: .. versionadded:: 2.4 70 has_source_access = True 71 72 def get_source(self, environment, template): 73 """Get the template source, filename and reload helper for a template. 74 It's passed the environment and template name and has to return a 75 tuple in the form ``(source, filename, uptodate)`` or raise a 76 `TemplateNotFound` error if it can't locate the template. 77 78 The source part of the returned tuple must be the source of the 79 template as unicode string or a ASCII bytestring. The filename should 80 be the name of the file on the filesystem if it was loaded from there, 81 otherwise `None`. The filename is used by python for the tracebacks 82 if no loader extension is used. 83 84 The last item in the tuple is the `uptodate` function. If auto 85 reloading is enabled it's always called to check if the template 86 changed. No arguments are passed so the function must store the 87 old state somewhere (for example in a closure). If it returns `False` 88 the template will be reloaded. 89 """ 90 if not self.has_source_access: 91 raise RuntimeError('%s cannot provide access to the source' % 92 self.__class__.__name__) 93 raise TemplateNotFound(template) 94 95 def list_templates(self): 96 """Iterates over all templates. If the loader does not support that 97 it should raise a :exc:`TypeError` which is the default behavior. 98 """ 99 raise TypeError('this loader cannot iterate over all templates') 100 101 @internalcode 102 def load(self, environment, name, globals=None): 103 """Loads a template. This method looks up the template in the cache 104 or loads one by calling :meth:`get_source`. Subclasses should not 105 override this method as loaders working on collections of other 106 loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`) 107 will not call this method but `get_source` directly. 108 """ 109 code = None 110 if globals is None: 111 globals = {} 112 113 # first we try to get the source for this template together 114 # with the filename and the uptodate function. 115 source, filename, uptodate = self.get_source(environment, name) 116 117 # try to load the code from the bytecode cache if there is a 118 # bytecode cache configured. 119 bcc = environment.bytecode_cache 120 if bcc is not None: 121 bucket = bcc.get_bucket(environment, name, filename, source) 122 code = bucket.code 123 124 # if we don't have code so far (not cached, no longer up to 125 # date) etc. we compile the template 126 if code is None: 127 code = environment.compile(source, name, filename) 128 129 # if the bytecode cache is available and the bucket doesn't 130 # have a code so far, we give the bucket the new code and put 131 # it back to the bytecode cache. 132 if bcc is not None and bucket.code is None: 133 bucket.code = code 134 bcc.set_bucket(bucket) 135 136 return environment.template_class.from_code(environment, code, 137 globals, uptodate) 138 139 140 class FileSystemLoader(BaseLoader): 141 """Loads templates from the file system. This loader can find templates 142 in folders on the file system and is the preferred way to load them. 143 144 The loader takes the path to the templates as string, or if multiple 145 locations are wanted a list of them which is then looked up in the 146 given order: 147 148 >>> loader = FileSystemLoader('/path/to/templates') 149 >>> loader = FileSystemLoader(['/path/to/templates', '/other/path']) 150 151 Per default the template encoding is ``'utf-8'`` which can be changed 152 by setting the `encoding` parameter to something else. 153 """ 154 155 def __init__(self, searchpath, encoding='utf-8'): 156 if isinstance(searchpath, basestring): 157 searchpath = [searchpath] 158 self.searchpath = list(searchpath) 159 self.encoding = encoding 160 161 def get_source(self, environment, template): 162 pieces = split_template_path(template) 163 for searchpath in self.searchpath: 164 filename = path.join(searchpath, *pieces) 165 f = open_if_exists(filename) 166 if f is None: 167 continue 168 try: 169 contents = f.read().decode(self.encoding) 170 finally: 171 f.close() 172 173 mtime = path.getmtime(filename) 174 def uptodate(): 175 try: 176 return path.getmtime(filename) == mtime 177 except OSError: 178 return False 179 return contents, filename, uptodate 180 raise TemplateNotFound(template) 181 182 def list_templates(self): 183 found = set() 184 for searchpath in self.searchpath: 185 for dirpath, dirnames, filenames in os.walk(searchpath): 186 for filename in filenames: 187 template = os.path.join(dirpath, filename) \ 188 [len(searchpath):].strip(os.path.sep) \ 189 .replace(os.path.sep, '/') 190 if template[:2] == './': 191 template = template[2:] 192 if template not in found: 193 found.add(template) 194 return sorted(found) 195 196 197 class PackageLoader(BaseLoader): 198 """Load templates from python eggs or packages. It is constructed with 199 the name of the python package and the path to the templates in that 200 package:: 201 202 loader = PackageLoader('mypackage', 'views') 203 204 If the package path is not given, ``'templates'`` is assumed. 205 206 Per default the template encoding is ``'utf-8'`` which can be changed 207 by setting the `encoding` parameter to something else. Due to the nature 208 of eggs it's only possible to reload templates if the package was loaded 209 from the file system and not a zip file. 210 """ 211 212 def __init__(self, package_name, package_path='templates', 213 encoding='utf-8'): 214 from pkg_resources import DefaultProvider, ResourceManager, \ 215 get_provider 216 provider = get_provider(package_name) 217 self.encoding = encoding 218 self.manager = ResourceManager() 219 self.filesystem_bound = isinstance(provider, DefaultProvider) 220 self.provider = provider 221 self.package_path = package_path 222 223 def get_source(self, environment, template): 224 pieces = split_template_path(template) 225 p = '/'.join((self.package_path,) + tuple(pieces)) 226 if not self.provider.has_resource(p): 227 raise TemplateNotFound(template) 228 229 filename = uptodate = None 230 if self.filesystem_bound: 231 filename = self.provider.get_resource_filename(self.manager, p) 232 mtime = path.getmtime(filename) 233 def uptodate(): 234 try: 235 return path.getmtime(filename) == mtime 236 except OSError: 237 return False 238 239 source = self.provider.get_resource_string(self.manager, p) 240 return source.decode(self.encoding), filename, uptodate 241 242 def list_templates(self): 243 path = self.package_path 244 if path[:2] == './': 245 path = path[2:] 246 elif path == '.': 247 path = '' 248 offset = len(path) 249 results = [] 250 def _walk(path): 251 for filename in self.provider.resource_listdir(path): 252 fullname = path + '/' + filename 253 if self.provider.resource_isdir(fullname): 254 _walk(fullname) 255 else: 256 results.append(fullname[offset:].lstrip('/')) 257 _walk(path) 258 results.sort() 259 return results 260 261 262 class DictLoader(BaseLoader): 263 """Loads a template from a python dict. It's passed a dict of unicode 264 strings bound to template names. This loader is useful for unittesting: 265 266 >>> loader = DictLoader({'index.html': 'source here'}) 267 268 Because auto reloading is rarely useful this is disabled per default. 269 """ 270 271 def __init__(self, mapping): 272 self.mapping = mapping 273 274 def get_source(self, environment, template): 275 if template in self.mapping: 276 source = self.mapping[template] 277 return source, None, lambda: source != self.mapping.get(template) 278 raise TemplateNotFound(template) 279 280 def list_templates(self): 281 return sorted(self.mapping) 282 283 284 class FunctionLoader(BaseLoader): 285 """A loader that is passed a function which does the loading. The 286 function becomes the name of the template passed and has to return either 287 an unicode string with the template source, a tuple in the form ``(source, 288 filename, uptodatefunc)`` or `None` if the template does not exist. 289 290 >>> def load_template(name): 291 ... if name == 'index.html': 292 ... return '...' 293 ... 294 >>> loader = FunctionLoader(load_template) 295 296 The `uptodatefunc` is a function that is called if autoreload is enabled 297 and has to return `True` if the template is still up to date. For more 298 details have a look at :meth:`BaseLoader.get_source` which has the same 299 return value. 300 """ 301 302 def __init__(self, load_func): 303 self.load_func = load_func 304 305 def get_source(self, environment, template): 306 rv = self.load_func(template) 307 if rv is None: 308 raise TemplateNotFound(template) 309 elif isinstance(rv, basestring): 310 return rv, None, None 311 return rv 312 313 314 class PrefixLoader(BaseLoader): 315 """A loader that is passed a dict of loaders where each loader is bound 316 to a prefix. The prefix is delimited from the template by a slash per 317 default, which can be changed by setting the `delimiter` argument to 318 something else:: 319 320 loader = PrefixLoader({ 321 'app1': PackageLoader('mypackage.app1'), 322 'app2': PackageLoader('mypackage.app2') 323 }) 324 325 By loading ``'app1/index.html'`` the file from the app1 package is loaded, 326 by loading ``'app2/index.html'`` the file from the second. 327 """ 328 329 def __init__(self, mapping, delimiter='/'): 330 self.mapping = mapping 331 self.delimiter = delimiter 332 333 def get_source(self, environment, template): 334 try: 335 prefix, name = template.split(self.delimiter, 1) 336 loader = self.mapping[prefix] 337 except (ValueError, KeyError): 338 raise TemplateNotFound(template) 339 try: 340 return loader.get_source(environment, name) 341 except TemplateNotFound: 342 # re-raise the exception with the correct fileame here. 343 # (the one that includes the prefix) 344 raise TemplateNotFound(template) 345 346 def list_templates(self): 347 result = [] 348 for prefix, loader in self.mapping.iteritems(): 349 for template in loader.list_templates(): 350 result.append(prefix + self.delimiter + template) 351 return result 352 353 354 class ChoiceLoader(BaseLoader): 355 """This loader works like the `PrefixLoader` just that no prefix is 356 specified. If a template could not be found by one loader the next one 357 is tried. 358 359 >>> loader = ChoiceLoader([ 360 ... FileSystemLoader('/path/to/user/templates'), 361 ... FileSystemLoader('/path/to/system/templates') 362 ... ]) 363 364 This is useful if you want to allow users to override builtin templates 365 from a different location. 366 """ 367 368 def __init__(self, loaders): 369 self.loaders = loaders 370 371 def get_source(self, environment, template): 372 for loader in self.loaders: 373 try: 374 return loader.get_source(environment, template) 375 except TemplateNotFound: 376 pass 377 raise TemplateNotFound(template) 378 379 def list_templates(self): 380 found = set() 381 for loader in self.loaders: 382 found.update(loader.list_templates()) 383 return sorted(found) 384 385 386 class _TemplateModule(ModuleType): 387 """Like a normal module but with support for weak references""" 388 389 390 class ModuleLoader(BaseLoader): 391 """This loader loads templates from precompiled templates. 392 393 Example usage: 394 395 >>> loader = ChoiceLoader([ 396 ... ModuleLoader('/path/to/compiled/templates'), 397 ... FileSystemLoader('/path/to/templates') 398 ... ]) 399 400 Templates can be precompiled with :meth:`Environment.compile_templates`. 401 """ 402 403 has_source_access = False 404 405 def __init__(self, path): 406 package_name = '_jinja2_module_templates_%x' % id(self) 407 408 # create a fake module that looks for the templates in the 409 # path given. 410 mod = _TemplateModule(package_name) 411 if isinstance(path, basestring): 412 path = [path] 413 else: 414 path = list(path) 415 mod.__path__ = path 416 417 sys.modules[package_name] = weakref.proxy(mod, 418 lambda x: sys.modules.pop(package_name, None)) 419 420 # the only strong reference, the sys.modules entry is weak 421 # so that the garbage collector can remove it once the 422 # loader that created it goes out of business. 423 self.module = mod 424 self.package_name = package_name 425 426 @staticmethod 427 def get_template_key(name): 428 return 'tmpl_' + sha1(name.encode('utf-8')).hexdigest() 429 430 @staticmethod 431 def get_module_filename(name): 432 return ModuleLoader.get_template_key(name) + '.py' 433 434 @internalcode 435 def load(self, environment, name, globals=None): 436 key = self.get_template_key(name) 437 module = '%s.%s' % (self.package_name, key) 438 mod = getattr(self.module, module, None) 439 if mod is None: 440 try: 441 mod = __import__(module, None, None, ['root']) 442 except ImportError: 443 raise TemplateNotFound(name) 444 445 # remove the entry from sys.modules, we only want the attribute 446 # on the module object we have stored on the loader. 447 sys.modules.pop(module, None) 448 449 return environment.template_class.from_module_dict( 450 environment, mod.__dict__, globals) 451