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 zope.interface import implementer 

2from zope.interface.interfaces import IInterface 

3 

4from pyramid.interfaces import ( 

5 IResourceURL, 

6 IRequestFactory, 

7 ITraverser, 

8 VH_ROOT_KEY, 

9) 

10 

11from pyramid.compat import ( 

12 PY2, 

13 native_, 

14 text_, 

15 ascii_native_, 

16 text_type, 

17 binary_type, 

18 is_nonstr_iter, 

19 decode_path_info, 

20 unquote_bytes_to_wsgi, 

21 lru_cache, 

22) 

23 

24from pyramid.encode import url_quote 

25from pyramid.exceptions import URLDecodeError 

26from pyramid.location import lineage 

27from pyramid.threadlocal import get_current_registry 

28 

29PATH_SEGMENT_SAFE = "~!$&'()*+,;=:@" # from webob 

30PATH_SAFE = PATH_SEGMENT_SAFE + "/" 

31 

32empty = text_('') 

33 

34 

35def find_root(resource): 

36 """ Find the root node in the resource tree to which ``resource`` 

37 belongs. Note that ``resource`` should be :term:`location`-aware. 

38 Note that the root resource is available in the request object by 

39 accessing the ``request.root`` attribute. 

40 """ 

41 for location in lineage(resource): 

42 if location.__parent__ is None: 

43 resource = location 

44 break 

45 return resource 

46 

47 

48def find_resource(resource, path): 

49 """ Given a resource object and a string or tuple representing a path 

50 (such as the return value of :func:`pyramid.traversal.resource_path` or 

51 :func:`pyramid.traversal.resource_path_tuple`), return a resource in this 

52 application's resource tree at the specified path. The resource passed 

53 in *must* be :term:`location`-aware. If the path cannot be resolved (if 

54 the respective node in the resource tree does not exist), a 

55 :exc:`KeyError` will be raised. 

56 

57 This function is the logical inverse of 

58 :func:`pyramid.traversal.resource_path` and 

59 :func:`pyramid.traversal.resource_path_tuple`; it can resolve any 

60 path string or tuple generated by either of those functions. 

61 

62 Rules for passing a *string* as the ``path`` argument: if the 

63 first character in the path string is the ``/`` 

64 character, the path is considered absolute and the resource tree 

65 traversal will start at the root resource. If the first character 

66 of the path string is *not* the ``/`` character, the path is 

67 considered relative and resource tree traversal will begin at the resource 

68 object supplied to the function as the ``resource`` argument. If an 

69 empty string is passed as ``path``, the ``resource`` passed in will 

70 be returned. Resource path strings must be escaped in the following 

71 manner: each Unicode path segment must be encoded as UTF-8 and as 

72 each path segment must escaped via Python's :mod:`urllib.quote`. 

73 For example, ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or 

74 ``to%20the/La%20Pe%C3%B1a`` (relative). The 

75 :func:`pyramid.traversal.resource_path` function generates strings 

76 which follow these rules (albeit only absolute ones). 

77 

78 Rules for passing *text* (Unicode) as the ``path`` argument are the same 

79 as those for a string. In particular, the text may not have any nonascii 

80 characters in it. 

81 

82 Rules for passing a *tuple* as the ``path`` argument: if the first 

83 element in the path tuple is the empty string (for example ``('', 

84 'a', 'b', 'c')``, the path is considered absolute and the resource tree 

85 traversal will start at the resource tree root object. If the first 

86 element in the path tuple is not the empty string (for example 

87 ``('a', 'b', 'c')``), the path is considered relative and resource tree 

88 traversal will begin at the resource object supplied to the function 

89 as the ``resource`` argument. If an empty sequence is passed as 

90 ``path``, the ``resource`` passed in itself will be returned. No 

91 URL-quoting or UTF-8-encoding of individual path segments within 

92 the tuple is required (each segment may be any string or unicode 

93 object representing a resource name). Resource path tuples generated by 

94 :func:`pyramid.traversal.resource_path_tuple` can always be 

95 resolved by ``find_resource``. 

96 """ 

97 if isinstance(path, text_type): 

98 path = ascii_native_(path) 

99 D = traverse(resource, path) 

100 view_name = D['view_name'] 

101 context = D['context'] 

102 if view_name: 

