Index: tbroadcast/HEAD/python/threadpool.py
===================================================================
--- tbroadcast/HEAD/python/threadpool.py	(revision 392)
+++ tbroadcast/HEAD/python/threadpool.py	(revision 508)
@@ -1,5 +1,12 @@
-"""Easy to use object-oriented thread pool framework.
-
-A thread pool is an object that maintains a pool of worker threads to perform
+"""
+
+@author: Christopher Arndt
+@version: 1.1
+
+@see: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/435883
+
+Easy to use object-oriented thread pool framework.
+
+A thread pool is a class that maintains a pool of worker threads to perform
 time consuming operations in parallel. It assigns jobs to the threads
 by putting them in a work request queue, where they are picked up by the
@@ -7,5 +14,5 @@
 background and puts the results in a another queue.
 
-The thread pool object can then collect the results from all threads from
+The thread pool class can then collect the results from all threads from
 this queue as soon as they become available or after all threads have
 finished their work. It's also possible, to define callbacks to handle
@@ -16,49 +23,32 @@
 "Threaded Program Architecture". I wrapped the main program logic in the
 ThreadPool class, added the WorkRequest class and the callback system and
-tweaked the code here and there. Kudos also to Florent Aide for the exception
-handling mechanism.
+tweaked the code here and there.
 
 Basic usage:
 
->>> pool = TreadPool(poolsize)
+>>> main = ThreadPool(poolsize)
 >>> requests = makeRequests(some_callable, list_of_args, callback)
->>> [pool.putRequest(req) for req in requests]
->>> pool.wait()
+>>> [main.putRequests(req) for req in requests]
+>>> main.wait()
 
 See the end of the module code for a brief, annotated usage example.
-
-Website : http://chrisarndt.de/en/software/python/threadpool/
 """
 
-__all__ = [
-  'makeRequests',
-  'NoResultsPending',
-  'NoWorkersAvailable',
-  'ThreadPool',
-  'WorkRequest',
-  'WorkerThread'
-]
-
-__author__ = "Christopher Arndt"
-__version__ = "1.2.3"
-__revision__ = "$Revision: 1.5 $"
-__date__ = "$Date: 2006/06/23 12:32:25 $"
-__license__ = 'Python license'
-
-# standard library modules
-import sys
-import threading
-import Queue
-
-# exceptions
+__all__ = ['makeRequests', 'NoResultsPending', 'NoWorkersAvailable',
+  'ThreadPool', 'WorkRequest', 'WorkerThread']
+
+__author__ = 'Christopher Arndt'
+__version__ = '1.1'
+__date__ = '2005-07-19'
+
+import threading, Queue
+
 class NoResultsPending(Exception):
     """All work requests have been processed."""
     pass
-
 class NoWorkersAvailable(Exception):
     """No worker threads available to process remaining requests."""
     pass
 
-# classes
 class WorkerThread(threading.Thread):
     """Background thread connected to the requests/results queues.
@@ -68,11 +58,10 @@
     """
 
-    def __init__(self, requestsQueue, resultsQueue, **kwds):
-        """Set up thread in daemonic mode and start it immediatedly.
+    def __init__ (self, requestsQueue, resultsQueue, **kwds):
+        """Set up thread in damonic mode and start it immediatedly.
 
         requestsQueue and resultQueue are instances of Queue.Queue passed
         by the ThreadPool class when it creates a new worker thread.
         """
-
         threading.Thread.__init__(self, **kwds)
         self.setDaemon(1)
@@ -83,5 +72,6 @@
 
     def run(self):
-        """Repeatedly process the job queue until told to exit."""
+        """Repeatedly process the job queue until told to exit.
+        """
 
         while not self._dismissed.isSet():
@@ -89,14 +79,11 @@
             request = self.workRequestQueue.get()
             if self._dismissed.isSet():
-                # if told to exit, return the work request we just picked up
+                # return the work request we just picked up
                 self.workRequestQueue.put(request)
                 break # and exit
