Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/mako/lookup.py: 33%

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

113 statements  

1# mako/lookup.py 

2# Copyright 2006-2026 the Mako authors and contributors <see AUTHORS file> 

3# 

4# This module is part of Mako and is released under 

5# the MIT License: http://www.opensource.org/licenses/mit-license.php 

6 

7import os 

8import posixpath 

9import re 

10import stat 

11import threading 

12 

13from mako import exceptions 

14from mako import util 

15from mako.template import Template 

16 

17 

18class TemplateCollection: 

19 """Represent a collection of :class:`.Template` objects, 

20 identifiable via URI. 

21 

22 A :class:`.TemplateCollection` is linked to the usage of 

23 all template tags that address other templates, such 

24 as ``<%include>``, ``<%namespace>``, and ``<%inherit>``. 

25 The ``file`` attribute of each of those tags refers 

26 to a string URI that is passed to that :class:`.Template` 

27 object's :class:`.TemplateCollection` for resolution. 

28 

29 :class:`.TemplateCollection` is an abstract class, 

30 with the usual default implementation being :class:`.TemplateLookup`. 

31 

32 """ 

33 

34 def has_template(self, uri): 

35 """Return ``True`` if this :class:`.TemplateLookup` is 

36 capable of returning a :class:`.Template` object for the 

37 given ``uri``. 

38 

39 :param uri: String URI of the template to be resolved. 

40 

41 """ 

42 try: 

43 self.get_template(uri) 

44 return True 

45 except exceptions.TemplateLookupException: 

46 return False 

47 

48 def get_template(self, uri, relativeto=None): 

49 """Return a :class:`.Template` object corresponding to the given 

50 ``uri``. 

51 

52 The default implementation raises 

53 :class:`.NotImplementedError`. Implementations should 

54 raise :class:`.TemplateLookupException` if the given ``uri`` 

55 cannot be resolved. 

56 

57 :param uri: String URI of the template to be resolved. 

58 :param relativeto: if present, the given ``uri`` is assumed to 

59 be relative to this URI. 

60 

61 """ 

62 raise NotImplementedError() 

63 

64 def filename_to_uri(self, uri, filename): 

65 """Convert the given ``filename`` to a URI relative to 

66 this :class:`.TemplateCollection`.""" 

67 

68 return uri 

69 

70 def adjust_uri(self, uri, filename): 

71 """Adjust the given ``uri`` based on the calling ``filename``. 

72 

73 When this method is called from the runtime, the 

74 ``filename`` parameter is taken directly to the ``filename`` 

75 attribute of the calling template. Therefore a custom 

76 :class:`.TemplateCollection` subclass can place any string 

77 identifier desired in the ``filename`` parameter of the 

78 :class:`.Template` objects it constructs and have them come back 

79 here. 

80 

81 """ 

82 return uri 

83 

84 

85class TemplateLookup(TemplateCollection): 

86 """Represent a collection of templates that locates template source files 

87 from the local filesystem. 

88 

89 The primary argument is the ``directories`` argument, the list of 

90 directories to search: 

91 

92 .. sourcecode:: python 

93 

94 lookup = TemplateLookup(["/path/to/templates"]) 

95 some_template = lookup.get_template("/index.html") 

96 

97 The :class:`.TemplateLookup` can also be given :class:`.Template` objects 

98 programatically using :meth:`.put_string` or :meth:`.put_template`: 

99 

100 .. sourcecode:: python 

101 

102 lookup = TemplateLookup() 

103 lookup.put_string("base.html", ''' 

104 <html><body>${self.next()}</body></html> 

105 ''') 

106 lookup.put_string("hello.html", ''' 

107 <%include file='base.html'/> 

108 

109 Hello, world ! 

110 ''') 

111 

112 

113 :param directories: A list of directory names which will be 

114 searched for a particular template URI. The URI is appended 

115 to each directory and the filesystem checked. 

116 

117 :param collection_size: Approximate size of the collection used 

118 to store templates. If left at its default of ``-1``, the size 

119 is unbounded, and a plain Python dictionary is used to 

120 relate URI strings to :class:`.Template` instances. 

121 Otherwise, a least-recently-used cache object is used which 

122 will maintain the size of the collection approximately to 

123 the number given. 

124 

125 :param filesystem_checks: When at its default value of ``True``, 

126 each call to :meth:`.TemplateLookup.get_template()` will 

127 compare the filesystem last modified time to the time in 

128 which an existing :class:`.Template` object was created. 

129 This allows the :class:`.TemplateLookup` to regenerate a 

130 new :class:`.Template` whenever the original source has 

131 been updated. Set this to ``False`` for a very minor 

132 performance increase. 

133 

134 :param modulename_callable: A callable which, when present, 

135 is passed the path of the source file as well as the 

136 requested URI, and then returns the full path of the 

137 generated Python module file. This is used to inject 

138 alternate schemes for Python module location. If left at 

139 its default of ``None``, the built in system of generation 

140 based on ``module_directory`` plus ``uri`` is used. 

141 

142 All other keyword parameters available for 

143 :class:`.Template` are mirrored here. When new 

144 :class:`.Template` objects are created, the keywords 

145 established with this :class:`.TemplateLookup` are passed on 

146 to each new :class:`.Template`. 

147 

148 """ 