103 raise KeyError('%r has no subelement %s' % (context, view_name)) 

104 return context 

105 

106 

107find_model = find_resource # b/w compat (forever) 

108 

109 

110def find_interface(resource, class_or_interface): 

111 """ 

112 Return the first resource found in the :term:`lineage` of ``resource`` 

113 which, a) if ``class_or_interface`` is a Python class object, is an 

114 instance of the class or any subclass of that class or b) if 

115 ``class_or_interface`` is a :term:`interface`, provides the specified 

116 interface. Return ``None`` if no resource providing ``interface_or_class`` 

117 can be found in the lineage. The ``resource`` passed in *must* be 

118 :term:`location`-aware. 

119 """ 

120 if IInterface.providedBy(class_or_interface): 

121 test = class_or_interface.providedBy 

122 else: 

123 test = lambda arg: isinstance(arg, class_or_interface) 

124 for location in lineage(resource): 

125 if test(location): 

126 return location 

127 

128 

129def resource_path(resource, *elements): 

130 """ Return a string object representing the absolute physical path of the 

131 resource object based on its position in the resource tree, e.g 

132 ``/foo/bar``. Any positional arguments passed in as ``elements`` will be 

133 appended as path segments to the end of the resource path. For instance, 

134 if the resource's path is ``/foo/bar`` and ``elements`` equals ``('a', 

135 'b')``, the returned string will be ``/foo/bar/a/b``. The first 

136 character in the string will always be the ``/`` character (a leading 

137 ``/`` character in a path string represents that the path is absolute). 

138 

139 Resource path strings returned will be escaped in the following 

140 manner: each unicode path segment will be encoded as UTF-8 and 

141 each path segment will be escaped via Python's :mod:`urllib.quote`. 

142 For example, ``/path/to%20the/La%20Pe%C3%B1a``. 

143 

144 This function is a logical inverse of 

145 :mod:`pyramid.traversal.find_resource`: it can be used to generate 

146 path references that can later be resolved via that function. 

147 

148 The ``resource`` passed in *must* be :term:`location`-aware. 

149 

150 .. note:: 

151 

152 Each segment in the path string returned will use the ``__name__`` 

153 attribute of the resource it represents within the resource tree. Each 

154 of these segments *should* be a unicode or string object (as per the 

155 contract of :term:`location`-awareness). However, no conversion or 

156 safety checking of resource names is performed. For instance, if one of 

157 the resources in your tree has a ``__name__`` which (by error) is a 

158 dictionary, the :func:`pyramid.traversal.resource_path` function will 

159 attempt to append it to a string and it will cause a 

160 :exc:`pyramid.exceptions.URLDecodeError`. 

161 

162 .. note:: 

163 

164 The :term:`root` resource *must* have a ``__name__`` attribute with a 

165 value of either ``None`` or the empty string for paths to be generated 

166 properly. If the root resource has a non-null ``__name__`` attribute, 

167 its name will be prepended to the generated path rather than a single 

168 leading '/' character. 

169 """ 

170 # joining strings is a bit expensive so we delegate to a function 

171 # which caches the joined result for us 

172 return _join_path_tuple(resource_path_tuple(resource, *elements)) 

173 

174 

175model_path = resource_path # b/w compat (forever) 

176 

177 

178def traverse(resource, path): 

