home contents changes options help subscribe

Author

JimFulton

Status

IsImplementedProposal

Problem

Zope 3's component architecture has become simpler over time. We now recognize a small number of core component types:

  • Utilities
  • Adapters
  • Subscribers (and handlers)

For global components, we provide relatively clean APIs for registering components:

    provideUtility(component, provides=None, name=u''):
    provideAdapter(factory, adapts=None, provides=None, name=''):
    provideSubscriptionAdapter(factory, adapts=None, provides=None):
    provideHandler(factory, adapts=None):

For local components, the story is substantially more complicated. There are historical reasons for this.

  • Originally, we thought that most software would need to be stored in the ZODB to accommodate software updates in ZEO clusters without downtime in situations where updates changed data schemas. This was an overly ambitious goal.

    We later realized another strategy for updating ZEO clusters. We can safely update a ZEO cluster in 2 steps:

    1. Update the cluster with software that uses existing data schemas but that can work with updated schemas.
    2. Update the cluster again with software that creates data with the new schemas.
  • We thought that we needed separate "registration" objects to manage data beyond basic component registration data, including:
    • Registration descriptions
    • Component permissions
    • Other information such as component factory implementation information.

    We now know that:

    • Permissions should be handled independently from registration. In most cases, components that are pure software should be public.
    • We don't need other information for basic core components.

    It is useful to associate a remark with a registration. For example, when registrations are loaded from ZCML, it's helpful to provide (at least optionally) run-time information about where in ZCML the component was registered.

  • Originally, we had many more kinds of components than we had now and we needed (or thought we needed) complex registration mechanisms to decide which sort of component an object was.

I think it's time to radically simplify things.

Proposal

