Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/genshi/template/loader.py: 20%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

128 statements  

1# -*- coding: utf-8 -*- 

2# 

3# Copyright (C) 2006-2010 Edgewall Software 

4# All rights reserved. 

5# 

6# This software is licensed as described in the file COPYING, which 

7# you should have received as part of this distribution. The terms 

8# are also available at http://genshi.edgewall.org/wiki/License. 

9# 

10# This software consists of voluntary contributions made by many 

11# individuals. For the exact contribution history, see the revision 

12# history and logs, available at http://genshi.edgewall.org/log/. 

13 

14"""Template loading and caching.""" 

15 

16import os 

17try: 

18 import threading 

19except ImportError: 

20 import dummy_threading as threading 

21 

22from genshi.compat import string_types 

23from genshi.template.base import TemplateError 

24from genshi.util import LRUCache 

25 

26__all__ = ['TemplateLoader', 'TemplateNotFound', 'directory', 'package', 

27 'prefixed'] 

28__docformat__ = 'restructuredtext en' 

29 

30 

31class TemplateNotFound(TemplateError): 

32 """Exception raised when a specific template file could not be found.""" 

33 

34 def __init__(self, name, search_path): 

35 """Create the exception. 

36  

37 :param name: the filename of the template 

38 :param search_path: the search path used to lookup the template 

39 """ 

40 TemplateError.__init__(self, 'Template "%s" not found' % name) 

41 self.search_path = search_path 

42 

43 

44class TemplateLoader(object): 

45 """Responsible for loading templates from files on the specified search 

46 path. 

47  

48 >>> import tempfile 

49 >>> fd, path = tempfile.mkstemp(suffix='.html', prefix='template') 

50 >>> os.write(fd, u'<p>$var</p>'.encode('utf-8')) 

51 11 

52 >>> os.close(fd) 

53  

54 The template loader accepts a list of directory paths that are then used 

55 when searching for template files, in the given order: 

56  

57 >>> loader = TemplateLoader([os.path.dirname(path)]) 

58  

59 The `load()` method first checks the template cache whether the requested 

60 template has already been loaded. If not, it attempts to locate the 

61 template file, and returns the corresponding `Template` object: 

62  

63 >>> from genshi.template import MarkupTemplate 

64 >>> template = loader.load(os.path.basename(path)) 

65 >>> isinstance(template, MarkupTemplate) 

66 True 

67  

68 Template instances are cached: requesting a template with the same name 

69 results in the same instance being returned: 

70  

71 >>> loader.load(os.path.basename(path)) is template 

72 True 

73  

74 The `auto_reload` option can be used to control whether a template should 

75 be automatically reloaded when the file it was loaded from has been 

76 changed. Disable this automatic reloading to improve performance. 

77  

78 >>> os.remove(path) 

79 """ 

80 def __init__(self, search_path=None, auto_reload=False, 

81 default_encoding=None, max_cache_size=25, default_class=None, 

82 variable_lookup='strict', allow_exec=True, callback=None): 

83 """Create the template laoder. 

84  

85 :param search_path: a list of absolute path names that should be 

86 searched for template files, or a string containing 

87 a single absolute path; alternatively, any item on 

88 the list may be a ''load function'' that is passed 

89 a filename and returns a file-like object and some 

90 metadata 

91 :param auto_reload: whether to check the last modification time of 

92 template files, and reload them if they have changed 

93 :param default_encoding: the default encoding to assume when loading 

94 templates; defaults to UTF-8 

95 :param max_cache_size: the maximum number of templates to keep in the 

96 cache 

97 :param default_class: the default `Template` subclass to use when 

98 instantiating templates 

99 :param variable_lookup: the variable lookup mechanism; either "strict" 

100 (the default), "lenient", or a custom lookup 

101 class 

102 :param allow_exec: whether to allow Python code blocks in templates 

103 :param callback: (optional) a callback function that is invoked after a 

104 template was initialized by this loader; the function 

105 is passed the template object as only argument. This 

106 callback can be used for example to add any desired 

107 filters to the template 

108 :see: `LenientLookup`, `StrictLookup` 

109  

110 :note: Changed in 0.5: Added the `allow_exec` argument 

111 """ 

112 from genshi.template.markup import MarkupTemplate 

113 

114 self.search_path = search_path 

