[Checkins] SVN: zope.generic/trunk/src/zope/generic/ simple support for nested configuration (prototype)

Dominik Huber dominik.huber at perse.ch
Thu Jun 8 13:21:58 EDT 2006


Log message for revision 68530:
  simple support for nested configuration (prototype)

Changed:
  U   zope.generic/trunk/src/zope/generic/configuration/README.txt
  U   zope.generic/trunk/src/zope/generic/configuration/api.py
  U   zope.generic/trunk/src/zope/generic/configuration/base.py
  A   zope.generic/trunk/src/zope/generic/configuration/field.py
  U   zope.generic/trunk/src/zope/generic/configuration/helper.py
  U   zope.generic/trunk/src/zope/generic/configuration/interfaces.py
  U   zope.generic/trunk/src/zope/generic/configuration/metaconfigure.py
  U   zope.generic/trunk/src/zope/generic/informationprovider/metaconfigure.py

-=-
Modified: zope.generic/trunk/src/zope/generic/configuration/README.txt
===================================================================
--- zope.generic/trunk/src/zope/generic/configuration/README.txt	2006-06-08 17:21:28 UTC (rev 68529)
+++ zope.generic/trunk/src/zope/generic/configuration/README.txt	2006-06-08 17:21:57 UTC (rev 68530)
@@ -237,88 +237,185 @@
 
 The configuration directive does evaluate if an key interface is nested:
 
-    >>> from zope.schema import Object
+    >>> class IMySubConfiguration(interface.Interface):
+    ...    foo = api.SubConfiguration(schema=IFooConfiguration)
 
-    >>> class ISubConfigurationConfiguration(interface.Interface):
-    ...    foo = Object(schema=IFooConfiguration)
-
     >>> registerDirective('''
     ... <generic:configuration
-    ...     keyface="example.ISubConfigurationConfiguration"
+    ...     keyface="example.IMySubConfiguration"
     ...     />
     ... ''') 
 
-    >>> api.INestedConfigurationType.providedBy(ISubConfigurationConfiguration)
+    >>> api.INestedConfigurationType.providedBy(IMySubConfiguration)
     True
 
-    >>> from zope.schema import Tuple
+    >>> from zope.schema import Text
 
-    >>> class ISubTupleConfiguration(interface.Interface):
-    ...    foo = Tuple(value_type=TextLine())
+    >>> class IMySubConfigurationList(interface.Interface):
+    ...    foo = api.SubConfigurationList(value_type=Text())
+    ...    bar = api.SubConfigurationList(required=False, 
+    ...        value_type=api.SubConfiguration(schema=IFooConfiguration))
 
     >>> registerDirective('''
     ... <generic:configuration
-    ...     keyface="example.ISubTupleConfiguration"
+    ...     keyface="example.IMySubConfigurationList"
     ...     />
     ... ''')
 
-    >>> api.INestedConfigurationType.providedBy(ISubTupleConfiguration)
+    >>> api.INestedConfigurationType.providedBy(IMySubConfigurationList)
     True
 
-    >>> from zope.schema import Tuple
+    >>> from zope.schema import BytesLine
 
+    >>> class IMySubConfigurationDict(interface.Interface):
+    ...    foo = api.SubConfigurationDict(value_type=Text())
+    ...    bar = api.SubConfigurationDict(required=False, 
+    ...        value_type=api.SubConfiguration(schema=IFooConfiguration))
+
+    >>> registerDirective('''
+    ... <generic:configuration
+    ...     keyface="example.IMySubConfigurationDict"
+    ...     />
+    ... ''')
+
+    >>> api.INestedConfigurationType.providedBy(IMySubConfigurationDict)
+    True
+
 You can suppress the evaluation by an explicite declaration using the nested
 attribute:
 
-    >>> class ISuppressedContainerConfiguration(interface.Interface):
-    ...    foo = Tuple(value_type=TextLine())
+    >>> class ISuppressedNestedConfiguration(interface.Interface):
+    ...    foo = api.SubConfigurationDict(value_type=Text())
 
     >>> registerDirective('''
     ... <generic:configuration
-    ...     keyface="example.ISuppressedContainerConfiguration"
+    ...     keyface="example.ISuppressedNestedConfiguration"
     ...     nested="False"
     ...     />
     ... ''')
 
