diff -r 000000000000 -r c8e21d7dd685 src/pyams_utils/container.py --- /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 +# 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)