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 contextlib 

2import warnings 

3 

4from pyramid.compat import urlparse 

5from pyramid.interfaces import ( 

6 IRequest, 

7 IRouteRequest, 

8 IRoutesMapper, 

9 PHASE2_CONFIG, 

10) 

11 

12from pyramid.exceptions import ConfigurationError 

13import pyramid.predicates 

14from pyramid.request import route_request_iface 

15from pyramid.urldispatch import RoutesMapper 

16 

17from pyramid.util import as_sorted_tuple, is_nonstr_iter 

18 

19from pyramid.config.actions import action_method 

20from pyramid.config.predicates import normalize_accept_offer, predvalseq 

21 

22 

23class RoutesConfiguratorMixin(object): 

24 @action_method 

25 def add_route( 

26 self, 

27 name, 

28 pattern=None, 

29 factory=None, 

30 for_=None, 

31 header=None, 

32 xhr=None, 

33 accept=None, 

34 path_info=None, 

35 request_method=None, 

36 request_param=None, 

37 traverse=None, 

38 custom_predicates=(), 

39 use_global_views=False, 

40 path=None, 

41 pregenerator=None, 

42 static=False, 

43 **predicates 

44 ): 

45 """ Add a :term:`route configuration` to the current 

46 configuration state, as well as possibly a :term:`view 

47 configuration` to be used to specify a :term:`view callable` 

48 that will be invoked when this route matches. The arguments 

49 to this method are divided into *predicate*, *non-predicate*, 

50 and *view-related* types. :term:`Route predicate` arguments 

51 narrow the circumstances in which a route will be match a 

52 request; non-predicate arguments are informational. 

53 

54 Non-Predicate Arguments 

55 

56 name 

57 

58 The name of the route, e.g. ``myroute``. This attribute is 

59 required. It must be unique among all defined routes in a given 

60 application. 

61 

62 factory 

63 

64 A Python object (often a function or a class) or a :term:`dotted 

65 Python name` which refers to the same object that will generate a 

66 :app:`Pyramid` root resource object when this route matches. For 

67 example, ``mypackage.resources.MyFactory``. If this argument is 

68 not specified, a default root factory will be used. See 

69 :ref:`the_resource_tree` for more information about root factories. 

70 

71 traverse 

72 

73 If you would like to cause the :term:`context` to be 

74 something other than the :term:`root` object when this route 

75 matches, you can spell a traversal pattern as the 

76 ``traverse`` argument. This traversal pattern will be used 

77 as the traversal path: traversal will begin at the root 

78 object implied by this route (either the global root, or the 

79 object returned by the ``factory`` associated with this 

80 route). 

81 

82 The syntax of the ``traverse`` argument is the same as it is 

83 for ``pattern``. For example, if the ``pattern`` provided to 

84 ``add_route`` is ``articles/{article}/edit``, and the 

85 ``traverse`` argument provided to ``add_route`` is 

86 ``/{article}``, when a request comes in that causes the route 

87 to match in such a way that the ``article`` match value is 

88 ``'1'`` (when the request URI is ``/articles/1/edit``), the 

89 traversal path will be generated as ``/1``. This means that 

90 the root object's ``__getitem__`` will be called with the 

91 name ``'1'`` during the traversal phase. If the ``'1'`` object 

92 exists, it will become the :term:`context` of the request. 

93 :ref:`traversal_chapter` has more information about 

94 traversal. 

95 

96 If the traversal path contains segment marker names which 

97 are not present in the ``pattern`` argument, a runtime error 

98 will occur. The ``traverse`` pattern should not contain 

99 segment markers that do not exist in the ``pattern`` 

100 argument. 

101 

102 A similar combining of routing and traversal is available 

103 when a route is matched which contains a ``*traverse`` 

104 remainder marker in its pattern (see 

105 :ref:`using_traverse_in_a_route_pattern`). The ``traverse`` 

106 argument to add_route allows you to associate route patterns 

107 with an arbitrary traversal path without using a 

108 ``*traverse`` remainder marker; instead you can use other 

109 match information. 

110 

111 Note that the ``traverse`` argument to ``add_route`` is 

112 ignored when attached to a route that has a ``*traverse`` 

113 remainder marker in its pattern. 

114 

115 pregenerator 

116 

117 This option should be a callable object that implements the 

118 :class:`pyramid.interfaces.IRoutePregenerator` interface. A 

119 :term:`pregenerator` is a callable called by the 

120 :meth:`pyramid.request.Request.route_url` function to augment or 

121 replace the arguments it is passed when generating a URL for the 

122 route. This is a feature not often used directly by applications, 

123 it is meant to be hooked by frameworks that use :app:`Pyramid` as 

124 a base. 

125 

126 use_global_views 

127 

128 When a request matches this route, and view lookup cannot 

129 find a view which has a ``route_name`` predicate argument 

130 that matches the route, try to fall back to using a view 

131 that otherwise matches the context, request, and view name 

132 (but which does not match the route_name predicate). 

133 

134 static 

135 

136 If ``static`` is ``True``, this route will never match an incoming 

137 request; it will only be useful for URL generation. By default, 

138 ``static`` is ``False``. See :ref:`static_route_narr`. 

139 

140 .. versionadded:: 1.1 

141 

142 Predicate Arguments 

143 

144 pattern 

145 

146 The pattern of the route e.g. ``ideas/{idea}``. This 

147 argument is required. See :ref:`route_pattern_syntax` 

148 for information about the syntax of route patterns. If the 

149 pattern doesn't match the current URL, route matching 

150 continues. 

151 

152 .. note:: 

153 

154 For backwards compatibility purposes (as of :app:`Pyramid` 1.0), a 

155 ``path`` keyword argument passed to this function will be used to 

156 represent the pattern value if the ``pattern`` argument is 

157 ``None``. If both ``path`` and ``pattern`` are passed, 

158 ``pattern`` wins. 

159 

160 xhr 

161 

162 This value should be either ``True`` or ``False``. If this 

163 value is specified and is ``True``, the :term:`request` must 

164 possess an ``HTTP_X_REQUESTED_WITH`` (aka 

165 ``X-Requested-With``) header for this route to match. This 

166 is useful for detecting AJAX requests issued from jQuery, 

167 Prototype and other Javascript libraries. If this predicate 

168 returns ``False``, route matching continues. 

169 

170 request_method 

171 

172 A string representing an HTTP method name, e.g. ``GET``, ``POST``, 

173 ``HEAD``, ``DELETE``, ``PUT`` or a tuple of elements containing 

174 HTTP method names. If this argument is not specified, this route 

175 will match if the request has *any* request method. If this 

176 predicate returns ``False``, route matching continues. 

177 

178 .. versionchanged:: 1.2 

179 The ability to pass a tuple of items as ``request_method``. 

180 Previous versions allowed only a string. 

181 

182 path_info 

183 

184 This value represents a regular expression pattern that will 

185 be tested against the ``PATH_INFO`` WSGI environment 

186 variable. If the regex matches, this predicate will return 

187 ``True``. If this predicate returns ``False``, route 

188 matching continues. 

189 

190 request_param 

191 

192 This value can be any string or an iterable of strings. A view 

193 declaration with this argument ensures that the associated route will 

194 only match when the request has a key in the ``request.params`` 

195 dictionary (an HTTP ``GET`` or ``POST`` variable) that has a 

196 name which matches the supplied value. If the value 

197 supplied as the argument has a ``=`` sign in it, 

198 e.g. ``request_param="foo=123"``, then the key 

199 (``foo``) must both exist in the ``request.params`` dictionary, and 

200 the value must match the right hand side of the expression (``123``) 

201 for the route to "match" the current request. If this predicate 

202 returns ``False``, route matching continues. 

203 

204 header 

205 

206 This argument represents an HTTP header name or a header 

207 name/value pair. If the argument contains a ``:`` (colon), 

208 it will be considered a name/value pair 

209 (e.g. ``User-Agent:Mozilla/.*`` or ``Host:localhost``). If 

210 the value contains a colon, the value portion should be a 

211 regular expression. If the value does not contain a colon, 

212 the entire value will be considered to be the header name 

213 (e.g. ``If-Modified-Since``). If the value evaluates to a 

214 header name only without a value, the header specified by 

215 the name must be present in the request for this predicate 

216 to be true. If the value evaluates to a header name/value 

217 pair, the header specified by the name must be present in 

218 the request *and* the regular expression specified as the 

219 value must match the header value. Whether or not the value 

220 represents a header name or a header name/value pair, the 

221 case of the header name is not significant. If this 

222 predicate returns ``False``, route matching continues. 

223 

224 accept 

225 

226 A :term:`media type` that will be matched against the ``Accept`` 

227 HTTP request header. If this value is specified, it may be a 

228 specific media type such as ``text/html``, or a list of the same. 

229 If the media type is acceptable by the ``Accept`` header of the 

230 request, or if the ``Accept`` header isn't set at all in the request, 

231 this predicate will match. If this does not match the ``Accept`` 

232 header of the request, route matching continues. 

233 

234 If ``accept`` is not specified, the ``HTTP_ACCEPT`` HTTP header is 

235 not taken into consideration when deciding whether or not to select 

236 the route. 

237 

238 Unlike the ``accept`` argument to 

239 :meth:`pyramid.config.Configurator.add_view`, this value is 

240 strictly a predicate and supports :func:`pyramid.config.not_`. 

241 

242 .. versionchanged:: 1.10 

243 

244 Specifying a media range is deprecated due to changes in WebOb 

245 and ambiguities that occur when trying to match ranges against 

246 ranges in the ``Accept`` header. Support will be removed in 

247 :app:`Pyramid` 2.0. Use a list of specific media types to match 

248 more than one type. 

249 

250 effective_principals 

251 

252 If specified, this value should be a :term:`principal` identifier or 

253 a sequence of principal identifiers. If the 

254 :attr:`pyramid.request.Request.effective_principals` property 

255 indicates that every principal named in the argument list is present 

256 in the current request, this predicate will return True; otherwise it 

257 will return False. For example: 

258 ``effective_principals=pyramid.security.Authenticated`` or 

259 ``effective_principals=('fred', 'group:admins')``. 

260 

261 .. versionadded:: 1.4a4 

262 

263 custom_predicates 

264 

265 .. deprecated:: 1.5 

266 This value should be a sequence of references to custom 

267 predicate callables. Use custom predicates when no set of 

268 predefined predicates does what you need. Custom predicates 

269 can be combined with predefined predicates as necessary. 

270 Each custom predicate callable should accept two arguments: 

271 ``info`` and ``request`` and should return either ``True`` 

272 or ``False`` after doing arbitrary evaluation of the info 

273 and/or the request. If all custom and non-custom predicate 

274 callables return ``True`` the associated route will be 

275 considered viable for a given request. If any predicate 

276 callable returns ``False``, route matching continues. Note 

277 that the value ``info`` passed to a custom route predicate 

278 is a dictionary containing matching information; see 

279 :ref:`custom_route_predicates` for more information about 

280 ``info``. 

281 

282 predicates 

283 

284 Pass a key/value pair here to use a third-party predicate 

285 registered via 

286 :meth:`pyramid.config.Configurator.add_route_predicate`. More than 

287 one key/value pair can be used at the same time. See 

288 :ref:`view_and_route_predicates` for more information about 

289 third-party predicates. 

290 

291 .. versionadded:: 1.4 

292 

293 """ 

