Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Release v1.2.1

Conflicts:
	README.rst
	docs/conf.py
  • Loading branch information...
commit 9c05f32bd98d4ea1fbf7bcd65d7ae8e628322c2c 2 parents a850782 + 61de415
@jodal authored
Showing with 481 additions and 283 deletions.
  1. +5 −0 .coveragerc
  2. +1 −0  .gitignore
  3. +14 −1 .travis.yml
  4. +10 −1 MANIFEST.in
  5. +1 −1  README.rst
  6. +25 −0 dev-requirements.txt
  7. +13 −0 docs/changes.rst
  8. +10 −1 docs/conf.py
  9. +2 −0  examples/counter.py
  10. +2 −0  examples/deadlock_debugging.py
  11. +1 −0  examples/plain_actor.py
  12. +1 −0  examples/resolver.py
  13. +2 −2 examples/typed_actor.py
  14. +20 −5 pykka/__init__.py
  15. +41 −80 pykka/actor.py
  16. +24 −0 pykka/compat.py
  17. +20 −13 pykka/debug.py
  18. +29 −19 pykka/eventlet.py
  19. +8 −0 pykka/exceptions.py
  20. +12 −69 pykka/future.py
  21. +23 −15 pykka/gevent.py
  22. +14 −6 pykka/proxy.py
  23. +17 −13 pykka/registry.py
  24. +106 −0 pykka/threading.py
  25. +0 −6 requirements-dev.txt
  26. +0 −1  requirements-eventlet.txt
  27. +0 −2  requirements-gevent.txt
  28. +7 −0 setup.cfg
  29. +3 −1 setup.py
  30. +1 −0  tests/__init__.py
  31. +0 −8 tests/__main__.py
  32. +9 −4 tests/actor_test.py
  33. +3 −1 tests/field_access_test.py
  34. +3 −2 tests/future_test.py
  35. +6 −3 tests/logging_test.py
  36. +10 −1 tests/method_call_test.py
  37. +3 −2 tests/namespace_test.py
  38. +1 −2  tests/performance.py
  39. +3 −3 tests/proxy_test.py
  40. +5 −3 tests/ref_test.py
  41. +5 −3 tests/registry_test.py
  42. +21 −15 tox.ini
