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

Guido van Rossum guido@python.org
Thu, 13 Mar 2003 17:28:40 -0500


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

Modified Files:
      Tag: local-utility-branch
	README.txt utility.py utility.zcml 
Log Message:
Checkpoint checkin (Jim+Guido).

=== Zope3/src/zope/app/services/README.txt 1.1.2.1 => 1.1.2.2 ===
--- Zope3/src/zope/app/services/README.txt:1.1.2.1	Thu Mar 13 13:31:24 2003
+++ Zope3/src/zope/app/services/README.txt	Thu Mar 13 17:28:09 2003
@@ -14,23 +14,38 @@
 remember.
 
 A service is a component that implements a specific interface *and*
-that has the responsability to collaborate with services above it. 
+that has the responsibility 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.
 
+A few words on the difference between local and global services:
+
+- Local services (usually) exist in the ZODB; global services don't.
+
+- Local services apply to a specific part of the object hierarchy;
+  global services (as their name suggests) don't.
+
+- Local services are (usually) created and configured through the ZMI;
+  global services are created and configured by ZCML directives.
+
+- Local services are expected to collaborate with services "above"
+  them in the object hierarchy, or with the global service; global
+  services by definition have nothing "above" them.
+
 Let's walk through an example step by step.  We'll implement a
-utility service. A utility service must implement the 
+local 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::
+service above it, in the file ``utility.py``::
 
   from persistence import Persistent
-  from zope.component.exceptions import ComponentLookupError 
+  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
@@ -53,8 +68,7 @@
 The local service subclasses two classes:
 
 ``Persistent``
-  Provides support for transparent persistent in the
-  ZODB.
+  Provides support for transparent persistent in the ZODB.
 
 ``ContextAware``
   Causes all of the methods or properties defined in
@@ -64,29 +78,30 @@
   the methods to context methods individually, but it's easier to just
   mix-in context aware.
 
-The ``getUtility`` method simply delegates to ``queryUtility``. The
+The ``getUtility`` method simply delegates to ``queryUtility``.  The
 ``queryUtility`` method delegates to the next utility service using
-``getNextService``.  
+``getNextService``.  (Both methods are specified by the
+``IUtilityService`` interface.)
 
 The function ``getNextService`` looks up the next service above the
-current service. It takes a location and a service name.  We use it
+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::
+I created the service in the ``utility`` module in this package (the
+file ``utility.py``).  This package is already pretty large.  To avoid
+a really large zcml file, I've started giving each service its own
+zcml file.  So I also created an ``utility.zcml`` file::
+
+  <zopeConfigure xmlns="http://namespaces.zope.org/zope">
 
-  <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::
@@ -94,16 +109,16 @@
   <!-- 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
+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'>
-   
+   <zopeConfigure xmlns="http://namespaces.zope.org/browser">
+
    <menuItem
          for="zope.app.interfaces.container.IAdding"
          menu="add_service"
@@ -111,96 +126,114 @@
          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. 
+delegate to a service above it.  (To actually create a utility service
+instance, I have to go to the service manager and use its ``Add
+service`` action.  The service manager is reached from the root folder
+by using the ``Manage local services`` action.)
+
 
 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
+track of utility components by name and 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,
+registrations.  At most 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 new version of the utility and installs
+it.  The new 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 persistent objects in (site-management) folders.
 
