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 itertools 

2import sys 

3import inspect 

4 

5import venusian 

6 

7from zope.interface import providedBy 

8 

9from pyramid.interfaces import ( 

10 IRoutesMapper, 

11 IMultiView, 

12 ISecuredView, 

13 IView, 

14 IViewClassifier, 

15 IRequest, 

16 IExceptionViewClassifier, 

17) 

18 

19from pyramid.compat import decode_path_info 

20from pyramid.compat import reraise as reraise_ 

21 

22from pyramid.exceptions import ConfigurationError, PredicateMismatch 

23 

24from pyramid.httpexceptions import ( 

25 HTTPNotFound, 

26 HTTPTemporaryRedirect, 

27 default_exceptionresponse_view, 

28) 

29 

30from pyramid.threadlocal import get_current_registry, manager 

31 

32from pyramid.util import hide_attrs 

33 

34_marker = object() 

35 

36 

37def render_view_to_response(context, request, name='', secure=True): 

38 """ Call the :term:`view callable` configured with a :term:`view 

39 configuration` that matches the :term:`view name` ``name`` 

40 registered against the specified ``context`` and ``request`` and 

41 return a :term:`response` object. This function will return 

42 ``None`` if a corresponding :term:`view callable` cannot be found 

43 (when no :term:`view configuration` matches the combination of 

44 ``name`` / ``context`` / and ``request``). 

45 

46 If `secure`` is ``True``, and the :term:`view callable` found is 

47 protected by a permission, the permission will be checked before calling 

48 the view function. If the permission check disallows view execution 

49 (based on the current :term:`authorization policy`), a 

50 :exc:`pyramid.httpexceptions.HTTPForbidden` exception will be raised. 

51 The exception's ``args`` attribute explains why the view access was 

52 disallowed. 

53 

54 If ``secure`` is ``False``, no permission checking is done.""" 

55 

56 registry = getattr(request, 'registry', None) 

57 if registry is None: 

58 registry = get_current_registry() 

59 

60 context_iface = providedBy(context) 

61 # We explicitly pass in the interfaces provided by the request as 

62 # request_iface to _call_view; we don't want _call_view to use 

63 # request.request_iface, because render_view_to_response and friends are 

64 # pretty much limited to finding views that are not views associated with 

65 # routes, and the only thing request.request_iface is used for is to find 

66 # route-based views. The render_view_to_response API is (and always has 

67 # been) a stepchild API reserved for use of those who actually use 

68 # traversal. Doing this fixes an infinite recursion bug introduced in 

69 # Pyramid 1.6a1, and causes the render_view* APIs to behave as they did in 

70 # 1.5 and previous. We should probably provide some sort of different API 

71 # that would allow people to find views for routes. See 

72 # https://github.com/Pylons/pyramid/issues/1643 for more info. 

73 request_iface = providedBy(request) 

74 

75 response = _call_view( 

76 registry, 

77 request, 

78 context, 

79 context_iface, 

80 name, 

81 secure=secure, 

82 request_iface=request_iface, 

83 ) 

84 

85 return response # NB: might be None 

86 

87 

88def render_view_to_iterable(context, request, name='', secure=True): 

89 """ Call the :term:`view callable` configured with a :term:`view 

90 configuration` that matches the :term:`view name` ``name`` 

91 registered against the specified ``context`` and ``request`` and 

92 return an iterable object which represents the body of a response. 

93 This function will return ``None`` if a corresponding :term:`view 

94 callable` cannot be found (when no :term:`view configuration` 

95 matches the combination of ``name`` / ``context`` / and 

96 ``request``). Additionally, this function will raise a 

97 :exc:`ValueError` if a view function is found and called but the 

98 view function's result does not have an ``app_iter`` attribute. 

99 

100 You can usually get the bytestring representation of the return value of 

101 this function by calling ``b''.join(iterable)``, or just use 

102 :func:`pyramid.view.render_view` instead. 

103 

104 If ``secure`` is ``True``, and the view is protected by a permission, the 

105 permission will be checked before the view function is invoked. If the 

106 permission check disallows view execution (based on the current 

107 :term:`authentication policy`), a 

108 :exc:`pyramid.httpexceptions.HTTPForbidden` exception will be raised; its 

109 ``args`` attribute explains why the view access was disallowed. 

110 

111 If ``secure`` is ``False``, no permission checking is 

112 done.""" 