-            try:
-                self.resultQueue.put(
-                    (request, request.callable(*request.args, **request.kwds))
-                )
-            except:
-                request.exception = True
-                self.resultQueue.put((request, sys.exc_info()))
+            # XXX catch exceptions here and stick them to request object
+            self.resultQueue.put(
+                (request, request.callable(*request.args, **request.kwds))
+            )
 
     def dismiss(self):
@@ -111,13 +98,11 @@
 
     See the module function makeRequests() for the common case
-    where you want to build several WorkRequests for the same callable
-    but with different arguments for each call.
-    """
-
-    def __init__(self, callable, args=None, kwds=None, requestID=None,
-      callback=None, exc_callback=None):
-        """Create a work request for a callable and attach callbacks.
-
-        A work request consists of the a callable to be executed by a
+    where you want to build several work requests for the same callable
+    but different arguments for each call.
+    """
+
+    def __init__ (self, callable, args=None, kwds=None, requestID=None,
+      callback=None):
+        """A work request consists of the a callable to be executed by a
         worker thread, a list of positional arguments, a dictionary
         of keyword arguments.
@@ -125,28 +110,17 @@
         A callback function can be specified, that is called when the results
         of the request are picked up from the result queue. It must accept
-        two arguments, the request object and the results of the callable,
-        in that order. If you want to pass additional information to the
-        callback, just stick it on the request object.
-
-        You can also give a callback for when an exception occurs. It should
-        also accept two arguments, the work request and a tuple with the
-        exception details as returned by sys.exc_info().
-
-        requestID, if given, must be hashable since it is used by the
-        ThreadPool object to store the results of that work request in a
-        dictionary. It defaults to the return value of id(self).
-        """
-
+        two arguments, the request object and it's results in that order.
+        If you want to pass additional information to the callback, just stick
+        it on the request object.
+
+        requestID, if given, must be hashable as it is used by the ThreadPool
+        class to store the results of that work request in a dictionary.
+        It defaults to the return value of id(self).
+        """
         if requestID is None:
             self.requestID = id(self)
         else:
-            try:
-                hash(requestID)
-            except TypeError:
-                raise TypeError("requestID must be hashable.")
             self.requestID = requestID
-        self.exception = False
         self.callback = callback
-        self.exc_callback = exc_callback
         self.callable = callable
         self.args = args or []
@@ -160,11 +134,11 @@
     """
 
-    def __init__(self, num_workers, q_size=0):
+    def __init__ (self, num_workers, q_size=0):
         """Set up the thread pool and start num_workers worker threads.
 
         num_workers is the number of worker threads to start initialy.
-        If q_size > 0 the size of the work request queue is limited and
-        the thread pool blocks when the queue is full and it tries to put
-        more work requests in it (see putRequest method).
+        If q_size > 0 the size of the work request is limited and the
+        thread pool blocks when queue is full and it tries to put more
+        work requests in it.
         """
 
@@ -183,6 +157,5 @@
 
     def dismissWorkers(self, num_workers):
-        """Tell num_workers worker threads to quit after their current task.
-        """
+        """Tell num_workers worker threads to to quit when they're done."""
 
         for i in range(min(num_workers, len(self.workers))):
@@ -190,30 +163,23 @@
             worker.dismiss()
 
-    def putRequest(self, request, block=True, timeout=0):
-        """Put work request into work queue and save its id for later."""
-
-        assert isinstance(request, WorkRequest)
-        self.requestsQueue.put(item=request, block=block)#,timeout=timeout)
+    def putRequest(self, request):
+        """Put work request into work queue and save for later."""
+        self.requestsQueue.put(request)
         self.workRequests[request.requestID] = request
 
     def poll(self, block=False):
         """Process any new results in the queue."""
-
-        while True:
-            # still results pending?
-            if not self.workRequests:
-                raise NoResultsPending
-            # are there still workers to process remaining requests?
-            elif block and not self.workers:
-                raise NoWorkersAvailable
+        while 1:
             try:
