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-2025 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 

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

21 identifiable via URI. 

22 

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

24 all template tags that address other templates, such 

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

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

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

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

29 

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

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

32 

33 """ 

34 

35 def has_template(self, uri): 

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

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

38 given ``uri``. 

39 

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

41 

42 """ 

43 try: 

44 self.get_template(uri) 

45 return True 

46 except exceptions.TemplateLookupException: 

47 return False 

48 

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

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

51 ``uri``. 

52 

53 The default implementation raises 

54 :class:`.NotImplementedError`. Implementations should 

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

56 cannot be resolved. 

57 

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

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

60 be relative to this URI. 

61 

62 """ 

63 raise NotImplementedError() 

64 

65 def filename_to_uri(self, uri, filename): 

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

67 this :class:`.TemplateCollection`.""" 

68 

69 return uri 

70 

71 def adjust_uri(self, uri, filename): 

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

73 

74 When this method is called from the runtime, the 

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

76 attribute of the calling template. Therefore a custom 

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

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

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

80 here. 

81 

82 """ 

83 return uri 

84 

85 

86class TemplateLookup(TemplateCollection): 

87 

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

89 from the local filesystem. 

90 

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

92 directories to search: 

93 

94 .. sourcecode:: python 

95 

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

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

98 

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

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

101 

102 .. sourcecode:: python 

103 

104 lookup = TemplateLookup() 

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

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

107 ''') 

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

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

110 

111 Hello, world ! 

112 ''') 

113 

114 

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

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

117 to each directory and the filesystem checked. 

118 

119 :param collection_size: Approximate size of the collection used 

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

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

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

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

124 will maintain the size of the collection approximately to 

125 the number given. 

126 

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

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

129 compare the filesystem last modified time to the time in 

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

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

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

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

134 performance increase. 

135 

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

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

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

139 generated Python module file. This is used to inject 

140 alternate schemes for Python module location. If left at 

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

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

143 

144 All other keyword parameters available for 

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

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

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

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

149 

150 """ 

151 

152 def __init__( 

153 self, 

154 directories=None, 

155 module_directory=None, 

156 filesystem_checks=True, 

157 collection_size=-1, 

158 format_exceptions=False, 

159 error_handler=None, 

160 output_encoding=None, 

161 encoding_errors="strict", 

162 cache_args=None, 

163 cache_impl="beaker", 

164 cache_enabled=True, 

165 cache_type=None, 

166 cache_dir=None, 

167 cache_url=None, 

168 modulename_callable=None, 

169 module_writer=None, 

170 default_filters=None, 

171 buffer_filters=(), 

172 strict_undefined=False, 

173 imports=None, 

174 future_imports=None, 

175 enable_loop=True, 

176 input_encoding=None, 

177 preprocessor=None, 

178 lexer_cls=None, 

179 include_error_handler=None, 

180 ): 

181 self.directories = [ 

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

183 ] 

184 self.module_directory = module_directory 

185 self.modulename_callable = modulename_callable 

186 self.filesystem_checks = filesystem_checks 

187 self.collection_size = collection_size 

188 

189 if cache_args is None: 

190 cache_args = {} 

191 # transfer deprecated cache_* args 

192 if cache_dir: 

193 cache_args.setdefault("dir", cache_dir) 

194 if cache_url: 

195 cache_args.setdefault("url", cache_url) 

196 if cache_type: 

197 cache_args.setdefault("type", cache_type) 

198 

199 self.template_args = { 

200 "format_exceptions": format_exceptions, 

201 "error_handler": error_handler, 

202 "include_error_handler": include_error_handler, 

203 "output_encoding": output_encoding, 

204 "cache_impl": cache_impl, 

205 "encoding_errors": encoding_errors, 

206 "input_encoding": input_encoding, 

207 "module_directory": module_directory, 

208 "module_writer": module_writer, 

209 "cache_args": cache_args, 

210 "cache_enabled": cache_enabled, 

211 "default_filters": default_filters, 

212 "buffer_filters": buffer_filters, 

213 "strict_undefined": strict_undefined, 

214 "imports": imports, 

215 "future_imports": future_imports, 

216 "enable_loop": enable_loop, 

217 "preprocessor": preprocessor, 

218 "lexer_cls": lexer_cls, 

219 } 

220 

221 if collection_size == -1: 

222 self._collection = {} 

223 self._uri_cache = {} 

224 else: 

225 self._collection = util.LRUCache(collection_size) 

226 self._uri_cache = util.LRUCache(collection_size) 

227 self._mutex = threading.Lock() 

228 

229 def get_template(self, uri): 

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

231 ``uri``. 

232 

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

234 the moment. 

235 

236 """ 

237 

238 try: 

239 if self.filesystem_checks: 

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

241 else: 

242 return self._collection[uri] 

243 except KeyError as e: 

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

245 for dir_ in self.directories: 

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

247 # on POSIX and cannot be used. 

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

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

250 if os.path.isfile(srcfile): 

251 return self._load(srcfile, uri) 

252 else: 

253 raise exceptions.TopLevelLookupException( 

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

255 ) from e 

256 

257 def adjust_uri(self, uri, relativeto): 

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

259 

260 key = (uri, relativeto) 

261 if key in self._uri_cache: 

262 return self._uri_cache[key] 

263 

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

265 v = self._uri_cache[key] = uri 

266 elif relativeto is not None: 

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

268 posixpath.dirname(relativeto), uri 

269 ) 

270 else: 

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

272 return v 

273 

274 def filename_to_uri(self, filename): 

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

276 this :class:`.TemplateCollection`.""" 

277 

278 try: 

279 return self._uri_cache[filename] 

280 except KeyError: 

281 value = self._relativeize(filename) 

282 self._uri_cache[filename] = value 

283 return value 

284 

285 def _relativeize(self, filename): 

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

287 to the directories in this lookup. 

288 

289 """ 

290 

291 filename = posixpath.normpath(filename) 

292 for dir_ in self.directories: 

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

294 return filename[len(dir_) :] 

295 else: 

296 return None 

297 

298 def _load(self, filename, uri): 

299 self._mutex.acquire() 

300 try: 

301 try: 

302 # try returning from collection one 

303 # more time in case concurrent thread already loaded 

304 return self._collection[uri] 

305 except KeyError: 

306 pass 

307 try: 

308 if self.modulename_callable is not None: 

309 module_filename = self.modulename_callable(filename, uri) 

310 else: 

311 module_filename = None 

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

313 uri=uri, 

314 filename=posixpath.normpath(filename), 

315 lookup=self, 

316 module_filename=module_filename, 

317 **self.template_args, 

318 ) 

319 return template 

320 except: 

321 # if compilation fails etc, ensure 

322 # template is removed from collection, 

323 # re-raise 

324 self._collection.pop(uri, None) 

325 raise 

326 finally: 

327 self._mutex.release() 

328 

329 def _check(self, uri, template): 

330 if template.filename is None: 

331 return template 

332 

333 try: 

334 template_stat = os.stat(template.filename) 

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

336 return template 

337 self._collection.pop(uri, None) 

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

339 except OSError as e: 

340 self._collection.pop(uri, None) 

341 raise exceptions.TemplateLookupException( 

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

343 ) from e 

344 

345 def put_string(self, uri, text): 

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

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

348 ``text``. 

349 

350 """ 

351 self._collection[uri] = Template( 

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

353 ) 

354 

355 def put_template(self, uri, template): 

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

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

358 :class:`.Template` object. 

359 

360 """ 

361 self._collection[uri] = template