[Zope-CVS] CVS: Packages/zasync - README.txt:1.3 bforests.py:1.2 manager.py:1.5

Gary Poster gary at zope.com
Fri Nov 12 20:11:22 EST 2004

Update of /cvs-repository/Packages/zasync
In directory cvs.zope.org:/tmp/cvs-serv17226

Modified Files:
	README.txt bforests.py manager.py 
Log Message:
A new diagnostic page for the ZMI call manager.  An attempt to improve the README file to give people more of a chance to get this set up and going.  A clean up of the two example configuration scripts to remove references to Zope 3, which really probably just confused matters.  I probably will announce this version as a beta sometime this weekend.

=== Packages/zasync/README.txt 1.2 => 1.3 ===
--- Packages/zasync/README.txt:1.2	Thu Oct 14 12:36:35 2004
+++ Packages/zasync/README.txt	Fri Nov 12 20:10:52 2004
@@ -11,49 +11,115 @@
 other asynchronous processes such as instant message servers; and
 performing intelligent agent types of tasks.
-ZAsync is used in production; however, its design and usefulness are
-still under evaluation.  As usual, release into open source is not a
-guarantee of perpetual maintenance.
-The two basic pieces that zasync provides are a Zope 2 tool,
-manager.py in this directory, and the Twisted-based ZEO client in
-the client directory (client/zasync/client.py).
-The client directory in this package is not a Python package, but
-the top of a python path.  see client/zasync/example_start_script
-for an example of a script that can start up zasync.  The
-zasync.conf file also in that directory is an example of the
-required configuration file.  Be sure that the sections in
-zasync.conf that duplicate those in zope.conf match the settings of
-the Zope installation with which zasync is to interact.
-The manager should typically be installed in the root of your Zope
-via the ZMI: add an Asynchronous Call Manager from the product
-Some aspects of zasync are used in production; some are skeletons that have
-not yet been tested or used.  Let's call it a beta, with some release-quality
-basic behaviors.
 Requires that you run a ZEO server as the database source for your 
 Zope application.