-    >>> api.INestedConfigurationType.providedBy(ISuppressedContainerConfiguration)
+    >>> api.INestedConfigurationType.providedBy(ISuppressedNestedConfiguration)
     False
 
 We can create nested configurations by a dictionary where the hierarchy is
 reflected by dotted names.
 
-First test nested configuration:
+Nested sub-configuration:
 
-#    >>> data = {'foo.foo': u'bla', 'foo.optional': u'Blu'}
-#    >>> config = api.createConfiguration(ISubConfigurationConfiguration, data)
-#    >>> IFooConfiguration.providedBy(config.foo)
-#    True
-#    >>> config.foo.foo
-#    u'bla'
-#    >>> config.foo.optional
-#    u'Blu'
-#
-#    >>> data = {'foo.foo': u'xxx'}
-#    >>> config = api.createConfiguration(ISubConfigurationConfiguration, data)
-#    >>> IFooConfiguration.providedBy(config.foo)
-#    True
-#    >>> config.foo.foo
-#    u'xxx'
-#    >>> config.foo.optional
-#    u'Bla'
-#
-#    >>> data = {'foo.optional': u'Blu'}
-#    >>> config = api.createConfiguration(ISubConfigurationConfiguration, data)
-#    Traceback (most recent call last):
-#    ...
-#    TypeError: __init__ requires 'foo.foo' of 'ISubConfigurationConfiguration'.
-#
-#    >>> subdata = {'foo': u'bla', 'optional': u'Blu'}
-#    >>> subconfig = api.createConfiguration(IFooConfiguration, subdata)
-#    >>> data = {'foo': subconfig}
-#    >>> config = api.createConfiguration(ISubConfigurationConfiguration, data)
-#    >>> IFooConfiguration.providedBy(config.foo)
-#    True
-#    >>> config.foo.foo
-#    u'bla'
-#    >>> config.foo.optional
-#    u'Blu'
+    >>> data = {'foo.foo': u'bla', 'foo.optional': u'Blu'}
+    >>> config = api.createConfiguration(IMySubConfiguration, data)
+    >>> IFooConfiguration.providedBy(config.foo)
+    True
+    >>> config.foo.foo
+    u'bla'
+    >>> config.foo.optional
+    u'Blu'
+
+    >>> data = {'foo.foo': u'xxx'}
+    >>> config = api.createConfiguration(IMySubConfiguration, data)
+    >>> IFooConfiguration.providedBy(config.foo)
+    True
+    >>> config.foo.foo
+    u'xxx'
+    >>> config.foo.optional
+    u'Bla'
+
+    >>> data = {'foo.optional': u'Blu'}
+    >>> config = api.createConfiguration(IMySubConfiguration, data)
+    Traceback (most recent call last):
+    ...
+    TypeError: __init__ requires 'foo' of 'IFooConfiguration'.
+
+    >>> subdata = {'foo': u'bla', 'optional': u'Blu'}
+    >>> subconfig = api.createConfiguration(IFooConfiguration, subdata)
+    >>> data = {'foo': subconfig}
+    >>> config = api.createConfiguration(IMySubConfiguration, data)
+    >>> IFooConfiguration.providedBy(config.foo)
+    True
+    >>> config.foo.foo
+    u'bla'
+    >>> config.foo.optional
+    u'Blu'
+
+Simple sub-configuration lists:
+
+    >>> data = {'foo.0': u'bla', 'foo.1': u'Blu'}
+    >>> config = api.createConfiguration(IMySubConfigurationList, data)
+    >>> from persistent.list import PersistentList
+    >>> isinstance(config.foo, PersistentList)
+    True
+    >>> config.foo[0]
+    u'bla'
+    >>> config.foo[1]
+    u'Blu'
+
+    >>> data = {'foo.1': u'bla', 'foo.2': u'Blu'}
+    >>> config = api.createConfiguration(IMySubConfigurationList, data)
+    Traceback (most recent call last):
+    ...
+    IndexError: list index out of range
+
+Nested sub-configuration lists:
+
+    >>> data = {'foo.0': u'bla', 'foo.1': u'Blu', 
+    ...         'bar.0.foo': u'bla 0', 'bar.0.optional': u'Blu 0', 
+    ...         'bar.1.foo': u'bla 1'}
+    >>> config = api.createConfiguration(IMySubConfigurationList, data)
+    >>> from persistent.list import PersistentList
+    >>> isinstance(config.bar, PersistentList)
+    True
+    >>> config.foo[0]
+    u'bla'
+    >>> config.foo[1]
+    u'Blu'
+    >>> IFooConfiguration.providedBy(config.bar[0])
+    True
+    >>> config.bar[0].foo
+    u'bla 0'
+    >>> config.bar[0].optional
+    u'Blu 0'
+    >>> config.bar[1].foo
+    u'bla 1'
+
+    >>> data = {'foo.0': u'bla', 'foo.1': u'Blu', 
+    ...         'bar.0.foo': u'bla 0', 'bar.0.optional': u'Blu 0', 
+    ...         'bar.2.foo': u'bla 1'}
+    >>> config = api.createConfiguration(IMySubConfigurationList, data)
+    Traceback (most recent call last):
+    ...
+    IndexError: list index out of range
+
+Simple sub-configuration dicts:
+
+    >>> data = {'foo.a': u'bla', 'foo.b': u'Blu'}
+    >>> config = api.createConfiguration(IMySubConfigurationDict, data)
+    >>> from persistent.dict import PersistentDict
+    >>> isinstance(config.foo, PersistentDict)
+    True
+    >>> config.foo['a']
+    u'bla'
+    >>> config.foo['b']
+    u'Blu'
+
+Nested sub-configuration dicts:
+
+    >>> data = {'foo.a': u'bla', 'foo.b': u'Blu', 
+    ...         'bar.c.foo': u'bla C', 'bar.c.optional': u'Blu C', 
+    ...         'bar.d.foo': u'bla D'}
+    >>> config = api.createConfiguration(IMySubConfigurationDict, data)
+    >>> from persistent.dict import PersistentDict
+    >>> isinstance(config.bar, PersistentDict)
+    True
+    >>> config.foo['a']
+    u'bla'
+    >>> config.foo['b']
+    u'Blu'
+    >>> IFooConfiguration.providedBy(config.bar['c'])
+    True
+    >>> config.bar['c'].foo
+    u'bla C'
+    >>> config.bar['c'].optional
+    u'Blu C'
+    >>> config.bar['d'].foo
+    u'bla D'
+

