Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1import os 

2import posixpath 

3import sys 

4 

5from pyramid.asset import ( 

6 abspath_from_asset_spec, 

7 ) 

8 

9from pyramid.path import AssetResolver 

10 

11from pyramid.settings import asbool, aslist 

12 

13from mako.lookup import TemplateLookup 

14from mako.exceptions import ( 

15 TemplateLookupException, 

16 TopLevelLookupException, 

17 text_error_template, 

18) 

19 

20from .compat import ( 

21 is_nonstr_iter, 

22 reraise, 

23) 

24 

25class PkgResourceTemplateLookup(TemplateLookup): 

26 """TemplateLookup subclass that handles asset specification URIs""" 

27 def adjust_uri(self, uri, relativeto): 

28 """Called from within a Mako template, avoids adjusting the 

29 uri if it looks like an asset specification""" 

30 # Don't adjust asset spec names 

31 isabs = os.path.isabs(uri) 

32 if (not isabs) and (':' in uri): 

33 return uri 

34 if not(isabs) and ('$' in uri): 

35 return uri.replace('$', ':') 

36 if relativeto is not None: 

37 relativeto = relativeto.replace('$', ':') 

38 if not(':' in uri) and (':' in relativeto): 

39 if uri.startswith('/'): 

40 return uri 

41 pkg, relto = relativeto.split(':') 

42 _uri = posixpath.join(posixpath.dirname(relto), uri) 

43 return '{0}:{1}'.format(pkg, _uri) 

44 if not(':' in uri) and not(':' in relativeto): 

45 return posixpath.join(posixpath.dirname(relativeto), uri) 

46 return TemplateLookup.adjust_uri(self, uri, relativeto) 

47 

48 def get_template(self, uri): 

49 """Fetch a template from the cache, or check the filesystem 

50 for it 

51 

52 In addition to the basic filesystem lookup, this subclass will 

53 use pkg_resource to load a file using the asset 

54 specification syntax. 

55 

56 """ 

57 isabs = os.path.isabs(uri) 

58 if (not isabs) and (':' in uri): 

59 # Windows can't cope with colons in filenames, so we replace the 

60 # colon with a dollar sign in the filename mako uses to actually 

61 # store the generated python code in the mako module_directory or 

62 # in the temporary location of mako's modules 

63 adjusted = uri.replace(':', '$') 

64 try: 

65 if self.filesystem_checks: 

66 return self._check(adjusted, self._collection[adjusted]) 

67 else: 

68 return self._collection[adjusted] 

69 except KeyError: 

70 asset = AssetResolver().resolve(uri) 

71 if asset.exists(): 

72 srcfile = asset.abspath() 

73 return self._load(srcfile, adjusted) 

74 raise TopLevelLookupException( 

75 "Can not locate template for uri %r" % uri) 

76 try: 

77 return TemplateLookup.get_template(self, uri) 

78 except TemplateLookupException: 

79 if isabs: 

80 return self._load(uri, uri) 

81 else: 

82 raise 

83 

84class MakoRenderingException(Exception): 

85 def __init__(self, text): 

86 self.text = text 

87 

88 def __repr__(self): 

89 return self.text 

90 

91 __str__ = __repr__ 

92 

93class MakoLookupTemplateRenderer(object): 

94 """ Render a :term:`Mako` template using the ``template``. 

95 If a ``defname`` is defined, in the form of 

96 ``package:path/to/template#defname.mako``, a function named ``defname`` 

97 inside the ``template`` will then be rendered. 

98 """ 

99 

100 @property 

101 def template(self): 

102 spec = self.spec 

103 isabspath = os.path.isabs(spec) 

104 colon_in_name = ':' in spec 

105 isabsspec = colon_in_name and (not isabspath) 

106 isrelspec = (not isabsspec) and (not isabspath) 

107 

108 try: 

109 # try to find the template using default search paths 

110 template = self.lookup.get_template(spec) 

111 except TemplateLookupException: 

112 if isrelspec: 

113 # convert relative asset spec to absolute asset spec 

114 resolver = AssetResolver(self.package) 

115 asset = resolver.resolve(spec) 

116 spec = asset.absspec() 

117 template = self.lookup.get_template(spec) 

118 else: 

119 raise 

120 

121 return template 

122 

123 def __init__(self, lookup, spec, defname, package): 

124 self.lookup = lookup 

125 self.spec = spec 

126 self.defname = defname 

127 self.package = package 

128 

129 def __call__(self, value, system): 

130 # Update the system dictionary with the values from the user 

131 try: 

132 system.update(value) 

133 except (TypeError, ValueError): 

134 raise ValueError('renderer was passed non-dictionary as value') 

135 

136 # Check if 'context' in the dictionary 

137 context = system.pop('context', None) 

138 

139 # Rename 'context' to '_context' because Mako internally already has a 

140 # variable named 'context' 

141 if context is not None: 

142 system['_context'] = context 

143 

144 template = self.template 

145 if self.defname is not None: 

146 template = template.get_def(self.defname) 

147 try: 

148 result = template.render_unicode(**system) 

149 except: 

150 try: 

