--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/container.py Wed Dec 05 12:45:56 2018 +0100
@@ -0,0 +1,137 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+
+# import interfaces
+from zope.container.interfaces import IContainer, IContained
+from zope.lifecycleevent.interfaces import IObjectMovedEvent
+from zope.location.interfaces import ISublocations
+
+# import packages
+from BTrees.OOBTree import OOBTree
+from persistent.list import PersistentList
+from pyams_utils.adapter import adapter_config, ContextAdapter
+from pyramid.threadlocal import get_current_registry
+from zope.container.ordered import OrderedContainer
+
+
+class BTreeOrderedContainer(OrderedContainer):
+ """BTree based ordered container
+
+ This container maintain a manual order of it's contents
+ """
+
+ def __init__(self):
+ self._data = OOBTree()
+ self._order = PersistentList()
+
+
+class ParentSelector(object):
+ """Interface based parent selector
+
+ This selector can be used as a subscriber predicate on IObjectAddedEvent to define
+ an interface that the new parent must support for the event to be applied::
+
+ .. code-block:: python
+
+ from pyams_utils.interfaces.site import ISiteRoot
+
+ @subscriber(IObjectAddedEvent, parent_selector=ISiteRoot)
+ def siteroot_object_added_event_handler(event):
+ '''This is an event handler for an ISiteRoot object added event'''
+ """
+
+ def __init__(self, ifaces, config):
+ if not isinstance(ifaces, (list, tuple, set)):
+ ifaces = (ifaces,)
+ self.interfaces = ifaces
+
+ def text(self):
+ return 'parent_selector = %s' % str(self.interfaces)
+
+ phash = text
+
+ def __call__(self, event):
+ if not IObjectMovedEvent.providedBy(event):
+ return False
+ for intf in self.interfaces:
+ try:
+ if intf.providedBy(event.newParent):
+ return True
+ except (AttributeError, TypeError):
+ if isinstance(event.newParent, intf):
+ return True
+ return False
+
+
+@adapter_config(context=IContained, provides=ISublocations)
+class ContainerSublocationsAdapter(ContextAdapter):
+ """Contained object sub-locations adapter
+
+ This adapter checks for custom ISublocations interface adapters which can
+ be defined by any component to get access to inner locations, defined for
+ example via annotations.
+ """
+
+ def sublocations(self):
+ """See `zope.location.interfaces.ISublocations` interface"""
+ context = self.context
+ # Check for adapted sub-locations first...
+ registry = get_current_registry()
+ for name, adapter in registry.getAdapters((context,), ISublocations):
+ if not name: # don't reuse default adapter!!
+ continue
+ yield from adapter.sublocations()
+ # then yield container items
+ if IContainer.providedBy(context):
+ yield from context.values()
+
+
+def find_objects_matching(root, condition, ignore_root=False):
+ """Find all objects in root that match the condition
+
+ The condition is a Python callable object that takes an object as
+ argument and must return a boolean result.
+
+ All sub-objects of the root will also be searched recursively.
+
+ :param object root: the parent object from which search is started
+ :param callable condition: a callable object which may return true for a given
+ object to be selected
+ :param boolean ignore_root: if *True*, the root object will not be returned, even if it matches
+ the given condition
+ :return: an iterator for all root's sub-objects matching condition
+ """
+ if (not ignore_root) and condition(root):
+ yield root
+ locations = ISublocations(root, None)
+ if locations is not None:
+ for location in locations.sublocations():
+ if condition(location):
+ yield location
+ yield from find_objects_matching(location, condition, ignore_root=True)
+
+
+def find_objects_providing(root, interface):
+ """Find all objects in root that provide the specified interface
+
+ All sub-objects of the root will also be searched recursively.
+
+ :param object root: object; the parent object from which search is started
+ :param Interface interface: interface; an interface that sub-objects should provide
+ :return: an iterator for all root's sub-objects that provide the given interface
+ """
+ yield from find_objects_matching(root, interface.providedBy)