[Zope3-checkins] CVS: Zope3/src/zope/app/services - README.txt:1.1.2.1 utility.py:1.1.2.1 utility.zcml:1.1.2.1

Jim Fulton jim@zope.com
Thu, 13 Mar 2003 13:31:25 -0500


Update of /cvs-repository/Zope3/src/zope/app/services
In directory cvs.zope.org:/tmp/cvs-serv31900

Added Files:
      Tag: local-utility-branch
	README.txt utility.py utility.zcml 
Log Message:
commiting to branch

=== Added File Zope3/src/zope/app/services/README.txt ===
Local Services
==============

:Author: Jim Fulton
:Version: $Revision: 1.1.2.1 $

.. contents::

This package includes implementations of several local services.
It also contains infrastructure for implementing local services.

This document describes how to implement local services.  It's not
too difficult, but there can be a lot of details that are hard to
remember.

A service is a component that implements a specific interface *and*
that has the responsability to collaborate with services above it. 
Local services are stored in the Zope object database, so they also
need to be persistent.  Finally, many local services support modular
configuration through configuration objects.

Let's walk through an example step by step.  We'll implement a
utility service. A utility service must implement the 
interface ``zope.component.interfaces.IUtilityService``.

Step 1. Create a minimal service
--------------------------------

Create a minimal service that delagates everything to the
service above it::

  from persistence import Persistent
  from zope.component.exceptions import ComponentLookupError 
  from zope.proxy.context import ContextAware
  from zope.app.component.nextservice import getNextService
  from zope.component.interfaces import IUtilityService
  from zope.app.interfaces.services.interfaces import ISimpleService

  class LocalUtilityService(Persistent, ContextAware):

      __implements__ = IUtilityService, ISimpleService

      def getUtility(self, interface, name=''):
          utility = self.queryUtility(interface, name=name)
          if utility is None:
              raise ComponentLookupError("utility", interface, name)
          return utility

      def queryUtility(self, interface, default=None, name=''):
          next = getNextService(self, "Utilities")
          return next.queryUtility(interface, default, name)

The local service subclasses two classes:

``Persistent``
  Provides support for transparent persistent in the
  ZODB.

``ContextAware``
  Causes all of the methods or properties defined in
  the class (or base classes) to be bound to context-wrapped
  instances.  This is needed if the methods or properties are going to
  call APIs that need acquisition context.  We could convert each of
  the methods to context methods individually, but it's easier to just
  mix-in context aware.

The ``getUtility`` method simply delegates to ``queryUtility``. The
``queryUtility`` method delegates to the next utility service using
``getNextService``.  

The function ``getNextService`` looks up the next service above the
current service. It takes a location and a service name.  We use it
to get the interface service defined above our service, which may be
the global service, and delegate to it.

I created the service in the ``utility`` module in this package. This
package is pretty large. To avoid a really large zcml file, I've
started giving each service it's own zcml file.  So I also created
an ``utility.zcml`` file::

  <zopeConfigure xmlns='http://namespaces.zope.org/zope'>
  
  <content class=".utility.LocalUtilityService">
    <factory
        id="zope.app.services.UtilityService"
        permission="zope.ManageServices"
        />
  </content>
  
  </zopeConfigure>

and I added an include to the package configuration file::

  <!-- Utility Service --> <include file="utility.zcml" />

To make it possible to add the utility service, I need to add an entry to
the ``add_component`` browser menu. The ``add_component`` menu is the menu
used by site folders for adding objects. To do this, I need to add a
browser menu configuration.  Eventually, the local interface will
have a number of views, so I create a package, ``utility``, for
it in ``zope/app/browser/services``.  [1]_ In that
package, I put a configuration that defines the needed browser menu
item::

   <zopeConfigure xmlns='http://namespaces.zope.org/browser'>
   
   <menuItem
         for="zope.app.interfaces.container.IAdding"
         menu="add_service"
         action="zope.app.services.UtilityService"
         title="Utility Service"
         permission="zope.ManageServices"
         />
   
   </zopeConfigure>
   
and I added an include to the configuration file in
zope.app.browser.services::

   <!-- Utility Service --> <include package=".utility" />