-Requires Twisted.  Tested with Twisted-1.1.1 and Zope 2.7.2.  The
-installation used for development has Twisted in Zope's python's
+Requires Twisted.  Tested with Twisted-1.1.1 and Zope 2.7.2/2.7.3.  
+The installation used for development has Twisted in Zope's python's
 site-packages (i.e., (zope's
-Of the four plugins provided in client/zasync/plugins.py,
+Of the five plugins provided in client/zasync/plugins.py,
 two require python-ldap.  They are tested with 
+python-ldap-2.0.0pre18.  They are not installed in the default 
+configuration; see SETUP below for instructions in installing them.
+The start script is for *nix systems.  The start script is pretty 
+basic, so it probably would be pretty easy to write an equivalent
+for Windows.  Contributions welcome!  This has been tested on 
+Mac OS X and Linux.
-Many things, including...
+The two basic pieces that zasync provides are a Zope 2 tool,
+manager.py in this directory, and the Twisted-based ZEO client in
+the client directory (client/zasync/client.py).  To use zasync, you
+must set up both pieces.  Setting up the zasync client is the majority 
+of the work.  These instructions assume you have Twisted available to
+the Python that runs Zope, as described in "PREREQUISITES" above.
+The manager is typically installed as a singleton in the root of your
+Zope via the ZMI: add an Asynchronous Call Manager from the product
+dropdown.  The add page suggests "asynchronous_call_manager" as an id,
+which is what the default client configuration expects.  Once you have
+installed the manager, you are done with that side of the setup.
+Installing the client requires more work.  Here are the steps involved.
+- If you want to run zasync on another machine, install an identical 
+  Zope software stack on the other machine.  All following instructions
+  will apply to this second machine.  (If you want to run it on the same
+  machine, no special action is needed.)
+- zasync uses a ZConfig-based configuration file that should look
+  largely familiar to you if you are familiar with zope.conf.  An example
+  configuration file can be found in 
+  (zasync package)/client/zasync/zasync.conf.  The scheme that it uses is
+  defined in (zasync package)/client/zasync/schema.xml.  I put a copy of 
+  the configuration file in the same etc directory as you zope.conf file,
+  but you can put it whereever you wish--just make sure that the start
+  script, described below, has the correct path.  The default
+  configuration file will require some hand editing:
+  * all of the paths to software must be correct for your installation;
+  * the ZODB section must be pointing to the correct ZEO server(s); and
+  * the zasync behavior must be set up as you desire. :-)  In particular,
+    if you have python-ldap installed and would like to use the ldap 
+    plugins, just remove comment marks that precede those lines.
+- The client directory in this package is not a Python package, but
+  the top of a python path.  see client/zasync/example_start_script
+  for an example of a script that can start up zasync.  I suggest you 
+  copy this file to the bin directory of your Zope software instance and 
+  call it "zasync".  You will almost certainly need to edit it.  Other 
+  startup scripts are welcome if they provide a better experience for a
+  standard Zope install.
+The asynchronous call manager currently exposes four core methods for regular
+usage: putCall, putSessionCall, getDeferred, and getSessionDeferred.  By 
+default, authenticated users can make session-based calls and administrators 
+can make generic calls, but this can be changed on the manager's permissions
+tab ("Make Asynchronous Calls" and "Make Asynchronous Session Calls").
+To put a call into the call manager, use "putCall" or "putSessionCall".  The
+first argument is the plugin name, and following arguments are passed through
+to the plugin.  References to persistent objects are generally not good 
+arguments to use: the code sanitizes them into objects that have a path and 
+a way to dereference them.  Currently no validation is performed to see if 
+the arguments match the plugin signature.
+"putCall" or "putSessionCall" then return a ZopeDeferred object.  The 
+ZopeDeferred object represents the upcoming result to the asynchronous 
+call.  It supports two forms of interaction with the result: pull and push.
+To have a result cause the performance of given actions ("push"), use the errback and 
+callback API on the deferred.  In a design modelled after the Twisted deferred
+approach, users can add TALES expressions that will be called in the event of
+a success (callback) or failure (errback).  errbacks receive a Twisted failure
+object, which provide a number of useful capabilties including easily
+obtaining the traceback.  If an errback returns a failure (or raises another 
+error) the next errback, if any, is called; otherwise, the processing switches
+to the next callback.  See the manager.txt documentation for in-depth examples
+and see Twisted documentation for more information on callback chains.
+To poll for a result, get the key off of the deferred returned by putCall
+and putSessionCall and then use the "getDeferred" and "getSessionDeferred" to 
+obtain the deferred and "getStatus" to determine if the deferred has resolved,
+and "getValue" if it has.
+XXX more needed, editing needed :-)
 - automated tests of client and plugins

=== Packages/zasync/bforests.py => 1.2 ===
--- Packages/zasync/bforests.py:	Sun Oct 10 19:37:05 2004
+++ Packages/zasync/bforests.py	Fri Nov 12 20:10:52 2004
@@ -180,11 +180,6 @@
         # bucket.  If you want a dict, cast it to a dict, or if you want
         # another one of these but with all of the keys in the first bucket,
         # call obj.__class__(obj)
-        # if we were using new style class persistence, I'd say
-        #
-        # copy = self.__class__.__new__(self.__class__)
-        # 
-        # We'll do this instead:
         copy = self.__class__(count=0)
         copy.buckets = [self._treeclass(t) for t in self.buckets]
         return copy

=== Packages/zasync/manager.py 1.4 => 1.5 ===
--- Packages/zasync/manager.py:1.4	Fri Oct 29 21:39:22 2004
+++ Packages/zasync/manager.py	Fri Nov 12 20:10:52 2004
@@ -171,13 +171,12 @@
         res = compiled(econtext)
         if isinstance(res, Exception):
             raise res
-        #print 'returning %s from %s' % (`res`, self.text)
         return res
 class Deferred(SimpleItem):
     result = failure = original_result = original_failure = None
-    key = local_key = None
+    key = local_key = resolution_date = None
     timeout = 60 * 60 * 24 # one day; zasync plugins generally constrain 
     # timeouts more strictly
@@ -259,6 +258,7 @@
         callbacks = self.__callbacks
         assert self.raw_state != UNCALLED
         res = (self.raw_state == FAILURE and self.failure or self.result)
+        self.resolution_date = datetime.datetime.now()
         if not callbacks:
             return res
         user = self.getWrappedOwner()
@@ -426,12 +426,50 @@
         return timeout
