merge default doc-dc
authorDamien Correia
Fri, 25 May 2018 11:27:33 +0200
branchdoc-dc
changeset 257 a958ccc01e40
parent 256 24d96b180e55 (current diff)
parent 183 5e239a213d1d (diff)
child 258 86d0f27c7f46
merge default
--- a/src/pyams_utils/adapter.py	Fri May 18 11:32:22 2018 +0200
+++ b/src/pyams_utils/adapter.py	Fri May 25 11:27:33 2018 +0200
@@ -76,7 +76,7 @@
 
     Annotation parameters can be:
 
-    :param str name: (default=''), name of the adapter
+    :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
     """
@@ -140,10 +140,10 @@
     :param factory: if annotations key is not found, this is the factory which will be used to
         create a new object
     :param markers: if not None, list of marker interfaces which created adapter should provide
-    :param bool notify: if 'False', no notification event will be sent on object creation
-    :param bool locate: if 'False', the new object is not attached to any parent
-    :param object parent: parent to which new object is attached
-    :param str name: if locate is not False, this is the name with which the new object is attached
+    :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.
     """
     annotations = IAnnotations(context, None)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_utils/factory.py	Fri May 25 11:27:33 2018 +0200
@@ -0,0 +1,126 @@
+#
+# Copyright (c) 2008-2018 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 logging
+logger = logging.getLogger('PyAMS (utils)')
+
+import venusian
+
+# import interfaces
+from pyams_utils.interfaces import IObjectFactory
+
+# import packages
+from pyams_utils.registry import get_global_registry
+from zope.component import adapter, queryAdapter
+from zope.interface import implementer, Interface
+
+
+@adapter(Interface)
+@implementer(IObjectFactory)
+class ObjectFactoryAdapter(object):
+    """Most basic object factory adapter"""
+
+    factory = None
+
+    def __init__(self, context):
+        self.context = context
+
+    def __call__(self):
+        return self.factory()
+
+
+def get_interface_name(iface):
+    """Get interface full name"""
+    return iface.__module__ + '.' + iface.__name__
+
+
+def register_factory(interface, klass, registry=None, name=''):
+    """Register factory for a given interface
+
+    :param interface: the interface for which the factory is registered
+    :param klass: the object factory
+    :param registry: the registry into which factory adapter should be registered; if None, the global
+        registry is used
+    :param name: custom name given to registered factory
+    """
+
+    class Temp(ObjectFactoryAdapter):
+        factory = klass
+
+    if_name = get_interface_name(interface)
+    if name:
+        if_name = '{0}::{1}'.format(if_name, name)
+    if registry is None:
+        registry = get_global_registry()
+    registry.registerAdapter(Temp, name=if_name)
+
+
+class factory_config(object):
+    """Class decorator to declare a default object factory"""
+
+    venusian = venusian
+
+    def __init__(self, **settings):
+        self.__dict__.update(settings)
+
+    def __call__(self, wrapped):
+        settings = self.__dict__.copy()
+        depth = settings.pop('_depth', 0)
+
+        def callback(context, name, ob):
+            name = settings.get('name', '')
+            provided = settings.get('provided')
+            if not provided:
+                raise TypeError("No provided interface(s) was given for registered factory %r" % ob)
+            if not isinstance(provided, tuple):
+                provided = (provided,)
+
+            config = context.config.with_package(info.module)
+            for interface in provided:
+                if name:
+                    logger.debug("Registering factory {0} for interface {1} with name {2}".format(str(ob),
+                                                                                                  str(interface),
+                                                                                                  name))
+                else:
+                    logger.debug("Registering default factory {0} for interface {1}".format(str(ob), str(interface)))
+                register_factory(interface, ob, config.registry, name)
+
+        info = self.venusian.attach(wrapped, callback, category='pyams_factory', 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_object_factory(interface, registry=None, name=''):
+    """Get registered factory for given interface
+
+    :param interface: the interface for which a factory is requested
+    :param registry: the registry into which registered factory should be looked for
+    :param name: name of requested factory
+    :return: the requested object factory, or None if it can't be found
+    """
+    if_name = get_interface_name(interface)
+    if name:
+        if_name = '{0}::{1}'.format(if_name, name)
+    if registry is None:
+        registry = get_global_registry()
+    return registry.queryAdapter(interface, IObjectFactory, name=if_name)
--- a/src/pyams_utils/interfaces/__init__.py	Fri May 18 11:32:22 2018 +0200
+++ b/src/pyams_utils/interfaces/__init__.py	Fri May 25 11:27:33 2018 +0200
@@ -101,5 +101,19 @@
     """Error raised when no request is available"""
 
 
+class IObjectFactory(Interface):
+    """Object factory interface
+
+    This interface can be used to register an "interface's object factory".
+    For a given interface, such factory can be used to get an instance of an object providing
+    this interface; several factories can be registered for the same interface if they have distinct
+    names. See :py:mod:`pyams_utils.factory` module.
+    """
+
+
 class ICacheKeyValue(Interface):
-    """Interface used to get string representation of a given object as cache key"""
+    """Interface used to get string representation of a given object as cache key
+
+    Several default adapters are given for objects (using their "id()"), strings (using string as key)
+    and for persistent objects (using their persistent OID); you are free to provide your own adapters.
+    """
--- a/src/pyams_utils/vocabulary.py	Fri May 18 11:32:22 2018 +0200
+++ b/src/pyams_utils/vocabulary.py	Fri May 25 11:27:33 2018 +0200
@@ -17,6 +17,8 @@
 import logging
 logger = logging.getLogger('PyAMS (utils)')
 
+import venusian
+
 # import interfaces
 from zope.schema.interfaces import IVocabularyFactory
 
@@ -64,11 +66,30 @@
                                          required=False)
     """
 
-    def __init__(self, name):
+    venusian = venusian
+
+    def __init__(self, name, **settings):
         self.name = name
+        self.__dict__.update(settings)
+
+    def __call__(self, wrapped):
+        settings = self.__dict__.copy()
+        depth = settings.pop('_depth', 0)
 
-    def __call__(self, klass):
-        logger.debug('Registering class {0} as vocabulary with name "{1}"'.format(str(klass), self.name))
-        directlyProvides(klass, IVocabularyFactory)
-        getVocabularyRegistry().register(self.name, klass)
-        return klass
+        def callback(context, name, ob):
+            logger.debug('Registering class {0} as vocabulary with name "{1}"'.format(str(ob), self.name))
+            directlyProvides(ob, IVocabularyFactory)
+            getVocabularyRegistry().register(self.name, ob)
+
+        info = self.venusian.attach(wrapped, callback, category='pyams_vocabulary',
+                                    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