179 """Given a resource object as ``resource`` and a string or tuple 

180 representing a path as ``path`` (such as the return value of 

181 :func:`pyramid.traversal.resource_path` or 

182 :func:`pyramid.traversal.resource_path_tuple` or the value of 

183 ``request.environ['PATH_INFO']``), return a dictionary with the 

184 keys ``context``, ``root``, ``view_name``, ``subpath``, 

185 ``traversed``, ``virtual_root``, and ``virtual_root_path``. 

186 

187 A definition of each value in the returned dictionary: 

188 

189 - ``context``: The :term:`context` (a :term:`resource` object) found 

190 via traversal or url dispatch. If the ``path`` passed in is the 

191 empty string, the value of the ``resource`` argument passed to this 

192 function is returned. 

193 

194 - ``root``: The resource object at which :term:`traversal` begins. 

195 If the ``resource`` passed in was found via url dispatch or if the 

196 ``path`` passed in was relative (non-absolute), the value of the 

197 ``resource`` argument passed to this function is returned. 

198 

199 - ``view_name``: The :term:`view name` found during 

200 :term:`traversal` or :term:`url dispatch`; if the ``resource`` was 

201 found via traversal, this is usually a representation of the 

202 path segment which directly follows the path to the ``context`` 

203 in the ``path``. The ``view_name`` will be a Unicode object or 

204 the empty string. The ``view_name`` will be the empty string if 

205 there is no element which follows the ``context`` path. An 

206 example: if the path passed is ``/foo/bar``, and a resource 

207 object is found at ``/foo`` (but not at ``/foo/bar``), the 'view 

208 name' will be ``u'bar'``. If the ``resource`` was found via 

209 urldispatch, the view_name will be the name the route found was 

210 registered with. 

211 

212 - ``subpath``: For a ``resource`` found via :term:`traversal`, this 

213 is a sequence of path segments found in the ``path`` that follow 

214 the ``view_name`` (if any). Each of these items is a Unicode 

215 object. If no path segments follow the ``view_name``, the 

216 subpath will be the empty sequence. An example: if the path 

217 passed is ``/foo/bar/baz/buz``, and a resource object is found at 

218 ``/foo`` (but not ``/foo/bar``), the 'view name' will be 

219 ``u'bar'`` and the :term:`subpath` will be ``[u'baz', u'buz']``. 

220 For a ``resource`` found via url dispatch, the subpath will be a 

221 sequence of values discerned from ``*subpath`` in the route 

222 pattern matched or the empty sequence. 

223 

224 - ``traversed``: The sequence of path elements traversed from the 

225 root to find the ``context`` object during :term:`traversal`. 

226 Each of these items is a Unicode object. If no path segments 

227 were traversed to find the ``context`` object (e.g. if the 

228 ``path`` provided is the empty string), the ``traversed`` value 

229 will be the empty sequence. If the ``resource`` is a resource found 

230 via :term:`url dispatch`, traversed will be None. 

231 

232 - ``virtual_root``: A resource object representing the 'virtual' root 

233 of the resource tree being traversed during :term:`traversal`. 

234 See :ref:`vhosting_chapter` for a definition of the virtual root 

235 object. If no virtual hosting is in effect, and the ``path`` 

236 passed in was absolute, the ``virtual_root`` will be the 

237 *physical* root resource object (the object at which :term:`traversal` 

238 begins). If the ``resource`` passed in was found via :term:`URL 

239 dispatch` or if the ``path`` passed in was relative, the 

240 ``virtual_root`` will always equal the ``root`` object (the 

241 resource passed in). 

242 

243 - ``virtual_root_path`` -- If :term:`traversal` was used to find 

244 the ``resource``, this will be the sequence of path elements 

245 traversed to find the ``virtual_root`` resource. Each of these 

246 items is a Unicode object. If no path segments were traversed 

247 to find the ``virtual_root`` resource (e.g. if virtual hosting is 

248 not in effect), the ``traversed`` value will be the empty list. 

249 If url dispatch was used to find the ``resource``, this will be 

250 ``None``. 

251 

252 If the path cannot be resolved, a :exc:`KeyError` will be raised. 

253 

254 Rules for passing a *string* as the ``path`` argument: if the 

255 first character in the path string is the with the ``/`` 

256 character, the path will considered absolute and the resource tree 

257 traversal will start at the root resource. If the first character 

258 of the path string is *not* the ``/`` character, the path is 

259 considered relative and resource tree traversal will begin at the resource 

260 object supplied to the function as the ``resource`` argument. If an 

261 empty string is passed as ``path``, the ``resource`` passed in will 

262 be returned. Resource path strings must be escaped in the following 

263 manner: each Unicode path segment must be encoded as UTF-8 and 

264 each path segment must escaped via Python's :mod:`urllib.quote`. 

265 For example, ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or 

266 ``to%20the/La%20Pe%C3%B1a`` (relative). The 

267 :func:`pyramid.traversal.resource_path` function generates strings 

268 which follow these rules (albeit only absolute ones). 

269 

270 Rules for passing a *tuple* as the ``path`` argument: if the first 

271 element in the path tuple is the empty string (for example ``('', 

272 'a', 'b', 'c')``, the path is considered absolute and the resource tree 

273 traversal will start at the resource tree root object. If the first 

274 element in the path tuple is not the empty string (for example 

275 ``('a', 'b', 'c')``), the path is considered relative and resource tree 

276 traversal will begin at the resource object supplied to the function 

277 as the ``resource`` argument. If an empty sequence is passed as 

278 ``path``, the ``resource`` passed in itself will be returned. No 

279 URL-quoting or UTF-8-encoding of individual path segments within 

280 the tuple is required (each segment may be any string or unicode 

281 object representing a resource name). 

282 

283 Explanation of the conversion of ``path`` segment values to 

284 Unicode during traversal: Each segment is URL-unquoted, and 

285 decoded into Unicode. Each segment is assumed to be encoded using 

286 the UTF-8 encoding (or a subset, such as ASCII); a 

287 :exc:`pyramid.exceptions.URLDecodeError` is raised if a segment 

288 cannot be decoded. If a segment name is empty or if it is ``.``, 

289 it is ignored. If a segment name is ``..``, the previous segment 

290 is deleted, and the ``..`` is ignored. As a result of this 

291 process, the return values ``view_name``, each element in the 

292 ``subpath``, each element in ``traversed``, and each element in 

293 the ``virtual_root_path`` will be Unicode as opposed to a string, 

294 and will be URL-decoded. 

295 """ 