+def getDeferredInfo(dictionary, sort_field=None, reverse=False):
+    res = []
+    for d in dictionary.values():
+        plugin, args, kwargs = d.getSignature()
+        state = d.getState()
+        value = original_value = original_state = None
+        if state is not None:
+            value = d.getValue()
+            if d.original_failure is None:
+                original_value = d.original_result
+                original_state = state_name_map[CALLED]
+            else:
+                original_value = d.original_failure
+                original_state = state_name_map[FAILURE]
+        info ={
+            'key': d.key, 
+            'user': d.getOwnerTuple(), 
+            'plugin': plugin,
+            'args': args,
+            'kwargs': kwargs,
+            'state': state,
+            'value': value,
+            'creation_date': d.creation_date,
+            'resolution_date': d.resolution_date,
+            'original_state': original_state,
+            'original_value': original_value}
+        if sort_field is None:
+            res.append(info)
+        else:
+            res.append((info.get(sort_field), info))
+    if sort_field is not None:
+        res.sort()
+        if reverse:
+            res.reverse()
+        res = [val for sort, val in res]
+    return res
 class AsynchronousCallManager(PropertyManager, SimpleItem):
     """a tool that holds deferreds for zasync to manipulate"""
     security = ClassSecurityInfo()
     manage_options = (
-        ({'label':'Overview', 'action':'manage_overview',},) +
+        ({'label':'Overview', 'action':'manage_overview',},
+         {'label':'Calls', 'action':'manage_calls',},) +
         + SimpleItem.manage_options)
@@ -477,6 +515,13 @@
         'www/controlAsynchronousCallManagerForm.zpt', globals(),
+    security.declareProtected(permissions.ViewManagementScreens, 
+                              'manage_calls')
+    manage_calls = PageTemplateFile(
+        'www/analyzeCalls.zpt', globals(),
+        __name__='manage_calls')
     def ping(self, REQUEST=None):
@@ -611,7 +656,7 @@
     def getSessionDeferred(self, d_id, default=None):
         # XXX this API doesn't support the use case of users who have limited
         # permissions but still should be able to store away keys for later
-        # look-up, irresepctive of sessions.  We should either have three 
+        # look-up, irrespective of sessions.  We should either have three 
         # levels of deferred call or let plugins specify permissions--but then
         # in what context?
         bid = self._getBrowserId()
@@ -627,6 +672,54 @@
     def __nonzero__(self):
         return True
+    security.declareProtected(
+        permissions.ViewManagementScreens, 'listNewCalls')
+    def listNewCalls(self, sort='creation_date', reverse=True):
+        return getDeferredInfo(self._new, sort, reverse)
+    security.declareProtected(
+        permissions.ViewManagementScreens, 'listAcceptedCalls')
+    def listAcceptedCalls(self, sort='creation_date', reverse=True):
+        return getDeferredInfo(self._accepted, sort, reverse)
+    security.declareProtected(
+        permissions.ViewManagementScreens, 'listResolvedCalls')
+    def listResolvedCalls(self, bucket=None, sort='creation_date', reverse=True):
+        if bucket is None:
+            d = self._resolved
+        else:
+            d = self._resolved.buckets[bucket]
+        return getDeferredInfo(d, sort, reverse)
+    security.declareProtected(
+        permissions.ViewManagementScreens, 'lenNewCalls')
+    def lenNewCalls(self):
+        return len(self._new)
+    security.declareProtected(
+        permissions.ViewManagementScreens, 'lenAcceptedCalls')
+    def lenAcceptedCalls(self):
+        return len(self._accepted)
+    security.declareProtected(
+        permissions.ViewManagementScreens, 'lenResolvedCalls')
+    def lenResolvedCalls(self, bucket=None):
+        if bucket is None:
+            d = self._resolved
+        else:
+            d = self._resolved.buckets[bucket]
+        return len(d)
+    security.declareProtected(
+        permissions.ViewManagementScreens, 'lenResolvedBuckets')
+    def lenResolvedBuckets(self):
+        return len(self._resolved.buckets)
+    security.declareProtected(
+        permissions.ViewManagementScreens, 'nextResolvedBucketRotation')
+    def nextResolvedBucketRotation(self):
+        return self._next_rotate
     def acceptAll(self):
@@ -652,7 +745,6 @@
                 self._next_rotate = None
         if self._last_ping is not None and self._last_pong is None:
             self._last_pong = now
-        # maybe optionally fire off clock tick event?  TBD
 constructAsynchronousCallManagerForm = PageTemplateFile(

More information about the Zope-CVS mailing list