149 

150 def __init__( 

151 self, 

152 directories=None, 

153 module_directory=None, 

154 filesystem_checks=True, 

155 collection_size=-1, 

156 format_exceptions=False, 

157 error_handler=None, 

158 output_encoding=None, 

159 encoding_errors="strict", 

160 cache_args=None, 

161 cache_impl="beaker", 

162 cache_enabled=True, 

163 cache_type=None, 

164 cache_dir=None, 

165 cache_url=None, 

166 modulename_callable=None, 

167 module_writer=None, 

168 default_filters=None, 

169 buffer_filters=(), 

170 strict_undefined=False, 

171 imports=None, 

172 future_imports=None, 

173 enable_loop=True, 

174 input_encoding=None, 

175 preprocessor=None, 

176 lexer_cls=None, 

177 include_error_handler=None, 

178 ): 

179 self.directories = [ 

180 posixpath.normpath(d) for d in util.to_list(directories, ()) 

181 ] 

182 self.module_directory = module_directory 

183 self.modulename_callable = modulename_callable 

184 self.filesystem_checks = filesystem_checks 

185 self.collection_size = collection_size 

186 

187 if cache_args is None: 

188 cache_args = {} 

189 # transfer deprecated cache_* args 

190 if cache_dir: 

191 cache_args.setdefault("dir", cache_dir) 

192 if cache_url: 

193 cache_args.setdefault("url", cache_url) 

194 if cache_type: 

195 cache_args.setdefault("type", cache_type) 

196 

197 self.template_args = { 

198 "format_exceptions": format_exceptions, 

199 "error_handler": error_handler, 

200 "include_error_handler": include_error_handler, 

201 "output_encoding": output_encoding, 

202 "cache_impl": cache_impl, 

203 "encoding_errors": encoding_errors, 

204 "input_encoding": input_encoding, 

205 "module_directory": module_directory, 

206 "module_writer": module_writer, 

207 "cache_args": cache_args, 

208 "cache_enabled": cache_enabled, 

209 "default_filters": default_filters, 

210 "buffer_filters": buffer_filters, 

211 "strict_undefined": strict_undefined, 

212 "imports": imports, 

213 "future_imports": future_imports, 

214 "enable_loop": enable_loop, 

215 "preprocessor": preprocessor, 

216 "lexer_cls": lexer_cls, 

217 } 

218 

219 if collection_size == -1: 

220 self._collection = {} 

221 self._uri_cache = {} 

222 else: 

223 self._collection = util.LRUCache(collection_size) 

224 self._uri_cache = util.LRUCache(collection_size) 

225 self._mutex = threading.Lock() 

226 

227 def get_template(self, uri): 

228 """Return a :class:`.Template` object corresponding to the given 

229 ``uri``. 

230 

231 .. note:: The ``relativeto`` argument is not supported here at 

232 the moment. 

233 

234 """ 

235 

236 try: 