115 if self.search_path is None: 

116 self.search_path = [] 

117 elif not isinstance(self.search_path, (list, tuple)): 

118 self.search_path = [self.search_path] 

119 

120 self.auto_reload = auto_reload 

121 """Whether templates should be reloaded when the underlying file is 

122 changed""" 

123 

124 self.default_encoding = default_encoding 

125 self.default_class = default_class or MarkupTemplate 

126 self.variable_lookup = variable_lookup 

127 self.allow_exec = allow_exec 

128 if callback is not None and not hasattr(callback, '__call__'): 

129 raise TypeError('The "callback" parameter needs to be callable') 

130 self.callback = callback 

131 self._cache = LRUCache(max_cache_size) 

132 self._uptodate = {} 

133 self._lock = threading.RLock() 

134 

135 def __getstate__(self): 

136 state = self.__dict__.copy() 

137 state['_lock'] = None 

138 return state 

139 

140 def __setstate__(self, state): 

141 self.__dict__ = state 

142 self._lock = threading.RLock() 

143 

144 def load(self, filename, relative_to=None, cls=None, encoding=None): 

145 """Load the template with the given name. 

146  

147 If the `filename` parameter is relative, this method searches the 

148 search path trying to locate a template matching the given name. If the 

149 file name is an absolute path, the search path is ignored. 

150  

151 If the requested template is not found, a `TemplateNotFound` exception 

152 is raised. Otherwise, a `Template` object is returned that represents 

153 the parsed template. 

154  

155 Template instances are cached to avoid having to parse the same 

156 template file more than once. Thus, subsequent calls of this method 

157 with the same template file name will return the same `Template` 

158 object (unless the ``auto_reload`` option is enabled and the file was 

159 changed since the last parse.) 

160  

161 If the `relative_to` parameter is provided, the `filename` is 

162 interpreted as being relative to that path. 

163  

164 :param filename: the relative path of the template file to load 

165 :param relative_to: the filename of the template from which the new 

166 template is being loaded, or ``None`` if the 

167 template is being loaded directly 

168 :param cls: the class of the template object to instantiate 

169 :param encoding: the encoding of the template to load; defaults to the 

170 ``default_encoding`` of the loader instance 

171 :return: the loaded `Template` instance 

172 :raises TemplateNotFound: if a template with the given name could not 

173 be found 

174 """ 

175 if cls is None: 

176 cls = self.default_class 

177 search_path = self.search_path 

178 

179 # Make the filename relative to the template file its being loaded 

180 # from, but only if that file is specified as a relative path, or no 

181 # search path has been set up 

182 if relative_to and (not search_path or not os.path.isabs(relative_to)): 

183 filename = os.path.join(os.path.dirname(relative_to), filename) 

184 

185 filename = os.path.normpath(filename) 

186 cachekey = filename 

187 

188 self._lock.acquire() 

189 try: 

190 # First check the cache to avoid reparsing the same file 

191 try: 

192 tmpl = self._cache[cachekey] 

193 if not self.auto_reload: 

194 return tmpl 

195 uptodate = self._uptodate[cachekey] 

196 if uptodate is not None and uptodate(): 

197 return tmpl 

198 except (KeyError, OSError): 

199 pass 

200 

201 isabs = False 

202 

203 if os.path.isabs(filename): 

204 # Bypass the search path if the requested filename is absolute 

205 search_path = [os.path.dirname(filename)] 

206 isabs = True 

207 

208 elif relative_to and os.path.isabs(relative_to): 

209 # Make sure that the directory containing the including 

210 # template is on the search path 

211 dirname = os.path.dirname(relative_to) 

212 if dirname not in search_path: 

213 search_path = list(search_path) + [dirname] 

214 isabs = True 

215 

216 elif not search_path: 

217 # Uh oh, don't know where to look for the template 

218 raise TemplateError('Search path for templates not configured') 

219 

220 for loadfunc in search_path: 

221 if isinstance(loadfunc, string_types): 

222 loadfunc = directory(loadfunc) 

223 try: 

224 filepath, filename, fileobj, uptodate = loadfunc(filename) 

225 except IOError: 

226 continue 

227 else: 

228 try: 

229 if isabs: 

230 # If the filename of either the included or the  

231 # including template is absolute, make sure the 

