[Checkins] SVN: zope.generic/trunk/src/zope/generic/ generic configuration adapters:

Dominik Huber dominik.huber at perse.ch
Tue Apr 18 12:48:57 EDT 2006


Log message for revision 67080:
  generic configuration adapters:
  Sub directive configurationAdapter
  Transient ConfigurationAdapterClass factory
  
  Fix:
  ConfigurationData -> allow to store IPersistent attributes

Changed:
  U   zope.generic/trunk/src/zope/generic/component/base.py
  U   zope.generic/trunk/src/zope/generic/type/README.txt
  U   zope.generic/trunk/src/zope/generic/type/adapter.py
  U   zope.generic/trunk/src/zope/generic/type/helper.py
  U   zope.generic/trunk/src/zope/generic/type/meta.zcml
  U   zope.generic/trunk/src/zope/generic/type/metaconfigure.py
  U   zope.generic/trunk/src/zope/generic/type/metadirectives.py
  U   zope.generic/trunk/src/zope/generic/type/testing.py
  U   zope.generic/trunk/src/zope/generic/type/tests.py

-=-
Modified: zope.generic/trunk/src/zope/generic/component/base.py
===================================================================
--- zope.generic/trunk/src/zope/generic/component/base.py	2006-04-18 16:22:46 UTC (rev 67079)
+++ zope.generic/trunk/src/zope/generic/component/base.py	2006-04-18 16:48:55 UTC (rev 67080)
@@ -19,6 +19,7 @@
 __docformat__ = 'restructuredtext'
 
 from persistent import Persistent
+from persistent import IPersistent
 from persistent.dict import PersistentDict
 
 from zope.app.annotation.interfaces import IAttributeAnnotatable
@@ -220,7 +221,7 @@
         schema = self.__dict__['__keyface__']
         data = self.__dict__['_ConfigurationData__data']
 
-        if name != '__provides__':
+        if not(name == '__provides__' or name in IPersistent):
             try:
                 field = schema[name]
             except KeyError:
@@ -234,7 +235,7 @@
 
 
 
