[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
-dropdown.
-
STATUS
-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.
+Beta.
PREREQUISITES
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
python)/lib/python/site-packages/twisted).
-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.
+python-ldap-2.0.0pre18. They are not installed in the default
+configuration; see SETUP below for instructions in installing them.
-TO DO
+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...
+SETUP
+
+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.
+
+USAGE
+
+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 :-)
+
+TO DO
- automated tests of client and plugins
=== Packages/zasync/bforests.py 1.1.1.1 => 1.2 ===
--- Packages/zasync/bforests.py:1.1.1.1 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
InitializeClass(Deferred)
+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',},) +
PropertyManager.manage_options
+ SimpleItem.manage_options)
@@ -477,6 +515,13 @@
'www/controlAsynchronousCallManagerForm.zpt', globals(),
__name__='manage_overview')
+
+ security.declareProtected(permissions.ViewManagementScreens,
+ 'manage_calls')
+ manage_calls = PageTemplateFile(
+ 'www/analyzeCalls.zpt', globals(),
+ __name__='manage_calls')
+
security.declareProtected(permissions.ViewManagementScreens,
'ping')
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
+
security.declarePrivate('acceptAll')
def acceptAll(self):
self._accepted.update(self._new)
@@ -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
InitializeClass(AsynchronousCallManager)
constructAsynchronousCallManagerForm = PageTemplateFile(
More information about the Zope-CVS
mailing list