I propose to simplify component registration as follows:

  • Define a new interface, IComponentRegistry?:
        class IComponentRegistry(interface.Interface):
            """Register components
            """
    
            def registerUtility(component, provided=None, name=u'', info=u''):
                """Register a utility
    
                component
                   The registered component
    
                provided
                   This is the interface provided by the utility.  If the
                   component provides a single interface, then this
                   argument is optional and the component-implemented
                   interface will be used.
    
                name
                   The utility name.
    
                info
                   An object that can be converted to a string to provide
                   information about the registration.
                """
    
            def unregisterUtility(component=None, provided=None, name=u''):
                """Unregister a utility
    
                A boolean is returned indicating whether the registry was
                changed.  If the given component is None and there is no
                component registered, or if the given component is not
                None and is not registered, then the function returns
                False, otherwise it returns True.
    
                component
                   The registered component The given component can be
                   None, in which case any component registered to provide
                   the given provided interface with the given name is
                   unregistered.
    
                provided
                   This is the interface provided by the utility.  If the
                   component is not None and provides a single interface,
                   then this argument is optional and the
                   component-implemented interface will be used.
    
                name
                   The utility name.
                """
    
            def registeredUtilities():
                """Return an iterable of utility-information objects
    
                The information objects will have attributes:
    
                provided
                   The provided interface
    
                name
                   The name
    
                component
                   The registered component
    
                info
                   An object that can be converted to a string to provide
                   information about the registration.
                """
    
            def registerAdapter(factory, required=None, provided=None, name=u'',
                               info=u''):
                """Register an adapter factory
    
                Parameters:
    
                factory
                    The object used to compute the adapter
    
                required
                    This is a sequence of specifications for objects to be
                    adapted.  If omitted, then the value of the factory's
                    __component_adapts__ attribute will be used.  The
                    __component_adapts__ attribute is usually attribute is
                    normally set in class definitions using adapts
                    function, or for callables using the adapter
                    decorator.  If the factory doesn't have a
                    __component_adapts__ adapts attribute, then this
                    argument is required. 
    
                provided
                    This is the interface provided by the adapter and
                    implemented by the factory.  If the factory
                    implements a single interface, then this argument is
                    optional and the factory-implemented interface will be
                    used. 
    
                name
                    The adapter name.
    
                info
                   An object that can be converted to a string to provide
                   information about the registration.
                """
    
            def unregisterAdapter(factory=None, required=None,
                                  provided=None, name=u''):
                """Register an adapter factory
    
                A boolean is returned indicating whether the registry was
                changed.  If the given component is None and there is no
                component registered, or if the given component is not
                None and is not registered, then the function returns
                False, otherwise it returns True.
    
                Parameters:
    
                factory
                    This is the object used to compute the adapter. The
                    factory can be None, in which case any factory
                    registered to implement the given provided interface
                    for the given required specifications with the given
                    name is unregistered.
    
                required
                    This is a sequence of specifications for objects to be
                    adapted.  If the factory is not None and the required
                    arguments is omitted, then the value of the factory's
                    __component_adapts__ attribute will be used.  The
                    __component_adapts__ attribute attribute is normally
                    set in class definitions using adapts function, or for
                    callables using the adapter decorator.  If the factory
                    is None or doesn't have a __component_adapts__ adapts
                    attribute, then this argument is required.
    
                provided
                    This is the interface provided by the adapter and
                    implemented by the factory.  If the factory is not
                    None and implements a single interface, then this
                    argument is optional and the factory-implemented
                    interface will be used.
    
                name
                    The adapter name.
                """
    
            def registeredAdapters():
                """Return an iterable of adapter-information objects
    
                The adapter information objects will have attributes:
    
                required
                   An iterable of required interfaces
    
                provided
                   The provided interface
    
                name
                   The name
    
                component
                   The registered factory
    
                info
                   Provide some info about this particular adapter registration.
                """
    
            def registerSubscriptionAdapter(factory, required=None, provides=None,
                                            name=u'', info=''):
                """Register a subscriber factory
    
                Parameters:
    
                factory
                    The object used to compute the adapter
    
                required
                    This is a sequence of specifications for objects to be
                    adapted.  If omitted, then the value of the factory's
                    __component_adapts__ attribute will be used.  The
                    __component_adapts__ attribute is usually attribute is
                    normally set in class definitions using adapts
                    function, or for callables using the adapter
                    decorator.  If the factory doesn't have a
                    __component_adapts__ adapts attribute, then this
                    argument is required. 
    
                provided
                    This is the interface provided by the adapter and
                    implemented by the factory.  If the factory implements
                    a single interface, then this argument is optional and
                    the factory-implemented interface will be used.
    
                name
                    The adapter name.
    
                    Note that this parameter is ignored and is reserved
                    for future use when named subscribers are implemented.
    
                info
                   An object that can be converted to a string to provide
                   information about the registration.
                """
    
            def unregisterSubscriptionAdapter(factory=None, required=None, 
                                              provides=None, name=u''):
                """Register a subscriber factory
    
                A boolean is returned indicating whether the registry was
                changed.  If the given component is None and there is no
                component registered, or if the given component is not
                None and is not registered, then the function returns
                False, otherwise it returns True.
    
                Parameters:
    
                factory
                    This is the object used to compute the adapter. The
                    factory can be None, in which case any factories
                    registered to implement the given provided interface
                    for the given required specifications with the given
                    name are unregistered.
    
                required
                    This is a sequence of specifications for objects to be
                    adapted.  If the factory is not None and the required
                    arguments is omitted, then the value of the factory's
                    __component_adapts__ attribute will be used.  The
                    __component_adapts__ attribute attribute is normally
                    set in class definitions using adapts function, or for
                    callables using the adapter decorator.  If the factory
                    is None or doesn't have a __component_adapts__ adapts
                    attribute, then this argument is required.
    
                provided
                    This is the interface provided by the adapter and
                    implemented by the factory.  If the factory is not
                    None implements a single interface, then this argument
                    is optional and the factory-implemented interface will
                    be used.
    
                name
                    The adapter name.
    
                    Note that this parameter is ignored and is reserved
                    for future use when named subscribers are implemented.
                """
    
            def registeredSubscriptionAdapters():
                """Return an iterable of subscriber-information objects
    
                The subscriber information objects will have attributes:
    
                required
                   An iterable of required interfaces
    
                provided
                   The provided interface
    
                name
                   The name
    
                component
                   The registered factory
    
                info
                   An object that can be converted to a string to provide
                   information about the registration.
                """
    
            def registerHandler(handler, adapts=None, name=u'', info=''):
                """Register a handler.
    
                A handler is a subscriber that doesn't compute an adapter
                but performs some function when called.
    
                Parameters:
    
                handler
                    The object used to handle some event represented by
                    the objects passed to it.
    
                required
                    This is a sequence of specifications for objects to be
                    adapted.  If omitted, then the value of the factory's
                    __component_adapts__ attribute will be used.  The
                    __component_adapts__ attribute is usually attribute is
                    normally set in class definitions using adapts
                    function, or for callables using the adapter
                    decorator.  If the factory doesn't have a
                    __component_adapts__ adapts attribute, then this
                    argument is required. 
    
                name
                    The handler name.
    
                    Note that this parameter is ignored and is reserved
                    for future use when named handlers are implemented.
    
                info
                   An object that can be converted to a string to provide
                   information about the registration.
                """
    
            def unregisterHandler(handler=None, adapts=None, name=u''):
                """Register a handler.
    
                A handler is a subscriber that doesn't compute an adapter
                but performs some function when called.
    
                Parameters:
    
                handler
                    This is the object used to handle some event
                    represented by the objects passed to it. The handler
                    can be None, in which case any handlers registered for
                    the given required specifications with the given are
                    unregistered.
    
                required
                    This is a sequence of specifications for objects to be
                    adapted.  If omitted, then the value of the factory's
                    __component_adapts__ attribute will be used.  The
                    __component_adapts__ attribute is usually attribute is
                    normally set in class definitions using adapts
                    function, or for callables using the adapter
                    decorator.  If the factory doesn't have a
                    __component_adapts__ adapts attribute, then this
                    argument is required. 
    
                name
                    The handler name.
    
                    Note that this parameter is ignored and is reserved
                    for future use when named handlers are implemented.
                """
    
            def registeredHandlers():
                """Return an iterable of handler-information objects
    
                The subscriber information objects will have attributes:
    
                required
                   An iterable of required interfaces
    
                name
                   The name
    
                component
                   The registered handler
    
                info
                   An object that can be converted to a string to provide
                   information about the registration.
                """
    
  • Global and local site managers will provide this interface. Any object can be registered from anywhere by simply calling one of the provide methods. It will no longer be necessary to create separate registration objects. There will no longer be a need for registration managers. Registries may place restrictions on objects registered. In particular, local registries stored in the ZODB will have to be able to store registered objects (or references to registered objects) in the ZODB.

    As far as these APIs are concerned, the location of a registered object won't matter. In fact, a registered object need not be located in a folder at all. Locating components will, however, be encouraged as will registering components only with an immediately including site. I expect that supporting user-interfaces will assume this.

  • Local components will not required to provide any special magic interfaces (e.g. ILocalUtility?). Local components will not be required to be annotatable.
  • We will still provide site-management folders to allow components to be stored outside of content space if desired.
  • We will create new views on all objects available to users with the zope.ManageSite? permissions. These will allow for registration of objects as utilities, adapters or subscribers, as appropriate. It's up to some UI volunteers to work out the details of this, probably starting with utilities.

    Note that there is no need to go through these views to register components. Python code can register components using the above API.

Of course, we will deprecate the old APIs?, which will still work for a time.