-class InformationProvider(KeyfaceDescription, dict):
+class InformationProvider(KeyfaceDescription):
     """Generic information provider.
 
     Information do relate a dedicated type of information marked as an interface

Modified: zope.generic/trunk/src/zope/generic/type/README.txt
===================================================================
--- zope.generic/trunk/src/zope/generic/type/README.txt	2006-04-18 16:22:46 UTC (rev 67079)
+++ zope.generic/trunk/src/zope/generic/type/README.txt	2006-04-18 16:48:55 UTC (rev 67080)
@@ -165,6 +165,9 @@
     ...	       keyface='example.IAnyConfiguration'
     ...        data='example.typedata'
     ...	   />
+    ...    <configurationAdapter
+    ...        keyface='example.IAnyConfiguration'
+    ...       />
     ... </generic:type>
     ... ''')
 
@@ -221,3 +224,26 @@
 
 	>>> api.acquireObjectConfiguration(bar, IAnyConfiguration).any
 	u'Guguseli from Object!'
+
+The configurationAdapter subdirective provides an adapter too:
+
+    >>> IOtherConfiguration(bar)
+    Traceback (most recent call last):
+    ...
+    TypeError: ('Could not adapt', ... example.IOtherConfiguration>) 
+
+    >>> IAnyConfiguration(bar).any
+    u'Guguseli from Object!'
+
+If we remove the object's configuration the adapter will invoke
+the type configuration, but only the object's configuration can be set:
+
+    >>> from zope.generic.component.api import deleteInformation
+    >>> deleteInformation(bar, IAnyConfiguration)
+
+    >>> IAnyConfiguration(bar).any
+    u'Guguseli from Type!'
+
+    >>> IAnyConfiguration(bar).any = u'Guguseli from Object another time!'
+    >>> IAnyConfiguration(bar).any
+    u'Guguseli from Object another time!'

Modified: zope.generic/trunk/src/zope/generic/type/adapter.py
===================================================================
--- zope.generic/trunk/src/zope/generic/type/adapter.py	2006-04-18 16:22:46 UTC (rev 67079)
+++ zope.generic/trunk/src/zope/generic/type/adapter.py	2006-04-18 16:48:55 UTC (rev 67080)
@@ -18,17 +18,16 @@
 
 __docformat__ = 'restructuredtext'
 
-from BTrees.OOBTree import OOBTree
-import transaction
-from UserDict import DictMixin
-
 from zope.app.location import Location
-from zope.app.location.interfaces import ILocation
 from zope.component import adapts
-from zope.event import notify
+from zope.interface import classImplements
 from zope.interface import implements
 
+from zope.generic.component import IAttributeKeyface
+from zope.generic.component import IConfigurations
+from zope.generic.component import IConfigurationType
 from zope.generic.component.api import provideInformation
+from zope.generic.component.api import ConfigurationData
 
 from zope.generic.type import IInitializer
 from zope.generic.type import IInitializerConfiguration
@@ -58,3 +57,190 @@
 
             if config.handler:
                 config.handler(self.context, *pos, **kws)
+
+
+_marker = object()
+
+class ConfigurationAdapterProperty(object):
+    """Compute configuration adapter attributes based on schema fields
+
+    Field properties provide default values, data validation and error messages
+    based on data found in field meta-data.
+
+    Note that ConfigurationAdapterProperty cannot be used with slots. 
+    They can only be used for attributes stored in instance configurations
+    dictionaries.
+    """
+
+    def __init__(self, field, name=None):
+        if name is None:
+            name = field.__name__
+
+        self.__field = field
+        self.__name = name
+
+    def __get__(self, inst, klass):
+        if inst is None:
+            return self
+
+        configurations = inst.__configurations__
+        keyface = inst.__keyface__
+        context = inst.__context__
+
+        configuration = inst.__keyface__(configurations, None)
+        if configuration is None:
+            # Try to evaluate a type configuration
+            configuration = queryTypeConfiguration(context, keyface)
+
+        value = getattr(configuration, self.__name, _marker)
+        if value is _marker:
+            field = self.__field.bind(inst)
+            value = getattr(field, 'default', _marker)
+            if value is _marker:
+                raise AttributeError(self.__name)
+
+        return value
+
+    def __set__(self, inst, value):
+        field = self.__field.bind(inst)
+        field.validate(value)
+        if field.readonly:
+            raise ValueError(self.__name, 'field is readonly')
+
+        configurations = inst.__configurations__
+        keyface = inst.__keyface__
+        # update existing configuration
+        if keyface in configurations:
+            configurations.update(keyface, {self.__name: value})
+            
+        # create a new configuration
+        else:
+            configurations[keyface] = ConfigurationData(keyface, {self.__name: value})
+
+        inst.__dict__[self.__name] = value
+
+    def __getattr__(self, name):
+        return getattr(self.__field, name)
+
+
+
+class ConfigurationAdapterBase(Location):
+    """Base mixin for simple configuration adapter."""
+
+    __keyface__ = None
+
+    def __init__(self, context):
+        self.__context__ = context
+        self.__configurations__ = IConfigurations(context)
+
+
+
+def ConfigurationAdapterClass(keyface, bases=()):
+    """Generic configuration adapter class factory.
+
+    The generic configuration adapter is a generic adapter class for 
+    configurations based instances. First we declare a configuration
+    schema:
+
+        >>> from zope.interface import Interface
+        >>> from zope.schema import TextLine
+
+        >>> class IFooConfiguration(Interface):
+        ...    foo = TextLine(title=u'Foo')
+
+    We register the configuration schema using generic:configuration directive:
+
+        >>> registerDirective('''
+        ... <generic:configuration
+        ...     keyface="example.IFooConfiguration"
+        ...     />
+        ... ''') 
+
+        >>> from zope.generic.component import IConfigurationType
+        >>> IConfigurationType.providedBy(IFooConfiguration)
+        True
+
+    We use a generic type including a default configuration for our example, too:
+
+        >>> class IFoo(Interface):
+        ...    pass
+
+        >>>
+
+        >>> from zope.generic.component.api import ConfigurationData
+
+        >>> foo_configuration = ConfigurationData(IFooConfiguration, {'foo': u'Type Foo'})
+
+        >>> registerDirective('''
+        ... <generic:type
+        ...     keyface="example.IFoo"
+        ...        class='zope.generic.type.api.Object'
+        ...     >
+        ...    <configuration
+        ...        keyface='example.IFooConfiguration'
+        ...        data='example.foo_configuration'
+        ...       />
+        ... </generic:type>
+        ... ''')
+
+        >>> foo = api.createObject(IFoo)
+        >>> IFoo.providedBy(foo)
+        True
+        >>> api.queryTypeConfiguration(foo, IFooConfiguration).foo
+        u'Type Foo'
+
+    At the moment the foo instance does not provide a foo configuration:
+
+        >>> api.queryObjectConfiguration(foo, IFooConfiguration) is None
+        True
+
+    The simple configuration adapter function will construct an adapter class
+    implementing the IFooConfiguration interface:
+
+        >>> from zope.generic.type.adapter import ConfigurationAdapterClass
+        
+        >>> FooConfigurationAdapter = ConfigurationAdapterClass(IFooConfiguration)
+        >>> IFooConfiguration.implementedBy(FooConfigurationAdapter)
+        True
+
+    We can adapt our foo to IFooConfiguration:
+
+        >>> adapted = FooConfigurationAdapter(foo)
+        >>> IFooConfiguration.providedBy(adapted)
+        True
+        >>> adapted.foo
+        u'Type Foo'
+
+        >>> adapted.foo = u'Object Foo.'
+        >>> adapted.foo
+        u'Object Foo.'
+
+        >>> api.queryObjectConfiguration(foo, IFooConfiguration).foo
+        u'Object Foo.'
+
+    """
+
+    # preconditions
+    if not IConfigurationType.providedBy(keyface):
+        raise ValueError('Interface must provide %s.' % IConfigurationType.__name__)
+
+    # essentails
+    if not bases:
+        bases = (ConfigurationAdapterBase, )
+
+    class_ = type('ConfigurationAdapterClass from %s' % keyface, bases,
+                  {'__keyface__': keyface})
+
+    classImplements(class_, keyface)
+
+    # add field properties for each object field
+    for name in keyface:
+        field = keyface[name]
+        setattr(class_, name, ConfigurationAdapterProperty(keyface[name]))
+
+        # security assertions
+        # protectName(class_, name, 'zope.ManageSite')
+        # protectSetAttribute(class_, name, 'zope.ManageSite')
+
+    return class_
+

Modified: zope.generic/trunk/src/zope/generic/type/helper.py
===================================================================
--- zope.generic/trunk/src/zope/generic/type/helper.py	2006-04-18 16:22:46 UTC (rev 67079)
+++ zope.generic/trunk/src/zope/generic/type/helper.py	2006-04-18 16:48:55 UTC (rev 67080)
@@ -31,9 +31,9 @@
 
 
 
-def createObject(interface, *pos, **kws):
+def createObject(keyface, *pos, **kws):
     """Create an instance of a logical type using the type marker."""
-    return component.createObject(toDottedName(interface), *pos, **kws)
+    return component.createObject(toDottedName(keyface), *pos, **kws)
 
 
 def createParameter(keyface):

Modified: zope.generic/trunk/src/zope/generic/type/meta.zcml
===================================================================
--- zope.generic/trunk/src/zope/generic/type/meta.zcml	2006-04-18 16:22:46 UTC (rev 67079)
+++ zope.generic/trunk/src/zope/generic/type/meta.zcml	2006-04-18 16:48:55 UTC (rev 67080)
@@ -20,6 +20,12 @@
           schema="zope.generic.type.metadirectives.IInitializerSubdirective"
           />
 
+
+      <meta:subdirective
+          name="configurationAdapter"
+          schema=".metadirectives.IConfigurationAdapterSubdirective"
+          />
+
     </meta:complexDirective>
 
   </meta:directives>

Modified: zope.generic/trunk/src/zope/generic/type/metaconfigure.py
===================================================================
--- zope.generic/trunk/src/zope/generic/type/metaconfigure.py	2006-04-18 16:22:46 UTC (rev 67079)
+++ zope.generic/trunk/src/zope/generic/type/metaconfigure.py	2006-04-18 16:48:55 UTC (rev 67080)
@@ -20,6 +20,7 @@
 
 from types import ModuleType
 
+from zope.app.component.contentdirective import ClassDirective
 from zope.app.component.metaconfigure import proxify
 from zope.app.component.metaconfigure import adapter
 from zope.component import provideAdapter
@@ -40,6 +41,7 @@
 from zope.generic.type import ITypeInformation
 from zope.generic.type import ITypeType
 from zope.generic.type.adapter import Initializer
+from zope.generic.type.adapter import ConfigurationAdapterClass
 from zope.generic.type.factory import TypeFactory
 from zope.generic.type.helper import queryTypeInformation
 
@@ -120,7 +122,9 @@
         if keyface is not None:
             data['keyface'] = keyface
 
-        adapter(self._context, [Initializer], None, [self._keyface], None, '', True, False)
+        adapter(self._context, factory=[Initializer], provides=None, 
+                for_=[self._keyface], permission=None, name='', trusted=True, 
+                locate=False)
 
         _context.action(
             discriminator = (
@@ -128,3 +132,23 @@
             callable = provideTypeConfiguration,
             args = (self._keyface, IInitializerConfiguration, data),
             )
+
+    def configurationAdapter(self, _context, keyface, class_=None, writePermission=None, readPermission=None):
+        """Provide a generic configuration adatper."""
+
+        # we will provide a generic adapter class
+        if class_ is None:
+            class_ = ConfigurationAdapterClass(keyface)
+
+        # register class
+        class_directive = ClassDirective(_context, class_)
+        if writePermission:
+            class_directive.require(_context, permission=writePermission, set_schema=[keyface])
+
+        if readPermission:
+            class_directive.require(_context, permission=readPermission, interface=[keyface])
+
+        # register adapter
+        adapter(self._context, factory=[class_], provides=keyface, 
+                for_=[self._keyface], permission=None, name='', trusted=True, 
+                locate=False)

Modified: zope.generic/trunk/src/zope/generic/type/metadirectives.py
===================================================================
--- zope.generic/trunk/src/zope/generic/type/metadirectives.py	2006-04-18 16:22:46 UTC (rev 67079)
+++ zope.generic/trunk/src/zope/generic/type/metadirectives.py	2006-04-18 16:48:55 UTC (rev 67080)
@@ -19,6 +19,7 @@
 __docformat__ = 'restructuredtext'
 
 from zope.app.i18n import ZopeMessageFactory as _
+from zope.app.security.fields import Permission
 from zope.configuration.fields import Bool
 from zope.configuration.fields import GlobalInterface
 from zope.configuration.fields import GlobalObject
@@ -56,3 +57,35 @@
         description=_('Callable (context, *pos, **kws).'),
         required=False
         )
+
+
+
+class IConfigurationAdapterSubdirective(Interface):
+    """Provide an adapter to a certain configuration."""
+
+    keyface = GlobalInterface(
+        title=_('Configuration Key Interface3'),
+        description=_('Configuration interface defining adapter interface.'),
+        required=True
+        )
+
+    class_ = GlobalObject(
+        title=_('Adapter class'),
+        description=_('If not declared a generic implementation will be used.'),
+        required=False
+        )
+
+    writePermission = Permission(
+        title=_('Write Permission'),
+        description=_('Specifies the permission by id that will be required ' +
+            ' to mutate the attributes and methods specified.'),
+        required=False,
+        )
+
+    readPermission = Permission(
+        title=_('Read Permission'),
+        description=_('Specifies the permission by id that will be required ' +
+            ' to accessthe attributes and methods specified.'),
+        required=False,
+        )
+

Modified: zope.generic/trunk/src/zope/generic/type/testing.py
===================================================================
--- zope.generic/trunk/src/zope/generic/type/testing.py	2006-04-18 16:22:46 UTC (rev 67079)
+++ zope.generic/trunk/src/zope/generic/type/testing.py	2006-04-18 16:48:55 UTC (rev 67080)
@@ -21,8 +21,6 @@
 from zope.configuration.xmlconfig import XMLConfig
 
 import zope.app.testing.placelesssetup
-import zope.generic.component.testing
-import zope.generic.component.testing
 import zope.generic.directlyprovides.testing
 import zope.generic.component.testing
 import zope.generic.testing.testing
@@ -63,8 +61,6 @@
         zope.generic.testing.testing.setUp(doctest)
         zope.generic.directlyprovides.testing.setUp(doctest)
         zope.generic.component.testing.setUp(doctest)
-        zope.generic.component.testing.setUp(doctest)
-        zope.generic.component.testing.setUp(doctest)
         # internal setup
         setUp(doctest)
 
@@ -74,8 +70,6 @@
         zope.generic.testing.testing.tearDown(doctest)
         zope.generic.directlyprovides.testing.tearDown(doctest)
         zope.generic.component.testing.tearDown(doctest)
-        zope.generic.component.testing.tearDown(doctest)
-        zope.generic.component.testing.tearDown(doctest)
         # internal teardown
         tearDown(doctest)
 

Modified: zope.generic/trunk/src/zope/generic/type/tests.py
===================================================================
--- zope.generic/trunk/src/zope/generic/type/tests.py	2006-04-18 16:22:46 UTC (rev 67079)
+++ zope.generic/trunk/src/zope/generic/type/tests.py	2006-04-18 16:48:55 UTC (rev 67080)
@@ -24,6 +24,7 @@
 from zope.testing import doctest
 
 
+from zope.generic.type import api
 from zope.generic.type import testing
 from zope.generic.testing.testing import registerDirective
 
@@ -31,6 +32,14 @@
 
 def test_suite():
     return unittest.TestSuite((
+        doctest.DocTestSuite('zope.generic.type.adapter',
+                             setUp=testing.placelesssetup.setUp,
+                             tearDown=testing.placelesssetup.tearDown,
+                             globs={'component': component, 'interface': interface,
+                             'registerDirective': registerDirective,
+                             'testing': testing, 'api': api},
+                             optionflags=doctest.NORMALIZE_WHITESPACE+
+                                            doctest.ELLIPSIS),
         doctest.DocTestSuite('zope.generic.type.factory'),
         doctest.DocTestSuite('zope.generic.type.metaconfigure'),
         doctest.DocFileSuite('README.txt',



More information about the Checkins mailing list