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
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
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
7import os
8import posixpath
9import re
10import stat
11import threading
13from mako import exceptions
14from mako import util
15from mako.template import Template
18class TemplateCollection:
19 """Represent a collection of :class:`.Template` objects,
20 identifiable via URI.
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.
29 :class:`.TemplateCollection` is an abstract class,
30 with the usual default implementation being :class:`.TemplateLookup`.
32 """
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``.
39 :param uri: String URI of the template to be resolved.
41 """
42 try:
43 self.get_template(uri)
44 return True
45 except exceptions.TemplateLookupException:
46 return False
48 def get_template(self, uri, relativeto=None):
49 """Return a :class:`.Template` object corresponding to the given
50 ``uri``.
52 The default implementation raises
53 :class:`.NotImplementedError`. Implementations should
54 raise :class:`.TemplateLookupException` if the given ``uri``
55 cannot be resolved.
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.
61 """
62 raise NotImplementedError()
64 def filename_to_uri(self, uri, filename):
65 """Convert the given ``filename`` to a URI relative to
66 this :class:`.TemplateCollection`."""
68 return uri
70 def adjust_uri(self, uri, filename):
71 """Adjust the given ``uri`` based on the calling ``filename``.
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.
81 """
82 return uri
85class TemplateLookup(TemplateCollection):
86 """Represent a collection of templates that locates template source files
87 from the local filesystem.
89 The primary argument is the ``directories`` argument, the list of
90 directories to search:
92 .. sourcecode:: python
94 lookup = TemplateLookup(["/path/to/templates"])
95 some_template = lookup.get_template("/index.html")
97 The :class:`.TemplateLookup` can also be given :class:`.Template` objects
98 programatically using :meth:`.put_string` or :meth:`.put_template`:
100 .. sourcecode:: python
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'/>
109 Hello, world !
110 ''')
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.
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.
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.
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.
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`.
148 """
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
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)
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 }
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()
227 def get_template(self, uri):
228 """Return a :class:`.Template` object corresponding to the given
229 ``uri``.
231 .. note:: The ``relativeto`` argument is not supported here at
232 the moment.
234 """
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
255 def adjust_uri(self, uri, relativeto):
256 """Adjust the given ``uri`` based on the given relative URI."""
258 key = (uri, relativeto)
259 if key in self._uri_cache:
260 return self._uri_cache[key]
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
272 def filename_to_uri(self, filename):
273 """Convert the given ``filename`` to a URI relative to
274 this :class:`.TemplateCollection`."""
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
283 def _relativeize(self, filename):
284 """Return the portion of a filename that is 'relative'
285 to the directories in this lookup.
287 """
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
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()
327 def _check(self, uri, template):
328 if template.filename is None:
329 return template
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
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``.
348 """
349 self._collection[uri] = Template(
350 text, lookup=self, uri=uri, **self.template_args
351 )
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.
358 """
359 self._collection[uri] = template