296 

297 if is_nonstr_iter(path): 

298 # the traverser factory expects PATH_INFO to be a string, not 

299 # unicode and it expects path segments to be utf-8 and 

300 # urlencoded (it's the same traverser which accepts PATH_INFO 

301 # from user agents; user agents always send strings). 

302 if path: 

303 path = _join_path_tuple(tuple(path)) 

304 else: 

305 path = '' 

306 

307 # The user is supposed to pass us a string object, never Unicode. In 

308 # practice, however, users indeed pass Unicode to this API. If they do 

309 # pass a Unicode object, its data *must* be entirely encodeable to ASCII, 

310 # so we encode it here as a convenience to the user and to prevent 

311 # second-order failures from cropping up (all failures will occur at this 

312 # step rather than later down the line as the result of calling 

313 # ``traversal_path``). 

314 

315 path = ascii_native_(path) 

316 

317 if path and path[0] == '/': 

318 resource = find_root(resource) 

319 

320 reg = get_current_registry() 

321 

322 request_factory = reg.queryUtility(IRequestFactory) 

323 if request_factory is None: 

324 from pyramid.request import Request # avoid circdep 

325 

326 request_factory = Request 

327 

328 request = request_factory.blank(path) 

329 request.registry = reg 

330 traverser = reg.queryAdapter(resource, ITraverser) 

331 if traverser is None: 

332 traverser = ResourceTreeTraverser(resource) 

333 

334 return traverser(request) 

335 

336 

337def resource_path_tuple(resource, *elements): 

338 """ 

339 Return a tuple representing the absolute physical path of the 

340 ``resource`` object based on its position in a resource tree, e.g 

341 ``('', 'foo', 'bar')``. Any positional arguments passed in as 

342 ``elements`` will be appended as elements in the tuple 

343 representing the resource path. For instance, if the resource's 

344 path is ``('', 'foo', 'bar')`` and elements equals ``('a', 'b')``, 

345 the returned tuple will be ``('', 'foo', 'bar', 'a', 'b')``. The 

346 first element of this tuple will always be the empty string (a 

347 leading empty string element in a path tuple represents that the 

348 path is absolute). 

349 

350 This function is a logical inverse of 

351 :func:`pyramid.traversal.find_resource`: it can be used to 

352 generate path references that can later be resolved by that function. 

353 

354 The ``resource`` passed in *must* be :term:`location`-aware. 

355 

356 .. note:: 

357 

358 Each segment in the path tuple returned will equal the ``__name__`` 

359 attribute of the resource it represents within the resource tree. Each 

360 of these segments *should* be a unicode or string object (as per the 

361 contract of :term:`location`-awareness). However, no conversion or 

362 safety checking of resource names is performed. For instance, if one of 

363 the resources in your tree has a ``__name__`` which (by error) is a 

364 dictionary, that dictionary will be placed in the path tuple; no warning 

365 or error will be given. 

366 

367 .. note:: 

368 

369 The :term:`root` resource *must* have a ``__name__`` attribute with a 

370 value of either ``None`` or the empty string for path tuples to be 

371 generated properly. If the root resource has a non-null ``__name__`` 

372 attribute, its name will be the first element in the generated path 

373 tuple rather than the empty string. 

374 """ 