151 exc_info = sys.exc_info() 

152 errtext = text_error_template().render( 

153 error=exc_info[1], 

154 traceback=exc_info[2] 

155 ) 

156 reraise(MakoRenderingException(errtext), None, exc_info[2]) 

157 finally: 

158 del exc_info 

159 

160 return result 

161 

162class MakoRendererFactory(object): 

163 lookup = None 

164 renderer_factory = staticmethod(MakoLookupTemplateRenderer) # testing 

165 

166 def __call__(self, info): 

167 defname = None 

168 asset, ext = info.name.rsplit('.', 1) 

169 if '#' in asset: 

170 asset, defname = asset.rsplit('#', 1) 

171 

172 spec = '%s.%s' % (asset, ext) 

173 

174 return self.renderer_factory(self.lookup, spec, defname, info.package) 

175 

176def parse_options_from_settings(settings, settings_prefix, maybe_dotted): 

177 """ Parse options for use with Mako's TemplateLookup from settings.""" 

178 def sget(name, default=None): 

179 return settings.get(settings_prefix + name, default) 

180 

181 reload_templates = sget('reload_templates', None) 

182 if reload_templates is None: 

183 reload_templates = settings.get('pyramid.reload_templates', None) 

184 reload_templates = asbool(reload_templates) 

185 directories = sget('directories', []) 

186 module_directory = sget('module_directory', None) 

187 input_encoding = sget('input_encoding', 'utf-8') 

188 error_handler = sget('error_handler', None) 

189 default_filters = sget('default_filters', 'h') 

190 imports = sget('imports', None) 

191 future_imports = sget('future_imports', None) 

192 strict_undefined = asbool(sget('strict_undefined', False)) 

193 preprocessor = sget('preprocessor', None) 

194 preprocessor_wants_settings = asbool(sget('preprocessor_wants_settings', None)) 

195 if not is_nonstr_iter(directories): 

196 # Since we parse a value that comes from an .ini config, 

197 # we treat whitespaces and newline characters equally as list item separators. 

198 directories = aslist(directories, flatten=True) 

199 directories = [abspath_from_asset_spec(d) for d in directories] 

200 

201 if module_directory is not None: 

202 module_directory = abspath_from_asset_spec(module_directory) 

203 

204 if error_handler is not None: 

205 error_handler = maybe_dotted(error_handler) 

206 

207 if default_filters is not None: 

208 if not is_nonstr_iter(default_filters): 

209 default_filters = aslist(default_filters) 

210 

211 if imports is not None: 

212 if not is_nonstr_iter(imports): 

213 imports = aslist(imports, flatten=False) 

214 

215 if future_imports is not None: 

216 if not is_nonstr_iter(future_imports): 

217 future_imports = aslist(future_imports) 

218 

219 if preprocessor is not None: 

220 preprocessor_function = maybe_dotted(preprocessor) 

221 if preprocessor_wants_settings: 

222 def preprocessor_injector(template): 

223 return preprocessor_function(template, settings) 

224 preprocessor = preprocessor_injector 

225 else: 

226 preprocessor = preprocessor_function 

227 

228 return dict( 

229 directories=directories, 

230 module_directory=module_directory, 

231 input_encoding=input_encoding, 

232 error_handler=error_handler, 

233 default_filters=default_filters, 

234 imports=imports, 

235 future_imports=future_imports, 

236 filesystem_checks=reload_templates, 

237 strict_undefined=strict_undefined, 

238 preprocessor=preprocessor, 

239 ) 

240 

241def add_mako_renderer(config, extension, settings_prefix='mako.'): 

242 """ Register a Mako renderer for a template extension. 

243 

244 This function is available on the Pyramid configurator after 

245 including the package: 

246 

247 .. code-block:: python 

248 

249 config.add_mako_renderer('.html', settings_prefix='mako.') 

250 

251 The renderer will load its configuration from a prefix in the Pyramid 

252 settings dictionary. The default prefix is 'mako.'. 

253 """ 

254 renderer_factory = MakoRendererFactory() 

255 config.add_renderer(extension, renderer_factory) 

256 

257 def register(): 

258 registry = config.registry 

259 opts = parse_options_from_settings( 

260 registry.settings, settings_prefix, config.maybe_dotted) 

261 lookup = PkgResourceTemplateLookup(**opts) 

262 

263 renderer_factory.lookup = lookup 

264 

265 config.action(('mako-renderer', extension), register) 

266 

267def includeme(config): 

268 """ Set up standard configurator registrations. Use via: 

269 

270 .. code-block:: python 

271 

272 config = Configurator() 

273 config.include('pyramid_mako') 

274 

275 Once this function has been invoked, the ``.mako`` and ``.mak`` renderers 

276 are available for use in Pyramid. This can be overridden and more may be 

277 added via the ``config.add_mako_renderer`` directive. See 

278 :func:`~pyramid_mako.add_mako_renderer` documentation for more information. 

279 """ 

280 config.add_directive('add_mako_renderer', add_mako_renderer) 

281 

282 config.add_mako_renderer('.mako') 

283 config.add_mako_renderer('.mak')