113 response = render_view_to_response(context, request, name, secure) 

114 if response is None: 

115 return None 

116 return response.app_iter 

117 

118 

119def render_view(context, request, name='', secure=True): 

120 """ Call the :term:`view callable` configured with a :term:`view 

121 configuration` that matches the :term:`view name` ``name`` 

122 registered against the specified ``context`` and ``request`` 

123 and unwind the view response's ``app_iter`` (see 

124 :ref:`the_response`) into a single bytestring. This function will 

125 return ``None`` if a corresponding :term:`view callable` cannot be 

126 found (when no :term:`view configuration` matches the combination 

127 of ``name`` / ``context`` / and ``request``). Additionally, this 

128 function will raise a :exc:`ValueError` if a view function is 

129 found and called but the view function's result does not have an 

130 ``app_iter`` attribute. This function will return ``None`` if a 

131 corresponding view cannot be found. 

132 

133 If ``secure`` is ``True``, and the view is protected by a permission, the 

134 permission will be checked before the view is invoked. If the permission 

135 check disallows view execution (based on the current :term:`authorization 

136 policy`), a :exc:`pyramid.httpexceptions.HTTPForbidden` exception will be 

137 raised; its ``args`` attribute explains why the view access was 

138 disallowed. 

139 

140 If ``secure`` is ``False``, no permission checking is done.""" 

141 iterable = render_view_to_iterable(context, request, name, secure) 

142 if iterable is None: 

143 return None 

144 return b''.join(iterable) 

145 

146 

147class view_config(object): 

148 """ A function, class or method :term:`decorator` which allows a 

149 developer to create view registrations nearer to a :term:`view 

150 callable` definition than use :term:`imperative 

151 configuration` to do the same. 

152 

153 For example, this code in a module ``views.py``:: 

154 

155 from resources import MyResource 

156 

157 @view_config(name='my_view', context=MyResource, permission='read', 

158 route_name='site1') 

159 def my_view(context, request): 

160 return 'OK' 

161 

162 Might replace the following call to the 

163 :meth:`pyramid.config.Configurator.add_view` method:: 

164 

165 import views 

166 from resources import MyResource 

167 config.add_view(views.my_view, context=MyResource, name='my_view', 

168 permission='read', route_name='site1') 

169 

170 .. note: :class:`pyramid.view.view_config` is also importable, for 

171 backwards compatibility purposes, as the name 

172 :class:`pyramid.view.bfg_view`. 

173 

174 :class:`pyramid.view.view_config` supports the following keyword 

175 arguments: ``context``, ``exception``, ``permission``, ``name``, 

176 ``request_type``, ``route_name``, ``request_method``, ``request_param``, 

177 ``containment``, ``xhr``, ``accept``, ``header``, ``path_info``, 

178 ``custom_predicates``, ``decorator``, ``mapper``, ``http_cache``, 

179 ``require_csrf``, ``match_param``, ``check_csrf``, ``physical_path``, and 

180 ``view_options``. 

181 

182 The meanings of these arguments are the same as the arguments passed to 

183 :meth:`pyramid.config.Configurator.add_view`. If any argument is left 

184 out, its default will be the equivalent ``add_view`` default. 

185 

186 Two additional keyword arguments which will be passed to the 

187 :term:`venusian` ``attach`` function are ``_depth`` and ``_category``. 

188 

189 ``_depth`` is provided for people who wish to reuse this class from another 

190 decorator. The default value is ``0`` and should be specified relative to 

191 the ``view_config`` invocation. It will be passed in to the 

192 :term:`venusian` ``attach`` function as the depth of the callstack when 

193 Venusian checks if the decorator is being used in a class or module 

194 context. It's not often used, but it can be useful in this circumstance. 

195 

196 ``_category`` sets the decorator category name. It can be useful in 

197 combination with the ``category`` argument of ``scan`` to control which 

198 views should be processed. 

199 

200 See the :py:func:`venusian.attach` function in Venusian for more 

201 information about the ``_depth`` and ``_category`` arguments. 

202 

203 .. seealso:: 

204 

205 See also :ref:`mapping_views_using_a_decorator_section` for 

206 details about using :class:`pyramid.view.view_config`. 

207 

208 .. warning:: 

209 

210 ``view_config`` will work ONLY on module top level members 

211 because of the limitation of ``venusian.Scanner.scan``. 

212 

213 """ 

