Source code for pyams_utils.adapter

#
# 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.
"""

import logging

import venusian
from zope.annotation.interfaces import IAnnotations
from zope.interface import alsoProvides, implementedBy
from zope.lifecycleevent import ObjectCreatedEvent
from zope.location import locate as zope_locate

from pyams_utils.factory import get_object_factory, is_interface
from pyams_utils.registry import get_current_registry


__docformat__ = 'restructuredtext'

LOGGER = logging.getLogger('PyAMS (utils)')


[docs]class ContextAdapter: """Context adapter""" def __init__(self, context): self.context = context
[docs]class ContextRequestAdapter: """Context + request multi-adapter""" def __init__(self, context, request): self.context = context self.request = request
[docs]class ContextRequestViewAdapter: """Context + request + view multi-adapter""" def __init__(self, context, request, view): self.context = context self.request = request self.view = view
[docs]class NullAdapter: """An adapter which always return None! Can be useful to override a default adapter... """ def __new__(cls, *args, **kwargs): # pylint: disable=unused-argument return None
[docs]class adapter_config: # pylint: disable=invalid-name """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 :param registry: the registry into which adapter registration should be made """ 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, obj): adapts = settings.get('context') if adapts is None: adapts = getattr(obj, '__component_adapts__', None) if adapts is None: raise TypeError("No for argument was provided for %r and " "can't determine what the factory adapts." % obj) if not isinstance(adapts, tuple): adapts = (adapts,) provides = settings.get('provides') if provides is None: intfs = list(implementedBy(obj)) if len(intfs) == 1: provides = intfs[0] if provides is None: raise TypeError("Missing 'provides' argument") LOGGER.debug("Registering adapter %s for %s providing %s", str(obj), str(adapts), str(provides)) registry = settings.get('registry') if registry is None: config = context.config.with_package(info.module) # pylint: disable=no-member registry = config.registry registry.registerAdapter(obj, adapts, provides, settings.get('name', '')) info = self.venusian.attach(wrapped, callback, category='pyams_adapter', depth=depth + 1) if info.scope == 'class': # pylint: disable=no-member # 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 # pylint: disable=no-member return wrapped
[docs]def get_annotation_adapter(context, key, factory=None, markers=None, notify=True, locate=True, parent=None, name=None, callback=None, **kwargs): # pylint: disable=too-many-arguments """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; factory can be a class or callable object, or an interface for which a factory has been registered; 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; if None, object is attached to context :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 object creation """ annotations = IAnnotations(context, None) if annotations is None: return None adapter = annotations.get(key) # pylint: disable=assignment-from-no-return if adapter is None: if 'default' in kwargs: return kwargs['default'] if factory is None: return None 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
[docs]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