+                # still results pending?
+                if not self.workRequests:
+                    raise NoResultsPending
+                # are there still workers to process remaining requests?
+                elif block and not self.workers:
+                    raise NoWorkersAvailable
                 # get back next results
                 request, result = self.resultsQueue.get(block=block)
-                # has an exception occured?
-                if request.exception and request.exc_callback:
-                    request.exc_callback(request, result)
-                # hand results to callback, if any
-                if request.callback and not \
-                  (request.exception and request.exc_callback):
+                # and hand them to the callback, if any
+                if request.callback:
                     request.callback(request, result)
                 del self.workRequests[request.requestID]
@@ -223,39 +189,86 @@
     def wait(self):
         """Wait for results, blocking until all have arrived."""
+
         while 1:
             try:
                 self.poll(True)
             except NoResultsPending:
-                break                
-# helper functions
-def makeRequests(callable, args_list, callback=None, exc_callback=None):
-    """Create several work requests for same callable with different arguments.
-    
-    Convenience function for creating several work requests for the same
-    callable where each invocation of the callable receives different values
-    for its arguments.
+                break
+
+def makeRequests(callable, args_list, callback=None):
+    """Convenience function for building several work requests for the same
+    callable with different arguments for each call.
 
     args_list contains the parameters for each invocation of callable.
-    Each item in 'args_list' should be either a 2-item tuple of the list of
+    Each item in 'argslist' should be either a 2-item tuple of the list of
     positional arguments and a dictionary of keyword arguments or a single,
     non-tuple argument.
 
-    See docstring for WorkRequest for info on callback and exc_callback.
+    callback is called when the results arrive in the result queue.
     """
 
     requests = []
+    
     for item in args_list:
-        if isinstance(item, tuple):
+        
+        if item is None:
+            """no arguments"""
             requests.append(
-              WorkRequest(callable, item[0], item[1], callback=callback,
-                exc_callback=exc_callback)
-            )
+              WorkRequest(callable, None, None, callback=callback))
+            
+        elif item == isinstance(item, tuple):
+            """arguments and keywords"""
+            requests.append(
+              WorkRequest(callable, item[0], item[1], callback=callback))
         else:
+            """only keywords"""
             requests.append(
-              WorkRequest(callable, [item], None, callback=callback,
-                exc_callback=exc_callback)
-            )
+              WorkRequest(callable, [item], None, callback=callback))
     return requests
 
 
- #------------------------------ EoF ------------------------------#
+if __name__ == '__main__':
+    import random
+    import time
+
+    # the work the threads will have to do (rather trivial in our example)
+    def do_something(data):
+        time.sleep(random.randint(1,5))
+        return round(random.random() * data, 5)
+
+    # this will be called each time a result is available
+    def print_result(request, result):
+        print "Result: %s from request #%s" % (result, request.requestID)
+
+    # assemble the arguments for each job to a list...
+    data = [random.randint(1,10) for i in range(20)]
+    # ... and build a WorkRequest object for each item in data
+    requests = makeRequests(do_something, data, print_result)
+
+    # we create a pool of 10 worker threads
+    main = ThreadPool(3)
+
+    # then we put the work requests in the queue...
+    for req in requests:
+        main.putRequest(req)
+        print "Work request #%s added." % req.requestID
+    # or shorter:
+    # [main.putRequest(req) for req in requests]
+
+    # ...and wait for the results to arrive in the result queue
+    # wait() will return when results for all work requests have arrived
+    # main.wait()
+
+    # alternatively poll for results while doing something else:
+    i = 0
+    while 1:
+        try:
+            main.poll()
+            print "Main thread working..."
+            time.sleep(0.5)
+            if i == 10:
+                print "Adding 3 more worker threads..."
+                main.createWorkers(3)
+            i += 1
+        except (KeyboardInterrupt, NoResultsPending):
+            break