294 if custom_predicates: 

295 warnings.warn( 

296 ( 

297 'The "custom_predicates" argument to ' 

298 'Configurator.add_route is deprecated as of Pyramid 1.5. ' 

299 'Use "config.add_route_predicate" and use the registered ' 

300 'route predicate as a predicate argument to add_route ' 

301 'instead. See "Adding A Third Party View, Route, or ' 

302 'Subscriber Predicate" in the "Hooks" chapter of the ' 

303 'documentation for more information.' 

304 ), 

305 DeprecationWarning, 

306 stacklevel=3, 

307 ) 

308 

309 if accept is not None: 

310 if not is_nonstr_iter(accept): 

311 if '*' in accept: 

312 warnings.warn( 

313 ( 

314 'Passing a media range to the "accept" argument ' 

315 'of Configurator.add_route is deprecated as of ' 

316 'Pyramid 1.10. Use a list of explicit media types.' 

317 ), 

318 DeprecationWarning, 

319 stacklevel=3, 

320 ) 

321 # XXX switch this to False when range support is dropped 

322 accept = [normalize_accept_offer(accept, allow_range=True)] 

323 

324 else: 

325 accept = [ 

326 normalize_accept_offer(accept_option) 

327 for accept_option in accept 

328 ] 

329 

330 # these are route predicates; if they do not match, the next route 