-- As module global objects. (A variation is to point to a class
+- 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
+We want to make it possible for folders 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 registry objects manage multiple configurations for
+  the same configuration parameters (at most one of which is active at
+  any given time).  For example, in the case of a utility service
+  these would be configurations for the same interface and name.
 
-- Configuration objects provide configuration data,
+- Configuration objects provide configuration data.
 
 - Configuration managers support management of configuration objects
-  in packages.
+  in folders.
 
 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.
 
+First, we'll pick a data structure.  We'll use a persistent dictionary
+mapping utility names to implementor registries.  An implementor
+registry implements a mapping from interfaces to objects; it's not
+quite the same as a mapping because it understands subclassing
+relationships between the interfaces used as keys.  In this case, the
+implementor registries themselves map interfaces to configuration
+registries.
+
+We also need to implement
+zope.app.interfaces.services.configuration.IConfigurable.  This
+defines two methods, ``queryConfigurationsFor`` and
+``createConfigurationsFor``.
+
 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.
+``IConfigurable``.  It takes a configuration object and returns the
+corresponding 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
+configuration object holding configuration parameters and creates a
+configuration registry for the parameters (if none already exists).
+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
+assures 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
+``queryConfigurations``.  If we get one, we call its ``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:
+Our local utility service is now complete, except for a user
+interface.  We need to provide utility configuration objects.  The
+utility configuration objects need to manage several bits of
+information:
 
 - name
 
@@ -210,32 +243,79 @@
 
 - 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
+We'll start by creating a configuration class for utility components
+stored in folders.  Somewhat different approaches are taken for
+configuring components contained in folders and objects contained in
+modules.  Objects contained in folders 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.  When we add these objects, we typically add
 configurations at the same time.
 
-To create the configuration object, we'll start by defining a
+To create the configuration class, 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
+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. 
+interface.
 
-A ``UtilityConfiguration`` class is added to the `utility` module in
+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.  
+work.
+
+
+For utility components stored in folders, we want to do the
+configuration through the components themselves.  That is, the site
+admin should be able to walk up to a component that implements a
+utility and add or change a utility configuration for it.  The most
+common case is actually that a site manager creates a utility
+component and configures it right away.  (This is the same common case
+that is used for creating and configuring services.)
+
+There's a view on utility components for managing their
+configurations; similar to the corresponding view on service
+components, it shows a list of all configurations for the component,
+and a link to add one.
+
+To implement this view, we need them to keep track of the
+configuration objects for given utility components.  The reference
+from a configuration object to the component it configures is
+contained in the configuration object itself.  In order to find the
+configurations pertaining to a component, we have to implement back
+pointers somehow.  Requiring each component to keep track of these
+back pointers would be impractical; fortunately there's a general
+mechanism that we can use.
+
+The general mechanism is called "annotations".  See
+zope/app/interfaces/annotation.py for the full scoop.  The short
+version: by adapting an object to IAnnotations, we get a mapping-like
+interface that allows us to store arbitrary named data for the object.
+In the most common implemenation variant (see
+zope/app/attributeannotations.py), all annotation data is stored in a
+dictionary which itself is stored as the attribute __annotations__.
+Because we don't want to stomp on a component's attributes (even if
+they have a __funky__ name) without its permission, a component must
+declare that it implements IAttributeAnnotatable; the implementation
+is registered as an adaptor from this interface to IAnnotations.
+
+To store the configuration back pointers on components, we use an
+annotation named "zope.app.services.configuration.UseConfiguration".
+(By convention, annotation keys should contain the full dotted name of
+the module or class that uses them.)  By adapting a component to
+IUseConfiguration, we get an interface defining methods ``addUsage``,
+``removeUsage`` and ``usages``, which provide access to the back
+pointers.
+
+XXX now write the code for this view.
+
+- XXX ILocalUtility: to be used as a utility; adds IUseConfigurable,
+  promises adaptable to IUseConfigurationm enables "Utility
+  Configuration" tab.
+
 
-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.  
 
 
 
@@ -299,4 +379,4 @@
 ---------------------------------------------------------------
 
 .. [1] Of course, I initially forgot to include a nearly empty
-   ``__init__.py`` file and added one.
+   ``__init__.py`` file and had to add one later.


=== Zope3/src/zope/app/services/utility.py 1.1.2.1 => 1.1.2.2 ===
--- Zope3/src/zope/app/services/utility.py:1.1.2.1	Thu Mar 13 13:31:24 2003
+++ Zope3/src/zope/app/services/utility.py	Thu Mar 13 17:28:09 2003
@@ -1,7 +1,7 @@
 ##############################################################################
 # 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
@@ -9,17 +9,17 @@
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
 # FOR A PARTICULAR PURPOSE.
 ##############################################################################
-"""XXX short summary goes here.
+"""Local utility service implementation.
 
-XXX longer description goes here.
+Besides being functional, this module also serves as an example of
+creating a local service; see README.txt.
 
 $Id$
 """
-__metaclass__ = type
 
 from persistence import Persistent
 from persistence.dict import PersistentDict
-from zope.component.exceptions import ComponentLookupError 
+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
@@ -28,7 +28,9 @@
 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
+from zope.app.services.configuration import NamedComponentConfiguration
+from zope.app.interfaces.services.service import ISimpleService
+from zope.app.interfaces.services.utility import IUtilityConfiguration
 
 class LocalUtilityService(Persistent, ContextAware):
 
@@ -49,7 +51,7 @@
             configuration = registry.active()
             if configuration is not None:
                 return configuration.getComponent()
-            
+
         next = getNextService(self, "Utilities")
         return next.queryUtility(interface, default, name)
 
@@ -67,7 +69,7 @@
             return default
 
         return registry
-        
+
     def createConfigurationsFor(self, configuration):
         return self.createConfiguration(configuration.name,
                                         configuration.interface)
@@ -82,9 +84,9 @@
         if registry is None:
             registry = ConfigurationRegistry()
             utilities.register(interface, registry)
-            
+
         return registry
-        
+
 
 class UtilityConfiguration(NamedComponentConfiguration):
     """Utility component configuration for persistent components
@@ -94,6 +96,8 @@
 
     """
 
+    __implements__ = IUtilityConfiguration
+
     def __init__(self, name, interface, component_path, permission=None):
         super(UtilityConfiguration, self).__init__(
             name, component_path, permission)
@@ -103,13 +107,13 @@
         NamedComponentConfiguration.afterAddHook(self,
                                                  configuration,
                                                  container)
-        service = configuration.getComponent()
-        adapter = getAdapter(service, IUseConfiguration)
+        utility = configuration.getComponent()
+        adapter = getAdapter(utility, IUseConfiguration)
         adapter.addUsage(getPhysicalPathString(configuration))
 
     def beforeDeleteHook(self, configuration, container):
-        service = configuration.getComponent()
-        adapter = getAdapter(service, IUseConfiguration)
+        utility = configuration.getComponent()
+        adapter = getAdapter(utility, IUseConfiguration)
         adapter.removeUsage(getPhysicalPathString(configuration))
         NamedComponentConfiguration.beforeDeleteHook(self,
                                                      configuration,


=== Zope3/src/zope/app/services/utility.zcml 1.1.2.1 => 1.1.2.2 ===
--- Zope3/src/zope/app/services/utility.zcml:1.1.2.1	Thu Mar 13 13:31:24 2003
+++ Zope3/src/zope/app/services/utility.zcml	Thu Mar 13 17:28:09 2003
@@ -1,13 +1,18 @@
 <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>
+
+<content class=".utility.UtilityConfiguration">
+  <require
+    permission="zope.ManageServices"
+    interface="zope.app.interfaces.services.utility.IUtilityConfiguration"
+    set_schema="zope.app.interfaces.services.utility.IUtilityConfiguration"
+    />
+ </content>
 
 </zopeConfigure>