232 # included template gets an absolute path, too, 

233 # so that nested includes work properly without a 

234 # search path 

235 filename = filepath 

236 tmpl = self._instantiate(cls, fileobj, filepath, 

237 filename, encoding=encoding) 

238 if self.callback: 

239 self.callback(tmpl) 

240 self._cache[cachekey] = tmpl 

241 self._uptodate[cachekey] = uptodate 

242 finally: 

243 if hasattr(fileobj, 'close'): 

244 fileobj.close() 

245 return tmpl 

246 

247 raise TemplateNotFound(filename, search_path) 

248 

249 finally: 

250 self._lock.release() 

251 

252 def _instantiate(self, cls, fileobj, filepath, filename, encoding=None): 

253 """Instantiate and return the `Template` object based on the given 

254 class and parameters. 

255  

256 This function is intended for subclasses to override if they need to 

257 implement special template instantiation logic. Code that just uses 

258 the `TemplateLoader` should use the `load` method instead. 

259  

260 :param cls: the class of the template object to instantiate 

261 :param fileobj: a readable file-like object containing the template 

262 source 

263 :param filepath: the absolute path to the template file 

264 :param filename: the path to the template file relative to the search 

265 path 

266 :param encoding: the encoding of the template to load; defaults to the 

267 ``default_encoding`` of the loader instance 

268 :return: the loaded `Template` instance 

269 :rtype: `Template` 

270 """ 

271 if encoding is None: 

272 encoding = self.default_encoding 

273 return cls(fileobj, filepath=filepath, filename=filename, loader=self, 

274 encoding=encoding, lookup=self.variable_lookup, 

275 allow_exec=self.allow_exec) 

276 

277 @staticmethod 

278 def directory(path): 

279 """Loader factory for loading templates from a local directory. 

280  

281 :param path: the path to the local directory containing the templates 

282 :return: the loader function to load templates from the given directory 

283 :rtype: ``function`` 

284 """ 

285 def _load_from_directory(filename): 

286 filepath = os.path.join(path, filename) 

287 fileobj = open(filepath, 'rb') 

288 mtime = os.path.getmtime(filepath) 

289 def _uptodate(): 

290 return mtime == os.path.getmtime(filepath) 

291 return filepath, filename, fileobj, _uptodate 

292 return _load_from_directory 

293 

294 @staticmethod 

295 def package(name, path): 

296 """Loader factory for loading templates from egg package data. 

297  

298 :param name: the name of the package containing the resources 

299 :param path: the path inside the package data 

300 :return: the loader function to load templates from the given package 

301 :rtype: ``function`` 

302 """ 

303 from pkg_resources import resource_stream 

304 def _load_from_package(filename): 

305 filepath = os.path.join(path, filename) 

306 return filepath, filename, resource_stream(name, filepath), None 

307 return _load_from_package 

308 

309 @staticmethod 

310 def prefixed(**delegates): 

311 """Factory for a load function that delegates to other loaders 

312 depending on the prefix of the requested template path. 

313  

314 The prefix is stripped from the filename when passing on the load 

315 request to the delegate. 

316  

317 >>> load = prefixed( 

318 ... app1 = lambda filename: ('app1', filename, None, None), 

319 ... app2 = lambda filename: ('app2', filename, None, None) 

320 ... ) 

321 >>> print(load('app1/foo.html')) 

322 ('app1', 'app1/foo.html', None, None) 

323 >>> print(load('app2/bar.html')) 

324 ('app2', 'app2/bar.html', None, None) 

325  

326 :param delegates: mapping of path prefixes to loader functions 

327 :return: the loader function 

328 :rtype: ``function`` 

329 """ 

330 def _dispatch_by_prefix(filename): 

331 for prefix, delegate in delegates.items(): 

332 if filename.startswith(prefix): 

333 if isinstance(delegate, string_types): 

334 delegate = directory(delegate) 

335 filepath, _, fileobj, uptodate = delegate( 

336 filename[len(prefix):].lstrip('/\\') 

337 ) 

338 return filepath, filename, fileobj, uptodate 

339 raise TemplateNotFound(filename, list(delegates.keys())) 

340 return _dispatch_by_prefix 

341 

342 

343directory = TemplateLoader.directory 

344package = TemplateLoader.package 

345prefixed = TemplateLoader.prefixed