331 # in the routelist will be tried 

332 if request_method is not None: 

333 request_method = as_sorted_tuple(request_method) 

334 

335 factory = self.maybe_dotted(factory) 

336 if pattern is None: 

337 pattern = path 

338 if pattern is None: 

339 raise ConfigurationError('"pattern" argument may not be None') 

340 

341 # check for an external route; an external route is one which is 

342 # is a full url (e.g. 'http://example.com/{id}') 

343 parsed = urlparse.urlparse(pattern) 

344 external_url = pattern 

345 

346 if parsed.hostname: 

347 pattern = parsed.path 

348 

349 original_pregenerator = pregenerator 

350 

351 def external_url_pregenerator(request, elements, kw): 

352 if '_app_url' in kw: 

353 raise ValueError( 

354 'You cannot generate a path to an external route ' 

355 'pattern via request.route_path nor pass an _app_url ' 

356 'to request.route_url when generating a URL for an ' 

357 'external route pattern (pattern was "%s") ' 

358 % (pattern,) 

359 ) 

360 if '_scheme' in kw: 

361 scheme = kw['_scheme'] 

362 elif parsed.scheme: 

363 scheme = parsed.scheme 

364 else: 

365 scheme = request.scheme 

366 kw['_app_url'] = '{0}://{1}'.format(scheme, parsed.netloc) 

