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 collections import deque 

2import json 

3 

4from zope.interface import implementer 

5from zope.interface.interface import InterfaceClass 

6 

7from webob import BaseRequest 

8 

9from pyramid.interfaces import ( 

10 IRequest, 

11 IRequestExtensions, 

12 IResponse, 

13 ISessionFactory, 

14) 

15 

16from pyramid.compat import text_, bytes_, native_, iteritems_ 

17 

18from pyramid.decorator import reify 

19from pyramid.i18n import LocalizerRequestMixin 

20from pyramid.response import Response, _get_response_factory 

21from pyramid.security import AuthenticationAPIMixin, AuthorizationAPIMixin 

22from pyramid.url import URLMethodsMixin 

23from pyramid.util import InstancePropertyHelper, InstancePropertyMixin 

24from pyramid.view import ViewMethodsMixin 

25 

26 

27class TemplateContext(object): 

28 pass 

29 

30 

31class CallbackMethodsMixin(object): 

32 @reify 

33 def finished_callbacks(self): 

34 return deque() 

35 

36 @reify 

37 def response_callbacks(self): 

38 return deque() 

39 

40 def add_response_callback(self, callback): 

41 """ 

42 Add a callback to the set of callbacks to be called by the 

43 :term:`router` at a point after a :term:`response` object is 

44 successfully created. :app:`Pyramid` does not have a 

45 global response object: this functionality allows an 

46 application to register an action to be performed against the 

47 response once one is created. 

48 

49 A 'callback' is a callable which accepts two positional 

50 parameters: ``request`` and ``response``. For example: 

51 

52 .. code-block:: python 

53 :linenos: 

54 

55 def cache_callback(request, response): 

56 'Set the cache_control max_age for the response' 

57 response.cache_control.max_age = 360 

58 request.add_response_callback(cache_callback) 

59 

60 Response callbacks are called in the order they're added 

61 (first-to-most-recently-added). No response callback is 

62 called if an exception happens in application code, or if the 

63 response object returned by :term:`view` code is invalid. 

64 

65 All response callbacks are called *after* the tweens and 

66 *before* the :class:`pyramid.events.NewResponse` event is sent. 

67 

68 Errors raised by callbacks are not handled specially. They 

69 will be propagated to the caller of the :app:`Pyramid` 

70 router application. 

71 

72 .. seealso:: 

73 

74 See also :ref:`using_response_callbacks`. 

75 """ 

76 

77 self.response_callbacks.append(callback) 

78 

79 def _process_response_callbacks(self, response): 

80 callbacks = self.response_callbacks 

81 while callbacks: 

82 callback = callbacks.popleft() 

83 callback(self, response) 

84 

85 def add_finished_callback(self, callback): 

86 """ 

87 Add a callback to the set of callbacks to be called 

88 unconditionally by the :term:`router` at the very end of 

89 request processing. 

90 

91 ``callback`` is a callable which accepts a single positional 

92 parameter: ``request``. For example: 

93 

94 .. code-block:: python 

95 :linenos: 

96 

97 import transaction 

98 

99 def commit_callback(request): 

100 '''commit or abort the transaction associated with request''' 

101 if request.exception is not None: 

102 transaction.abort() 

103 else: 

104 transaction.commit() 

105 request.add_finished_callback(commit_callback) 

106 

107 Finished callbacks are called in the order they're added ( 

108 first- to most-recently- added). Finished callbacks (unlike 

109 response callbacks) are *always* called, even if an exception 

110 happens in application code that prevents a response from 

111 being generated. 

112 

113 The set of finished callbacks associated with a request are 

114 called *very late* in the processing of that request; they are 

115 essentially the last thing called by the :term:`router`. They 

116 are called after response processing has already occurred in a 

117 top-level ``finally:`` block within the router request 

118 processing code. As a result, mutations performed to the 

119 ``request`` provided to a finished callback will have no 

120 meaningful effect, because response processing will have 

121 already occurred, and the request's scope will expire almost 

122 immediately after all finished callbacks have been processed. 

123 

124 Errors raised by finished callbacks are not handled specially. 

125 They will be propagated to the caller of the :app:`Pyramid` 

126 router application. 

127 

128 .. seealso:: 

129 

130 See also :ref:`using_finished_callbacks`. 

131 """ 