375 return tuple(_resource_path_list(resource, *elements)) 

376 

377 

378model_path_tuple = resource_path_tuple # b/w compat (forever) 

379 

380 

381def _resource_path_list(resource, *elements): 

382 """ Implementation detail shared by resource_path and 

383 resource_path_tuple""" 

384 path = [loc.__name__ or '' for loc in lineage(resource)] 

385 path.reverse() 

386 path.extend(elements) 

387 return path 

388 

389 

390_model_path_list = _resource_path_list # b/w compat, not an API 

391 

392 

393def virtual_root(resource, request): 

394 """ 

395 Provided any :term:`resource` and a :term:`request` object, return 

396 the resource object representing the :term:`virtual root` of the 

397 current :term:`request`. Using a virtual root in a 

398 :term:`traversal` -based :app:`Pyramid` application permits 

399 rooting. For example, the resource at the traversal path ``/cms`` will 

400 be found at ``http://example.com/`` instead of rooting it at 

401 ``http://example.com/cms/``. 

402 

403 If the ``resource`` passed in is a context obtained via 

404 :term:`traversal`, and if the ``HTTP_X_VHM_ROOT`` key is in the 

405 WSGI environment, the value of this key will be treated as a 

406 'virtual root path': the :func:`pyramid.traversal.find_resource` 

407 API will be used to find the virtual root resource using this path; 

408 if the resource is found, it will be returned. If the 

409 ``HTTP_X_VHM_ROOT`` key is not present in the WSGI environment, 

410 the physical :term:`root` of the resource tree will be returned instead. 

411 

412 Virtual roots are not useful at all in applications that use 

413 :term:`URL dispatch`. Contexts obtained via URL dispatch don't 

414 really support being virtually rooted (each URL dispatch context 

415 is both its own physical and virtual root). However if this API 

416 is called with a ``resource`` argument which is a context obtained 

417 via URL dispatch, the resource passed in will be returned 

418 unconditionally.""" 

419 try: 

420 reg = request.registry 

421 except AttributeError: 

422 reg = get_current_registry() 

423 url_adapter = reg.queryMultiAdapter((resource, request), IResourceURL) 

424 if url_adapter is None: 

425 url_adapter = ResourceURL(resource, request) 

426 

427 vpath, rpath = url_adapter.virtual_path, url_adapter.physical_path 

428 if rpath != vpath and rpath.endswith(vpath): 

429 vroot_path = rpath[: -len(vpath)] 

430 return find_resource(resource, vroot_path) 

431 

432 try: 

433 return request.root 

434 except AttributeError: 

435 return find_root(resource) 

436 

437 

438def traversal_path(path): 

439 """ Variant of :func:`pyramid.traversal.traversal_path_info` suitable for 

440 decoding paths that are URL-encoded. 

441 

442 If this function is passed a Unicode object instead of a sequence of 

443 bytes as ``path``, that Unicode object *must* directly encodeable to 

444 ASCII. For example, u'/foo' will work but u'/<unprintable unicode>' (a 

445 Unicode object with characters that cannot be encoded to ascii) will 

446 not. A :exc:`UnicodeEncodeError` will be raised if the Unicode cannot be 

447 encoded directly to ASCII. 

448 """ 

449 if isinstance(path, text_type): 

450 # must not possess characters outside ascii 

451 path = path.encode('ascii') 

452 # we unquote this path exactly like a PEP 3333 server would 

453 path = unquote_bytes_to_wsgi(path) # result will be a native string 

454 return traversal_path_info(path) # result will be a tuple of unicode 

455 

456 

457@lru_cache(1000) 

458def traversal_path_info(path): 