367 

368 if original_pregenerator: 

369 elements, kw = original_pregenerator(request, elements, kw) 

370 return elements, kw 

371 

372 pregenerator = external_url_pregenerator 

373 static = True 

374 

375 elif self.route_prefix: 

376 pattern = self.route_prefix.rstrip('/') + '/' + pattern.lstrip('/') 

377 

378 mapper = self.get_routes_mapper() 

379 

380 introspectables = [] 

381 

382 intr = self.introspectable( 

383 'routes', name, '%s (pattern: %r)' % (name, pattern), 'route' 

384 ) 

385 intr['name'] = name 

386 intr['pattern'] = pattern 

387 intr['factory'] = factory 

388 intr['xhr'] = xhr 

389 intr['request_methods'] = request_method 

390 intr['path_info'] = path_info 

391 intr['request_param'] = request_param 

392 intr['header'] = header 

393 intr['accept'] = accept 

394 intr['traverse'] = traverse 

395 intr['custom_predicates'] = custom_predicates 

396 intr['pregenerator'] = pregenerator 

397 intr['static'] = static 

398 intr['use_global_views'] = use_global_views 

399 

400 if static is True: 

401 intr['external_url'] = external_url 

402 

403 introspectables.append(intr) 

404 

405 if factory: 

406 factory_intr = self.introspectable( 

407 'root factories', 

408 name, 

409 self.object_description(factory), 

410 'root factory', 

411 ) 

412 factory_intr['factory'] = factory 

413 factory_intr['route_name'] = name 

414 factory_intr.relate('routes', name) 

415 introspectables.append(factory_intr) 

416 

417 def register_route_request_iface(): 

418 request_iface = self.registry.queryUtility( 

419 IRouteRequest, name=name 

420 ) 

421 if request_iface is None: 

422 if use_global_views: 

423 bases = (IRequest,) 

424 else: 

425 bases = () 

426 request_iface = route_request_iface(name, bases) 

427 self.registry.registerUtility( 

428 request_iface, IRouteRequest, name=name 

429 ) 

430 

431 def register_connect(): 

432 pvals = predicates.copy() 

433 pvals.update( 

434 dict( 

435 xhr=xhr, 

436 request_method=request_method, 

437 path_info=path_info, 

438 request_param=request_param, 

439 header=header, 

440 accept=accept, 

441 traverse=traverse, 

442 custom=predvalseq(custom_predicates), 

443 ) 

444 ) 

445 

446 predlist = self.get_predlist('route') 