With this in place, I can add a local service that does nothing but
delegate to a service above it. 

Step 2. Providing functionality
-------------------------------

Now it's time to add some functionality.  A utility service keeps
track of utility components by name and by interface.  It allows
components to be registered and then looked up later.

An important feature of component registration services, like the
utility service, is that they support multiple conflicting
registrations. One of the registrations is active.  A site developer
can switch between alternate components by simply changing which one
is active.

Consider the following scenario. a product provides a utility. A
site manager gets a better version of the utility and installs
it. The better version is active.  The site developer then finds a
bug in the new version. Because the old utility is still registered,
the site developer can easily switch back to it by making it active.

Utilities can be provided in two ways:

- As persistent objects in packages

- As module global objects. (A variation is to point to a class
  that can be called without arguments to create a utility.)

We'd like to avoid making the utility service have to deal with
these variations directly.

We want to make it possible for packages to provide configurations
that can be reused in different sites.

To support the configuration flexibility described above, Zope
provides a configuration framework:

- Configuration registry objects manage multiple configurations,

- Configuration objects provide configuration data,

- Configuration managers support management of configuration objects
  in packages.

We'll start by updating the utility service to support configurations.
First, we'll pick a data structure.  We'll use a persistent dictionary
mapping utility names to implementor registries.  We also need to
implement zope.app.interfaces.services.configuration.IConfigurable.
The updated local utility service implementation can be found in
zope/app/services/utility.py.

A ``queryConfigurationsFor`` method is added to implement
``IConfigurable``. It takes a configuration object and returns a
matching configuration registry. The configuration object is used to
provide an abstract way to represent configuration
parameters. Typically, the configuration parameters are extracted and
a more concrete method is called. In the local utility service, we
extract the utility name and interface and call ``queryConfigurations``
with the name and interface.

Similarly, we add a ``createConfigurationsFor`` method that takes a
configuration object holding configuration parameters and create a
configuration registry for the parameters.  If we don't have a
implementor registry for a utility name, we create one and add
it. When we create the implementor registry, we pass a
``PersistentDict`` for it to use to store registration data. This
assurs that updates are made persistently. If there isn't implementor
data for the given interface, we create a configuration registry and
register it for the interface.

Finally, we modify ``queryUtility`` to use registered utility
configurations.  We try to get a configuration registery by calling
``queryConfigurations``.  If we get one, we call it's ``active``
method to get the active configuration, if any. Finally, we call
``getComponent`` on the active configuration to get the actual
component.  We leave it up to the configuration object to take care of
actually finding and returning the component.

Our local utility service is complete, except for a user interface. We
now need to provide utility configuration objects. The utility
configuration objects need to manage several bits of information:

- name

- interface

- permission

- The location of the actual component.

We'll start by creating a configuration object for utility components
stored in packages.  Somewhat different approaches are taken for
configuraing components contained in packages and objects contained in
modules. Objects contained in packages provide a configurations view
that shows the configurations for the object and lets you add
configurations. When we add these objects, we typically add
configurations at the same time.

To create the configuration object, we'll start by defining a
configuration schema in
``zope/app/interfaces/services/utility.py``.  The schema should extend
``zope.app.interfaces.services.configuration.IConfiguration``.
There's a more specific interface,
``zope.app.interfaces.services.configuration.INamedComponentConfiguration``
that is much closer to what we need. It has all of the fields and
methods we need except for an ``interface`` field to hold the utility
interface. 

A ``UtilityConfiguration`` class is added to the `utility` module in
``zope/app/services`` that implements the configuration interface.
We can subclass NamedComponentConfiguration, which does most of the
work.  

For utility components stored in modules, we want to do the
configuration through the objects themselves.  To do this, we need
them to keep track of their configuration objects.  




To do:

  Need a UI for browsing registered utilities in the utility service.

  Configuration of objects in folders

    - Need the configuration object class that keeps track of:

      o name

      o interface

      o component path

      o permission

    - Add view for the configuration

    - Edit view for the configuration

    - Summary view of the configuration in a configuration registry

    - Summary view of the configuration in a configuration manager

    - Configurations view

  Configuration of module globals

    - Need the configuration object class that keeps track of:

      o name

      o interface

      o dotted name

      o permission

    - Add view for the configuration

    - Edit view for the configuration

    - Summary view of the configuration in a configuration registry

    - Summary view of the configuration in a configuration manager











