src/pyams_utils/adapter.py
changeset 289 c8e21d7dd685
child 292 b338586588ad
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/adapter.py	Wed Dec 05 12:45:56 2018 +0100
@@ -0,0 +1,185 @@
+#
+# 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.
+#
+
+"""Adapters management package
+
+This package provides a small set of standard base adapters for *context*, *context* and *request*, and
+*context* and *request* and *view*.
+
+See :ref:`zca` to see how PyAMS can help components management.
+"""
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+import logging
+logger = logging.getLogger('PyAMS (utils)')
+
+import venusian
+
+# import interfaces
+from zope.annotation.interfaces import IAnnotations
+
+# import packages
+from pyams_utils.factory import get_object_factory, is_interface
+from pyams_utils.registry import get_current_registry
+from zope.interface import implementedBy, alsoProvides, Interface
+from zope.lifecycleevent import ObjectCreatedEvent
+from zope.location import locate as zope_locate
+
+
+class ContextAdapter(object):
+    """Context adapter"""
+
+    def __init__(self, context):
+        self.context = context
+
+
+class ContextRequestAdapter(object):
+    """Context + request multi-adapter"""
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+
+class ContextRequestViewAdapter(object):
+    """Context + request + view multi-adapter"""
+
+    def __init__(self, context, request, view):
+        self.context = context
+        self.request = request
+        self.view = view
+
+
+class NullAdapter(object):
+    """An adapter which always return None!
+
+    Can be useful to override a default adapter...
+    """
+
+    def __new__(cls, *arsg, **kwargs):
+        return None
+
+
+class adapter_config(object):
+    """Function or class decorator to declare an adapter
+
+    Annotation parameters can be:
+
+    :param str='' name: name of the adapter
+    :param [Interface...] context: an interface, or a tuple of interfaces, that the component adapts
+    :param Interface provides: the interface that the adapter provides
+    """
+
+    venusian = venusian
+
+    def __init__(self, **settings):
+        if 'for_' in settings:
+            if settings.get('context') is None:
+                settings['context'] = settings.pop('for_')
+        self.__dict__.update(settings)
+
+    def __call__(self, wrapped):
+        settings = self.__dict__.copy()
+        depth = settings.pop('_depth', 0)
+
+        def callback(context, name, ob):
+            adapts = settings.get('context')
+            if adapts is None:
+                adapts = getattr(ob, '__component_adapts__', None)
+                if adapts is None:
+                    raise TypeError("No for argument was provided for %r and "
+                                    "can't determine what the factory adapts." % ob)
+            if not isinstance(adapts, tuple):
+                adapts = (adapts,)
+
+            provides = settings.get('provides')
+            if provides is None:
+                intfs = list(implementedBy(ob))
+                if len(intfs) == 1:
+                    provides = intfs[0]
+                if provides is None:
+                    raise TypeError("Missing 'provides' argument")
+
+            config = context.config.with_package(info.module)
+            logger.debug("Registering adapter {0} for {1} providing {2}".format(str(ob),
+                                                                                str(adapts),
+                                                                                str(provides)))
+            config.registry.registerAdapter(ob, adapts, provides, settings.get('name', ''))
+
+        info = self.venusian.attach(wrapped, callback, category='pyams_adapter',
+                                    depth=depth + 1)
+
+        if info.scope == 'class':
+            # if the decorator was attached to a method in a class, or
+            # otherwise executed at class scope, we need to set an
+            # 'attr' into the settings if one isn't already in there
+            if settings.get('attr') is None:
+                settings['attr'] = wrapped.__name__
+
+        settings['_info'] = info.codeinfo  # fbo "action_method"
+        return wrapped
+
+
+def get_annotation_adapter(context, key, factory=None, markers=None, notify=True,
+                           locate=True, parent=None, name=None, callback=None, **kwargs):
+    """Get an adapter via object's annotations, creating it if not existent
+    
+    :param object context: context object which should be adapted
+    :param str key: annotations key to look for
+    :param factory: if annotations key is not found, this is the factory which will be used to
+        create a new object; if factory is None and is requested object can't be found, None is returned
+    :param markers: if not None, list of marker interfaces which created adapter should provide
+    :param bool=True notify: if 'False', no notification event will be sent on object creation
+    :param bool=True locate: if 'False', the new object is not attached to any parent
+    :param object=None parent: parent to which new object is attached
+    :param str=None name: if locate is not False, this is the name with which the new object is attached
+        to it's parent.
+    :param callback: if not None, callback function which will be called after
+    """
+    annotations = IAnnotations(context, None)
+    if annotations is None:
+        return None
+    adapter = annotations.get(key)
+    if adapter is None:
+        if 'default' in kwargs:
+            return kwargs['default']
+        elif factory is None:
+            return None
+        else:
+            if is_interface(factory):
+                factory = get_object_factory(factory)
+                assert factory is not None, "Missing object factory"
+            adapter = annotations[key] = factory()
+            if markers:
+                if not isinstance(markers, (list, tuple, set)):
+                    markers = {markers}
+                for marker in markers:
+                    alsoProvides(adapter, marker)
+            if notify:
+                get_current_registry().notify(ObjectCreatedEvent(adapter))
+            if locate:
+                zope_locate(adapter, context if parent is None else parent, name)
+            if callback:
+                callback(adapter)
+    return adapter
+
+
+def get_adapter_weight(item):
+    """Get adapters weight sort key"""
+    name, adapter = item
+    try:
+        return int(adapter.weight), name
+    except (TypeError, AttributeError):
+        return 0, name