447 _, preds, _ = predlist.make(self, **pvals) 

448 route = mapper.connect( 

449 name, 

450 pattern, 

451 factory, 

452 predicates=preds, 

453 pregenerator=pregenerator, 

454 static=static, 

455 ) 

456 intr['object'] = route 

457 return route 

458 

459 # We have to connect routes in the order they were provided; 

460 # we can't use a phase to do that, because when the actions are 

461 # sorted, actions in the same phase lose relative ordering 

462 self.action(('route-connect', name), register_connect) 

463 

464 # But IRouteRequest interfaces must be registered before we begin to 

465 # process view registrations (in phase 3) 

466 self.action( 

467 ('route', name), 

468 register_route_request_iface, 

469 order=PHASE2_CONFIG, 

470 introspectables=introspectables, 

471 ) 

472 

473 @action_method 

474 def add_route_predicate( 

475 self, name, factory, weighs_more_than=None, weighs_less_than=None 

476 ): 

477 """ Adds a route predicate factory. The view predicate can later be 

478 named as a keyword argument to 

479 :meth:`pyramid.config.Configurator.add_route`. 

480 

481 ``name`` should be the name of the predicate. It must be a valid 

482 Python identifier (it will be used as a keyword argument to 

483 ``add_route``). 

484 

485 ``factory`` should be a :term:`predicate factory` or :term:`dotted 

486 Python name` which refers to a predicate factory. 

487 

488 See :ref:`view_and_route_predicates` for more information. 

489 

490 .. versionadded:: 1.4 

491 """ 

492 self._add_predicate( 

493 'route', 

494 name, 

495 factory, 

496 weighs_more_than=weighs_more_than, 

497 weighs_less_than=weighs_less_than, 

498 ) 

499 

500 def add_default_route_predicates(self): 

501 p = pyramid.predicates 

502 for (name, factory) in ( 

503 ('xhr', p.XHRPredicate), 

504 ('request_method', p.RequestMethodPredicate), 

505 ('path_info', p.PathInfoPredicate), 

506 ('request_param', p.RequestParamPredicate), 

507 ('header', p.HeaderPredicate), 

508 ('accept', p.AcceptPredicate), 

509 ('effective_principals', p.EffectivePrincipalsPredicate), 

510 ('custom', p.CustomPredicate), 

511 ('traverse', p.TraversePredicate), 

512 ): 

513 self.add_route_predicate(name, factory) 

514 

515 def get_routes_mapper(self): 

516 """ Return the :term:`routes mapper` object associated with 

517 this configurator's :term:`registry`.""" 

518 mapper = self.registry.queryUtility(IRoutesMapper) 

519 if mapper is None: 

520 mapper = RoutesMapper() 

521 self.registry.registerUtility(mapper, IRoutesMapper) 

522 return mapper 

523 

524 @contextlib.contextmanager 

525 def route_prefix_context(self, route_prefix): 

526 """ Return this configurator with the 

527 :attr:`pyramid.config.Configurator.route_prefix` attribute mutated to 

528 include the new ``route_prefix``. 

529 

530 When the context exits, the ``route_prefix`` is reset to the original. 

531 

532 ``route_prefix`` is a string suitable to be used as a route prefix, 

533 or ``None``. 

534 

535 Example Usage: 

536 

537 .. code-block:: python 

538 

539 config = Configurator() 

540 with config.route_prefix_context('foo'): 

541 config.add_route('bar', '/bar') 

542 

543 .. versionadded:: 1.10 

544 

545 """ 

546 original_route_prefix = self.route_prefix 

547 

548 if route_prefix is None: 

549 route_prefix = '' 

550 

551 old_route_prefix = self.route_prefix 

552 if old_route_prefix is None: 

553 old_route_prefix = '' 

554 

555 route_prefix = '{}/{}'.format( 

556 old_route_prefix.rstrip('/'), route_prefix.lstrip('/') 

557 ) 

558 

559 route_prefix = route_prefix.strip('/') 

560 

561 if not route_prefix: 

562 route_prefix = None 

563 

564 self.begin() 

565 try: 

566 self.route_prefix = route_prefix 

567 yield 

568 

569 finally: 

570 self.route_prefix = original_route_prefix 

571 self.end()