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

1from __future__ import absolute_import 

2 

3import types 

4import warnings 

5import webob as wo 

6from pkg_resources import iter_entry_points, DistributionNotFound 

7from paste.deploy.converters import asbool, asint 

8 

9from . import core 

10 

11import logging 

12import six 

13log = logging.getLogger(__name__) 

14 

15 

16class Config(object): 

17 ''' 

18 ToscaWidgets Configuration Set 

19 

20 `translator` 

21 The translator function to use. (default: no-op) 

22 

23 `default_engine` 

24 The main template engine in use by the application. Widgets with no 

25 parent will display correctly inside this template engine. Other 

26 engines may require passing displays_on to :meth:`Widget.display`. 

27 (default:string) 

28 

29 `inject_resoures` 

30 Whether to inject resource links in output pages. (default: True) 

31 

32 `inject_resources_location` 

33 A location where the resources should be injected. (default: head) 

34 

35 `serve_resources` 

36 Whether to serve static resources. (default: True) 

37 

38 `res_prefix` 

39 The prefix under which static resources are served. This must start 

40 and end with a slash. (default: /resources/) 

41 

42 `res_max_age` 

43 The maximum time a cache can hold the resource. This is used to 

44 generate a Cache-control header. (default: 3600) 

45 

46 `serve_controllers` 

47 Whether to serve controller methods on widgets. (default: True) 

48 

49 `controller_prefix` 

50 The prefix under which controllers are served. This must start 

51 and end with a slash. (default: /controllers/) 

52 

53 `bufsize` 

54 Buffer size used by static resource server. (default: 4096) 

55 

56 `params_as_vars` 

57 Whether to present parameters as variables in widget templates. This 

58 is the behaviour from ToscaWidgets 0.9. (default: False) 

59 

60 `debug` 

61 Whether the app is running in development or production mode. 

62 (default: True) 

63 

64 `validator_msgs` 

65 A dictionary that maps validation message names to messages. This lets 

66 you override validation messages on a global basis. (default: {}) 

67 

68 `encoding` 

69 The encoding to decode when performing validation (default: utf-8) 

70 

71 `auto_reload_templates` 

72 Whether to automatically reload changed templates. Set this to False in 

73 production for efficiency. If this is None, it takes the same value as 

74 debug. (default: None) 

75 

76 `preferred_rendering_engines` 

77 List of rendering engines in order of preference. 

78 (default: ['mako','genshi','jinja','kajiki']) 

79 

80 `strict_engine_selection` 

81 If set to true, TW2 will only select rendering engines from within your 

82 preferred_rendering_engines, otherwise, it will try the default list if 

83 it does not find a template within your preferred list. (default: True) 

84 

85 `rendering_engine_lookup` 

86 A dictionary of file extensions you expect to use for each type of 

87 template engine. Default:: 

88  

89 { 

90 'mako':['mak', 'mako'], 

91 'genshi':['genshi', 'html'], 

92 'jinja':['jinja', 'html'], 

93 'kajiki':['kajiki', 'html'], 

94 } 

95 

96 `script_name` 

97 A name to prepend to the url for all resource links (different from 

98 res_prefix, as it may be shared across and entire wsgi app. 

99 (default: '') 

100  

101 ''' 

102 

103 translator = lambda self, s: s 

104 default_engine = 'string' 

105 inject_resources_location = 'head' 

106 inject_resources = True 

107 serve_resources = True 

108 res_prefix = '/resources/' 

109 res_max_age = 3600 

110 serve_controllers = True 

111 controller_prefix = '/controllers/' 

112 bufsize = 4 * 1024 

113 params_as_vars = False 

114 debug = True 

115 validator_msgs = {} 

116 encoding = 'utf-8' 

117 auto_reload_templates = None 

118 preferred_rendering_engines = ['mako', 'genshi', 'jinja', 'kajiki'] 

119 strict_engine_selection = True 

120 rendering_extension_lookup = { 

121 'mako': ['mak', 'mako'], 

122 'genshi': ['genshi', 'html'], 

123 'genshi_abs': ['genshi', 'html'], # just for backwards compatibility with tw2 2.0.0 

124 'jinja':['jinja', 'html'], 

125 'kajiki':['kajiki', 'html'], 

126 'chameleon': ['pt'] 

127 } 

128 script_name = '' 

129 

130 def __init__(self, **kw): 

131 for k, v in kw.items(): 

132 setattr(self, k, v) 

133 

134 # Set boolean properties 

135 boolean_props = ( 

136 'inject_resources', 

137 'serve_resources', 

138 'serve_controllers', 

139 'params_as_vars', 

140 'strict_engine_selection', 

141 'debug', 

142 ) 

143 for prop in boolean_props: 

144 setattr(self, prop, asbool(getattr(self, prop))) 

145 

146 # Set integer properties 

147 for prop in ('res_max_age', 'bufsize'): 

148 setattr(self, prop, asint(getattr(self, prop))) 

149 

150 if self.auto_reload_templates is None: 

151 self.auto_reload_templates = self.debug 

152 

153 

154class TwMiddleware(object): 

155 """ToscaWidgets middleware 

156 

157 This performs three tasks: 

158 * Clear request-local storage before and after each request. At the start 

159 of a request, a reference to the middleware instance is stored in 

160 request-local storage. 

161 * Proxy resource requests to ResourcesApp 

162 * Inject resources 

163 """ 

164 def __init__(self, app, controllers=None, **config): 

165 

166 # Here to avoid circular import 