View
5 .coveragerc
@@ -0,0 +1,5 @@
+[report]
+omit =
+ */pyshared/*
+ */python?.?/*
+ */site-packages/nose/*
View
1  .gitignore
@@ -1,3 +1,4 @@
+*.egg-info
*.py,cover
*.pyc
*.swp
View
15 .travis.yml
@@ -1,15 +1,28 @@
+sudo: false
+
language: python
+addons:
+ apt:
+ packages:
+ - libev-dev
+
env:
- TOX_ENV=py26
- TOX_ENV=py27
- TOX_ENV=py32
- TOX_ENV=py33
+ - TOX_ENV=py34
- TOX_ENV=pypy
+ - TOX_ENV=pypy3
+ - TOX_ENV=docs
+ - TOX_ENV=flake8
install:
- - "sudo apt-get install -qq libev-dev"
- "pip install tox"
script:
- "tox -e $TOX_ENV"
+
+after_success:
+ - "if [ $TOX_ENV == 'py27' ]; then pip install coveralls; coveralls; fi"
View
11 MANIFEST.in
@@ -1,5 +1,14 @@
-include LICENSE *.rst pylintrc setup.cfg tox.ini
+include *.rst
+include .coveragerc
+include .travis.yml
+include LICENSE
+include pylintrc
+include setup.cfg
+include tox.ini
+
recursive-include docs *
prune docs/_build
+
recursive-include examples *.py
+
recursive-include tests *.py
View
2  README.rst
@@ -365,7 +365,7 @@ To install the latest development snapshot::
License
=======
-Pykka is copyright 2010-2014 Stein Magnus Jodal and contributors.
+Pykka is copyright 2010-2015 Stein Magnus Jodal and contributors.
Pykka is licensed under the `Apache License, Version 2.0
<http://www.apache.org/licenses/LICENSE-2.0>`_.
View
25 dev-requirements.txt
@@ -0,0 +1,25 @@
+# Build documentation
+sphinx
+
+# Check code style, error, etc
+flake8
+flake8-import-order
+
+# Mock dependencies in tests
+mock
+
+# Test runners
+nose
+tox
+
+# Measure test's code coverage
+coverage
+
+# Check that MANIFEST.in matches Git repo contents before making a release
+check-manifest
+
+# To make wheel packages
+wheel
+
+# Securely upload packages to PyPI
+twine
View
13 docs/changes.rst
@@ -2,6 +2,19 @@
Changes
=======
+v1.2.1 (2015-07-20)
+===================
+
+- Increase log level of :func:`pykka.debug.log_thread_tracebacks` debugging
+ helper from :attr:`logging.INFO` to :attr:`logging.CRITICAL`.
+
+- Fix errors in docs examples. (PR: :issue:`29`, :issue:`43`)
+
+- Fix typos in docs.
+
+- Various project setup and development improvements.
+
+
v1.2.0 (2013-07-15)
===================
View
11 docs/conf.py
@@ -15,6 +15,7 @@
class Mock(object):
+
def __init__(self, *args, **kwargs):
pass
@@ -50,6 +51,7 @@ def __getattr__(self, name):
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.extlinks',
+ 'sphinx.ext.intersphinx',
'sphinx.ext.viewcode',
]
@@ -58,7 +60,7 @@ def __getattr__(self, name):
master_doc = 'index'
project = u'Pykka'
-copyright = u'2010-2014, Stein Magnus Jodal'
+copyright = u'2010-2015, Stein Magnus Jodal'
def get_version():
@@ -118,3 +120,10 @@ def get_version():
extlinks = {
'issue': ('https://github.com/jodal/pykka/issues/%s', '#'),
}
+
+
+# -- Options for intersphinx extension ----------------------------------------
+
+intersphinx_mapping = {
+ 'python': ('http://docs.python.org/2', None),
+}
View
2  examples/counter.py
@@ -4,12 +4,14 @@
class Adder(pykka.ThreadingActor):
+
def add_one(self, i):
print('{} is increasing {}'.format(self, i))
return i + 1
class Bookkeeper(pykka.ThreadingActor):
+
def __init__(self, adder):
super(Bookkeeper, self).__init__()
self.adder = adder
View
2  examples/deadlock_debugging.py
@@ -10,12 +10,14 @@
class DeadlockActorA(pykka.ThreadingActor):
+
def foo(self, b):
logging.debug('This is foo calling bar')
return b.bar().get()
class DeadlockActorB(pykka.ThreadingActor):
+
def __init__(self, a):
super(DeadlockActorB, self).__init__()
self.a = a
View
1  examples/plain_actor.py
@@ -4,6 +4,7 @@
class PlainActor(pykka.ThreadingActor):
+
def __init__(self):
super(PlainActor, self).__init__()
self.stored_messages = []
View
1  examples/resolver.py
@@ -22,6 +22,7 @@
class Resolver(pykka.ThreadingActor):
+
def resolve(self, ip):
try:
info = socket.gethostbyaddr(ip)
View
4 examples/typed_actor.py
@@ -1,10 +1,10 @@
#! /usr/bin/env python
-import pykka
-
import threading
import time
+import pykka
+
class AnActor(pykka.ThreadingActor):
field = 'this is the value of AnActor.field'
View
25 pykka/__init__.py
@@ -1,13 +1,27 @@
-# flake8: noqa
-from pykka.actor import Actor, ActorRef, ThreadingActor
+from pykka.actor import Actor, ActorRef
from pykka.exceptions import ActorDeadError, Timeout
-from pykka.future import Future, get_all, ThreadingFuture
+from pykka.future import Future, get_all
from pykka.proxy import ActorProxy
from pykka.registry import ActorRegistry
+from pykka.threading import ThreadingActor, ThreadingFuture
-#: Pykka's :pep:`386` and :pep:`396` compatible version number
-__version__ = '1.2.0'
+__all__ = [
+ 'Actor',
+ 'ActorDeadError',
+ 'ActorProxy',
+ 'ActorRef',
+ 'ActorRegistry',
+ 'Future',
+ 'ThreadingActor',
+ 'ThreadingFuture',
+ 'Timeout',
+ 'get_all',
+]
+
+
+#: Pykka's :pep:`396` and :pep:`440` compatible version number
+__version__ = '1.2.1'
def _add_null_handler_for_logging():
@@ -16,6 +30,7 @@ def _add_null_handler_for_logging():
NullHandler = logging.NullHandler # Python 2.7 and upwards
except AttributeError:
class NullHandler(logging.Handler):
+
def emit(self, record):
pass
logging.getLogger('pykka').addHandler(NullHandler())
View
121 pykka/actor.py
@@ -1,24 +1,25 @@
-import logging as _logging
-import sys as _sys
-import threading as _threading
-import uuid as _uuid
+from __future__ import absolute_import
-try:
- # Python 2.x
- import Queue as _queue
-except ImportError:
- # Python 3.x
- import queue as _queue # noqa
+import logging
+import sys
+import threading
+import uuid
-from pykka.exceptions import ActorDeadError as _ActorDeadError
-from pykka.future import ThreadingFuture as _ThreadingFuture
-from pykka.proxy import ActorProxy as _ActorProxy
-from pykka.registry import ActorRegistry as _ActorRegistry
+from pykka.exceptions import ActorDeadError
+from pykka.proxy import ActorProxy
+from pykka.registry import ActorRegistry
-_logger = _logging.getLogger('pykka')
+
+__all__ = [
+ 'Actor',
+ 'ActorRef',
+]
+
+logger = logging.getLogger('pykka')
class Actor(object):
+
"""
To create an actor:
@@ -94,8 +95,8 @@ def start(cls, *args, **kwargs):
assert obj.actor_ref is not None, (
'Actor.__init__() have not been called. '
'Did you forget to call super() in your override?')
- _ActorRegistry.register(obj.actor_ref)
- _logger.debug('Starting %s', obj)
+ ActorRegistry.register(obj.actor_ref)
+ logger.debug('Starting %s', obj)
obj._start_actor_loop()
return obj.actor_ref
@@ -149,9 +150,9 @@ def __init__(self, *args, **kwargs):
:meth:`__init__` is called before the actor is started and registered
in :class:`ActorRegistry <pykka.ActorRegistry>`.
"""
- self.actor_urn = _uuid.uuid4().urn
+ self.actor_urn = uuid.uuid4().urn
self.actor_inbox = self._create_actor_inbox()
- self.actor_stopped = _threading.Event()
+ self.actor_stopped = threading.Event()
self.actor_ref = ActorRef(self)
@@ -173,13 +174,13 @@ def _stop(self):
"""
Stops the actor immediately without processing the rest of the inbox.
"""
- _ActorRegistry.unregister(self.actor_ref)
+ ActorRegistry.unregister(self.actor_ref)
self.actor_stopped.set()
- _logger.debug('Stopped %s', self)
+ logger.debug('Stopped %s', self)
try:
self.on_stop()
except Exception:
- self._handle_failure(*_sys.exc_info())
+ self._handle_failure(*sys.exc_info())
def _actor_loop(self):
"""
@@ -190,7 +191,7 @@ def _actor_loop(self):
try:
self.on_start()
except Exception:
- self._handle_failure(*_sys.exc_info())
+ self._handle_failure(*sys.exc_info())
while not self.actor_stopped.is_set():
message = self.actor_inbox.get()
@@ -202,23 +203,23 @@ def _actor_loop(self):
reply_to.set(response)
except Exception:
if reply_to:
- _logger.debug(
+ logger.debug(
'Exception returned from %s to caller:' % self,
- exc_info=_sys.exc_info())
+ exc_info=sys.exc_info())
reply_to.set_exception()
else:
- self._handle_failure(*_sys.exc_info())
+ self._handle_failure(*sys.exc_info())
try:
- self.on_failure(*_sys.exc_info())
+ self.on_failure(*sys.exc_info())
except Exception:
- self._handle_failure(*_sys.exc_info())
+ self._handle_failure(*sys.exc_info())
except BaseException:
- exception_value = _sys.exc_info()[1]
- _logger.debug(
+ exception_value = sys.exc_info()[1]
+ logger.debug(
'%s in %s. Stopping all actors.' %
(repr(exception_value), self))
self._stop()
- _ActorRegistry.stop_all()
+ ActorRegistry.stop_all()
while not self.actor_inbox.empty():
msg = self.actor_inbox.get()
@@ -227,7 +228,7 @@ def _actor_loop(self):
if msg.get('command') == 'pykka_stop':
reply_to.set(None)
else:
- reply_to.set_exception(_ActorDeadError(
+ reply_to.set_exception(ActorDeadError(
'%s stopped before handling the message' %
self.actor_ref))
@@ -263,10 +264,10 @@ def on_stop(self):
def _handle_failure(self, exception_type, exception_value, traceback):
"""Logs unexpected failures, unregisters and stops the actor."""
- _logger.error(
+ logger.error(
'Unhandled exception in %s:' % self,
exc_info=(exception_type, exception_value, traceback))
- _ActorRegistry.unregister(self.actor_ref)
+ ActorRegistry.unregister(self.actor_ref)
self.actor_stopped.set()
def on_failure(self, exception_type, exception_value, traceback):
@@ -314,7 +315,7 @@ def on_receive(self, message):
:returns: anything that should be sent as a reply to the sender
"""
- _logger.warning('Unexpected message received by %s: %s', self, message)
+ logger.warning('Unexpected message received by %s: %s', self, message)
def _get_attribute_from_path(self, attr_path):
"""
@@ -326,48 +327,8 @@ def _get_attribute_from_path(self, attr_path):
return attr
-class ThreadingActor(Actor):
- """
- :class:`ThreadingActor` implements :class:`Actor` using regular Python
- threads.
-
- This implementation is slower than :class:`GeventActor
- <pykka.gevent.GeventActor>`, but can be used in a process with other
- threads that are not Pykka actors.
- """
-
- use_daemon_thread = False
- """
- A boolean value indicating whether this actor is executed on a thread that
- is a daemon thread (:class:`True`) or not (:class:`False`). This must be
- set before :meth:`pykka.Actor.start` is called, otherwise
- :exc:`RuntimeError` is raised.
-
- The entire Python program exits when no alive non-daemon threads are left.
- This means that an actor running on a daemon thread may be interrupted at
- any time, and there is no guarantee that cleanup will be done or that
- :meth:`pykka.Actor.on_stop` will be called.
-
- Actors do not inherit the daemon flag from the actor that made it. It
- always has to be set explicitly for the actor to run on a daemonic thread.
- """
-
- @staticmethod
- def _create_actor_inbox():
- return _queue.Queue()
-
- @staticmethod
- def _create_future():
- return _ThreadingFuture()
-
- def _start_actor_loop(self):
- thread = _threading.Thread(target=self._actor_loop)
- thread.name = thread.name.replace('Thread', self.__class__.__name__)
- thread.daemon = self.use_daemon_thread
- thread.start()
-
-
class ActorRef(object):
+
"""
Reference to a running actor which may safely be passed around.
@@ -434,7 +395,7 @@ def tell(self, message):
:return: nothing
"""
if not self.is_alive():
- raise _ActorDeadError('%s not found' % self)
+ raise ActorDeadError('%s not found' % self)
self.actor_inbox.put(message)
def ask(self, message, block=True, timeout=None):
@@ -468,7 +429,7 @@ def ask(self, message, block=True, timeout=None):
message['pykka_reply_to'] = future
try:
self.tell(message)
- except _ActorDeadError:
+ except ActorDeadError:
future.set_exception()
if block:
return future.get(timeout=timeout)
@@ -501,7 +462,7 @@ def _stop_result_converter(timeout):
try:
ask_future.get(timeout=timeout)
return True
- except _ActorDeadError:
+ except ActorDeadError:
return False
converted_future = ask_future.__class__()
@@ -528,4 +489,4 @@ def proxy(self):
:raise: :exc:`pykka.ActorDeadError` if actor is not available
:return: :class:`pykka.ActorProxy`
"""
- return _ActorProxy(self)
+ return ActorProxy(self)
View
24 pykka/compat.py
@@ -0,0 +1,24 @@
+import sys
+
+PY2 = sys.version_info[0] == 2
+PY3 = sys.version_info[0] == 3
+
+if PY2:
+ import Queue as queue # noqa
+
+ string_types = basestring # noqa
+
+ def reraise(tp, value, tb=None):
+ exec('raise tp, value, tb')
+
+else:
+ import queue # noqa
+
+ string_types = (str,)
+
+ def reraise(tp, value, tb=None):
+ if value is None:
+ value = tp()
+ if value.__traceback__ is not tb:
+ raise value.with_traceback(tb)
+ raise value
View
33 pykka/debug.py
@@ -1,14 +1,21 @@
-import logging as _logging
-import sys as _sys
-import threading as _threading
-import traceback as _traceback
+from __future__ import absolute_import
+import logging
+import sys
+import threading
+import traceback
-_logger = _logging.getLogger('pykka')
+
+logger = logging.getLogger('pykka')
+
+
+__all__ = [
+ 'log_thread_tracebacks',
+]
def log_thread_tracebacks(*args, **kwargs):
- """Logs at ``INFO`` level a traceback for each running thread.
+ """Logs at ``CRITICAL`` level a traceback for each running thread.
This can be a convenient tool for debugging deadlocks.
@@ -16,8 +23,8 @@ def log_thread_tracebacks(*args, **kwargs):
signal handler, but it does not use the arguments for anything.
To use this function as a signal handler, setup logging with a
- :attr:`logging.INFO` threshold or lower and make your main thread register
- this with the :mod:`signal` module::
+ :attr:`logging.CRITICAL` threshold or lower and make your main thread
+ register this with the :mod:`signal` module::
import logging
import signal
@@ -40,7 +47,7 @@ def log_thread_tracebacks(*args, **kwargs):
will only be handled, and the tracebacks logged, if your main thread is
available to do some work. Making your main thread idle using
:func:`time.sleep` is OK. The signal will awaken your main thread.
- Blocking your main thread on e.g. :func:`Queue.Queue.get` or
+ Blocking your main thread on e.g. :func:`Queue.Queue.get` or
:meth:`pykka.Future.get` will break signal handling, and thus you won't
be able to signal your process to print the thread tracebacks.
@@ -54,10 +61,10 @@ def log_thread_tracebacks(*args, **kwargs):
.. versionadded:: 1.1
"""
- thread_names = dict((t.ident, t.name) for t in _threading.enumerate())
+ thread_names = dict((t.ident, t.name) for t in threading.enumerate())
- for ident, frame in _sys._current_frames().items():
+ for ident, frame in sys._current_frames().items():
name = thread_names.get(ident, '?')
- stack = ''.join(_traceback.format_stack(frame))
- _logger.info(
+ stack = ''.join(traceback.format_stack(frame))
+ logger.critical(
'Current state of %s (ident: %s):\n%s', name, ident, stack)
View
48 pykka/eventlet.py
@@ -1,17 +1,25 @@
from __future__ import absolute_import
-import sys as _sys
+import sys
-import eventlet as _eventlet
-import eventlet.event as _eventlet_event
-import eventlet.queue as _eventlet_queue
+import eventlet
+import eventlet.event
+import eventlet.queue
-from pykka import Timeout as _Timeout
-from pykka.actor import Actor as _Actor
-from pykka.future import Future as _Future
+from pykka import Timeout
+from pykka.actor import Actor
+from pykka.future import Future
-class EventletEvent(_eventlet_event.Event):
+__all__ = [
+ 'EventletActor',
+ 'EventletEvent',
+ 'EventletFuture',
+]
+
+
+class EventletEvent(eventlet.event.Event):
+
"""
:class:`EventletEvent` adapts :class:`eventlet.event.Event` to
:class:`threading.Event` interface.
@@ -33,12 +41,12 @@ def clear(self):
def wait(self, timeout):
if timeout is not None:
- wait_timeout = _eventlet.Timeout(timeout)
+ wait_timeout = eventlet.Timeout(timeout)
try:
with wait_timeout:
super(EventletEvent, self).wait()
- except _eventlet.Timeout as t:
+ except eventlet.Timeout as t:
if t is not wait_timeout:
raise
return False
@@ -48,7 +56,8 @@ def wait(self, timeout):
return True
-class EventletFuture(_Future):
+class EventletFuture(Future):
+
"""
:class:`EventletFuture` implements :class:`pykka.Future` for use with
:class:`EventletActor`.
@@ -58,7 +67,7 @@ class EventletFuture(_Future):
def __init__(self):
super(EventletFuture, self).__init__()
- self.event = _eventlet_event.Event()
+ self.event = eventlet.event.Event()
def get(self, timeout=None):
try:
@@ -67,14 +76,14 @@ def get(self, timeout=None):
pass
if timeout is not None:
- wait_timeout = _eventlet.Timeout(timeout)
+ wait_timeout = eventlet.Timeout(timeout)
try:
with wait_timeout:
return self.event.wait()
- except _eventlet.Timeout as t:
+ except eventlet.Timeout as t:
if t is not wait_timeout:
raise
- raise _Timeout(t)
+ raise Timeout(t)
else:
return self.event.wait()
@@ -84,10 +93,11 @@ def set(self, value=None):
def set_exception(self, exc_info=None):
if isinstance(exc_info, BaseException):
exc_info = (exc_info,)
- self.event.send_exception(*(exc_info or _sys.exc_info()))
+ self.event.send_exception(*(exc_info or sys.exc_info()))
+
+class EventletActor(Actor):
-class EventletActor(_Actor):
"""
:class:`EventletActor` implements :class:`pykka.Actor` using the `eventlet
<http://eventlet.net/>`_ library.
@@ -97,11 +107,11 @@ class EventletActor(_Actor):
@staticmethod
def _create_actor_inbox():
- return _eventlet_queue.Queue()
+ return eventlet.queue.Queue()
@staticmethod
def _create_future():
return EventletFuture()
def _start_actor_loop(self):
- _eventlet.greenthread.spawn(self._actor_loop)
+ eventlet.greenthread.spawn(self._actor_loop)
View
8 pykka/exceptions.py
@@ -1,8 +1,16 @@
+__all__ = [
+ 'ActorDeadError',
+ 'Timeout',
+]
+
+
class ActorDeadError(Exception):
+
"""Exception raised when trying to use a dead or unavailable actor."""
pass
class Timeout(Exception):
+
"""Exception raised at future timeout."""
pass
View
81 pykka/future.py
@@ -1,25 +1,19 @@
-import collections as _collections
-import functools as _functools
-import sys as _sys
+import collections
+import functools
-try:
- # Python 2.x
- import Queue as _queue
- _basestring = basestring
- PY3 = False
-except ImportError:
- # Python 3.x
- import queue as _queue # noqa
- _basestring = str
- PY3 = True
+from pykka import compat
-from pykka.exceptions import Timeout as _Timeout
+
+__all__ = [
+ 'Future',
+ 'get_all',
+]
def _is_iterable(x):
return (
- isinstance(x, _collections.Iterable) and
- not isinstance(x, _basestring))
+ isinstance(x, collections.Iterable) and
+ not isinstance(x, compat.string_types))
def _map(func, *iterables):
@@ -30,6 +24,7 @@ def _map(func, *iterables):
class Future(object):
+
"""
A :class:`Future` is a handle to a value which are available or will be
available in the future.
@@ -254,63 +249,11 @@ def reduce(self, func, *args):
.. versionadded:: 1.2
"""
future = self.__class__()
- future.set_get_hook(lambda timeout: _functools.reduce(
+ future.set_get_hook(lambda timeout: functools.reduce(
func, self.get(timeout), *args))
return future
-class ThreadingFuture(Future):
- """
- :class:`ThreadingFuture` implements :class:`Future` for use with
- :class:`ThreadingActor <pykka.ThreadingActor>`.
-
- The future is implemented using a :class:`Queue.Queue`.
-
- The future does *not* make a copy of the object which is :meth:`set()
- <pykka.Future.set>` on it. It is the setters responsibility to only pass
- immutable objects or make a copy of the object before setting it on the
- future.
-
- .. versionchanged:: 0.14
- Previously, the encapsulated value was a copy made with
- :func:`copy.deepcopy`, unless the encapsulated value was a future, in
- which case the original future was encapsulated.
- """
-
- def __init__(self):
- super(ThreadingFuture, self).__init__()
- self._queue = _queue.Queue(maxsize=1)
- self._data = None
-
- def get(self, timeout=None):
- try:
- return super(ThreadingFuture, self).get(timeout=timeout)
- except NotImplementedError:
- pass
-
- try:
- if self._data is None:
- self._data = self._queue.get(True, timeout)
- if 'exc_info' in self._data:
- exc_info = self._data['exc_info']
- if PY3:
- raise exc_info[1].with_traceback(exc_info[2])
- else:
- exec('raise exc_info[0], exc_info[1], exc_info[2]')
- else:
- return self._data['value']
- except _queue.Empty:
- raise _Timeout('%s seconds' % timeout)
-
- def set(self, value=None):
- self._queue.put({'value': value}, block=False)
-
- def set_exception(self, exc_info=None):
- if isinstance(exc_info, BaseException):
- exc_info = (exc_info.__class__, exc_info, None)
- self._queue.put({'exc_info': exc_info or _sys.exc_info()})
-
-
def get_all(futures, timeout=None):
"""
Collect all values encapsulated in the list of futures.
View
38 pykka/gevent.py
@@ -1,17 +1,24 @@
from __future__ import absolute_import
-import sys as _sys
+import sys
-import gevent as _gevent
-import gevent.event as _gevent_event
-import gevent.queue as _gevent_queue
+import gevent
+import gevent.event
+import gevent.queue
-from pykka import Timeout as _Timeout
-from pykka.actor import Actor as _Actor
-from pykka.future import Future as _Future
+from pykka import Timeout
+from pykka.actor import Actor
+from pykka.future import Future
-class GeventFuture(_Future):
+__all__ = [
+ 'GeventActor',
+ 'GeventFuture',
+]
+
+
+class GeventFuture(Future):
+
"""
:class:`GeventFuture` implements :class:`pykka.Future` for use with
:class:`GeventActor`.
@@ -28,7 +35,7 @@ def __init__(self, async_result=None):
if async_result is not None:
self.async_result = async_result
else:
- self.async_result = _gevent_event.AsyncResult()
+ self.async_result = gevent.event.AsyncResult()
def get(self, timeout=None):
try:
@@ -38,8 +45,8 @@ def get(self, timeout=None):
try:
return self.async_result.get(timeout=timeout)
- except _gevent.Timeout as e:
- raise _Timeout(e)
+ except gevent.Timeout as e:
+ raise Timeout(e)
def set(self, value=None):
assert not self.async_result.ready(), 'value has already been set'
@@ -49,12 +56,13 @@ def set_exception(self, exc_info=None):
if isinstance(exc_info, BaseException):
exception = exc_info
else:
- exc_info = exc_info or _sys.exc_info()
+ exc_info = exc_info or sys.exc_info()
exception = exc_info[1]
self.async_result.set_exception(exception)
-class GeventActor(_Actor):
+class GeventActor(Actor):
+
"""
:class:`GeventActor` implements :class:`pykka.Actor` using the `gevent
<http://www.gevent.org/>`_ library. gevent is a coroutine-based Python
@@ -67,11 +75,11 @@ class GeventActor(_Actor):
@staticmethod
def _create_actor_inbox():
- return _gevent_queue.Queue()
+ return gevent.queue.Queue()
@staticmethod
def _create_future():
return GeventFuture()
def _start_actor_loop(self):
- _gevent.Greenlet.spawn(self._actor_loop)
+ gevent.Greenlet.spawn(self._actor_loop)
View
20 pykka/proxy.py
@@ -1,10 +1,16 @@
-import collections as _collections
-import sys as _sys
+import collections
+import sys
-from pykka.exceptions import ActorDeadError as _ActorDeadError
+from pykka.exceptions import ActorDeadError
+
+
+__all__ = [
+ 'ActorProxy',
+]
class ActorProxy(object):
+
"""
An :class:`ActorProxy` wraps an :class:`ActorRef <pykka.ActorRef>`
instance. The proxy allows the referenced actor to be used through regular
@@ -90,7 +96,7 @@ def do_more_work(self):
def __init__(self, actor_ref, attr_path=None):
if not actor_ref.is_alive():
- raise _ActorDeadError('%s not found' % actor_ref)
+ raise ActorDeadError('%s not found' % actor_ref)
self.actor_ref = actor_ref
self._actor = actor_ref._actor
self._attr_path = attr_path or tuple()
@@ -127,10 +133,10 @@ def _is_callable_attribute(self, attr):
# isinstance(attr, collections.Callable), as recommended by 2to3, does
# not work on CPython 2.6.4 if the attribute is an Queue.Queue, but
# works on 2.6.6.
- if _sys.version_info < (3,):
+ if sys.version_info < (3,):
return callable(attr)
else:
- return isinstance(attr, _collections.Callable)
+ return isinstance(attr, collections.Callable)
def _is_traversable_attribute(self, attr):
"""
@@ -194,7 +200,9 @@ def __setattr__(self, name, value):
class _CallableProxy(object):
+
"""Internal helper class for proxying callables."""
+
def __init__(self, ref, attr_path):
self.actor_ref = ref
self._attr_path = attr_path
View
30 pykka/registry.py
@@ -1,16 +1,20 @@
-import logging as _logging
-import threading as _threading
+from __future__ import absolute_import
-try:
- _basestring = basestring
-except NameError:
- # Python 3
- _basestring = str
+import logging
+import threading
-_logger = _logging.getLogger('pykka')
+from pykka import compat
+
+logger = logging.getLogger('pykka')
+
+
+__all__ = [
+ 'ActorRegistry',
+]
class ActorRegistry(object):
+
"""
Registry which provides easy access to all running actors.
@@ -18,7 +22,7 @@ class ActorRegistry(object):
"""
_actor_refs = []
- _actor_refs_lock = _threading.RLock()
+ _actor_refs_lock = threading.RLock()
@classmethod
def broadcast(cls, message, target_class=None):
@@ -34,7 +38,7 @@ def broadcast(cls, message, target_class=None):
:param target_class: optional actor class to broadcast the message to
:type target_class: class or class name
"""
- if isinstance(target_class, _basestring):
+ if isinstance(target_class, compat.string_types):
targets = cls.get_by_class_name(target_class)
elif target_class is not None:
targets = cls.get_by_class(target_class)
@@ -115,7 +119,7 @@ def register(cls, actor_ref):
"""
with cls._actor_refs_lock:
cls._actor_refs.append(actor_ref)
- _logger.debug('Registered %s', actor_ref)
+ logger.debug('Registered %s', actor_ref)
@classmethod
def stop_all(cls, block=True, timeout=None):
@@ -159,7 +163,7 @@ def unregister(cls, actor_ref):
cls._actor_refs.remove(actor_ref)
removed = True
if removed:
- _logger.debug('Unregistered %s', actor_ref)
+ logger.debug('Unregistered %s', actor_ref)
else:
- _logger.debug(
+ logger.debug(
'Unregistered %s (not found in registry)', actor_ref)
View
106 pykka/threading.py
@@ -0,0 +1,106 @@
+from __future__ import absolute_import
+
+import sys
+import threading
+
+from pykka import compat
+from pykka.actor import Actor
+from pykka.exceptions import Timeout
+from pykka.future import Future
+
+
+__all__ = [
+ 'ThreadingActor',
+ 'ThreadingFuture',
+]
+
+
+class ThreadingFuture(Future):
+
+ """
+ :class:`ThreadingFuture` implements :class:`Future` for use with
+ :class:`ThreadingActor <pykka.ThreadingActor>`.
+
+ The future is implemented using a :class:`Queue.Queue`.
+
+ The future does *not* make a copy of the object which is :meth:`set()
+ <pykka.Future.set>` on it. It is the setters responsibility to only pass
+ immutable objects or make a copy of the object before setting it on the
+ future.
+
+ .. versionchanged:: 0.14
+ Previously, the encapsulated value was a copy made with
+ :func:`copy.deepcopy`, unless the encapsulated value was a future, in
+ which case the original future was encapsulated.
+ """
+
+ def __init__(self):
+ super(ThreadingFuture, self).__init__()
+ self._queue = compat.queue.Queue(maxsize=1)
+ self._data = None
+
+ def get(self, timeout=None):
+ try:
+ return super(ThreadingFuture, self).get(timeout=timeout)
+ except NotImplementedError:
+ pass
+
+ try:
+ if self._data is None:
+ self._data = self._queue.get(True, timeout)
+ if 'exc_info' in self._data:
+ compat.reraise(*self._data['exc_info'])
+ else:
+ return self._data['value']
+ except compat.queue.Empty:
+ raise Timeout('%s seconds' % timeout)
+
+ def set(self, value=None):
+ self._queue.put({'value': value}, block=False)
+
+ def set_exception(self, exc_info=None):
+ if isinstance(exc_info, BaseException):
+ exc_info = (exc_info.__class__, exc_info, None)
+ self._queue.put({'exc_info': exc_info or sys.exc_info()})
+
+
+class ThreadingActor(Actor):
+
+ """
+ :class:`ThreadingActor` implements :class:`Actor` using regular Python
+ threads.
+
+ This implementation is slower than :class:`GeventActor
+ <pykka.gevent.GeventActor>`, but can be used in a process with other
+ threads that are not Pykka actors.
+ """
+
+ use_daemon_thread = False
+ """
+ A boolean value indicating whether this actor is executed on a thread that
+ is a daemon thread (:class:`True`) or not (:class:`False`). This must be
+ set before :meth:`pykka.Actor.start` is called, otherwise
+ :exc:`RuntimeError` is raised.
+
+ The entire Python program exits when no alive non-daemon threads are left.
+ This means that an actor running on a daemon thread may be interrupted at
+ any time, and there is no guarantee that cleanup will be done or that
+ :meth:`pykka.Actor.on_stop` will be called.
+
+ Actors do not inherit the daemon flag from the actor that made it. It
+ always has to be set explicitly for the actor to run on a daemonic thread.
+ """
+
+ @staticmethod
+ def _create_actor_inbox():
+ return compat.queue.Queue()
+
+ @staticmethod
+ def _create_future():
+ return ThreadingFuture()
+
+ def _start_actor_loop(self):
+ thread = threading.Thread(target=self._actor_loop)
+ thread.name = thread.name.replace('Thread', self.__class__.__name__)
+ thread.daemon = self.use_daemon_thread
+ thread.start()
View
6 requirements-dev.txt
@@ -1,6 +0,0 @@
-# To run tests
-mock
-nose
-
-# Optional addon for collecting test coverage data
-coverage
View
1  requirements-eventlet.txt
@@ -1 +0,0 @@
-eventlet>=0.12.1
View
2  requirements-gevent.txt
@@ -1,2 +0,0 @@
-cython
-https://github.com/surfly/gevent/archive/1.0rc2.tar.gz
View
7 setup.cfg
@@ -1,4 +1,11 @@
+[flake8]
+application-import-names = pykka,tests
+exclude = .git,.tox
+
[nosetests]
verbosity = 1
cover-package = pykka
cover-inclusive = 1
+
+[wheel]
+universal = 1
View
4 setup.py
@@ -1,6 +1,7 @@
-from distutils.core import setup
import re
+from setuptools import setup
+
def get_version():
init_py = open('pykka/__init__.py').read()
@@ -28,6 +29,7 @@ def get_version():
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
+ 'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Software Development :: Libraries',
View
1  tests/__init__.py
@@ -4,6 +4,7 @@
class TestLogHandler(logging.Handler):
+
def __init__(self, *args, **kwargs):
self.lock = threading.RLock()
with self.lock:
View
8 tests/__main__.py
@@ -1,8 +0,0 @@
-import nose
-import yappi
-
-try:
- yappi.start()
- nose.main()
-finally:
- yappi.print_stats()
View
13 tests/actor_test.py
@@ -1,14 +1,12 @@
import threading
import unittest
import uuid
-import time
-from pykka.actor import ThreadingActor
-from pykka.exceptions import ActorDeadError
-from pykka.registry import ActorRegistry
+from pykka import ActorDeadError, ActorRegistry, ThreadingActor
class AnActor(object):
+
def __init__(self, **events):
super(AnActor, self).__init__()
self.on_start_was_called = events['on_start_was_called']
@@ -49,6 +47,7 @@ def on_receive(self, message):
class EarlyStoppingActor(object):
+
def __init__(self, on_stop_was_called):
super(EarlyStoppingActor, self).__init__()
self.on_stop_was_called = on_stop_was_called
@@ -61,6 +60,7 @@ def on_stop(self):
class EarlyFailingActor(object):
+
def __init__(self, on_start_was_called):
super(EarlyFailingActor, self).__init__()
self.on_start_was_called = on_start_was_called
@@ -73,6 +73,7 @@ def on_start(self):
class LateFailingActor(object):
+
def __init__(self, on_stop_was_called):
super(LateFailingActor, self).__init__()
self.on_stop_was_called = on_stop_was_called
@@ -88,6 +89,7 @@ def on_stop(self):
class FailingOnFailureActor(object):
+
def __init__(self, on_failure_was_called):
super(FailingOnFailureActor, self).__init__()
self.on_failure_was_called = on_failure_was_called
@@ -106,6 +108,7 @@ def on_failure(self, *args):
class ActorTest(object):
+
def setUp(self):
self.on_start_was_called = self.event_class()
self.on_stop_was_called = self.event_class()
@@ -293,6 +296,7 @@ def test_actor_processes_all_messages_before_stop_on_self_stops_it(self):
def ConcreteActorTest(actor_class, event_class):
class C(ActorTest, unittest.TestCase):
+
class AnActor(AnActor, actor_class):
pass
@@ -317,6 +321,7 @@ class SuperInitActor(actor_class):
class ThreadingActorTest(ConcreteActorTest(ThreadingActor, threading.Event)):
+
class DaemonActor(ThreadingActor):
use_daemon_thread = True
View
4 tests/field_access_test.py
@@ -1,6 +1,6 @@
import unittest
-from pykka.actor import ThreadingActor
+from pykka import ThreadingActor
class SomeObject(object):
@@ -20,6 +20,7 @@ class ActorWithFields(object):
class FieldAccessTest(object):
+
def setUp(self):
self.proxy = self.ActorWithFields.start().proxy()
@@ -49,6 +50,7 @@ def test_attr_of_traversable_attr_can_be_read(self):
def ConcreteFieldAccessTest(actor_class):
class C(FieldAccessTest, unittest.TestCase):
+
class ActorWithFields(ActorWithFields, actor_class):
pass
C.__name__ = '%sFieldAccessTest' % actor_class.__name__
View
5 tests/future_test.py
@@ -2,11 +2,11 @@
import traceback
import unittest
-from pykka import Timeout
-from pykka.future import Future, ThreadingFuture, get_all
+from pykka import Future, ThreadingFuture, Timeout, get_all
class FutureBaseTest(unittest.TestCase):
+
def setUp(self):
self.future = Future()
@@ -21,6 +21,7 @@ def test_future_set_exception_is_not_implemented(self):
class FutureTest(object):
+
def setUp(self):
self.results = [self.future_class() for _ in range(3)]
View
9 tests/logging_test.py
@@ -2,15 +2,15 @@
import threading
import unittest
-from pykka.actor import ThreadingActor
-from pykka.registry import ActorRegistry
+from pykka import ActorRegistry, ThreadingActor
from tests import TestLogHandler
from tests.actor_test import (
- EarlyFailingActor, LateFailingActor, FailingOnFailureActor)
+ EarlyFailingActor, FailingOnFailureActor, LateFailingActor)
class LoggingNullHandlerTest(unittest.TestCase):
+
def test_null_handler_is_added_to_avoid_warnings(self):
logger = logging.getLogger('pykka')
handler_names = [h.__class__.__name__ for h in logger.handlers]
@@ -18,6 +18,7 @@ def test_null_handler_is_added_to_avoid_warnings(self):
class ActorLoggingTest(object):
+
def setUp(self):
self.on_stop_was_called = self.event_class()
self.on_failure_was_called = self.event_class()
@@ -131,6 +132,7 @@ def test_exception_in_on_failure_is_logged(self):
class AnActor(object):
+
def __init__(self, on_stop_was_called, on_failure_was_called):
super(AnActor, self).__init__()
self.on_stop_was_called = on_stop_was_called
@@ -156,6 +158,7 @@ def raise_exception(self):
def ConcreteActorLoggingTest(actor_class, event_class):
class C(ActorLoggingTest, unittest.TestCase):
+
class AnActor(AnActor, actor_class):
pass
View
11 tests/method_call_test.py
@@ -1,6 +1,6 @@
import unittest
-from pykka.actor import ThreadingActor
+from pykka import ThreadingActor
class ActorWithMethods(object):
@@ -20,6 +20,7 @@ def talk_with_self(self):
class ActorExtendableAtRuntime(object):
+
def add_method(self, name):
setattr(self, name, lambda: 'returned by ' + name)
@@ -28,6 +29,7 @@ def use_foo_through_self_proxy(self):
class StaticMethodCallTest(object):
+
def setUp(self):
self.proxy = self.ActorWithMethods.start().proxy()
@@ -64,6 +66,7 @@ def test_can_proxy_itself_for_offloading_work_to_the_future(self):
class DynamicMethodCallTest(object):
+
def setUp(self):
self.proxy = self.ActorExtendableAtRuntime.start().proxy()
@@ -89,11 +92,13 @@ def test_can_proxy_itself_and_use_attrs_added_at_runtime(self):
class ThreadingStaticMethodCallTest(StaticMethodCallTest, unittest.TestCase):
+
class ActorWithMethods(ActorWithMethods, ThreadingActor):
pass
class ThreadingDynamicMethodCallTest(DynamicMethodCallTest, unittest.TestCase):
+
class ActorExtendableAtRuntime(ActorExtendableAtRuntime, ThreadingActor):
pass
@@ -102,11 +107,13 @@ class ActorExtendableAtRuntime(ActorExtendableAtRuntime, ThreadingActor):
from pygga.gevent import GeventActor
class GeventStaticMethodCallTest(StaticMethodCallTest, unittest.TestCase):
+
class ActorWithMethods(ActorWithMethods, GeventActor):
pass
class GeventDynamicMethodCallTest(
DynamicMethodCallTest, unittest.TestCase):
+
class ActorExtendableAtRuntime(ActorExtendableAtRuntime, GeventActor):
pass
except ImportError:
@@ -118,11 +125,13 @@ class ActorExtendableAtRuntime(ActorExtendableAtRuntime, GeventActor):
class EventletStaticMethodCallTest(
StaticMethodCallTest, unittest.TestCase):
+
class ActorWithMethods(ActorWithMethods, EventletActor):
pass
class EventletDynamicMethodCallTest(
DynamicMethodCallTest, unittest.TestCase):
+
class ActorExtendableAtRuntime(
ActorExtendableAtRuntime, EventletActor):
pass
View
5 tests/namespace_test.py
@@ -2,6 +2,7 @@
class NamespaceTest(unittest.TestCase):
+
def test_actor_dead_error_import(self):
from pykka import ActorDeadError as ActorDeadError1
from pykka.exceptions import ActorDeadError as ActorDeadError2
@@ -24,7 +25,7 @@ def test_actor_ref_import(self):
def test_threading_actor_import(self):
from pykka import ThreadingActor as ThreadingActor1
- from pykka.actor import ThreadingActor as ThreadingActor2
+ from pykka.threading import ThreadingActor as ThreadingActor2
self.assertEqual(ThreadingActor1, ThreadingActor2)
def test_future_import(self):
@@ -39,7 +40,7 @@ def test_get_all_import(self):
def test_threading_future_import(self):
from pykka import ThreadingFuture as ThreadingFuture1
- from pykka.future import ThreadingFuture as ThreadingFuture2
+ from pykka.threading import ThreadingFuture as ThreadingFuture2
self.assertEqual(ThreadingFuture1, ThreadingFuture2)
def test_actor_proxy_import(self):
View
3  tests/performance.py
@@ -1,7 +1,6 @@
import time
-from pykka.actor import ThreadingActor
-from pykka.registry import ActorRegistry
+from pykka import ActorRegistry, ThreadingActor
def time_it(func):
View
6 tests/proxy_test.py
@@ -1,8 +1,6 @@
import unittest
-from pykka import ActorDeadError
-from pykka.actor import ThreadingActor
-from pykka.proxy import ActorProxy
+from pykka import ActorDeadError, ActorProxy, ThreadingActor
class SomeObject(object):
@@ -25,6 +23,7 @@ def func(self):
class ProxyTest(object):
+
def setUp(self):
self.proxy = ActorProxy(self.AnActor.start())
@@ -91,6 +90,7 @@ def test_actor_ref_may_be_retrieved_from_proxy_if_actor_is_dead(self):
def ConcreteProxyTest(actor_class):
class C(ProxyTest, unittest.TestCase):
+
class AnActor(AnActor, actor_class):
pass
C.__name__ = '%sProxyTest' % actor_class.__name__
View
8 tests/ref_test.py
@@ -1,12 +1,11 @@
import time
import unittest
-from pykka import ActorDeadError, Timeout
-from pykka.actor import ThreadingActor
-from pykka.future import ThreadingFuture
+from pykka import ActorDeadError, ThreadingActor, ThreadingFuture, Timeout
class AnActor(object):
+
def __init__(self, received_message):
super(AnActor, self).__init__()
self.received_message = received_message
@@ -20,6 +19,7 @@ def on_receive(self, message):
class RefTest(object):
+
def setUp(self):
self.received_message = self.future_class()
self.ref = self.AnActor.start(self.received_message)
@@ -112,7 +112,9 @@ def test_ask_nonblocking_fails_future_if_actor_is_stopped(self):
def ConcreteRefTest(actor_class, future_class, sleep_function):
class C(RefTest, unittest.TestCase):
+
class AnActor(AnActor, actor_class):
+
def sleep(self, seconds):
sleep_function(seconds)
View
8 tests/registry_test.py
@@ -1,11 +1,12 @@
-import mock
import unittest
-from pykka.actor import ThreadingActor
-from pykka.registry import ActorRegistry
+import mock
+
+from pykka import ActorRegistry, ThreadingActor
class ActorRegistryTest(object):
+
def setUp(self):
self.ref = self.AnActor.start()
self.a_actors = [self.AnActor.start() for _ in range(3)]
@@ -139,6 +140,7 @@ def on_receive(self, message):
def ConcreteRegistryTest(actor_class):
class C(ActorRegistryTest, unittest.TestCase):
+
class AnActor(AnActor, actor_class):
pass
View
36 tox.ini
@@ -1,27 +1,27 @@
[tox]
-envlist =
- py26, py27, py32, py33, pypy, docs
+envlist = py26, py27, py32, py33, py34, pypy, pypy3, docs, flake8
[testenv]
+deps =
+ coverage
+ mock
+ nose
commands =
- pip install -r requirements-dev.txt
- nosetests -v --where=tests --with-xunit --xunit-file=xunit-{envname}.xml
+ nosetests -v --where=tests --with-xunit --xunit-file=xunit-{envname}.xml --with-coverage --cover-package=pykka
-# gevent does not support Python 3 or PyPy yet, so we only run it on Python 2.6 and 2.7
+# gevent and eventlet only support Python 2.x
[testenv:py26]
-commands =
- pip install -r requirements-dev.txt
- pip install -r requirements-gevent.txt
- pip install -r requirements-eventlet.txt
- nosetests -v --where=tests --with-xunit --xunit-file=xunit-{envname}.xml
+deps =
+ {[testenv]deps}
+ eventlet
+ gevent
[testenv:py27]
-commands =
- pip install -r requirements-dev.txt
- pip install -r requirements-gevent.txt
- pip install -r requirements-eventlet.txt
- nosetests -v --where=tests --with-xunit --xunit-file=xunit-{envname}.xml
+deps =
+ {[testenv]deps}
+ eventlet
+ gevent
[testenv:docs]
changedir = docs
@@ -29,3 +29,9 @@ deps =
sphinx
commands =
sphinx-build -b html -d {envtmpdir}/doctrees . {envtmpdir}/html
+
+[testenv:flake8]
+deps =
+ flake8
+ flake8-import-order
+commands = flake8
Please sign in to comment.
Something went wrong with that request. Please try again.