214 

215 venusian = venusian # for testing injection 

216 

217 def __init__(self, **settings): 

218 if 'for_' in settings: 

219 if settings.get('context') is None: 

220 settings['context'] = settings['for_'] 

221 self.__dict__.update(settings) 

222 self._get_info() 

223 

224 def _get_info(self): 

225 depth = self.__dict__.get('_depth', 0) 

226 frame = sys._getframe(depth + 2) 

227 frameinfo = inspect.getframeinfo(frame) 

228 sourceline = frameinfo[3][0].strip() 

229 self._info = frameinfo[0], frameinfo[1], frameinfo[2], sourceline 

230 

231 def __call__(self, wrapped): 

232 settings = self.__dict__.copy() 

233 depth = settings.pop('_depth', 0) 

234 category = settings.pop('_category', 'pyramid') 

235 

236 def callback(context, name, ob): 

237 config = context.config.with_package(info.module) 

238 config.add_view(view=ob, **settings) 

239 

240 info = self.venusian.attach( 

241 wrapped, callback, category=category, depth=depth + 1 

242 ) 

243 

244 if info.scope == 'class': 

245 # if the decorator was attached to a method in a class, or 

246 # otherwise executed at class scope, we need to set an 

247 # 'attr' into the settings if one isn't already in there 

248 if settings.get('attr') is None: 

249 settings['attr'] = wrapped.__name__ 

250 

251 return wrapped 

252 

253 

254bfg_view = view_config # bw compat (forever) 

255 

256 

257def view_defaults(**settings): 

258 """ A class :term:`decorator` which, when applied to a class, will 

259 provide defaults for all view configurations that use the class. This 

260 decorator accepts all the arguments accepted by 

261 :meth:`pyramid.view.view_config`, and each has the same meaning. 

262 

263 See :ref:`view_defaults` for more information. 

264 """ 

265 

266 def wrap(wrapped): 

267 wrapped.__view_defaults__ = settings 

268 return wrapped 

269 

270 return wrap 

271 

272 

273class AppendSlashNotFoundViewFactory(object): 

274 """ There can only be one :term:`Not Found view` in any 

275 :app:`Pyramid` application. Even if you use 

276 :func:`pyramid.view.append_slash_notfound_view` as the Not 

277 Found view, :app:`Pyramid` still must generate a ``404 Not 

278 Found`` response when it cannot redirect to a slash-appended URL; 

279 this not found response will be visible to site users. 

280 

281 If you don't care what this 404 response looks like, and you only 

282 need redirections to slash-appended route URLs, you may use the 

283 :func:`pyramid.view.append_slash_notfound_view` object as the 

284 Not Found view. However, if you wish to use a *custom* notfound 

285 view callable when a URL cannot be redirected to a slash-appended 

286 URL, you may wish to use an instance of this class as the Not 

287 Found view, supplying a :term:`view callable` to be used as the 

288 custom notfound view as the first argument to its constructor. 

289 For instance: 

290 

291 .. code-block:: python 

292 

293 from pyramid.httpexceptions import HTTPNotFound 

294 from pyramid.view import AppendSlashNotFoundViewFactory 

295 

296 def notfound_view(context, request): return HTTPNotFound('nope') 

297 

298 custom_append_slash = AppendSlashNotFoundViewFactory(notfound_view) 

299 config.add_view(custom_append_slash, context=HTTPNotFound) 

300 

301 The ``notfound_view`` supplied must adhere to the two-argument 

302 view callable calling convention of ``(context, request)`` 

303 (``context`` will be the exception object). 

304 

305 .. deprecated:: 1.3 

306 

307 """ 

308 

309 def __init__( 

310 self, notfound_view=None, redirect_class=HTTPTemporaryRedirect 

311 ): 

312 if notfound_view is None: 