459 """ Given``path``, return a tuple representing that path which can be 

460 used to traverse a resource tree. ``path`` is assumed to be an 

461 already-URL-decoded ``str`` type as if it had come to us from an upstream 

462 WSGI server as the ``PATH_INFO`` environ variable. 

463 

464 The ``path`` is first decoded to from its WSGI representation to Unicode; 

465 it is decoded differently depending on platform: 

466 

467 - On Python 2, ``path`` is decoded to Unicode from bytes using the UTF-8 

468 decoding directly; a :exc:`pyramid.exc.URLDecodeError` is raised if a the 

469 URL cannot be decoded. 

470 

471 - On Python 3, as per the PEP 3333 spec, ``path`` is first encoded to 

472 bytes using the Latin-1 encoding; the resulting set of bytes is 

473 subsequently decoded to text using the UTF-8 encoding; a 

474 :exc:`pyramid.exc.URLDecodeError` is raised if a the URL cannot be 

475 decoded. 

476 

477 The ``path`` is split on slashes, creating a list of segments. If a 

478 segment name is empty or if it is ``.``, it is ignored. If a segment 

479 name is ``..``, the previous segment is deleted, and the ``..`` is 

480 ignored. 

481 

482 Examples: 

483 

484 ``/`` 

485 

486 () 

487 

488 ``/foo/bar/baz`` 

489 

490 (u'foo', u'bar', u'baz') 

491 

492 ``foo/bar/baz`` 

493 

494 (u'foo', u'bar', u'baz') 

495 

496 ``/foo/bar/baz/`` 

497 

498 (u'foo', u'bar', u'baz') 

499 

500 ``/foo//bar//baz/`` 

501 

502 (u'foo', u'bar', u'baz') 

503 

504 ``/foo/bar/baz/..`` 

505 

506 (u'foo', u'bar') 

507 

508 ``/my%20archives/hello`` 

509 

510 (u'my archives', u'hello') 

511 

512 ``/archives/La%20Pe%C3%B1a`` 

513 

514 (u'archives', u'<unprintable unicode>') 

515 

516 .. note:: 

517 

518 This function does not generate the same type of tuples that 

519 :func:`pyramid.traversal.resource_path_tuple` does. In particular, the 

520 leading empty string is not present in the tuple it returns, unlike 

521 tuples returned by :func:`pyramid.traversal.resource_path_tuple`. As a 

522 result, tuples generated by ``traversal_path`` are not resolveable by 

523 the :func:`pyramid.traversal.find_resource` API. ``traversal_path`` is 

524 a function mostly used by the internals of :app:`Pyramid` and by people 

525 writing their own traversal machinery, as opposed to users writing 

526 applications in :app:`Pyramid`. 

527 """ 

528 try: 

529 path = decode_path_info(path) # result will be Unicode 

530 except UnicodeDecodeError as e: 

531 raise URLDecodeError(e.encoding, e.object, e.start, e.end, e.reason) 

532 return split_path_info(path) # result will be tuple of Unicode 

533 

534 

535@lru_cache(1000) 

536def split_path_info(path): 

537 # suitable for splitting an already-unquoted-already-decoded (unicode) 

538 # path value 

539 path = path.strip('/') 

540 clean = [] 

541 for segment in path.split('/'): 

542 if not segment or segment == '.': 

543 continue 

544 elif segment == '..': 

545 if clean: 

546 del clean[-1] 

547 else: 

548 clean.append(segment) 

549 return tuple(clean) 

550 

551 

552_segment_cache = {} 

553 

554quote_path_segment_doc = """ \ 

555Return a quoted representation of a 'path segment' (such as 

556the string ``__name__`` attribute of a resource) as a string. If the 

557``segment`` passed in is a unicode object, it is converted to a 

558UTF-8 string, then it is URL-quoted using Python's 

559``urllib.quote``. If the ``segment`` passed in is a string, it is 

560URL-quoted using Python's :mod:`urllib.quote`. If the segment 

561passed in is not a string or unicode object, an error will be 

562raised. The return value of ``quote_path_segment`` is always a 

563string, never Unicode. 

564 

565You may pass a string of characters that need not be encoded as 

566the ``safe`` argument to this function. This corresponds to the 

567``safe`` argument to :mod:`urllib.quote`. 

568 

569.. note:: 

570 

571 The return value for each segment passed to this 

572 function is cached in a module-scope dictionary for 

573 speed: the cached version is returned when possible 

574 rather than recomputing the quoted version. No cache 

575 emptying is ever done for the lifetime of an 

576 application, however. If you pass arbitrary 

577 user-supplied strings to this function (as opposed to 

578 some bounded set of values from a 'working set' known to 

579 your application), it may become a memory leak. 

580""" 