237 if self.filesystem_checks: 

238 return self._check(uri, self._collection[uri]) 

239 else: 

240 return self._collection[uri] 

241 except KeyError as e: 

242 u = re.sub(r"^\/+", "", uri.replace("\\", "/")) 

243 for dir_ in self.directories: 

244 # make sure the path seperators are posix - os.altsep is empty 

245 # on POSIX and cannot be used. 

246 dir_ = dir_.replace(os.path.sep, posixpath.sep) 

247 srcfile = posixpath.normpath(posixpath.join(dir_, u)) 

248 if os.path.isfile(srcfile): 

249 return self._load(srcfile, uri) 

250 else: 

251 raise exceptions.TopLevelLookupException( 

252 "Can't locate template for uri %r" % uri 

253 ) from e 

254 

255 def adjust_uri(self, uri, relativeto): 

256 """Adjust the given ``uri`` based on the given relative URI.""" 

257 

258 key = (uri, relativeto) 

259 if key in self._uri_cache: 

260 return self._uri_cache[key] 

261 

262 if uri[0] == "/": 

263 v = self._uri_cache[key] = uri 

264 elif relativeto is not None: 

265 v = self._uri_cache[key] = posixpath.join( 

266 posixpath.dirname(relativeto), uri 

267 ) 

268 else: 

269 v = self._uri_cache[key] = "/" + uri 

270 return v 

271 

272 def filename_to_uri(self, filename): 

273 """Convert the given ``filename`` to a URI relative to 

274 this :class:`.TemplateCollection`.""" 

275 

276 try: 

277 return self._uri_cache[filename] 

278 except KeyError: 

279 value = self._relativeize(filename) 

280 self._uri_cache[filename] = value 

281 return value 

282 

283 def _relativeize(self, filename): 

284 """Return the portion of a filename that is 'relative' 

285 to the directories in this lookup. 

286 

287 """ 

288 

289 filename = posixpath.normpath(filename) 

290 for dir_ in self.directories: 

291 if filename[0 : len(dir_)] == dir_: 

292 return filename[len(dir_) :] 

293 else: 

294 return None 

295 

296 def _load(self, filename, uri): 

297 self._mutex.acquire() 

298 try: 

299 try: 

300 # try returning from collection one 

301 # more time in case concurrent thread already loaded 

302 return self._collection[uri] 

303 except KeyError: 

304 pass 

305 try: 

306 if self.modulename_callable is not None: 

307 module_filename = self.modulename_callable(filename, uri) 

308 else: 

309 module_filename = None 

310 self._collection[uri] = template = Template( 

311 uri=uri, 

312 filename=posixpath.normpath(filename), 

313 lookup=self, 

314 module_filename=module_filename, 

315 **self.template_args, 

316 ) 

317 return template 

318 except: 

319 # if compilation fails etc, ensure 

320 # template is removed from collection, 

321 # re-raise 

322 self._collection.pop(uri, None) 

323 raise 

324 finally: 

325 self._mutex.release() 

326 

327 def _check(self, uri, template): 

328 if template.filename is None: 

329 return template 

330 

331 try: 

332 template_stat = os.stat(template.filename) 

333 if template.module._modified_time >= template_stat[stat.ST_MTIME]: 

334 return template 

335 self._collection.pop(uri, None) 

336 return self._load(template.filename, uri) 

337 except OSError as e: 

338 self._collection.pop(uri, None) 

339 raise exceptions.TemplateLookupException( 

340 "Can't locate template for uri %r" % uri 

341 ) from e 

342 

343 def put_string(self, uri, text): 

344 """Place a new :class:`.Template` object into this 

345 :class:`.TemplateLookup`, based on the given string of 

346 ``text``. 

347 

348 """ 

349 self._collection[uri] = Template( 

350 text, lookup=self, uri=uri, **self.template_args 

351 ) 

352 

353 def put_template(self, uri, template): 

354 """Place a new :class:`.Template` object into this 

355 :class:`.TemplateLookup`, based on the given 

356 :class:`.Template` object. 

357 

358 """ 

359 self._collection[uri] = template