313 notfound_view = default_exceptionresponse_view 

314 self.notfound_view = notfound_view 

315 self.redirect_class = redirect_class 

316 

317 def __call__(self, context, request): 

318 path = decode_path_info(request.environ['PATH_INFO'] or '/') 

319 registry = request.registry 

320 mapper = registry.queryUtility(IRoutesMapper) 

321 if mapper is not None and not path.endswith('/'): 

322 slashpath = path + '/' 

323 for route in mapper.get_routes(): 

324 if route.match(slashpath) is not None: 

325 qs = request.query_string 

326 if qs: 

327 qs = '?' + qs 

328 return self.redirect_class( 

329 location=request.path + '/' + qs 

330 ) 

331 return self.notfound_view(context, request) 

332 

333 

334append_slash_notfound_view = AppendSlashNotFoundViewFactory() 

335append_slash_notfound_view.__doc__ = """\ 

336For behavior like Django's ``APPEND_SLASH=True``, use this view as the 

337:term:`Not Found view` in your application. 

338 

339When this view is the Not Found view (indicating that no view was found), and 

340any routes have been defined in the configuration of your application, if the 

341value of the ``PATH_INFO`` WSGI environment variable does not already end in 

342a slash, and if the value of ``PATH_INFO`` *plus* a slash matches any route's 

343path, do an HTTP redirect to the slash-appended PATH_INFO. Note that this 

344will *lose* ``POST`` data information (turning it into a GET), so you 

345shouldn't rely on this to redirect POST requests. Note also that static 

346routes are not considered when attempting to find a matching route. 

347 

348Use the :meth:`pyramid.config.Configurator.add_view` method to configure this 

349view as the Not Found view:: 

350 

351 from pyramid.httpexceptions import HTTPNotFound 

352 from pyramid.view import append_slash_notfound_view 

353 config.add_view(append_slash_notfound_view, context=HTTPNotFound) 

354 

355.. deprecated:: 1.3 

356 

357""" 

358 

359 

360class notfound_view_config(object): 

361 """ 

362 .. versionadded:: 1.3 

363 

364 An analogue of :class:`pyramid.view.view_config` which registers a 

365 :term:`Not Found View` using 

366 :meth:`pyramid.config.Configurator.add_notfound_view`. 

367 

368 The ``notfound_view_config`` constructor accepts most of the same arguments 

369 as the constructor of :class:`pyramid.view.view_config`. It can be used 

370 in the same places, and behaves in largely the same way, except it always 

371 registers a not found exception view instead of a 'normal' view. 

372 

373 Example: 

374 

375 .. code-block:: python 

376 

377 from pyramid.view import notfound_view_config 

378 from pyramid.response import Response 

379 

380 @notfound_view_config() 

381 def notfound(request): 

382 return Response('Not found!', status='404 Not Found') 

383 

384 All arguments except ``append_slash`` have the same meaning as 

385 :meth:`pyramid.view.view_config` and each predicate 

386 argument restricts the set of circumstances under which this notfound 

387 view will be invoked. 

388 

389 If ``append_slash`` is ``True``, when the Not Found View is invoked, and 

390 the current path info does not end in a slash, the notfound logic will 

391 attempt to find a :term:`route` that matches the request's path info 

392 suffixed with a slash. If such a route exists, Pyramid will issue a 

393 redirect to the URL implied by the route; if it does not, Pyramid will 

394 return the result of the view callable provided as ``view``, as normal. 

395 

396 If the argument provided as ``append_slash`` is not a boolean but 

397 instead implements :class:`~pyramid.interfaces.IResponse`, the 

398 append_slash logic will behave as if ``append_slash=True`` was passed, 

399 but the provided class will be used as the response class instead of 

400 the default :class:`~pyramid.httpexceptions.HTTPTemporaryRedirect` 

401 response class when a redirect is performed. For example: 

402 

403 .. code-block:: python 

404 

405 from pyramid.httpexceptions import ( 

406 HTTPMovedPermanently, 

407 HTTPNotFound 

408 ) 

409 

410 @notfound_view_config(append_slash=HTTPMovedPermanently) 

411 def aview(request): 

412 return HTTPNotFound('not found') 

413 

414 The above means that a redirect to a slash-appended route will be 

415 attempted, but instead of 

416 :class:`~pyramid.httpexceptions.HTTPTemporaryRedirect` 

417 being used, :class:`~pyramid.httpexceptions.HTTPMovedPermanently will 

418 be used` for the redirect response if a slash-appended route is found. 

419 

420 See :ref:`changing_the_notfound_view` for detailed usage information. 

421 

422 .. versionchanged:: 1.9.1 

423 Added the ``_depth`` and ``_category`` arguments. 

424 

425 """ 