581 

582 

583if PY2: 

584 # special-case on Python 2 for speed? unchecked 

585 def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE): 

586 """ %s """ % quote_path_segment_doc 

587 # The bit of this code that deals with ``_segment_cache`` is an 

588 # optimization: we cache all the computation of URL path segments 

589 # in this module-scope dictionary with the original string (or 

590 # unicode value) as the key, so we can look it up later without 

591 # needing to reencode or re-url-quote it 

592 try: 

593 return _segment_cache[(segment, safe)] 

594 except KeyError: 

595 if ( 

596 segment.__class__ is text_type 

597 ): # isinstance slighly slower (~15%) 

598 result = url_quote(segment.encode('utf-8'), safe) 

599 else: 

600 result = url_quote(str(segment), safe) 

601 # we don't need a lock to mutate _segment_cache, as the below 

602 # will generate exactly one Python bytecode (STORE_SUBSCR) 

603 _segment_cache[(segment, safe)] = result 

604 return result 

605 

606 

607else: 

608 

609 def quote_path_segment(segment, safe=PATH_SEGMENT_SAFE): 

610 """ %s """ % quote_path_segment_doc 

611 # The bit of this code that deals with ``_segment_cache`` is an 

612 # optimization: we cache all the computation of URL path segments 

613 # in this module-scope dictionary with the original string (or 

614 # unicode value) as the key, so we can look it up later without 

615 # needing to reencode or re-url-quote it 

616 try: 

617 return _segment_cache[(segment, safe)] 

618 except KeyError: 

619 if segment.__class__ not in (text_type, binary_type): 

620 segment = str(segment) 

621 result = url_quote(native_(segment, 'utf-8'), safe) 

622 # we don't need a lock to mutate _segment_cache, as the below 

623 # will generate exactly one Python bytecode (STORE_SUBSCR) 

624 _segment_cache[(segment, safe)] = result 

625 return result 

626 

627 

628slash = text_('/') 

629 

630 

631@implementer(ITraverser) 

632class ResourceTreeTraverser(object): 

633 """ A resource tree traverser that should be used (for speed) when 

634 every resource in the tree supplies a ``__name__`` and 

635 ``__parent__`` attribute (ie. every resource in the tree is 

636 :term:`location` aware) .""" 

637 

638 VH_ROOT_KEY = VH_ROOT_KEY 

639 VIEW_SELECTOR = '@@' 

640 

641 def __init__(self, root): 

642 self.root = root 

643 

644 def __call__(self, request): 

645 environ = request.environ 

646 matchdict = request.matchdict 

647 

648 if matchdict is not None: 

649 

650 path = matchdict.get('traverse', slash) or slash 

651 if is_nonstr_iter(path): 

652 # this is a *traverse stararg (not a {traverse}) 

653 # routing has already decoded these elements, so we just 

654 # need to join them 

655 path = '/' + slash.join(path) or slash 

656 

657 subpath = matchdict.get('subpath', ()) 

658 if not is_nonstr_iter(subpath): 

659 # this is not a *subpath stararg (just a {subpath}) 

660 # routing has already decoded this string, so we just need 

661 # to split it 

662 subpath = split_path_info(subpath) 

663 

664 else: 

665 # this request did not match a route 

666 subpath = () 

667 try: 

668 # empty if mounted under a path in mod_wsgi, for example 

669 path = request.path_info or slash 

670 except KeyError: 

671 # if environ['PATH_INFO'] is just not there 

672 path = slash 

673 except UnicodeDecodeError as e: 

674 raise URLDecodeError( 

675 e.encoding, e.object, e.start, e.end, e.reason 

676 ) 

677 

678 if self.VH_ROOT_KEY in environ: 

679 # HTTP_X_VHM_ROOT 

680 vroot_path = decode_path_info(environ[self.VH_ROOT_KEY]) 

681 vroot_tuple = split_path_info(vroot_path) 

682 vpath = ( 

683 vroot_path + path 

684 ) # both will (must) be unicode or asciistr 

685 vroot_idx = len(vroot_tuple) - 1 

686 else: 

687 vroot_tuple = () 

688 vpath = path 