167 from . import resources 

168 self._resources_module = resources 

169 

170 self.app = app 

171 self.config = Config(**config) 

172 self.resources = resources.ResourcesApp(self.config) 

173 self.controllers = controllers or ControllersApp() 

174 

175 rl = core.request_local() 

176 # Load up controllers that wanted to be registered before we were ready 

177 for widget, path in rl.get('queued_controllers', []): 

178 self.controllers.register(widget, path) 

179 

180 rl['queued_controllers'] = [] 

181 

182 # Load up resources that wanted to be registered before we were ready 

183 for modname, filename, whole_dir in rl.get('queued_resources', []): 

184 self.resources.register(modname, filename, whole_dir) 

185 

186 rl['queued_resources'] = [] 

187 

188 # Future resource registrations should know to just plug themselves into 

189 # me right away (instead of being queued). 

190 rl['middleware'] = self 

191 

192 def __call__(self, environ, start_response): 

193 rl = core.request_local() 

194 rl.clear() 

195 rl['middleware'] = self 

196 req = wo.Request(environ) 

197 

198 path = req.path_info 

199 if self.config.serve_resources and \ 

200 path.startswith(self.config.res_prefix): 

201 return self.resources(environ, start_response) 

202 else: 

203 if self.config.serve_controllers and \ 

204 path.startswith(self.config.controller_prefix): 

205 resp = self.controllers(req) 

206 else: 

207 if self.app: 

208 resp = req.get_response(self.app, catch_exc_info=True) 

209 else: 

210 resp = wo.Response(status="404 Not Found") 

211 

212 ct = resp.headers.get('Content-Type', 'text/plain').lower() 

213 

214 should_inject = ( 

215 self.config.inject_resources 

216 and 'html' in ct 

217 and not isinstance(resp.app_iter, types.GeneratorType) 

218 ) 

219 if should_inject: 

220 if resp.charset: 

221 body = self._resources_module.inject_resources( 

222 resp.body.decode(resp.charset), 

223 ).encode(resp.charset) 

224 else: 

225 body = self._resources_module.inject_resources( 

226 resp.body, 

227 ) 

228 

229 if isinstance(body, six.text_type): 

230 resp.unicode_body = body 

231 else: 

232 resp.body = body 

233 core.request_local().clear() 

234 return resp(environ, start_response) 

235 

236 

237class ControllersApp(object): 

238 """ 

239 """ 

240 

241 def __init__(self): 

242 self._widgets = {} 

243 

244 def register(self, widget, path=None): 

245 log.info("Registered controller %r->%r" % (path, widget)) 

246 if path is None: 

247 path = widget.id 

248 self._widgets[path] = widget 

249 

250 def controller_path(self, target_widget): 

251 """ Return the path against which a given widget is mounted or None if 

252 it is not registered. 

253 """ 

254 

255 for path, widget in six.iteritems(self._widgets): 

256 if target_widget == widget: 

257 return path 

258 

259 return None 

260 

261 def __call__(self, req): 

262 config = rl = core.request_local()['middleware'].config 

263 path = req.path_info.split('/')[1:] 

264 pre = config.controller_prefix.strip('/') 

265 if pre and path[0] != pre: 

266 return wo.Response(status="404 Not Found") 

267 path = path[1] if pre else path[0] 

268 widget_name = path or 'index' 

269 try: 

270 widget = self._widgets[widget_name] 

271 except KeyError: 

272 resp = wo.Response(status="404 Not Found") 

273 else: 

274 resp = widget.request(req) 

275 return resp 

276 

277 

278def register_resource(modname, filename, whole_dir): 

279 """ API function for registering resources *for serving*. 

280 

281 This should not be confused with resource registration for *injection*. 

282 A resource must be registered for serving for it to be also registered for 

283 injection. 

284 

285 If the middleware is available, the resource is directly registered with 

286 the ResourcesApp. 

287 

288 If the middleware is not available, the resource is stored in the 

289 request_local dict. When the middleware is later initialized, those 

290 waiting registrations are processed. 

291 """ 

292 

293 rl = core.request_local() 

294 mw = rl.get('middleware') 

295 if mw: 

296 mw.resources.register(modname, filename, whole_dir) 

297 else: 

298 rl['queued_resources'] = rl.get('queued_resources', []) + [ 

299 (modname, filename, whole_dir) 

300 ] 

301 log.debug("No middleware in place. Queued %r->%r(%r) registration." % 

302 (modname, filename, whole_dir)) 

303 

304 

305def register_controller(widget, path): 

306 """ API function for registering widget controllers. 

307 

308 If the middleware is available, the widget is directly registered with the 

309 ControllersApp. 

310 

311 If the middleware is not available, the widget is stored in the 

312 request_local dict. When the middleware is later initialized, those 

313 waiting registrations are processed. 

314 """ 

315 

316 rl = core.request_local() 

317 mw = rl.get('middleware') 

318 if mw: 

319 mw.controllers.register(widget, path) 

320 else: 

321 rl['queued_controllers'] = \ 

322 rl.get('queued_controllers', []) + [(widget, path)] 

323 log.debug("No middleware in place. Queued %r->%r registration." % 

324 (path, widget)) 

325 

326 

327def make_middleware(app=None, config=None, **kw): 

328 config = (config or {}).copy() 

329 config.update(kw) 

330 app = TwMiddleware(app, **config) 

331 return app 

332 

333 

334def make_app(config=None, **kw): 

335 return make_middleware(app=None, config=config, **kw)