426 

427 venusian = venusian 

428 

429 def __init__(self, **settings): 

430 self.__dict__.update(settings) 

431 

432 def __call__(self, wrapped): 

433 settings = self.__dict__.copy() 

434 depth = settings.pop('_depth', 0) 

435 category = settings.pop('_category', 'pyramid') 

436 

437 def callback(context, name, ob): 

438 config = context.config.with_package(info.module) 

439 config.add_notfound_view(view=ob, **settings) 

440 

441 info = self.venusian.attach( 

442 wrapped, callback, category=category, depth=depth + 1 

443 ) 

444 

445 if info.scope == 'class': 

446 # if the decorator was attached to a method in a class, or 

447 # otherwise executed at class scope, we need to set an 

448 # 'attr' into the settings if one isn't already in there 

449 if settings.get('attr') is None: 

450 settings['attr'] = wrapped.__name__ 

451 

452 settings['_info'] = info.codeinfo # fbo "action_method" 

453 return wrapped 

454 

455 

456class forbidden_view_config(object): 

457 """ 

458 .. versionadded:: 1.3 

459 

460 An analogue of :class:`pyramid.view.view_config` which registers a 

461 :term:`forbidden view` using 

462 :meth:`pyramid.config.Configurator.add_forbidden_view`. 

463 

464 The forbidden_view_config constructor accepts most of the same arguments 

465 as the constructor of :class:`pyramid.view.view_config`. It can be used 

466 in the same places, and behaves in largely the same way, except it always 

467 registers a forbidden exception view instead of a 'normal' view. 

468 

469 Example: 

470 

471 .. code-block:: python 

472 

473 from pyramid.view import forbidden_view_config 

474 from pyramid.response import Response 

475 

476 @forbidden_view_config() 

477 def forbidden(request): 

478 return Response('You are not allowed', status='403 Forbidden') 

479 

480 All arguments passed to this function have the same meaning as 

481 :meth:`pyramid.view.view_config` and each predicate argument restricts 

482 the set of circumstances under which this notfound view will be invoked. 

483 

484 See :ref:`changing_the_forbidden_view` for detailed usage information. 

485 

486 .. versionchanged:: 1.9.1 

487 Added the ``_depth`` and ``_category`` arguments. 

488 

489 """ 

490 

491 venusian = venusian 

492 

493 def __init__(self, **settings): 

494 self.__dict__.update(settings) 

495 

496 def __call__(self, wrapped): 

497 settings = self.__dict__.copy() 

498 depth = settings.pop('_depth', 0) 

499 category = settings.pop('_category', 'pyramid') 

500 

501 def callback(context, name, ob): 

502 config = context.config.with_package(info.module) 

503 config.add_forbidden_view(view=ob, **settings) 

504 

505 info = self.venusian.attach( 

506 wrapped, callback, category=category, depth=depth + 1 

507 ) 

508 

509 if info.scope == 'class': 

510 # if the decorator was attached to a method in a class, or 

511 # otherwise executed at class scope, we need to set an 

512 # 'attr' into the settings if one isn't already in there 

513 if settings.get('attr') is None: 

514 settings['attr'] = wrapped.__name__ 

515 

516 settings['_info'] = info.codeinfo # fbo "action_method" 

517 return wrapped 

518 

519 

520class exception_view_config(object): 

