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-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
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:
20 """Represent a collection of :class:`.Template` objects,
21 identifiable via URI.
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.
30 :class:`.TemplateCollection` is an abstract class,
31 with the usual default implementation being :class:`.TemplateLookup`.
33 """
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``.
40 :param uri: String URI of the template to be resolved.
42 """
43 try:
44 self.get_template(uri)
45 return True
46 except exceptions.TemplateLookupException:
47 return False
49 def get_template(self, uri, relativeto=None):
50 """Return a :class:`.Template` object corresponding to the given
51 ``uri``.
53 The default implementation raises
54 :class:`.NotImplementedError`. Implementations should
55 raise :class:`.TemplateLookupException` if the given ``uri``
56 cannot be resolved.
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.
62 """
63 raise NotImplementedError()
65 def filename_to_uri(self, uri, filename):
66 """Convert the given ``filename`` to a URI relative to
67 this :class:`.TemplateCollection`."""
69 return uri
71 def adjust_uri(self, uri, filename):
72 """Adjust the given ``uri`` based on the calling ``filename``.
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.
82 """
83 return uri
86class TemplateLookup(TemplateCollection):
88 """Represent a collection of templates that locates template source files
89 from the local filesystem.
91 The primary argument is the ``directories`` argument, the list of
92 directories to search:
94 .. sourcecode:: python
96 lookup = TemplateLookup(["/path/to/templates"])
97 some_template = lookup.get_template("/index.html")
99 The :class:`.TemplateLookup` can also be given :class:`.Template` objects
100 programatically using :meth:`.put_string` or :meth:`.put_template`:
102 .. sourcecode:: python
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'/>
111 Hello, world !
112 ''')
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.
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.
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.
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.
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`.
150 """
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
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)
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 }
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()
229 def get_template(self, uri):
230 """Return a :class:`.Template` object corresponding to the given
231 ``uri``.
233 .. note:: The ``relativeto`` argument is not supported here at
234 the moment.
236 """
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
257 def adjust_uri(self, uri, relativeto):
258 """Adjust the given ``uri`` based on the given relative URI."""
260 key = (uri, relativeto)
261 if key in self._uri_cache:
262 return self._uri_cache[key]
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
274 def filename_to_uri(self, filename):
275 """Convert the given ``filename`` to a URI relative to
276 this :class:`.TemplateCollection`."""
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
285 def _relativeize(self, filename):
286 """Return the portion of a filename that is 'relative'
287 to the directories in this lookup.
289 """
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
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()
329 def _check(self, uri, template):
330 if template.filename is None:
331 return template
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
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``.
350 """
351 self._collection[uri] = Template(
352 text, lookup=self, uri=uri, **self.template_args
353 )
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.
360 """
361 self._collection[uri] = template