Modified: zope.generic/trunk/src/zope/generic/configuration/api.py
===================================================================
--- zope.generic/trunk/src/zope/generic/configuration/api.py	2006-06-08 17:21:28 UTC (rev 68529)
+++ zope.generic/trunk/src/zope/generic/configuration/api.py	2006-06-08 17:21:57 UTC (rev 68530)
@@ -23,6 +23,12 @@
 from zope.generic.configuration import *
 from zope.generic.configuration.adapter import AttributeConfigurations
 from zope.generic.configuration.base import createConfiguration
+from zope.generic.configuration.field import ISubConfiguration
+from zope.generic.configuration.field import ISubConfigurationDict
+from zope.generic.configuration.field import ISubConfigurationList
+from zope.generic.configuration.field import SubConfiguration
+from zope.generic.configuration.field import SubConfigurationDict
+from zope.generic.configuration.field import SubConfigurationList
 from zope.generic.configuration.helper import configurationToDict
 from zope.generic.configuration.helper import provideConfigurationType
 from zope.generic.configuration.helper import namesInOrder

Modified: zope.generic/trunk/src/zope/generic/configuration/base.py
===================================================================
--- zope.generic/trunk/src/zope/generic/configuration/base.py	2006-06-08 17:21:28 UTC (rev 68529)
+++ zope.generic/trunk/src/zope/generic/configuration/base.py	2006-06-08 17:21:57 UTC (rev 68530)
@@ -18,14 +18,15 @@
 
 __docformat__ = 'restructuredtext'
 
+from persistent import IPersistent
 from persistent import Persistent
-from persistent import IPersistent
+from persistent.dict import PersistentDict
+from persistent.list import PersistentList
 
 from zope.interface import directlyProvides
 from zope.interface import implements
 from zope.schema import ValidationError
 from zope.schema.interfaces import IField
