Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/mako/util.py: 24%
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/util.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
6from ast import parse
7import codecs
8import collections
9import operator
10import os
11import re
12import timeit
14from .compat import importlib_metadata_get
17def update_wrapper(decorated, fn):
18 decorated.__wrapped__ = fn
19 decorated.__name__ = fn.__name__
20 return decorated
23class PluginLoader:
24 def __init__(self, group):
25 self.group = group
26 self.impls = {}
28 def load(self, name):
29 if name in self.impls:
30 return self.impls[name]()
32 for impl in importlib_metadata_get(self.group):
33 if impl.name == name:
34 self.impls[name] = impl.load
35 return impl.load()
37 from mako import exceptions
39 raise exceptions.RuntimeException(
40 "Can't load plugin %s %s" % (self.group, name)
41 )
43 def register(self, name, modulepath, objname):
44 def load():
45 mod = __import__(modulepath)
46 for token in modulepath.split(".")[1:]:
47 mod = getattr(mod, token)
48 return getattr(mod, objname)
50 self.impls[name] = load
53def verify_directory(dir_):
54 """create and/or verify a filesystem directory."""
56 tries = 0
58 while not os.path.exists(dir_):
59 try:
60 tries += 1
61 os.makedirs(dir_, 0o755)
62 except:
63 if tries > 5:
64 raise
67def to_list(x, default=None):
68 if x is None:
69 return default
70 if not isinstance(x, (list, tuple)):
71 return [x]
72 else:
73 return x
76class memoized_property:
77 """A read-only @property that is only evaluated once."""
79 def __init__(self, fget, doc=None):
80 self.fget = fget
81 self.__doc__ = doc or fget.__doc__
82 self.__name__ = fget.__name__
84 def __get__(self, obj, cls):
85 if obj is None:
86 return self
87 obj.__dict__[self.__name__] = result = self.fget(obj)
88 return result
91class memoized_instancemethod:
92 """Decorate a method memoize its return value.
94 Best applied to no-arg methods: memoization is not sensitive to
95 argument values, and will always return the same value even when
96 called with different arguments.
98 """
100 def __init__(self, fget, doc=None):
101 self.fget = fget
102 self.__doc__ = doc or fget.__doc__
103 self.__name__ = fget.__name__
105 def __get__(self, obj, cls):
106 if obj is None:
107 return self
109 def oneshot(*args, **kw):
110 result = self.fget(obj, *args, **kw)
112 def memo(*a, **kw):
113 return result
115 memo.__name__ = self.__name__
116 memo.__doc__ = self.__doc__
117 obj.__dict__[self.__name__] = memo
118 return result
120 oneshot.__name__ = self.__name__
121 oneshot.__doc__ = self.__doc__
122 return oneshot
125class SetLikeDict(dict):
126 """a dictionary that has some setlike methods on it"""
128 def union(self, other):
129 """produce a 'union' of this dict and another (at the key level).
131 values in the second dict take precedence over that of the first"""
132 x = SetLikeDict(**self)
133 x.update(other)
134 return x
137class FastEncodingBuffer:
138 """a very rudimentary buffer that is faster than StringIO,
139 and supports unicode data."""
141 def __init__(self, encoding=None, errors="strict"):
142 self.data = collections.deque()
143 self.encoding = encoding
144 self.delim = ""
145 self.errors = errors
146 self.write = self.data.append
148 def truncate(self):
149 self.data = collections.deque()
150 self.write = self.data.append
152 def getvalue(self):
153 if self.encoding:
154 return self.delim.join(self.data).encode(
155 self.encoding, self.errors
156 )
157 else:
158 return self.delim.join(self.data)
161class LRUCache(dict):
162 """A dictionary-like object that stores a limited number of items,
163 discarding lesser used items periodically.
165 this is a rewrite of LRUCache from Myghty to use a periodic timestamp-based
166 paradigm so that synchronization is not really needed. the size management
167 is inexact.
168 """
170 class _Item:
171 def __init__(self, key, value):
172 self.key = key
173 self.value = value
174 self.timestamp = timeit.default_timer()
176 def __repr__(self):
177 return repr(self.value)
179 def __init__(self, capacity, threshold=0.5):
180 self.capacity = capacity
181 self.threshold = threshold
183 def __getitem__(self, key):
184 item = dict.__getitem__(self, key)
185 item.timestamp = timeit.default_timer()
186 return item.value
188 def values(self):
189 return [i.value for i in dict.values(self)]
191 def setdefault(self, key, value):
192 if key in self:
193 return self[key]
194 self[key] = value
195 return value
197 def __setitem__(self, key, value):
198 item = dict.get(self, key)
199 if item is None:
200 item = self._Item(key, value)
201 dict.__setitem__(self, key, item)
202 else:
203 item.value = value
204 self._manage_size()
206 def _manage_size(self):
207 while len(self) > self.capacity + self.capacity * self.threshold:
208 bytime = sorted(
209 dict.values(self),
210 key=operator.attrgetter("timestamp"),
211 reverse=True,
212 )
213 for item in bytime[self.capacity :]:
214 try:
215 del self[item.key]
216 except KeyError:
217 # if we couldn't find a key, most likely some other thread
218 # broke in on us. loop around and try again
219 break
222# Regexp to match python magic encoding line
223_PYTHON_MAGIC_COMMENT_re = re.compile(
224 r"[ \t\f]* \# .* coding[=:][ \t]*([-\w.]+)", re.VERBOSE
225)
228def parse_encoding(fp):
229 """Deduce the encoding of a Python source file (binary mode) from magic
230 comment.
232 It does this in the same way as the `Python interpreter`__
234 .. __: http://docs.python.org/ref/encodings.html
236 The ``fp`` argument should be a seekable file object in binary mode.
237 """
238 pos = fp.tell()
239 fp.seek(0)
240 try:
241 line1 = fp.readline()
242 has_bom = line1.startswith(codecs.BOM_UTF8)
243 if has_bom:
244 line1 = line1[len(codecs.BOM_UTF8) :]
246 m = _PYTHON_MAGIC_COMMENT_re.match(line1.decode("ascii", "ignore"))
247 if not m:
248 try:
249 parse(line1.decode("ascii", "ignore"))
250 except (ImportError, SyntaxError):
251 # Either it's a real syntax error, in which case the source
252 # is not valid python source, or line2 is a continuation of
253 # line1, in which case we don't want to scan line2 for a magic
254 # comment.
255 pass
256 else:
257 line2 = fp.readline()
258 m = _PYTHON_MAGIC_COMMENT_re.match(
259 line2.decode("ascii", "ignore")
260 )
262 if has_bom:
263 if m:
264 raise SyntaxError(
265 "python refuses to compile code with both a UTF8"
266 " byte-order-mark and a magic encoding comment"
267 )
268 return "utf_8"
269 elif m:
270 return m.group(1)
271 else:
272 return None
273 finally:
274 fp.seek(pos)
277def sorted_dict_repr(d):
278 """repr() a dictionary with the keys in order.
280 Used by the lexer unit test to compare parse trees based on strings.
282 """
283 keys = list(d.keys())
284 keys.sort()
285 return "{" + ", ".join("%r: %r" % (k, d[k]) for k in keys) + "}"
288def read_file(path, mode="rb"):
289 with open(path, mode) as fp:
290 return fp.read()
293def read_python_file(path):
294 fp = open(path, "rb")
295 try:
296 encoding = parse_encoding(fp)
297 data = fp.read()
298 if encoding:
299 data = data.decode(encoding)
300 return data
301 finally:
302 fp.close()