132 self.finished_callbacks.append(callback) 

133 

134 def _process_finished_callbacks(self): 

135 callbacks = self.finished_callbacks 

136 while callbacks: 

137 callback = callbacks.popleft() 

138 callback(self) 

139 

140 

141@implementer(IRequest) 

142class Request( 

143 BaseRequest, 

144 URLMethodsMixin, 

145 CallbackMethodsMixin, 

146 InstancePropertyMixin, 

147 LocalizerRequestMixin, 

148 AuthenticationAPIMixin, 

149 AuthorizationAPIMixin, 

150 ViewMethodsMixin, 

151): 

152 """ 

153 A subclass of the :term:`WebOb` Request class. An instance of 

154 this class is created by the :term:`router` and is provided to a 

155 view callable (and to other subsystems) as the ``request`` 

156 argument. 

157 

158 The documentation below (save for the ``add_response_callback`` and 

159 ``add_finished_callback`` methods, which are defined in this subclass 

160 itself, and the attributes ``context``, ``registry``, ``root``, 

161 ``subpath``, ``traversed``, ``view_name``, ``virtual_root`` , and 

162 ``virtual_root_path``, each of which is added to the request by the 

163 :term:`router` at request ingress time) are autogenerated from the WebOb 

164 source code used when this documentation was generated. 

165 

166 Due to technical constraints, we can't yet display the WebOb 

167 version number from which this documentation is autogenerated, but 

168 it will be the 'prevailing WebOb version' at the time of the 

169 release of this :app:`Pyramid` version. See 

170 https://webob.org/ for further information. 

171 """ 

172 

173 exception = None 

174 exc_info = None 

175 matchdict = None 

176 matched_route = None 

177 request_iface = IRequest 

178 

179 ResponseClass = Response 

180 

181 @reify 

182 def tmpl_context(self): 

183 # docs-deprecated template context for Pylons-like apps; do not 

184 # remove. 

185 return TemplateContext() 

186 

187 @reify 

188 def session(self): 

189 """ Obtain the :term:`session` object associated with this 

190 request. If a :term:`session factory` has not been registered 

191 during application configuration, a 

192 :class:`pyramid.exceptions.ConfigurationError` will be raised""" 

193 factory = self.registry.queryUtility(ISessionFactory) 

194 if factory is None: 

195 raise AttributeError( 

196 'No session factory registered ' 

197 '(see the Sessions chapter of the Pyramid documentation)' 

198 ) 

199 return factory(self) 

200 

201 @reify 

202 def response(self): 

203 """This attribute is actually a "reified" property which returns an 

204 instance of the :class:`pyramid.response.Response`. class. The 

205 response object returned does not exist until this attribute is 

206 accessed. Subsequent accesses will return the same Response object. 

207 

208 The ``request.response`` API is used by renderers. A render obtains 

209 the response object it will return from a view that uses that renderer 

210 by accessing ``request.response``. Therefore, it's possible to use the 

211 ``request.response`` API to set up a response object with "the 

212 right" attributes (e.g. by calling ``request.response.set_cookie()``) 

213 within a view that uses a renderer. Mutations to this response object 

214 will be preserved in the response sent to the client.""" 

215 response_factory = _get_response_factory(self.registry) 

216 return response_factory(self) 

217 

218 def is_response(self, ob): 

219 """ Return ``True`` if the object passed as ``ob`` is a valid 

220 response object, ``False`` otherwise.""" 

221 if ob.__class__ is Response: 

222 return True 

223 registry = self.registry 

224 adapted = registry.queryAdapterOrSelf(ob, IResponse) 

225 if adapted is None: 

226 return False 

227 return adapted is ob 

228 

229 @property 

230 def json_body(self): 

231 return json.loads(text_(self.body, self.charset)) 

232 

233 

234def route_request_iface(name, bases=()): 