-from zope.schema.interfaces import IObject
 
 from zope.generic.face import IAttributeFaced
 from zope.generic.face import IFace
@@ -36,14 +37,64 @@
 from zope.generic.configuration import IConfigurationData
 from zope.generic.configuration import IConfigurations
 from zope.generic.configuration import IConfigurationType
+from zope.generic.configuration import INestedConfiguration
+from zope.generic.configuration.field import ISubConfiguration
+from zope.generic.configuration.field import ISubConfigurationDict
+from zope.generic.configuration.field import ISubConfigurationList
 
 
 
 def createConfiguration(keyface, data):
+    """Factory function for configuration data."""
+
     return ConfigurationData(keyface, data)
 
 
 
+def createConfigurationList(field, data):
+
+    if ISubConfiguration.providedBy(field.value_type):
+        subkeyface = field.value_type.schema
+        counter = 0
+        subconfigurations = []
+        while data:
+            subdata = subData(str(counter), data)
+            if subdata:
+                subconfigurations.append(createConfiguration(subkeyface, subdata))
+            else:
+                raise IndexError('list index out of range')
+
+            counter += 1
+            
+        return ConfigurationList(subconfigurations)
+
+    # other objects
+    else:
+        indices = data.keys()
+        indices.sort()
+        # precondition: assume that everythings is ok if the start and end point is ok
+        if int(indices[0]) != 0 or int(indices[-1]) != len(indices) - 1:
+            raise IndexError('list index out of range')
+        return ConfigurationList([data[index] for index in indices])
+
+
+
+def createConfigurationDict(field, data):
+    if ISubConfiguration.providedBy(field.value_type):
+        subkeyface = field.value_type.schema
+        subconfigurations = {}
+        while data:
+            prefix = iter(data).next().split('.')[0] # evaluate the next entry  
+            subconfigurations[prefix] = createConfiguration(subkeyface, subData(prefix, data))
+
+        return ConfigurationDict(subconfigurations)
+
+    # other objects
+    else:
+        return ConfigurationDict(data)
+
+
+
 def subData(name, data):
     """Return a subdata dict and remove the subdata from the given data dict.
 
@@ -78,28 +129,52 @@
         if name not in data:
             field = __keyface__[name]
             # handle nested configuration data
-            if IObject.providedBy(field) and IConfigurationType.providedBy(field.schema):
-                try:
-                    subdata = subData(name, data)
-                    if subdata or field.required is True:
-                        relevant_data[name] = createConfiguration(field.schema, subData(name, data))
-                        continue
+            if INestedConfiguration.providedBy(field):
+                subdata = subData(name, data)
 
-                except:
-                    pass
+                if subdata or field.required is True:
+                    if ISubConfiguration.providedBy(field):
+                        relevant_data[name] = createConfiguration(field.schema, subdata)
+                    elif ISubConfigurationList.providedBy(field):
+                        relevant_data[name] = createConfigurationList(field, subdata)
+                    elif ISubConfigurationDict.providedBy(field):
+                        relevant_data[name] = createConfigurationDict(field, subdata)
+                    else:
+                        raise NotImplementedError()
 
+                    continue
+
             if field.required is True:
                 missedArguments.append(name)
+
         else:
-            relevant_data[name] = data[name]
+            value = data[name]
+            if isinstance(value, list):
+                relevant_data[name] = ConfigurationList(value)
+
+            elif isinstance(value, dict):
+                relevant_data[name] = ConfigurationDict(value)
+           
+            else:
+                relevant_data[name] = data[name]
     
     if missedArguments:
         raise TypeError("__init__ requires '%s' of '%s'." % (', '.join(missedArguments), __keyface__.__name__))
     
     return relevant_data
-    
 
 
+
+class ConfigurationList(PersistentList):
+    """List-like configurations."""
+
+
+
+class ConfigurationDict(PersistentDict):
+    """Dict-like configurations."""
+
+
+
 _marker = object()
 
 class ConfigurationData(Persistent):

Added: zope.generic/trunk/src/zope/generic/configuration/field.py
===================================================================
--- zope.generic/trunk/src/zope/generic/configuration/field.py	2006-06-08 17:21:28 UTC (rev 68529)
+++ zope.generic/trunk/src/zope/generic/configuration/field.py	2006-06-08 17:21:57 UTC (rev 68530)
@@ -0,0 +1,82 @@
+##############################################################################
+#
+# Copyright (c) 2005, 2006 Zope Corporation and Contributors.
+# 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.
+#
+##############################################################################
+
+"""
+$Id$
+"""
+
+__docformat__ = 'restructuredtext'
+
+from zope.app.i18n import ZopeMessageFactory as _
+from zope.interface import Attribute
+from zope.interface import implements
+from persistent.dict import PersistentDict
+from persistent.list import PersistentList
+from zope.schema import BytesLine
+from zope.schema import Dict
+from zope.schema import List
+from zope.schema import Object
+from zope.schema.interfaces import IDict
+from zope.schema.interfaces import IField
+from zope.schema.interfaces import IList
+from zope.schema.interfaces import IObject
+
+from zope.generic.configuration import INestedConfiguration
+
+
+
+class ISubConfiguration(INestedConfiguration, IObject):
+    """Mark a single sub configuration field."""
+
+    schema = Attribute('schema',
+        _('The interface that defines the fields comprising the sub ' +
+          'configuration. The interface must be an IConfigurationType.'))
+
+
+
+class SubConfiguration(Object):
+    __doc__ = ISubConfiguration.__doc__
+
+    implements(ISubConfiguration)
+
+
+
+class ISubConfigurationList(INestedConfiguration, IList):
+    """Mark a list of sub configuration objects."""
+
+
+
+class SubConfigurationList(List):
+    __doc__ = ISubConfigurationList.__doc__
+
+    implements(ISubConfigurationList)
+
+    _type = PersistentList
+
+
+
+class ISubConfigurationDict(INestedConfiguration, IDict):
+    """Mark a dictionary of sub-configuration bojects."""
+
+
+
+class SubConfigurationDict(Dict):
+    __doc__ = ISubConfigurationDict.__doc__
+
+    implements(ISubConfigurationDict)
+
+    _type = PersistentDict
+
+    def __init__(self, value_type=None, **kw):
+        super(SubConfigurationDict, self).__init__(BytesLine(), value_type, **kw)


Property changes on: zope.generic/trunk/src/zope/generic/configuration/field.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Modified: zope.generic/trunk/src/zope/generic/configuration/helper.py
===================================================================
--- zope.generic/trunk/src/zope/generic/configuration/helper.py	2006-06-08 17:21:28 UTC (rev 68529)
+++ zope.generic/trunk/src/zope/generic/configuration/helper.py	2006-06-08 17:21:57 UTC (rev 68530)
@@ -23,6 +23,10 @@
 from zope.generic.face import IFace
 
 from zope.generic.configuration import IConfigurationType
+from zope.generic.configuration import INestedConfiguration
+from zope.generic.configuration.field import ISubConfiguration
+from zope.generic.configuration.field import ISubConfigurationDict
+from zope.generic.configuration.field import ISubConfigurationList
 
 
 def provideConfigurationType(interface):
@@ -66,25 +70,47 @@
     for name in keyface:
         value = getattr(configuration, name, _marker)
         field = keyface[name]
+        
+        if INestedConfiguration.providedBy(field):
 
-        if field.required is False:
-            if value is not _marker and value != field.default:
-               data[name] = value
+            if ISubConfiguration.providedBy(field):
+                data[name] = configurationToDict(value, all)
 
-            elif value == field.default:
-                if all:
-                    data[name] = value
+            elif ISubConfigurationList.providedBy(field):
+                if ISubConfiguration.providedBy(field.value_type):
+                    data[name] = [configurationToDict(v, all) for v in value]
+                # regular objects
+                else:
+                    data[name] = [v for v in value]
 
-            else:
-                if all:
-                    data[name] = field.default
+            elif ISubConfigurationDict.providedBy(field):
+                if ISubConfiguration.providedBy(field.value_type):
+                    data[name] = dict([(k, configurationToDict(v, all)) for k, v in value.items()])
+                # regular objects
+                else:
+                    data[name] = dict([item for item in value.items()])
 
-        elif value is not _marker:
-            data[name] = value
-
+        # no sub-configuraiton
         else:
-            raise RuntimeError('Data is missing', name)
 
+            if field.required is False:
+                if value is not _marker and value != field.default:
+                   data[name] = value
+    
+                elif value == field.default:
+                    if all:
+                        data[name] = value
+    
+                else:
+                    if all:
+                        data[name] = field.default
+    
+            elif value is not _marker:
+                data[name] = value
+    
+            else:
+                raise RuntimeError('Data is missing', name)
+
     return data
 
 

Modified: zope.generic/trunk/src/zope/generic/configuration/interfaces.py
===================================================================
--- zope.generic/trunk/src/zope/generic/configuration/interfaces.py	2006-06-08 17:21:28 UTC (rev 68529)
+++ zope.generic/trunk/src/zope/generic/configuration/interfaces.py	2006-06-08 17:21:57 UTC (rev 68530)
@@ -18,12 +18,14 @@
 
 __docformat__ = 'restructuredtext'
 
+from zope.app.i18n import ZopeMessageFactory as _
 from zope.location import ILocation
 from zope.component.interfaces import IObjectEvent
 from zope.interface import Attribute
 from zope.interface import alsoProvides
 from zope.interface import Interface
 from zope.lifecycleevent.interfaces import IModificationDescription
+from zope.schema.interfaces import IField
 
 from zope.generic.face import IFaced
 from zope.generic.face import IKeyfaceType
@@ -45,6 +47,11 @@
 
 
 
+class INestedConfiguration(IField):
+    """Mark nested configuration."""
+
+
+
 class IConfigurationData(IFaced):
     """Marker for configuration data implementations."""
 

Modified: zope.generic/trunk/src/zope/generic/configuration/metaconfigure.py
===================================================================
--- zope.generic/trunk/src/zope/generic/configuration/metaconfigure.py	2006-06-08 17:21:28 UTC (rev 68529)
+++ zope.generic/trunk/src/zope/generic/configuration/metaconfigure.py	2006-06-08 17:21:57 UTC (rev 68530)
@@ -24,6 +24,7 @@
 from zope.schema.interfaces import IObject
 
 from zope.generic.configuration import IConfigurationType
+from zope.generic.configuration import INestedConfiguration
 from zope.generic.configuration import INestedConfigurationType
 
 
@@ -43,12 +44,8 @@
     elif nested is None:
         for name in keyface:
             field = keyface[name]
-            if IObject.providedBy(field) and IConfigurationType.providedBy(field.schema):
+            if INestedConfiguration.providedBy(field):
                 type = INestedConfigurationType
                 break
-            
-            elif ISequence.providedBy(field) or IDict.providedBy(field):
-                type = INestedConfigurationType
-                break
 
     provideInterface(None, keyface, type)

Modified: zope.generic/trunk/src/zope/generic/informationprovider/metaconfigure.py
===================================================================
--- zope.generic/trunk/src/zope/generic/informationprovider/metaconfigure.py	2006-06-08 17:21:28 UTC (rev 68529)
+++ zope.generic/trunk/src/zope/generic/informationprovider/metaconfigure.py	2006-06-08 17:21:57 UTC (rev 68530)
@@ -178,31 +178,5 @@
         # handle wrong usage
         else:
             raise ConfigurationError('Information subdirective must provide ' +
-                'key and annotation or keyface and configuration.')
-
-
-
-def multiInformationProvidersDirective(_context, iniFiles=()):
-    """Ini-file based configurations for multi information provider."""
-        
-    for path in iniFiles:
-        for configuration, keyface, conface, data in iniFileToConfiguration(path):
-            # register corresponding configuration information
-            # provide type as soon as possilbe
-            if not IKeyfaceType.providedBy(keyface):
-                provideInterface(None, keyface, IKeyfaceType)
-    
-            if not IConfaceType.providedBy(conface):
-                provideInterface(None, conface, IConfaceType)
-    
-            # ensure the corresponding information provider
-            ensureInformationProvider(keyface, conface)
-
-            _context.action(
-                discriminator = (
-                'informationprovider.configuration', keyface, conface, configuration),
-                callable = provideConfiguration,
-                args = (keyface, conface, configuration, data),
-                )
-                
+                'key and annotation or keyface and configuration.')              
                 
\ No newline at end of file



More information about the Checkins mailing list