521 """ 

522 .. versionadded:: 1.8 

523 

524 An analogue of :class:`pyramid.view.view_config` which registers an 

525 :term:`exception view` using 

526 :meth:`pyramid.config.Configurator.add_exception_view`. 

527 

528 The ``exception_view_config`` constructor requires an exception context, 

529 and additionally accepts most of the same arguments as the constructor of 

530 :class:`pyramid.view.view_config`. It can be used in the same places, 

531 and behaves in largely the same way, except it always registers an 

532 exception view instead of a "normal" view that dispatches on the request 

533 :term:`context`. 

534 

535 Example: 

536 

537 .. code-block:: python 

538 

539 from pyramid.view import exception_view_config 

540 from pyramid.response import Response 

541 

542 @exception_view_config(ValueError, renderer='json') 

543 def error_view(request): 

544 return {'error': str(request.exception)} 

545 

546 All arguments passed to this function have the same meaning as 

547 :meth:`pyramid.view.view_config`, and each predicate argument restricts 

548 the set of circumstances under which this exception view will be invoked. 

549 

550 .. versionchanged:: 1.9.1 

551 Added the ``_depth`` and ``_category`` arguments. 

552 

553 """ 

554 

555 venusian = venusian 

556 

557 def __init__(self, *args, **settings): 

558 if 'context' not in settings and len(args) > 0: 

559 exception, args = args[0], args[1:] 

560 settings['context'] = exception 

561 if len(args) > 0: 

562 raise ConfigurationError('unknown positional arguments') 

563 self.__dict__.update(settings) 

564 

565 def __call__(self, wrapped): 

566 settings = self.__dict__.copy() 

567 depth = settings.pop('_depth', 0) 

568 category = settings.pop('_category', 'pyramid') 

569 

570 def callback(context, name, ob): 

571 config = context.config.with_package(info.module) 

572 config.add_exception_view(view=ob, **settings) 

573 

574 info = self.venusian.attach( 

575 wrapped, callback, category=category, depth=depth + 1 

576 ) 

577 

578 if info.scope == 'class': 

579 # if the decorator was attached to a method in a class, or 

580 # otherwise executed at class scope, we need to set an 

581 # 'attr' in the settings if one isn't already in there 

582 if settings.get('attr') is None: 

583 settings['attr'] = wrapped.__name__ 

584 

585 settings['_info'] = info.codeinfo # fbo "action_method" 

586 return wrapped 

587 

588 

589def _find_views( 

590 registry, 

591 request_iface, 

592 context_iface, 

593 view_name, 

594 view_types=None, 

595 view_classifier=None, 

596): 

597 if view_types is None: 

598 view_types = (IView, ISecuredView, IMultiView) 

599 if view_classifier is None: 

600 view_classifier = IViewClassifier 

601 registered = registry.adapters.registered 

602 cache = registry._view_lookup_cache 

603 views = cache.get((request_iface, context_iface, view_name)) 

604 if views is None: 

605 views = [] 

606 for req_type, ctx_type in itertools.product( 

607 request_iface.__sro__, context_iface.__sro__ 

608 ): 

609 source_ifaces = (view_classifier, req_type, ctx_type) 

610 for view_type in view_types: 

611 view_callable = registered( 

612 source_ifaces, view_type, name=view_name 

613 ) 

614 if view_callable is not None: 

615 views.append(view_callable) 

616 if views: 

617 # do not cache view lookup misses. rationale: dont allow cache to 

618 # grow without bound if somebody tries to hit the site with many 

619 # missing URLs. we could use an LRU cache instead, but then 

620 # purposeful misses by an attacker would just blow out the cache 

621 # anyway. downside: misses will almost always consume more CPU than 

622 # hits in steady state. 

623 with registry._lock: 

624 cache[(request_iface, context_iface, view_name)] = views 

625 

626 return views 

627 

628 

629def _call_view( 

630 registry, 

631 request, 

632 context, 

633 context_iface, 

634 view_name, 

635 view_types=None, 

636 view_classifier=None, 

637 secure=True, 

638 request_iface=None, 

639): 

640 if request_iface is None: 

641 request_iface = getattr(request, 'request_iface', IRequest) 

642 view_callables = _find_views( 

643 registry, 

644 request_iface, 

645 context_iface, 

646 view_name, 

647 view_types=view_types, 

648 view_classifier=view_classifier, 

649 ) 

650 

651 pme = None 

652 response = None 

653 

654 for view_callable in view_callables: 