---------------------------------------------------------------

.. [1] Of course, I initially forgot to include a nearly empty
   ``__init__.py`` file and added one.


=== Added File Zope3/src/zope/app/services/utility.py ===
##############################################################################
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
##############################################################################
"""XXX short summary goes here.

XXX longer description goes here.

$Id: utility.py,v 1.1.2.1 2003/03/13 18:31:24 jim Exp $
"""
__metaclass__ = type

from persistence import Persistent
from persistence.dict import PersistentDict
from zope.component.exceptions import ComponentLookupError 
from zope.proxy.context import ContextAware
from zope.proxy.introspection import removeAllProxies
from zope.app.component.nextservice import getNextService
from zope.component.interfaces import IUtilityService
from zope.interface.implementor import ImplementorRegistry
from zope.app.interfaces.services.configuration import IConfigurable
from zope.app.services.configuration import ConfigurationRegistry
from zope.app.services.configuration import SimpleConfiguration
from zope.app.interfaces.services.interfaces import ISimpleService

class LocalUtilityService(Persistent, ContextAware):

    __implements__ = IUtilityService, IConfigurable, ISimpleService

    def __init__(self):
        self._utilities = PersistentDict()

    def getUtility(self, interface, name=''):
        utility = self.queryUtility(interface, name=name)
        if utility is None:
            raise ComponentLookupError("utility", interface, name)
        return utility

    def queryUtility(self, interface, default=None, name=''):
        registry = self.queryConfigurations(name, interface)
        if registry is not None:
            configuration = registry.active()
            if configuration is not None:
                return configuration.getComponent()
            
        next = getNextService(self, "Utilities")
        return next.queryUtility(interface, default, name)

    def queryConfigurationsFor(self, configuration, default=None):
        return self.queryConfiguration(configuration.name,
                                       configuration.interface,
                                       default)

    def queryConfigurations(self, name, interface, default=None):
        utilities = self._utilities.get(name)
        if utilities is None:
            return default
        registry = utilities.getRegistered(interface)
        if registry is None:
            return default

        return registry
        
    def createConfigurationsFor(self, configuration):
        return self.createConfiguration(configuration.name,
                                        configuration.interface)

    def createConfigurations(self, name, interface):
        utilities = self._utilities.get(name)
        if utilities is None:
            utilities = ImplementorRegistry(PersistentDict())
            self._utilities[name] = utilities

        registry = utilities.getRegistered(interface)
        if registry is None:
            registry = ConfigurationRegistry()
            utilities.register(interface, registry)
            
        return registry
        

class UtilityConfiguration(NamedComponentConfiguration):
    """Utility component configuration for persistent components

    This configuration configures persistent components in packages to
    be utilities.

    """

    def __init__(self, name, interface, component_path, permission=None):
        super(UtilityConfiguration, self).__init__(
            name, component_path, permission)
        self.interface = interface

    def afterAddHook(self, configuration, container):
        NamedComponentConfiguration.afterAddHook(self,
                                                 configuration,
                                                 container)
        service = configuration.getComponent()
        adapter = getAdapter(service, IUseConfiguration)
        adapter.addUsage(getPhysicalPathString(configuration))

    def beforeDeleteHook(self, configuration, container):
        service = configuration.getComponent()
        adapter = getAdapter(service, IUseConfiguration)
        adapter.removeUsage(getPhysicalPathString(configuration))
        NamedComponentConfiguration.beforeDeleteHook(self,
                                                     configuration,
                                                     container)


=== Added File Zope3/src/zope/app/services/utility.zcml ===
<zopeConfigure xmlns='http://namespaces.zope.org/zope'>

<content class=".utility.LocalUtilityService">
  <implements
      interface="zope.app.interfaces.annotation.IAttributeAnnotatable"
      />
  <factory
      id="zope.app.services.UtilityService"
      permission="zope.ManageServices"
      />
</content>

</zopeConfigure>