[Checkins] SVN: zc.thread/trunk/ thread (and process) convenience library
Jim Fulton
jim at zope.com
Wed Nov 23 23:53:41 UTC 2011
Log message for revision 123478:
thread (and process) convenience library
Changed:
U zc.thread/trunk/README.txt
U zc.thread/trunk/buildout.cfg
U zc.thread/trunk/setup.py
A zc.thread/trunk/src/zc/thread/
A zc.thread/trunk/src/zc/thread/__init__.py
A zc.thread/trunk/src/zc/thread/tests.py
-=-
Modified: zc.thread/trunk/README.txt
===================================================================
--- zc.thread/trunk/README.txt 2011-11-23 23:52:35 UTC (rev 123477)
+++ zc.thread/trunk/README.txt 2011-11-23 23:53:41 UTC (rev 123478)
@@ -1,10 +1,48 @@
-Title Here
-**********
+Thread-creation helper
+**********************
+The thread-creation API provided by the Python ``threading`` module is
+annoying. :)
-To learn more, see
+This package provides a very simple thread-creation API that:
+- Makes threads daemonic and allows daemonicity to be passed to the
+ constructor. For example::
+ zc.thread.Thread(mythreadfunc)
+
+ Starts a daemonic thread named ``'mythreadfunc'`` running
+ ``mythreadfunc``.
+
+- Allows threads to be defined via decorators, as in::
+
+ import zc.thread
+
+ @zc.thread.Thread
+ def mythread():
+ ...
+
+ In the example above, a daemonic thread named ``mythread`` is
+ created and started. The thread is also assigned to the variable
+ ``mythread``.
+
+ You can control whether threads are daemonic and wether they are
+ started by default::
+
+ import zc.thread
+
+ @zc.thread.Thread(daemon=False, start=False)
+ def mythread():
+ ...
+
+- After a thread finished, you can get the return value of the
+ target function from the thread's ``value`` attribute, or, if the
+ function raises an exception, you can get the exception object from
+ the thread's ``exception`` attribute.
+
+There's also a Process constructor/decorator that works like Thread,
+but with multi-processing processes.
+
Changes
*******
Modified: zc.thread/trunk/buildout.cfg
===================================================================
--- zc.thread/trunk/buildout.cfg 2011-11-23 23:52:35 UTC (rev 123477)
+++ zc.thread/trunk/buildout.cfg 2011-11-23 23:53:41 UTC (rev 123478)
@@ -1,12 +1,12 @@
[buildout]
develop = .
-parts = test py
+parts = test
-[test]
-recipe = zc.recipe.testrunner
-eggs =
-
[py]
recipe = zc.recipe.egg
-eggs = ${test:eggs}
+eggs = zc.thread [test]
interpreter = py
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = ${py:eggs}
Modified: zc.thread/trunk/setup.py
===================================================================
--- zc.thread/trunk/setup.py 2011-11-23 23:52:35 UTC (rev 123477)
+++ zc.thread/trunk/setup.py 2011-11-23 23:53:41 UTC (rev 123478)
@@ -11,10 +11,10 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
-name, version = 'zc.', '0'
+name, version = 'zc.thread', '0'
install_requires = ['setuptools']
-extras_require = dict(test=['zope.testing'])
+extras_require = dict(test=['zope.testing', 'mock'])
entry_points = """
"""
Added: zc.thread/trunk/src/zc/thread/__init__.py
===================================================================
--- zc.thread/trunk/src/zc/thread/__init__.py (rev 0)
+++ zc.thread/trunk/src/zc/thread/__init__.py 2011-11-23 23:53:41 UTC (rev 123478)
@@ -0,0 +1,110 @@
+##############################################################################
+#
+# Copyright (c) Zope Foundation 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.
+#
+##############################################################################
+
+def _options(daemon=True, start=True, args=(), kwargs=None):
+ return daemon, start, args, kwargs or {}
+
+
+def Thread(func=None, **options):
+ """Create and (typically) start a thread
+
+ If no function is passed, then a decorator function is
+ returned. Typical usage is::
+
+ @zc.thread.Thread
+ def mythreadfunc():
+ ...
+
+ ...
+
+ mythread.join()
+
+ Options:
+
+ deamon=True
+ Thread daemon flag. Set to false to cause process exit to
+ block until the thread has exited.
+
+ start=True
+ True to automatically start the thread.
+
+ args=()
+ Positional arguments to pass to the thread function.
+
+ kwargs={}
+ keyword arguments to pass to the thread function.
+
+ """
+ if func is None:
+ return lambda f: Thread(f, **options)
+ daemon, start, args, kwargs = _options(**options)
+ import threading
+
+ def run(*args, **kw):
+ try:
+ v = func(*args, **kw)
+ thread.value = v
+ except Exception, v:
+ thread.exception = v
+
+ thread = threading.Thread(
+ target=run, name=getattr(func, '__name__', None),
+ args=args, kwargs=kwargs)
+ thread.setDaemon(daemon)
+ thread.value = thread.exception = None
+ if start:
+ thread.start()
+ return thread
+
+def Process(func=None, **options):
+ """Create and (typically) start a multiprocessing process
+
+ If no function is passed, then a decorator function is
+ returned. Typical usage is::
+
+ @zc.thread.Process
+ def mythreadfunc():
+ ...
+
+ ...
+
+ mythread.join()
+
+ Options:
+
+ deamon=True
+ Process daemon flag. Set to false to cause process exit to
+ block until the process has exited.
+
+ start=True
+ True to automatically start the process.
+
+ args=()
+ Positional arguments to pass to the process function.
+
+ kwargs={}
+ keyword arguments to pass to the process function.
+
+ """
+ if func is None:
+ return lambda f: Process(f, **options)
+ daemon, start, args, kwargs = _options(**options)
+ import multiprocessing
+ process = multiprocessing.Process(
+ target=func, name=getattr(func, '__name__', None),
+ args=args, kwargs=kwargs)
+ process.daemon = daemon
+ if start:
+ process.start()
+ return process
Property changes on: zc.thread/trunk/src/zc/thread/__init__.py
___________________________________________________________________
Added: svn:keywords
+ Id
Added: svn:eol-style
+ native
Added: zc.thread/trunk/src/zc/thread/tests.py
===================================================================
--- zc.thread/trunk/src/zc/thread/tests.py (rev 0)
+++ zc.thread/trunk/src/zc/thread/tests.py 2011-11-23 23:53:41 UTC (rev 123478)
@@ -0,0 +1,116 @@
+##############################################################################
+#
+# Copyright (c) Zope Foundation 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.
+#
+##############################################################################
+import doctest
+import mock
+import os
+import unittest
+import zc.thread
+
+class TestThread(unittest.TestCase):
+
+ def test_default(self):
+ with mock.patch('threading.Thread') as Thread:
+ @zc.thread.Thread
+ def foo():
+ return 42
+
+ Thread.call_args[1].pop('target')()
+ self.assert_(foo.value == 42 and foo.exception is None)
+ Thread.assert_called_with(name='foo', args=(), kwargs={})
+ foo.setDaemon.assert_called_with(True)
+ foo.start.assert_called_with()
+
+ def test_undecorated_and_exception_return(self):
+ with mock.patch('threading.Thread') as Thread:
+ def foo2():
+ raise ValueError(42)
+
+ t = zc.thread.Thread(foo2)
+ Thread.call_args[1].pop('target')()
+ Thread.assert_called_with(name='foo2', args=(), kwargs=dict())
+ t.setDaemon.assert_called_with(True)
+ t.start.assert_called_with()
+ self.assert_(t.value is None)
+ self.assert_(isinstance(t.exception, ValueError))
+ self.assert_(t.exception.args == (42,))
+
+ t = zc.thread.Thread(foo2, args=(1, 2))
+ Thread.call_args[1].pop('target')(1, 2)
+ Thread.assert_called_with(name='foo2', args=(1, 2), kwargs=dict())
+ t.setDaemon.assert_called_with(True)
+ t.start.assert_called_with()
+ self.assert_(t.value is None)
+ self.assert_(isinstance(t.exception, TypeError))
+
+ def test_passing_arguments(self):
+ with mock.patch('threading.Thread') as Thread:
+ @zc.thread.Thread(args=(1, 2), kwargs=dict(a=1), daemon=False,
+ start=False)
+ def foo(*a, **k):
+ return a, k
+
+ Thread.call_args[1].pop('target')(1, 2, **dict(a=1))
+ self.assert_(foo.value == ((1, 2), dict(a=1)))
+ Thread.assert_called_with(name='foo', args=(1, 2), kwargs=dict(a=1))
+ foo.setDaemon.assert_called_with(False)
+ self.assert_(not foo.start.called)
+
+ def test_Thread_wo_mock(self):
+ @zc.thread.Thread
+ def foo():
+ return 42
+
+ foo.join()
+ self.assert_(foo.value == 42)
+
+ def test_Process_w_mock(self):
+ with mock.patch('multiprocessing.Process') as Process:
+ @zc.thread.Process
+ def foo():
+ print 'foo called'
+ Process.call_args[1].pop('target')()
+ Process.assert_called_with(name='foo', args=(), kwargs={})
+ self.assert_(foo.daamon)
+ foo.start.assert_called_with()
+ Process.reset_mock()
+
+ def foo2():
+ pass
+ t = zc.thread.Process(foo2)
+ Process.assert_called_with(
+ target=foo2, name='foo2', args=(), kwargs={})
+ self.assert_(t.daamon)
+ t.start.assert_called_with()
+ Process.reset_mock()
+
+ @zc.thread.Process(daemon=False, start=False, args=(42,),
+ kwargs=dict(a=1))
+ def foo3():
+ print 'foo3 called'
+ Process.call_args[1].pop('target')()
+ Process.assert_called_with(name='foo3', args=(42,), kwargs=dict(a=1))
+ self.assert_(not foo3.daemon)
+ self.assert_(not foo3.start.called)
+
+ def test_Process_wo_mock(self):
+ import multiprocessing
+ queue = multiprocessing.Queue()
+ zc.thread.Process(run_process, args=(queue,)).join(11)
+ self.assert_(queue.get() != os.getpid())
+
+def run_process(queue):
+ queue.put(os.getpid())
+
+def test_suite():
+ return unittest.makeSuite(TestThread)
Property changes on: zc.thread/trunk/src/zc/thread/tests.py
___________________________________________________________________
Added: svn:keywords
+ Id
Added: svn:eol-style
+ native
More information about the checkins
mailing list