655 # look for views that meet the predicate criteria 

656 try: 

657 if not secure: 

658 # the view will have a __call_permissive__ attribute if it's 

659 # secured; otherwise it won't. 

660 view_callable = getattr( 

661 view_callable, '__call_permissive__', view_callable 

662 ) 

663 

664 # if this view is secured, it will raise a Forbidden 

665 # appropriately if the executing user does not have the proper 

666 # permission 

667 response = view_callable(context, request) 

668 return response 

669 except PredicateMismatch as _pme: 

670 pme = _pme 

671 

672 if pme is not None: 

673 raise pme 

674 

675 return response 

676 

677 

678class ViewMethodsMixin(object): 

679 """ Request methods mixin for BaseRequest having to do with executing 

680 views """ 

681 

682 def invoke_exception_view( 

683 self, exc_info=None, request=None, secure=True, reraise=False 

684 ): 

685 """ Executes an exception view related to the request it's called upon. 

686 The arguments it takes are these: 

687 

688 ``exc_info`` 

689 

690 If provided, should be a 3-tuple in the form provided by 

691 ``sys.exc_info()``. If not provided, 

692 ``sys.exc_info()`` will be called to obtain the current 

693 interpreter exception information. Default: ``None``. 

694 

695 ``request`` 

696 

697 If the request to be used is not the same one as the instance that 

698 this method is called upon, it may be passed here. Default: 

699 ``None``. 

700 

701 ``secure`` 

702 

703 If the exception view should not be rendered if the current user 

704 does not have the appropriate permission, this should be ``True``. 

705 Default: ``True``. 

706 

707 ``reraise`` 

708 

709 A boolean indicating whether the original error should be reraised 

710 if a :term:`response` object could not be created. If ``False`` 

711 then an :class:`pyramid.httpexceptions.HTTPNotFound`` exception 

712 will be raised. Default: ``False``. 

713 

714 If a response is generated then ``request.exception`` and 

715 ``request.exc_info`` will be left at the values used to render the 

716 response. Otherwise the previous values for ``request.exception`` and 

717 ``request.exc_info`` will be restored. 

718 

719 .. versionadded:: 1.7 

720 

721 .. versionchanged:: 1.9 

722 The ``request.exception`` and ``request.exc_info`` properties will 

723 reflect the exception used to render the response where previously 

724 they were reset to the values prior to invoking the method. 

725 

726 Also added the ``reraise`` argument. 

727 

728 """ 

729 if request is None: 

730 request = self 

731 registry = getattr(request, 'registry', None) 

732 if registry is None: 

733 registry = get_current_registry() 

734 

735 if registry is None: 

736 raise RuntimeError("Unable to retrieve registry") 

737 

738 if exc_info is None: 

739 exc_info = sys.exc_info() 

740 

741 exc = exc_info[1] 

742 attrs = request.__dict__ 

743 context_iface = providedBy(exc) 

744 

745 # clear old generated request.response, if any; it may 

746 # have been mutated by the view, and its state is not 

747 # sane (e.g. caching headers) 

748 with hide_attrs(request, 'response', 'exc_info', 'exception'): 

749 attrs['exception'] = exc 

750 attrs['exc_info'] = exc_info 

751 # we use .get instead of .__getitem__ below due to 

752 # https://github.com/Pylons/pyramid/issues/700 

753 request_iface = attrs.get('request_iface', IRequest) 

754 

755 manager.push({'request': request, 'registry': registry}) 

756 

757 try: 

758 response = _call_view( 

759 registry, 

760 request, 

761 exc, 

762 context_iface, 

763 '', 

764 view_types=None, 

765 view_classifier=IExceptionViewClassifier, 

766 secure=secure, 

767 request_iface=request_iface.combined, 

768 ) 

769 except Exception: 

770 if reraise: 

771 reraise_(*exc_info) 

772 raise 

773 finally: 

774 manager.pop() 

775 

776 if response is None: 

777 if reraise: 

778 reraise_(*exc_info) 

779 raise HTTPNotFound 

780 

781 # successful response, overwrite exception/exc_info 

782 attrs['exception'] = exc 

783 attrs['exc_info'] = exc_info 

784 return response