src/pyams_utils/traversing.py
changeset 1 3f89629b9e54
child 31 31d5cfdbd741
equal deleted inserted replaced
0:16d47bd81d84 1:3f89629b9e54
       
     1 #
       
     2 # Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
       
     3 # All Rights Reserved.
       
     4 #
       
     5 # This software is subject to the provisions of the Zope Public License,
       
     6 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
       
     7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
       
     8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
       
     9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
       
    10 # FOR A PARTICULAR PURPOSE.
       
    11 #
       
    12 
       
    13 __docformat__ = 'restructuredtext'
       
    14 
       
    15 
       
    16 # import standard library
       
    17 
       
    18 # import interfaces
       
    19 from pyramid.interfaces import VH_ROOT_KEY
       
    20 from zope.traversing.interfaces import ITraversable, BeforeTraverseEvent
       
    21 
       
    22 # import packages
       
    23 from pyramid.compat import decode_path_info, is_nonstr_iter
       
    24 from pyramid.exceptions import URLDecodeError, NotFound
       
    25 from pyramid.threadlocal import get_current_registry
       
    26 from pyramid.traversal import ResourceTreeTraverser, slash, split_path_info, empty
       
    27 from zope.interface import Interface
       
    28 
       
    29 
       
    30 class NamespaceTraverser(ResourceTreeTraverser):
       
    31     """Custom traverser handling views and namespaces
       
    32 
       
    33     This is an upgraded version of native Pyramid traverser.
       
    34     It adds:
       
    35      - a new BeforeTraverseEvent before traversing each object in the path
       
    36      - support for namespaces with "++" notation
       
    37     """
       
    38 
       
    39     NAMESPACE_SELECTOR = '++'
       
    40 
       
    41     def __call__(self, request):
       
    42 
       
    43         environ = request.environ
       
    44         matchdict = request.matchdict
       
    45 
       
    46         if matchdict is not None:
       
    47             path = matchdict.get('traverse', slash) or slash
       
    48             if is_nonstr_iter(path):
       
    49                 # this is a *traverse stararg (not a {traverse})
       
    50                 # routing has already decoded these elements, so we just
       
    51                 # need to join them
       
    52                 path = '/' + slash.join(path) or slash
       
    53 
       
    54             subpath = matchdict.get('subpath', ())
       
    55             if not is_nonstr_iter(subpath):
       
    56                 # this is not a *subpath stararg (just a {subpath})
       
    57                 # routing has already decoded this string, so we just need
       
    58                 # to split it
       
    59                 subpath = split_path_info(subpath)
       
    60 
       
    61         else:
       
    62             subpath = ()
       
    63             try:
       
    64                 # empty if mounted under a path in mod_wsgi, for example
       
    65                 path = request.path_info or slash
       
    66             except KeyError:
       
    67                 # if environ['PATH_INFO'] is just not there
       
    68                 path = slash
       
    69             except UnicodeDecodeError as e:
       
    70                 raise URLDecodeError(e.encoding, e.object, e.start, e.end, e.reason)
       
    71 
       
    72         if VH_ROOT_KEY in environ:
       
    73             # HTTP_X_VHM_ROOT
       
    74             vroot_path = decode_path_info(environ[VH_ROOT_KEY])
       
    75             vroot_tuple = split_path_info(vroot_path)
       
    76             vpath = vroot_path + path
       
    77             vroot_idx = len(vroot_tuple) - 1
       
    78         else:
       
    79             vroot_tuple = ()
       
    80             vpath = path
       
    81             vroot_idx = -1
       
    82 
       
    83         root = self.root
       
    84         ob = vroot = root
       
    85 
       
    86         request.registry.notify(BeforeTraverseEvent(root, request))
       
    87 
       
    88         if vpath == slash:
       
    89             # invariant: vpath must not be empty
       
    90             # prevent a call to traversal_path if we know it's going
       
    91             # to return the empty tuple
       
    92             vpath_tuple = ()
       
    93 
       
    94         else:
       
    95             # we do dead reckoning here via tuple slicing instead of
       
    96             # pushing and popping temporary lists for speed purposes
       
    97             # and this hurts readability; apologies
       
    98             i = 0
       
    99             view_selector = self.VIEW_SELECTOR
       
   100             ns_selector = self.NAMESPACE_SELECTOR
       
   101             vpath_tuple = split_path_info(vpath)
       
   102 
       
   103             for segment in vpath_tuple:
       
   104                 request.registry.notify(BeforeTraverseEvent(ob, request))
       
   105 
       
   106                 if segment[:2] == view_selector:
       
   107                     return {'context': ob,
       
   108                             'view_name': segment[2:],
       
   109                             'subpath': vpath_tuple[i + 1:],
       
   110                             'traversed': vpath_tuple[:vroot_idx + i + 1],
       
   111                             'virtual_root': vroot,
       
   112                             'virtual_root_path': vroot_tuple,
       
   113                             'root': root}
       
   114 
       
   115                 if segment[:2] == ns_selector:
       
   116                     ns, name = segment[2:].split(ns_selector, 1)
       
   117                     registry = get_current_registry()
       
   118                     traverser = registry.queryMultiAdapter((ob, request), ITraversable, ns)
       
   119                     if traverser is None:
       
   120                         traverser = registry.queryAdapter(ob, ITraversable, ns)
       
   121                     if traverser is None:
       
   122                         traverser = registry.queryAdapter(request, ITraversable, ns)
       
   123                     if traverser is None:
       
   124                         raise NotFound()
       
   125                     ob = traverser.traverse(name, vpath_tuple[vroot_idx + i + 1:])
       
   126                     i += 1
       
   127                     continue
       
   128 
       
   129                 try:
       
   130                     getitem = ob.__getitem__
       
   131                 except AttributeError:
       
   132                     return {'context': ob,
       
   133                             'view_name': segment,
       
   134                             'subpath': vpath_tuple[i + 1:],
       
   135                             'traversed': vpath_tuple[:vroot_idx + i + 1],
       
   136                             'virtual_root': vroot,
       
   137                             'virtual_root_path': vroot_tuple,
       
   138                             'root': root}
       
   139 
       
   140                 try:
       
   141                     next = getitem(segment)
       
   142                 except KeyError:
       
   143                     return {'context': ob,
       
   144                             'view_name': segment,
       
   145                             'subpath': vpath_tuple[i + 1:],
       
   146                             'traversed': vpath_tuple[:vroot_idx + i + 1],
       
   147                             'virtual_root': vroot,
       
   148                             'virtual_root_path': vroot_tuple,
       
   149                             'root': root}
       
   150                 if i == vroot_idx:
       
   151                     vroot = next
       
   152                 ob = next
       
   153                 i += 1
       
   154 
       
   155         return {'context': ob,
       
   156                 'view_name': empty,
       
   157                 'subpath': subpath,
       
   158                 'traversed': vpath_tuple,
       
   159                 'virtual_root': vroot,
       
   160                 'virtual_root_path': vroot_tuple,
       
   161                 'root': root}
       
   162 
       
   163 
       
   164 def get_parent(context, interface=Interface, allow_context=True):
       
   165     """Get first parent of the context that implements given interface"""
       
   166     if allow_context:
       
   167         parent = context
       
   168     else:
       
   169         parent = getattr(context, '__parent__', None)
       
   170     while parent is not None:
       
   171         if interface.providedBy(parent):
       
   172             return interface(parent)
       
   173         parent = getattr(parent, '__parent__', None)
       
   174     return None