src/pyams_utils/container.py
changeset 289 c8e21d7dd685
child 292 b338586588ad
equal deleted inserted replaced
-1:000000000000 289:c8e21d7dd685
       
     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 zope.container.interfaces import IContainer, IContained
       
    20 from zope.lifecycleevent.interfaces import IObjectMovedEvent
       
    21 from zope.location.interfaces import ISublocations
       
    22 
       
    23 # import packages
       
    24 from BTrees.OOBTree import OOBTree
       
    25 from persistent.list import PersistentList
       
    26 from pyams_utils.adapter import adapter_config, ContextAdapter
       
    27 from pyramid.threadlocal import get_current_registry
       
    28 from zope.container.ordered import OrderedContainer
       
    29 
       
    30 
       
    31 class BTreeOrderedContainer(OrderedContainer):
       
    32     """BTree based ordered container
       
    33 
       
    34     This container maintain a manual order of it's contents
       
    35     """
       
    36 
       
    37     def __init__(self):
       
    38         self._data = OOBTree()
       
    39         self._order = PersistentList()
       
    40 
       
    41 
       
    42 class ParentSelector(object):
       
    43     """Interface based parent selector
       
    44 
       
    45     This selector can be used as a subscriber predicate on IObjectAddedEvent to define
       
    46     an interface that the new parent must support for the event to be applied::
       
    47 
       
    48     .. code-block:: python
       
    49 
       
    50         from pyams_utils.interfaces.site import ISiteRoot
       
    51 
       
    52         @subscriber(IObjectAddedEvent, parent_selector=ISiteRoot)
       
    53         def siteroot_object_added_event_handler(event):
       
    54             '''This is an event handler for an ISiteRoot object added event'''
       
    55     """
       
    56 
       
    57     def __init__(self, ifaces, config):
       
    58         if not isinstance(ifaces, (list, tuple, set)):
       
    59             ifaces = (ifaces,)
       
    60         self.interfaces = ifaces
       
    61 
       
    62     def text(self):
       
    63         return 'parent_selector = %s' % str(self.interfaces)
       
    64 
       
    65     phash = text
       
    66 
       
    67     def __call__(self, event):
       
    68         if not IObjectMovedEvent.providedBy(event):
       
    69             return False
       
    70         for intf in self.interfaces:
       
    71             try:
       
    72                 if intf.providedBy(event.newParent):
       
    73                     return True
       
    74             except (AttributeError, TypeError):
       
    75                 if isinstance(event.newParent, intf):
       
    76                     return True
       
    77         return False
       
    78 
       
    79 
       
    80 @adapter_config(context=IContained, provides=ISublocations)
       
    81 class ContainerSublocationsAdapter(ContextAdapter):
       
    82     """Contained object sub-locations adapter
       
    83 
       
    84     This adapter checks for custom ISublocations interface adapters which can
       
    85     be defined by any component to get access to inner locations, defined for
       
    86     example via annotations.
       
    87     """
       
    88 
       
    89     def sublocations(self):
       
    90         """See `zope.location.interfaces.ISublocations` interface"""
       
    91         context = self.context
       
    92         # Check for adapted sub-locations first...
       
    93         registry = get_current_registry()
       
    94         for name, adapter in registry.getAdapters((context,), ISublocations):
       
    95             if not name:  # don't reuse default adapter!!
       
    96                 continue
       
    97             yield from adapter.sublocations()
       
    98         # then yield container items
       
    99         if IContainer.providedBy(context):
       
   100             yield from context.values()
       
   101 
       
   102 
       
   103 def find_objects_matching(root, condition, ignore_root=False):
       
   104     """Find all objects in root that match the condition
       
   105 
       
   106     The condition is a Python callable object that takes an object as
       
   107     argument and must return a boolean result.
       
   108 
       
   109     All sub-objects of the root will also be searched recursively.
       
   110 
       
   111     :param object root: the parent object from which search is started
       
   112     :param callable condition: a callable object which may return true for a given
       
   113         object to be selected
       
   114     :param boolean ignore_root: if *True*, the root object will not be returned, even if it matches
       
   115         the given condition
       
   116     :return: an iterator for all root's sub-objects matching condition
       
   117     """
       
   118     if (not ignore_root) and condition(root):
       
   119         yield root
       
   120     locations = ISublocations(root, None)
       
   121     if locations is not None:
       
   122         for location in locations.sublocations():
       
   123             if condition(location):
       
   124                 yield location
       
   125             yield from find_objects_matching(location, condition, ignore_root=True)
       
   126 
       
   127 
       
   128 def find_objects_providing(root, interface):
       
   129     """Find all objects in root that provide the specified interface
       
   130 
       
   131     All sub-objects of the root will also be searched recursively.
       
   132 
       
   133     :param object root: object; the parent object from which search is started
       
   134     :param Interface interface: interface; an interface that sub-objects should provide
       
   135     :return: an iterator for all root's sub-objects that provide the given interface
       
   136     """
       
   137     yield from find_objects_matching(root, interface.providedBy)