689 vroot_idx = -1 

690 

691 root = self.root 

692 ob = vroot = root 

693 

694 if vpath == slash: # invariant: vpath must not be empty 

695 # prevent a call to traversal_path if we know it's going 

696 # to return the empty tuple 

697 vpath_tuple = () 

698 else: 

699 # we do dead reckoning here via tuple slicing instead of 

700 # pushing and popping temporary lists for speed purposes 

701 # and this hurts readability; apologies 

702 i = 0 

703 view_selector = self.VIEW_SELECTOR 

704 vpath_tuple = split_path_info(vpath) 

705 for segment in vpath_tuple: 

706 if segment[:2] == view_selector: 

707 return { 

708 'context': ob, 

709 'view_name': segment[2:], 

710 'subpath': vpath_tuple[i + 1 :], 

711 'traversed': vpath_tuple[: vroot_idx + i + 1], 

712 'virtual_root': vroot, 

713 'virtual_root_path': vroot_tuple, 

714 'root': root, 

715 } 

716 try: 

717 getitem = ob.__getitem__ 

718 except AttributeError: 

719 return { 

720 'context': ob, 

721 'view_name': segment, 

722 'subpath': vpath_tuple[i + 1 :], 

723 'traversed': vpath_tuple[: vroot_idx + i + 1], 

724 'virtual_root': vroot, 

725 'virtual_root_path': vroot_tuple, 

726 'root': root, 

727 } 

728 

729 try: 

730 next = getitem(segment) 

731 except KeyError: 

732 return { 

733 'context': ob, 

734 'view_name': segment, 

735 'subpath': vpath_tuple[i + 1 :], 

736 'traversed': vpath_tuple[: vroot_idx + i + 1], 

737 'virtual_root': vroot, 

738 'virtual_root_path': vroot_tuple, 

739 'root': root, 

740 } 

741 if i == vroot_idx: 

742 vroot = next 

743 ob = next 

744 i += 1 

745 

746 return { 

747 'context': ob, 

748 'view_name': empty, 

749 'subpath': subpath, 

750 'traversed': vpath_tuple, 

751 'virtual_root': vroot, 

752 'virtual_root_path': vroot_tuple, 

753 'root': root, 

754 } 

755 

756 

757ModelGraphTraverser = ( 

758 ResourceTreeTraverser 

759) # b/w compat, not API, used in wild 

760 

761 

762@implementer(IResourceURL) 

763class ResourceURL(object): 

764 VH_ROOT_KEY = VH_ROOT_KEY 

765 

766 def __init__(self, resource, request): 

767 physical_path_tuple = resource_path_tuple(resource) 

768 physical_path = _join_path_tuple(physical_path_tuple) 

769 

770 if physical_path_tuple != ('',): 

771 physical_path_tuple = physical_path_tuple + ('',) 

772 physical_path = physical_path + '/' 

773 

774 virtual_path = physical_path 

775 virtual_path_tuple = physical_path_tuple 

776 

777 environ = request.environ 

778 vroot_path = environ.get(self.VH_ROOT_KEY) 

779 

780 # if the physical path starts with the virtual root path, trim it out 

781 # of the virtual path 

782 if vroot_path is not None: 

783 vroot_path = vroot_path.rstrip('/') 

784 if vroot_path and physical_path.startswith(vroot_path): 

785 vroot_path_tuple = tuple(vroot_path.split('/')) 

786 numels = len(vroot_path_tuple) 

787 virtual_path_tuple = ('',) + physical_path_tuple[numels:] 

788 virtual_path = physical_path[len(vroot_path) :] 

789 

790 self.virtual_path = virtual_path # IResourceURL attr 

791 self.physical_path = physical_path # IResourceURL attr 

792 self.virtual_path_tuple = virtual_path_tuple # IResourceURL attr (1.5) 

793 self.physical_path_tuple = ( 

794 physical_path_tuple 

795 ) # IResourceURL attr (1.5) 

796 

797 

798@lru_cache(1000) 

799def _join_path_tuple(tuple): 

800 return tuple and '/'.join([quote_path_segment(x) for x in tuple]) or '/' 

801 

802 

803class DefaultRootFactory: 

804 __parent__ = None 

805 __name__ = None 

806 

807 def __init__(self, request): 

808 pass