Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Split ActorProxy into ActorProxy and ActorRef. Lots of refactoring. A…

…ll tests green.
  • Loading branch information...
commit 34602253fefcc96fcf6afcdc97c1351325315601 1 parent 2e75935
@jodal authored
View
26 pykka/__init__.py
@@ -1,17 +1,39 @@
+import gevent.monkey
+gevent.monkey.patch_all()
+
from pykka.actor import Actor
-from pykka.future import Future, get_all, wait_all
from pykka.proxy import ActorProxy, CallableProxy
+from pykka.ref import ActorRef
from pykka.registry import ActorRegistry
-__all__ = ['Actor', 'ActorProxy', 'ActorRegistry', 'CallableProxy', 'Future',
+__all__ = ['Actor', 'ActorProxy', 'ActorRef', 'ActorRegistry', 'CallableProxy',
'get_all', 'wait_all']
VERSION = (0, 5)
def get_version():
+ """Returns a formatted version number"""
version = '%s.%s' % (VERSION[0], VERSION[1])
if len(VERSION) > 2:
version = '%s.%s' % (version, VERSION[2])
return version
+
+def get_all(results, timeout=None):
+ """
+ Get all values encapsulated in the list of
+ :class:`gevent.event.AsyncResult`.
+
+ :attr:`timeout` has the same behaviour as for :meth:`Future.get`.
+ """
+ return map(lambda result: result.get(timeout=timeout), results)
+
+def wait_all(results, timeout=None):
+ """
+ Block until all :class:`gevent.event.AsyncResult` in the list are avaiable.
+
+ An alias for :func:`get_all`, but with a name that is more describing if
+ you do not care about the return values.
+ """
+ return get_all(results, timeout)
View
87 pykka/actor.py
@@ -1,10 +1,9 @@
import gevent
import gevent.queue
import logging
-import sys
import uuid
-from pykka.proxy import ActorProxy
+from pykka.ref import ActorRef
from pykka.registry import ActorRegistry
logger = logging.getLogger('pykka')
@@ -46,6 +45,11 @@ class Actor(gevent.Greenlet):
To stop an actor, call :meth:`Actor.stop()`.
"""
+ actor_urn = None
+ actor_inbox = None
+ actor_ref = None
+ actor_runnable = True
+
@classmethod
def start(cls, *args, **kwargs):
"""
@@ -54,8 +58,8 @@ def start(cls, *args, **kwargs):
Any arguments passed to :meth:`start` will be passed on to the class
constructor.
- Returns a :class:`ActorProxy` which can be used to access the actor in
- a safe manner.
+ Returns a :class:`ActorRef` which can be used to access the actor in a
+ safe manner.
Behind the scenes, the following is happening when you call
:meth:`start`::
@@ -66,25 +70,26 @@ def start(cls, *args, **kwargs):
Greenlet.__init__()
UUID assignment
Inbox creation
- Proxy creation
+ ActorRef creation
Actor.__init__() # Your code can run here
Greenlet.start()
ActorRegistry.register()
"""
obj = cls(*args, **kwargs)
- super(Actor, obj).start()
+ gevent.Greenlet.start(obj)
logger.debug(u'Started %s', obj)
- ActorRegistry.register(obj._actor_proxy)
- return obj._actor_proxy
+ ActorRegistry.register(obj.actor_ref)
+ return obj.actor_ref
def __new__(cls, *args, **kwargs):
- obj = super(Actor, cls).__new__(cls, *args, **kwargs)
- super(Actor, obj).__init__()
- obj._actor_urn = uuid.uuid4().urn
- obj._actor_inbox = gevent.queue.Queue()
- obj._actor_proxy = ActorProxy(obj)
+ obj = gevent.Greenlet.__new__(cls, *args, **kwargs)
+ gevent.Greenlet.__init__(obj)
+ obj.actor_urn = uuid.uuid4().urn
+ obj.actor_inbox = gevent.queue.Queue()
+ obj.actor_ref = ActorRef(obj)
return obj
+ # pylint: disable=W0231
def __init__(self):
"""
Your are free to override :meth:`__init__` and do any setup you need to
@@ -92,47 +97,45 @@ def __init__(self):
that has already been done when your constructor is called.
When :meth:`__init__` is called, the internal fields
- :attr:`_actor_urn`, :attr:`_actor_inbox`, and :attr:`_actor_proxy` are
+ :attr:`actor_urn`, :attr:`actor_inbox`, and :attr:`actor_ref` are
already set, but the actor is not started or registered in
:class:`ActorRegistry`.
"""
pass
+ # pylint: enable=W0231
def __str__(self):
return '%(class)s (%(urn)s)' % {
'class': self.__class__.__name__,
- 'urn': self._actor_urn,
+ 'urn': self.actor_urn,
}
def stop(self):
"""
- Stop the actor and terminate its thread.
+ Stop the actor.
The actor will finish processing any messages already in its queue
- before stopping.
+ before stopping. It may not be restarted.
"""
- self.runnable = False
- ActorRegistry.unregister(self._actor_proxy)
+ self.actor_runnable = False
+ ActorRegistry.unregister(self.actor_ref)
logger.debug(u'Stopped %s', self)
def _run(self):
- self.runnable = True
- try:
- while self.runnable:
- self._event_loop()
- except KeyboardInterrupt:
- sys.exit()
-
- def _event_loop(self):
- """The actor's event loop which is called continously to handle
- incoming messages, one at the time."""
- message = self._actor_inbox.get()
- response = self._react(message)
- if 'reply_to' in message:
- message['reply_to'].set(response)
+ """The Greenlet main method"""
+ self.actor_runnable = True
+ while self.actor_runnable:
+ message = self.actor_inbox.get()
+ response = self._react(message)
+ if 'reply_to' in message:
+ message['reply_to'].set(response)
def _react(self, message):
"""Reacts to messages sent to the actor."""
+ if message.get('command') == 'get_attributes':
+ return self._get_attributes()
+ if message.get('command') == 'stop':
+ return self.stop()
if message.get('command') == 'call':
return getattr(self, message['attribute'])(
*message['args'], **message['kwargs'])
@@ -146,11 +149,19 @@ def react(self, message):
"""May be implemented for the actor to handle custom messages."""
raise NotImplementedError
- def get_attributes(self):
- """Returns a dict where the keys are all the available attributes and
- the value is whether the attribute is callable."""
+ def _is_exposable_attribute(self, attr):
+ """
+ Returns true for any attribute name that may be exposed through
+ :class:`ActorProxy`.
+ """
+ return not attr.startswith('_')
+
+ def _get_attributes(self):
+ """Gathers attribute information needed by :class:`ActorProxy`."""
result = {}
for attr in dir(self):
- if not attr.startswith('_'):
- result[attr] = callable(getattr(self, attr))
+ if self._is_exposable_attribute(attr):
+ result[attr] = {
+ 'callable': callable(getattr(self, attr)),
+ }
return result
View
59 pykka/future.py
@@ -1,59 +0,0 @@
-import gevent
-
-class Future(object):
- """
- A :class:`Future` is a handle to a value which will be available in the
- future.
-
- Typically returned by calls to actor methods or accesses to actor fields.
-
- To get hold of the encapsulated value, call :meth:`Future.get()`.
- """
- def __init__(self, connection):
- self.connection = connection
-
- def __str__(self):
- return str(self.get())
-
- def get(self, block=True, timeout=None):
- """
- Get the value encapsulated by the future.
-
- If *block* is :class:`True`, it will block until the value is available
- or the *timeout* in seconds is reached.
-
- If *block* is :class:`False` it will immediately return the value if
- available or None if not.
- """
- try:
- return self.connection.get(block, timeout)
- except gevent.Timeout:
- return None
-
- def wait(self, timeout=None):
- """
- Block until the future is available.
-
- An alias for :meth:`get`, but with a name that is more describing if
- you do not care about the return value.
- """
- return self.get(timeout=timeout)
-
-
-def get_all(futures, timeout=None):
- """
- Get all the values encapsulated by the given futures.
-
- :attr:`timeout` has the same behaviour as for :meth:`Future.get`.
- """
- return map(lambda future: future.wait(timeout), futures)
-
-
-def wait_all(futures, timeout=None):
- """
- Block until all the given features are available.
-
- An alias for :func:`get_all`, but with a name that is more describing if
- you do not care about the return values.
- """
- return get_all(futures, timeout)
View
109 pykka/proxy.py
@@ -1,105 +1,78 @@
-import gevent.event
-
-from pykka.future import Future
-
-
class ActorProxy(object):
"""
- Proxy for a running actor which allows the actor to be used through a
- normal method calls and field accesses.
+ An :class:`ActorProxy` wraps an :class:`ActorRef`. The proxy allows the
+ referenced actor to be used through a normal method calls and field
+ access.
- You should never need to create :class:`ActorProxy` instances yourself.
- """
+ You can create an :class:`ActorProxy` from any :class:`ActorRef` by::
- #: The actor URN is a universally unique identifier for the actor.
- #: It may be used for looking up a specific actor using
- #: :class:`ActorRegistry.get_by_urn`.
- actor_urn = None
+ actor_proxy = ActorProxy(actor_ref)
+ """
- def __init__(self, actor):
- self.actor_urn = actor._actor_urn
- self._actor_class = actor.__class__
- self._actor_inbox = actor._actor_inbox
- self._actor_attributes = actor.get_attributes()
+ def __init__(self, actor_ref):
+ self._actor_ref = actor_ref
+ self._actor_attributes = self._get_attributes()
def __repr__(self):
- return '<ActorProxy for %(urn)s of type %(class)s>' % {
- 'urn': self.actor_urn,
- 'class': self._actor_class.__name__,
- }
-
- def __str__(self):
- return '%(class)s (%(urn)s)' % {
- 'urn': self.actor_urn,
- 'class': self._actor_class.__name__,
- }
+ return '<ActorProxy for %s>' % self._actor_ref
- def send(self, message):
- """
- Send message to actor.
-
- The message must be a picklable dict.
- """
- self._actor_inbox.put(message)
+ def _get_attributes(self):
+ return self._actor_ref.send_request_reply(
+ {'command': 'get_attributes'})
def __getattr__(self, name):
- if not name in self._actor_attributes:
- self._actor_attributes = self.get_attributes().get()
- if not name in self._actor_attributes:
- raise AttributeError("proxy for '%s' object has no "
- "attribute '%s'" % (self._actor_class.__name__, name))
- if self._actor_attributes[name]:
- return CallableProxy(self._actor_inbox, name)
+ if name not in self._actor_attributes:
+ self._actor_attributes = self._get_attributes()
+ attr_info = self._actor_attributes.get(name)
+ if attr_info is None:
+ raise AttributeError('%s has no attribute "%s"' % (self, name))
+ if attr_info['callable']:
+ return CallableProxy(self._actor_ref, name)
else:
- return self._get_field(name)
+ return self._get_actor_field(name)
- def _get_field(self, name):
+ def __setattr__(self, name, value):
+ """Set a field on the actor."""
+ if name.startswith('_'):
+ return super(ActorProxy, self).__setattr__(name, value)
+ return self._set_actor_field(name, value)
+
+ def __dir__(self):
+ result = ['__class__']
+ result += self.__class__.__dict__.keys()
+ result += self.__dict__.keys()
+ result += self._actor_attributes.keys()
+ return sorted(result)
+
+ def _get_actor_field(self, name):
"""Get a field from the actor."""
- result = gevent.event.AsyncResult()
message = {
'command': 'read',
'attribute': name,
- 'reply_to': result,
}
- self._actor_inbox.put(message)
- return Future(result)
+ return self._actor_ref.send_request_reply(message, block=False)
- def __setattr__(self, name, value):
+ def _set_actor_field(self, name, value):
"""Set a field on the actor."""
- if name.startswith('_') or name.startswith('actor_'):
- return super(ActorProxy, self).__setattr__(name, value)
- result = gevent.event.AsyncResult()
message = {
'command': 'write',
'attribute': name,
'value': value,
- 'reply_to': result,
}
- self._actor_inbox.put(message)
- return Future(result)
-
- def __dir__(self):
- result = ['__class__']
- result += self.__class__.__dict__.keys()
- result += self.__dict__.keys()
- result += self._actor_attributes.keys()
- return sorted(result)
+ return self._actor_ref.send_request_reply(message, block=False)
class CallableProxy(object):
"""Helper class for proxying callables."""
- def __init__(self, actor_inbox, attribute):
- self._actor_inbox = actor_inbox
+ def __init__(self, ref, attribute):
+ self._actor_ref = ref
self._attribute = attribute
def __call__(self, *args, **kwargs):
- result = gevent.event.AsyncResult()
message = {
'command': 'call',
'attribute': self._attribute,
'args': args,
'kwargs': kwargs,
- 'reply_to': result,
}
- self._actor_inbox.put(message)
- return Future(result)
+ return self._actor_ref.send_request_reply(message, block=False)
View
63 pykka/ref.py
@@ -0,0 +1,63 @@
+import gevent.event
+
+
+class ActorRef(object):
+ """
+ Reference to a running actor which may safely be passed around.
+
+ You should never need to create :class:`ActorRef` instances yourself.
+ :class:`ActorRef` instances are returned by :meth:`Actor.start` and the
+ lookup methods in :class:`ActorRegistry`.
+ """
+
+ #: The actor URN is a universally unique identifier for the actor.
+ #: It may be used for looking up a specific actor using
+ #: :class:`ActorRegistry.get_by_urn`.
+ actor_urn = None
+
+ def __init__(self, actor):
+ self.actor_urn = actor.actor_urn
+ self.actor_class = actor.__class__
+ self.actor_inbox = actor.actor_inbox
+
+ def __repr__(self):
+ return '<ActorRef for %s>' % str(self)
+
+ def __str__(self):
+ return '%(class)s (%(urn)s)' % {
+ 'urn': self.actor_urn,
+ 'class': self.actor_class.__name__,
+ }
+
+ def stop(self, block=True, timeout=None):
+ """Send a message to the actor, asking it to stop."""
+ self.send_request_reply({'command': 'stop'}, block, timeout)
+
+ def send_one_way(self, message):
+ """
+ Send message to actor without waiting for any response.
+
+ The message must be a picklable dict.
+ """
+ self.actor_inbox.put(message)
+
+ def send_request_reply(self, message, block=True, timeout=None):
+ """
+ Send message to actor and wait for the reply.
+
+ If ``block`` is :class:`False`, it will immediately return a
+ :class:`gevent.event.AsyncResult` instead of blocking.
+
+ If ``block`` is :class:`True`, the timeout should be given in seconds
+ as a floating point number. By default, there is no timeout and it will
+ wait for an reply forever.
+
+ The message must be a picklable dict.
+ """
+ reply = gevent.event.AsyncResult()
+ message['reply_to'] = reply
+ self.send_one_way(message)
+ if block:
+ return reply.get(timeout=timeout)
+ else:
+ return reply
View
78 pykka/registry.py
@@ -11,28 +11,31 @@ class ActorRegistry(object):
Contains global state, but should be thread-safe.
"""
- _actors = []
- _actors_lock = gevent.coros.RLock()
+ _actor_refs = []
+ _actor_refs_lock = gevent.coros.RLock()
@classmethod
def get_all(cls):
- """Get all running actors"""
- with cls._actors_lock:
- return cls._actors[:]
+ """Get :class:`ActorRef` for all running actors"""
+ with cls._actor_refs_lock:
+ return cls._actor_refs[:]
@classmethod
def get_by_class(cls, actor_class):
- """Get all running actors of the given class"""
- with cls._actors_lock:
- return filter(lambda a: a._actor_class == actor_class, cls._actors)
+ """Get :class:`ActorRef` for all running actors of the given class"""
+ with cls._actor_refs_lock:
+ return [ref for ref in cls._actor_refs
+ if ref.actor_class == actor_class]
@classmethod
def get_by_class_name(cls, actor_class_name):
- """Get all running actors of the given class name"""
- with cls._actors_lock:
- return filter(
- lambda a: a._actor_class.__name__ == actor_class_name,
- cls._actors)
+ """
+ Get :class:`ActorRef` for all running actors of the given class
+ name
+ """
+ with cls._actor_refs_lock:
+ return [ref for ref in cls._actor_refs
+ if ref.actor_class.__name__ == actor_class_name]
@classmethod
def get_by_urn(cls, actor_urn):
@@ -41,40 +44,45 @@ def get_by_urn(cls, actor_urn):
Returns :class:`None` if no matching actor is found.
"""
- with cls._actors_lock:
- actors = filter(lambda a: a.actor_urn == actor_urn, cls._actors)
- if actors:
- return actors[0]
+ with cls._actor_refs_lock:
+ refs = [ref for ref in cls._actor_refs
+ if ref.actor_urn == actor_urn]
+ if refs:
+ return refs[0]
@classmethod
- def register(cls, actor):
+ def register(cls, ref):
"""
- Register an actor in the registry.
+ Register an :class:`ActorRef` in the registry.
- This is done automatically when an actor is started.
+ This is done automatically when an actor is started, e.g. by calling
+ :meth:`Actor.start`.
"""
- with cls._actors_lock:
- cls._actors.append(actor)
- logger.debug(u'Registered %s', actor)
+ with cls._actor_refs_lock:
+ cls._actor_refs.append(ref)
+ logger.debug(u'Registered %s', ref)
@classmethod
- def stop_all(cls):
+ def stop_all(cls, block=True, timeout=None):
"""
- Stops all running actors.
+ Stop all running actors.
+
+ If ``block`` is :class:`True`, it blocks forever or, if not
+ :class:`None`, until ``timeout`` seconds has passed.
- Returns a list of futures for all the stopping actors, so that you can
- block until they have stopped if you need to.
+ If ``block`` is False, it returns a list with a future for each stop
+ action.
"""
- with cls._actors_lock:
- return [a.stop() for a in cls._actors]
+ return [ref.stop(block, timeout) for ref in cls.get_all()]
@classmethod
- def unregister(cls, actor):
+ def unregister(cls, ref):
"""
- Remove an actor from the registry.
+ Remove an :class:`ActorRef` from the registry.
- This is done automatically when an actor is stopped.
+ This is done automatically when an actor is stopped, e.g. by calling
+ :meth:`Actor.stop`.
"""
- with cls._actors_lock:
- cls._actors.remove(actor)
- logger.debug(u'Unregistered %s', actor)
+ with cls._actor_refs_lock:
+ cls._actor_refs.remove(ref)
+ logger.debug(u'Unregistered %s', ref)
View
23 pylintrc
@@ -0,0 +1,23 @@
+[MESSAGES CONTROL]
+#
+# Disabled messages
+# -----------------
+#
+# C0103 - Invalid name "%s" (should match %s)
+# C0111 - Missing docstring
+# E0102 - %s already defined line %s
+# Does not understand @property getters and setters
+# E0202 - An attribute inherited from %s hide this method
+# Does not understand @property getters and setters
+# E1101 - %s %r has no %r member
+# Does not understand @property getters and setters
+# R0201 - Method could be a function
+# R0801 - Similar lines in %s files
+# R0903 - Too few public methods (%s/%s)
+# R0904 - Too many public methods (%s/%s)
+# R0921 - Abstract class not referenced
+# W0141 - Used builtin function '%s'
+# W0142 - Used * or ** magic
+# W0613 - Unused argument %r
+#
+disable = C0103,C0111,E0102,E0202,E1101,R0201,R0801,R0903,R0904,R0921,W0141,W0142,W0613
View
0  tests/__init__.py
No changes.
View
16 tests/actor_test.py
@@ -3,20 +3,6 @@
from pykka import Actor
-class ActorInterruptTest(unittest.TestCase):
- def setUp(self):
- class ActorWithInterrupt(Actor):
- def _event_loop(self):
- raise KeyboardInterrupt
- self.actor = ActorWithInterrupt()
-
- def test_issuing_keyboard_interrupt_stops_process(self):
- try:
- self.actor._run()
- self.fail('Should throw SystemExit exception')
- except SystemExit:
- pass
-
class ActorReactTest(unittest.TestCase):
def setUp(self):
@@ -61,5 +47,5 @@ def test_str_on_proxy_contains_actor_class_name(self):
self.assert_('AnActor' in str(self.unstarted_actor))
def test_str_on_proxy_contains_actor_urn(self):
- self.assert_(self.unstarted_actor._actor_urn
+ self.assert_(self.unstarted_actor.actor_urn
in str(self.unstarted_actor))
View
14 tests/field_access_test.py
@@ -1,20 +1,20 @@
import unittest
-from pykka import Actor
+from pykka import Actor, ActorProxy
class FieldAccessTest(unittest.TestCase):
def setUp(self):
class ActorWithField(Actor):
foo = 'bar'
- self.actor = ActorWithField.start()
+ self.proxy = ActorProxy(ActorWithField.start())
def tearDown(self):
- self.actor.stop()
+ self.proxy.stop()
def test_actor_field_can_be_read_using_get_postfix(self):
- self.assertEqual('bar', self.actor.foo.get())
+ self.assertEqual('bar', self.proxy.foo.get())
def test_actor_field_can_be_set_using_assignment(self):
- self.assertEqual('bar', self.actor.foo.get())
- self.actor.foo = 'baz'
- self.assertEqual('baz', self.actor.foo.get())
+ self.assertEqual('bar', self.proxy.foo.get())
+ self.proxy.foo = 'baz'
+ self.assertEqual('baz', self.proxy.foo.get())
View
59 tests/future_test.py
@@ -1,64 +1,33 @@
+import gevent
import gevent.event
import unittest
-import pykka
-
-class FutureTest(unittest.TestCase):
- def setUp(self):
- self.result = gevent.event.AsyncResult()
- self.future = pykka.Future(self.result)
-
- def test_future_str_returns_a_string(self):
- self.result.set(10)
- self.assertEqual('10', str(self.future))
-
- def test_future_repr_does_not_block(self):
- # Do not send anything on the connection
- repr(self.future)
-
- def test_future_repr_returns_a_string_which_includes_the_word_future(self):
- self.assert_('Future' in repr(self.future))
-
- def test_future_get_can_timeout_and_return_none(self):
- # Do not send anything on the connection
- self.assertEqual(None, self.future.get(timeout=0.1))
-
- def test_future_get_can_timeout_immediately(self):
- # Do not send anything on the connection
- self.assertEqual(None, self.future.get(timeout=False))
-
- def test_future_wait_is_alias_of_get(self):
- self.result.set(10)
- result1 = self.future.get()
- result2 = self.future.wait()
- self.assertEqual(result1, result2)
+from pykka import get_all, wait_all
class GetAllTest(unittest.TestCase):
def setUp(self):
- self.results = []
- self.futures = []
- for i in range(3):
- result = gevent.event.AsyncResult()
- self.results.append(result)
- self.futures.append(pykka.Future(result))
+ self.results = [gevent.event.AsyncResult() for _ in range(3)]
def test_get_all_blocks_until_all_futures_are_available(self):
self.results[0].set(0)
self.results[1].set(1)
self.results[2].set(2)
- result = pykka.get_all(self.futures)
+ result = get_all(self.results)
self.assertEqual(result, [0, 1, 2])
- def test_get_all_times_out_if_not_all_futures_are_available(self):
- self.results[0].set(0)
- self.results[2].set(2)
- result = pykka.get_all(self.futures, timeout=0)
- self.assertEqual(result, [0, None, 2])
+ def test_get_all_raises_timeout_if_not_all_futures_are_available(self):
+ try:
+ self.results[0].set(0)
+ self.results[2].set(2)
+ result = get_all(self.results, timeout=0)
+ self.fail('Should timeout')
+ except gevent.Timeout:
+ pass
def test_wait_all_is_alias_of_get_all(self):
self.results[0].set(0)
self.results[1].set(1)
self.results[2].set(2)
- result1 = pykka.get_all(self.futures)
- result2 = pykka.wait_all(self.futures)
+ result1 = get_all(self.results)
+ result2 = wait_all(self.results)
self.assertEqual(result1, result2)
View
52 tests/method_call_test.py
@@ -1,50 +1,54 @@
import unittest
-from pykka import Actor
+from pykka import Actor, ActorProxy
+
+class ActorWithMethods(Actor):
+ foo = 'bar'
+ def functional_hello(self, s):
+ return 'Hello, %s!' % s
+ def set_foo(self, s):
+ self.foo = s
+
+class ActorExtendableAtRuntime(Actor):
+ def add_method(self, name):
+ setattr(self, name, lambda: 'returned by ' + name)
+
class MethodCallTest(unittest.TestCase):
def setUp(self):
- class ActorWithMethods(Actor):
- foo = 'bar'
- def functional_hello(self, s):
- return 'Hello, %s!' % s
- def set_foo(self, s):
- self.foo = s
- self.actor = ActorWithMethods.start()
+ self.proxy = ActorProxy(ActorWithMethods.start())
def tearDown(self):
- self.actor.stop()
+ self.proxy.stop()
def test_functional_method_call_returns_correct_value(self):
self.assertEqual('Hello, world!',
- self.actor.functional_hello('world').get())
+ self.proxy.functional_hello('world').get())
self.assertEqual('Hello, moon!',
- self.actor.functional_hello('moon').get())
+ self.proxy.functional_hello('moon').get())
def test_side_effect_of_method_is_observable(self):
- self.assertEqual('bar', self.actor.foo.get())
- self.actor.set_foo('baz')
- self.assertEqual('baz', self.actor.foo.get())
+ self.assertEqual('bar', self.proxy.foo.get())
+ self.proxy.set_foo('baz')
+ self.assertEqual('baz', self.proxy.foo.get())
def test_calling_unknown_method_raises_attribute_error(self):
try:
- self.actor.unknown_method()
+ self.proxy.unknown_method()
self.fail('Should raise AttributeError')
except AttributeError as e:
- self.assertEquals("proxy for 'ActorWithMethods' object " +
- "has no attribute 'unknown_method'", str(e))
+ result = str(e)
+ self.assert_(result.startswith('<ActorProxy for ActorWithMethods'))
+ self.assert_(result.endswith('has no attribute "unknown_method"'))
class MethodAddedAtRuntimeTest(unittest.TestCase):
def setUp(self):
- class ActorExtendableAtRuntime(Actor):
- def add_method(self, name):
- setattr(self, name, lambda: 'returned by ' + name)
- self.actor = ActorExtendableAtRuntime().start()
+ self.proxy = ActorProxy(ActorExtendableAtRuntime().start())
def tearDown(self):
- self.actor.stop()
+ self.proxy.stop()
def test_can_call_method_that_was_added_at_runtime(self):
- self.actor.add_method('foo')
- self.assertEqual('returned by foo', self.actor.foo().get())
+ self.proxy.add_method('foo')
+ self.assertEqual('returned by foo', self.proxy.foo().get())
View
76 tests/proxy_test.py
@@ -1,77 +1,63 @@
import unittest
-from pykka import Actor
+from pykka import Actor, ActorProxy
+
+class AnActor(Actor):
+ pass
+
+
+class ActorWithAttributesAndCallables(Actor):
+ foo = 'bar'
+ def __init__(self):
+ self.baz = 'quox'
+ def func(self):
+ pass
+
class ProxyDirTest(unittest.TestCase):
def setUp(self):
- class ActorWithAttributesAndCallables(Actor):
- foo = 'bar'
- def __init__(self):
- super(ActorWithAttributesAndCallables, self).__init__()
- self.baz = 'quox'
- def func(self):
- pass
- self.actor = ActorWithAttributesAndCallables.start()
+ self.proxy = ActorProxy(ActorWithAttributesAndCallables.start())
def tearDown(self):
- self.actor.stop()
+ self.proxy.stop()
def test_dir_on_proxy_lists_attributes_of_the_actor(self):
- result = dir(self.actor)
+ result = dir(self.proxy)
self.assert_('foo' in result)
self.assert_('baz' in result)
self.assert_('func' in result)
def test_dir_on_proxy_lists_private_attributes_of_the_proxy(self):
- result = dir(self.actor)
+ result = dir(self.proxy)
self.assert_('__class__' in result)
self.assert_('__dict__' in result)
self.assert_('__getattr__' in result)
self.assert_('__setattr__' in result)
-class ProxySendMessageTest(unittest.TestCase):
- def setUp(self):
- class ActorWithCustomReact(Actor):
- received_messages = []
- def react(self, message):
- self.received_messages.append(message)
- self.actor = ActorWithCustomReact.start()
-
- def tearDown(self):
- self.actor.stop()
-
- def test_send_on_proxy_delivers_message_to_actors_custom_react(self):
- self.actor.send({'command': 'a custom message'})
- self.assert_({'command': 'a custom message'} in
- self.actor.received_messages.get())
-
-
class ProxyReprAndStrTest(unittest.TestCase):
def setUp(self):
- class AnyActor(Actor):
- pass
- self.actor = AnyActor.start()
+ self.proxy = ActorProxy(AnActor.start())
def tearDown(self):
- self.actor.stop()
+ self.proxy.stop()
- def test_repr_on_proxy_is_wrapped_in_lt_and_gt(self):
- result = repr(self.actor)
+ def test_repr_is_wrapped_in_lt_and_gt(self):
+ result = repr(self.proxy)
self.assert_(result.startswith('<'))
self.assert_(result.endswith('>'))
- def test_repr_on_proxy_reveals_that_this_is_a_proxy(self):
- self.assert_('ActorProxy' in repr(self.actor))
+ def test_repr_reveals_that_this_is_a_proxy(self):
+ self.assert_('ActorProxy' in repr(self.proxy))
- def test_repr_on_proxy_contains_actor_class_name(self):
- self.assert_('AnyActor' in repr(self.actor))
+ def test_repr_contains_actor_class_name(self):
+ self.assert_('AnActor' in repr(self.proxy))
- def test_repr_on_proxy_contains_actor_urn(self):
- self.assert_(self.actor.actor_urn in repr(self.actor))
+ def test_repr_contains_actor_urn(self):
+ self.assert_(self.proxy._actor_ref.actor_urn in repr(self.proxy))
- def test_str_on_proxy_contains_actor_class_name(self):
- self.assert_('AnyActor' in str(self.actor))
+ def test_str_contains_actor_class_name(self):
+ self.assert_('AnActor' in str(self.proxy))
- def test_str_on_proxy_contains_actor_urn(self):
- self.assert_(self.actor.actor_urn in str(self.actor))
+ def test_str_contains_actor_urn(self):
+ self.assert_(self.proxy._actor_ref.actor_urn in str(self.proxy))
View
77 tests/ref_test.py
@@ -0,0 +1,77 @@
+import gevent
+import gevent.event
+import unittest
+
+from pykka import Actor, ActorRef
+
+class AnActor(Actor):
+ pass
+
+
+class ActorWithCustomReact(Actor):
+ def __init__(self, received_message):
+ self.received_message = received_message
+
+ def react(self, message):
+ if message.get('command') == 'ping':
+ gevent.sleep(0.01)
+ return 'pong'
+ else:
+ self.received_message.set(message)
+
+
+class RefSendMessageTest(unittest.TestCase):
+ def setUp(self):
+ self.received_message = gevent.event.AsyncResult()
+ self.ref = ActorWithCustomReact.start(self.received_message)
+
+ def tearDown(self):
+ self.ref.stop()
+
+ def test_send_one_way_delivers_message_to_actors_custom_react(self):
+ self.ref.send_one_way({'command': 'a custom message'})
+ self.assertEqual({'command': 'a custom message'},
+ self.received_message.get())
+
+ def test_send_request_reply_blocks_until_response_arrives(self):
+ result = self.ref.send_request_reply({'command': 'ping'})
+ self.assertEqual('pong', result)
+
+ def test_send_request_reply_can_timeout_if_blocked_too_long(self):
+ try:
+ self.ref.send_request_reply({'command': 'ping'}, timeout=0)
+ self.fail('Should raise Timeout exception')
+ except gevent.Timeout:
+ pass
+
+ def test_send_request_reply_can_return_future_instead_of_blocking(self):
+ future = self.ref.send_request_reply({'command': 'ping'}, block=False)
+ self.assertEqual('pong', future.get())
+
+
+class RefReprAndStrTest(unittest.TestCase):
+ def setUp(self):
+ self.ref = AnActor.start()
+
+ def tearDown(self):
+ self.ref.stop()
+
+ def test_repr_is_wrapped_in_lt_and_gt(self):
+ result = repr(self.ref)
+ self.assert_(result.startswith('<'))
+ self.assert_(result.endswith('>'))
+
+ def test_repr_reveals_that_this_is_a_ref(self):
+ self.assert_('ActorRef' in repr(self.ref))
+
+ def test_repr_contains_actor_class_name(self):
+ self.assert_('AnActor' in repr(self.ref))
+
+ def test_repr_contains_actor_urn(self):
+ self.assert_(self.ref.actor_urn in repr(self.ref))
+
+ def test_str_contains_actor_class_name(self):
+ self.assert_('AnActor' in str(self.ref))
+
+ def test_str_contains_actor_urn(self):
+ self.assert_(self.ref.actor_urn in str(self.ref))
View
55 tests/registry_test.py
@@ -1,71 +1,74 @@
import unittest
-import pykka
+from pykka import Actor, ActorRegistry
+
+class AnActor(Actor):
+ pass
+
+
+class BeeActor(Actor):
+ pass
+
class ActorRegistrationTest(unittest.TestCase):
def setUp(self):
- class AnActor(pykka.Actor): pass
- self.actor = AnActor.start()
+ self.ref = AnActor.start()
def tearDown(self):
- pykka.ActorRegistry.stop_all()
+ ActorRegistry.stop_all()
def test_actor_is_registered_when_started(self):
- self.assert_(self.actor in pykka.ActorRegistry.get_all())
+ self.assert_(self.ref in ActorRegistry.get_all())
def test_actor_is_unregistered_when_stopped(self):
- self.assert_(self.actor in pykka.ActorRegistry.get_all())
- self.actor.stop().wait()
- self.assert_(self.actor not in pykka.ActorRegistry.get_all())
+ self.assert_(self.ref in ActorRegistry.get_all())
+ self.ref.stop()
+ self.assert_(self.ref not in ActorRegistry.get_all())
def test_actor_may_be_registered_manually(self):
- pykka.ActorRegistry.unregister(self.actor)
- self.assert_(self.actor not in pykka.ActorRegistry.get_all())
- pykka.ActorRegistry.register(self.actor)
- self.assert_(self.actor in pykka.ActorRegistry.get_all())
+ ActorRegistry.unregister(self.ref)
+ self.assert_(self.ref not in ActorRegistry.get_all())
+ ActorRegistry.register(self.ref)
+ self.assert_(self.ref in ActorRegistry.get_all())
class ActorLookupTest(unittest.TestCase):
- class AnActor(pykka.Actor): pass
- class BeeActor(pykka.Actor): pass
-
def setUp(self):
- self.a_actors = [self.AnActor.start() for i in range(3)]
- self.b_actors = [self.BeeActor.start() for i in range(5)]
+ self.a_actors = [AnActor.start() for i in range(3)]
+ self.b_actors = [BeeActor.start() for i in range(5)]
self.a_actor_0_urn = self.a_actors[0].actor_urn
def tearDown(self):
- pykka.ActorRegistry.stop_all()
+ ActorRegistry.stop_all()
def test_actors_may_be_looked_up_by_class(self):
- result = pykka.ActorRegistry.get_by_class(self.AnActor)
+ result = ActorRegistry.get_by_class(AnActor)
for a_actor in self.a_actors:
self.assert_(a_actor in result)
for b_actor in self.b_actors:
self.assert_(b_actor not in result)
def test_actors_may_be_looked_up_by_class_name(self):
- result = pykka.ActorRegistry.get_by_class_name('AnActor')
+ result = ActorRegistry.get_by_class_name('AnActor')
for a_actor in self.a_actors:
self.assert_(a_actor in result)
for b_actor in self.b_actors:
self.assert_(b_actor not in result)
def test_actors_may_be_looked_up_by_urn(self):
- result = pykka.ActorRegistry.get_by_urn(self.a_actor_0_urn)
+ result = ActorRegistry.get_by_urn(self.a_actor_0_urn)
self.assertEqual(self.a_actors[0], result)
def test_get_by_urn_returns_none_if_not_found(self):
- result = pykka.ActorRegistry.get_by_urn('urn:foo:bar')
+ result = ActorRegistry.get_by_urn('urn:foo:bar')
self.assertEqual(None, result)
class ActorStoppingTest(unittest.TestCase):
def setUp(self):
- class AnActor(pykka.Actor): pass
self.actors = [AnActor.start() for i in range(3)]
def test_all_actors_can_be_stopped_through_registry(self):
- self.assertEquals(3, len(pykka.ActorRegistry.get_all()))
- pykka.wait_all(pykka.ActorRegistry.stop_all())
- self.assertEquals(0, len(pykka.ActorRegistry.get_all()))
+ self.assertEquals(3, len(ActorRegistry.get_all()))
+ ActorRegistry.stop_all(block=True)
+ self.assertEquals(0, len(ActorRegistry.get_all()))
Please sign in to comment.
Something went wrong with that request. Please try again.