# HG changeset patch # User Damien Correia # Date 1527240453 -7200 # Node ID a958ccc01e404f2b280706af17a688d638d58a70 # Parent 24d96b180e55d41885169ba3409bc276308e78a6# Parent 5e239a213d1d6b089fee55e67059581669d2aac4 merge default diff -r 24d96b180e55 -r a958ccc01e40 src/pyams_utils/adapter.py --- 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) diff -r 24d96b180e55 -r a958ccc01e40 src/pyams_utils/factory.py --- /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 +# 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) diff -r 24d96b180e55 -r a958ccc01e40 src/pyams_utils/interfaces/__init__.py --- 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. + """ diff -r 24d96b180e55 -r a958ccc01e40 src/pyams_utils/vocabulary.py --- 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