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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

from __future__ import absolute_import 

 

import types 

import warnings 

import webob as wo 

from pkg_resources import iter_entry_points, DistributionNotFound 

from paste.deploy.converters import asbool, asint 

 

from . import core 

 

import logging 

import six 

log = logging.getLogger(__name__) 

 

 

class Config(object): 

    ''' 

    ToscaWidgets Configuration Set 

 

    `translator` 

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

 

    `default_engine` 

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

        parent will display correctly inside this template engine. Other 

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

        (default:string) 

 

    `inject_resoures` 

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

 

    `inject_resources_location` 

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

 

    `serve_resources` 

        Whether to serve static resources. (default: True) 

 

    `res_prefix` 

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

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

 

    `res_max_age` 

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

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

 

    `serve_controllers` 

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

 

    `controller_prefix` 

        The prefix under which controllers are served. This must start 

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

 

    `bufsize` 

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

 

    `params_as_vars` 

        Whether to present parameters as variables in widget templates. This 

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

 

    `debug` 

        Whether the app is running in development or production mode. 

        (default: True) 

 

    `validator_msgs` 

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

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

 

    `encoding` 

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

 

    `auto_reload_templates` 

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

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

        debug. (default: None) 

 

    `preferred_rendering_engines` 

        List of rendering engines in order of preference. 

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

 

    `strict_engine_selection` 

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

        preferred_rendering_engines, otherwise, it will try the default list if 

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

 

    `rendering_engine_lookup` 

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

        template engine. 

        (default: { 

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

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

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

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

        }) 

 

    `script_name` 

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

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

        (default: '') 

    ''' 

 

    translator = lambda self, s: s 

    default_engine = 'string' 

    inject_resources_location = 'head' 

    inject_resources = True 

    serve_resources = True 

    res_prefix = '/resources/' 

    res_max_age = 3600 

    serve_controllers = True 

    controller_prefix = '/controllers/' 

    bufsize = 4 * 1024 

    params_as_vars = False 

    debug = True 

    validator_msgs = {} 

    encoding = 'utf-8' 

    auto_reload_templates = None 

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

    strict_engine_selection = True 

    rendering_extension_lookup = { 

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

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

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

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

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

        'chameleon': ['pt'] 

    } 

    script_name = '' 

 

    def __init__(self, **kw): 

        for k, v in kw.items(): 

            setattr(self, k, v) 

 

        # Set boolean properties 

        boolean_props = ( 

            'inject_resources', 

            'serve_resources', 

            'serve_controllers', 

            'params_as_vars', 

            'strict_engine_selection', 

            'debug', 

        ) 

        for prop in boolean_props: 

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

 

        # Set integer properties 

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

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

 

        if self.auto_reload_templates is None: 

            self.auto_reload_templates = self.debug 

 

 

class TwMiddleware(object): 

    """ToscaWidgets middleware 

 

    This performs three tasks: 

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

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

       request-local storage. 

     * Proxy resource requests to ResourcesApp 

     * Inject resources 

    """ 

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

 

        # Here to avoid circular import 

        from . import resources 

        self._resources_module = resources 

 

        self.app = app 

        self.config = Config(**config) 

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

        self.controllers = controllers or ControllersApp() 

 

        rl = core.request_local() 

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

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

            self.controllers.register(widget, path) 

 

        rl['queued_controllers'] = [] 

 

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

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

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

 

        rl['queued_resources'] = [] 

 

        # Future resource registrations should know to just plug themselves into 

        # me right away (instead of being queued). 

        rl['middleware'] = self 

 

    def __call__(self, environ, start_response): 

        rl = core.request_local() 

        rl.clear() 

        rl['middleware'] = self 

        req = wo.Request(environ) 

 

        path = req.path_info 

        if self.config.serve_resources and \ 

           path.startswith(self.config.res_prefix): 

            return self.resources(environ, start_response) 

        else: 

            if self.config.serve_controllers and \ 

               path.startswith(self.config.controller_prefix): 

                resp = self.controllers(req) 

            else: 

                if self.app: 

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

                else: 

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

 

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

 

            should_inject = ( 

                self.config.inject_resources 

                and 'html' in ct 

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

            ) 

            if should_inject: 

                if resp.charset: 

                    body = self._resources_module.inject_resources( 

                        resp.body.decode(resp.charset), 

                    ).encode(resp.charset) 

                else: 

                    body = self._resources_module.inject_resources( 

                        resp.body, 

                    ) 

 

                if isinstance(body, six.text_type): 

                    resp.unicode_body = body 

                else: 

                    resp.body = body 

        core.request_local().clear() 

        return resp(environ, start_response) 

 

 

class ControllersApp(object): 

    """ 

    """ 

 

    def __init__(self): 

        self._widgets = {} 

 

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

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

        if path is None: 

            path = widget.id 

        self._widgets[path] = widget 

 

    def controller_path(self, target_widget): 

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

        it is not registered. 

        """ 

 

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

            if target_widget == widget: 

                return path 

 

        return None 

 

    def __call__(self, req): 

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

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

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

        if pre and path[0] != pre: 

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

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

        widget_name = path or 'index' 

        try: 

            widget = self._widgets[widget_name] 

        except KeyError: 

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

        else: 

            resp = widget.request(req) 

        return resp 

 

 

def register_resource(modname, filename, whole_dir): 

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

 

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

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

    injection. 

 

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

    the ResourcesApp. 

 

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

    request_local dict.  When the middleware is later initialized, those 

    waiting registrations are processed. 

    """ 

 

    rl = core.request_local() 

    mw = rl.get('middleware') 

    if mw: 

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

    else: 

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

            (modname, filename, whole_dir) 

        ] 

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

                  (modname, filename, whole_dir)) 

 

 

def register_controller(widget, path): 

    """ API function for registering widget controllers. 

 

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

    ControllersApp. 

 

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

    request_local dict.  When the middleware is later initialized, those 

    waiting registrations are processed. 

    """ 

 

    rl = core.request_local() 

    mw = rl.get('middleware') 

    if mw: 

        mw.controllers.register(widget, path) 

    else: 

        rl['queued_controllers'] = \ 

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

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

                  (path, widget)) 

 

 

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

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

    config.update(kw) 

    app = TwMiddleware(app, **config) 

    return app 

 

 

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

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