235 # zope.interface treats the __name__ as the __doc__ and changes __name__ 

236 # to None for interfaces that contain spaces if you do not pass a 

237 # nonempty __doc__ (insane); see 

238 # zope.interface.interface.Element.__init__ and 

239 # https://github.com/Pylons/pyramid/issues/232; as a result, always pass 

240 # __doc__ to the InterfaceClass constructor. 

241 iface = InterfaceClass( 

242 '%s_IRequest' % name, 

243 bases=bases, 

244 __doc__="route_request_iface-generated interface", 

245 ) 

246 # for exception view lookups 

247 iface.combined = InterfaceClass( 

248 '%s_combined_IRequest' % name, 

249 bases=(iface, IRequest), 

250 __doc__='route_request_iface-generated combined interface', 

251 ) 

252 return iface 

253 

254 

255def add_global_response_headers(request, headerlist): 

256 def add_headers(request, response): 

257 for k, v in headerlist: 

258 response.headerlist.append((k, v)) 

259 

260 request.add_response_callback(add_headers) 

261 

262 

263def call_app_with_subpath_as_path_info(request, app): 

264 # Copy the request. Use the source request's subpath (if it exists) as 

265 # the new request's PATH_INFO. Set the request copy's SCRIPT_NAME to the 

266 # prefix before the subpath. Call the application with the new request 

267 # and return a response. 

268 # 

269 # Postconditions: 

270 # - SCRIPT_NAME and PATH_INFO are empty or start with / 

271 # - At least one of SCRIPT_NAME or PATH_INFO are set. 

272 # - SCRIPT_NAME is not '/' (it should be '', and PATH_INFO should 

273 # be '/'). 

274 

275 environ = request.environ 

276 script_name = environ.get('SCRIPT_NAME', '') 

277 path_info = environ.get('PATH_INFO', '/') 

278 subpath = list(getattr(request, 'subpath', ())) 

279 

280 new_script_name = '' 

281 

282 # compute new_path_info 

283 new_path_info = '/' + '/'.join( 

284 [native_(x.encode('utf-8'), 'latin-1') for x in subpath] 

285 ) 

286 

287 if new_path_info != '/': # don't want a sole double-slash 

288 if path_info != '/': # if orig path_info is '/', we're already done 

289 if path_info.endswith('/'): 

290 # readd trailing slash stripped by subpath (traversal) 

291 # conversion 

292 new_path_info += '/' 

293 

294 # compute new_script_name 

295 workback = (script_name + path_info).split('/') 

296 

297 tmp = [] 

298 while workback: 

299 if tmp == subpath: 

300 break 

301 el = workback.pop() 

302 if el: 

303 tmp.insert(0, text_(bytes_(el, 'latin-1'), 'utf-8')) 

304 

305 # strip all trailing slashes from workback to avoid appending undue slashes 

306 # to end of script_name 

307 while workback and (workback[-1] == ''): 

308 workback = workback[:-1] 

309 

310 new_script_name = '/'.join(workback) 

311 

312 new_request = request.copy() 

313 new_request.environ['SCRIPT_NAME'] = new_script_name 

314 new_request.environ['PATH_INFO'] = new_path_info 

315 

316 return new_request.get_response(app) 

317 

318 

319def apply_request_extensions(request, extensions=None): 

320 """Apply request extensions (methods and properties) to an instance of 

321 :class:`pyramid.interfaces.IRequest`. This method is dependent on the 

322 ``request`` containing a properly initialized registry. 

323 

324 After invoking this method, the ``request`` should have the methods 

325 and properties that were defined using 

326 :meth:`pyramid.config.Configurator.add_request_method`. 

327 """ 

328 if extensions is None: 

329 extensions = request.registry.queryUtility(IRequestExtensions) 

330 if extensions is not None: 

331 for name, fn in iteritems_(extensions.methods): 

332 method = fn.__get__(request, request.__class__) 

333 setattr(request, name, method) 

334 

335 InstancePropertyHelper.apply_properties( 

336 request, extensions.descriptors 

337 )