Python3 Migrate

This commit is contained in:
MariuszC
2020-01-18 20:01:00 +01:00
parent ea05af2d15
commit 6cd7e0fe44
691 changed files with 201846 additions and 598 deletions

View File

@@ -0,0 +1,32 @@
import logging as _logging
from pykka._exceptions import ActorDeadError, Timeout
from pykka._future import Future, get_all
from pykka._proxy import ActorProxy, CallableProxy, traversable
from pykka._ref import ActorRef
from pykka._registry import ActorRegistry
from pykka._actor import Actor # noqa: Must be imported late
from pykka._threading import ThreadingActor, ThreadingFuture
__all__ = [
'Actor',
'ActorDeadError',
'ActorProxy',
'ActorRef',
'ActorRegistry',
'CallableProxy',
'Future',
'ThreadingActor',
'ThreadingFuture',
'Timeout',
'get_all',
'traversable',
]
#: Pykka's :pep:`396` and :pep:`440` compatible version number
__version__ = '2.0.2'
_logging.getLogger('pykka').addHandler(_logging.NullHandler())

View File

@@ -0,0 +1,356 @@
from __future__ import absolute_import
import logging
import sys
import threading
import uuid
from pykka import ActorDeadError, ActorRef, ActorRegistry, messages
__all__ = ['Actor']
logger = logging.getLogger('pykka')
class Actor(object):
"""
To create an actor:
1. subclass one of the :class:`Actor` implementations:
- :class:`~pykka.ThreadingActor`
- :class:`~pykka.gevent.GeventActor`
- :class:`~pykka.eventlet.EventletActor`
2. implement your methods, including :meth:`__init__`, as usual,
3. call :meth:`Actor.start` on your actor class, passing the method any
arguments for your constructor.
To stop an actor, call :meth:`Actor.stop()` or :meth:`ActorRef.stop()`.
For example::
import pykka
class MyActor(pykka.ThreadingActor):
def __init__(self, my_arg=None):
super().__init__()
... # My optional init code with access to start() arguments
def on_start(self):
... # My optional setup code in same context as on_receive()
def on_stop(self):
... # My optional cleanup code in same context as on_receive()
def on_failure(self, exception_type, exception_value, traceback):
... # My optional cleanup code in same context as on_receive()
def on_receive(self, message):
... # My optional message handling code for a plain actor
def a_method(self, ...):
... # My regular method to be used through an ActorProxy
my_actor_ref = MyActor.start(my_arg=...)
my_actor_ref.stop()
"""
@classmethod
def start(cls, *args, **kwargs):
"""
Start an actor and register it in the
:class:`ActorRegistry <pykka.ActorRegistry>`.
Any arguments passed to :meth:`start` will be passed on to the class
constructor.
Behind the scenes, the following is happening when you call
:meth:`start`:
1. The actor is created:
1. :attr:`actor_urn` is initialized with the assigned URN.
2. :attr:`actor_inbox` is initialized with a new actor inbox.
3. :attr:`actor_ref` is initialized with a :class:`pykka.ActorRef`
object for safely communicating with the actor.
4. At this point, your :meth:`__init__()` code can run.
2. The actor is registered in :class:`pykka.ActorRegistry`.
3. The actor receive loop is started by the actor's associated
thread/greenlet.
:returns: a :class:`ActorRef` which can be used to access the actor in
a safe manner
"""
obj = 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 {}'.format(obj))
obj._start_actor_loop()
return obj.actor_ref
@staticmethod
def _create_actor_inbox():
"""Internal method for implementors of new actor types."""
raise NotImplementedError('Use a subclass of Actor')
@staticmethod
def _create_future():
"""Internal method for implementors of new actor types."""
raise NotImplementedError('Use a subclass of Actor')
def _start_actor_loop(self):
"""Internal method for implementors of new actor types."""
raise NotImplementedError('Use a subclass of Actor')
#: The actor URN string is a universally unique identifier for the actor.
#: It may be used for looking up a specific actor using
#: :meth:`ActorRegistry.get_by_urn`.
actor_urn = None
#: The actor's inbox. Use :meth:`ActorRef.tell`, :meth:`ActorRef.ask`, and
#: friends to put messages in the inbox.
actor_inbox = None
#: The actor's :class:`ActorRef` instance.
actor_ref = None
#: A :class:`threading.Event` representing whether or not the actor should
#: continue processing messages. Use :meth:`stop` to change it.
actor_stopped = None
def __init__(self, *args, **kwargs):
"""
Your are free to override :meth:`__init__`, but you must call your
superclass' :meth:`__init__` to ensure that fields :attr:`actor_urn`,
:attr:`actor_inbox`, and :attr:`actor_ref` are initialized.
You can use :func:`super`::
super().__init__()
Or call you superclass directly::
pykka.ThreadingActor.__init__(self)
# or
pykka.gevent.GeventActor.__init__(self)
:meth:`__init__` is called before the actor is started and registered
in :class:`ActorRegistry <pykka.ActorRegistry>`.
"""
self.actor_urn = uuid.uuid4().urn
self.actor_inbox = self._create_actor_inbox()
self.actor_stopped = threading.Event()
self.actor_ref = ActorRef(self)
def __str__(self):
return '{} ({})'.format(self.__class__.__name__, self.actor_urn)
def stop(self):
"""
Stop the actor.
It's equivalent to calling :meth:`ActorRef.stop` with ``block=False``.
"""
self.actor_ref.tell(messages._ActorStop())
def _stop(self):
"""
Stops the actor immediately without processing the rest of the inbox.
"""
ActorRegistry.unregister(self.actor_ref)
self.actor_stopped.set()
logger.debug('Stopped {}'.format(self))
try:
self.on_stop()
except Exception:
self._handle_failure(*sys.exc_info())
def _actor_loop(self):
"""
The actor's event loop.
This is the method that will be executed by the thread or greenlet.
"""
try:
self.on_start()
except Exception:
self._handle_failure(*sys.exc_info())
while not self.actor_stopped.is_set():
envelope = self.actor_inbox.get()
try:
response = self._handle_receive(envelope.message)
if envelope.reply_to is not None:
envelope.reply_to.set(response)
except Exception:
if envelope.reply_to is not None:
logger.info(
'Exception returned from {} to caller:'.format(self),
exc_info=sys.exc_info(),
)
envelope.reply_to.set_exception()
else:
self._handle_failure(*sys.exc_info())
try:
self.on_failure(*sys.exc_info())
except Exception:
self._handle_failure(*sys.exc_info())
except BaseException:
exception_value = sys.exc_info()[1]
logger.debug(
'{!r} in {}. Stopping all actors.'.format(
exception_value, self
)
)
self._stop()
ActorRegistry.stop_all()
while not self.actor_inbox.empty():
envelope = self.actor_inbox.get()
if envelope.reply_to is not None:
if isinstance(envelope.message, messages._ActorStop):
envelope.reply_to.set(None)
else:
envelope.reply_to.set_exception(
exc_info=(
ActorDeadError,
ActorDeadError(
'{} stopped before handling the message'.format(
self.actor_ref
)
),
None,
)
)
def on_start(self):
"""
Hook for doing any setup that should be done *after* the actor is
started, but *before* it starts processing messages.
For :class:`ThreadingActor`, this method is executed in the actor's own
thread, while :meth:`__init__` is executed in the thread that created
the actor.
If an exception is raised by this method the stack trace will be
logged, and the actor will stop.
"""
pass
def on_stop(self):
"""
Hook for doing any cleanup that should be done *after* the actor has
processed the last message, and *before* the actor stops.
This hook is *not* called when the actor stops because of an unhandled
exception. In that case, the :meth:`on_failure` hook is called instead.
For :class:`ThreadingActor` this method is executed in the actor's own
thread, immediately before the thread exits.
If an exception is raised by this method the stack trace will be
logged, and the actor will stop.
"""
pass
def _handle_failure(self, exception_type, exception_value, traceback):
"""Logs unexpected failures, unregisters and stops the actor."""
logger.error(
'Unhandled exception in {}:'.format(self),
exc_info=(exception_type, exception_value, traceback),
)
ActorRegistry.unregister(self.actor_ref)
self.actor_stopped.set()
def on_failure(self, exception_type, exception_value, traceback):
"""
Hook for doing any cleanup *after* an unhandled exception is raised,
and *before* the actor stops.
For :class:`ThreadingActor` this method is executed in the actor's own
thread, immediately before the thread exits.
The method's arguments are the relevant information from
:func:`sys.exc_info`.
If an exception is raised by this method the stack trace will be
logged, and the actor will stop.
"""
pass
def _handle_receive(self, message):
"""Handles messages sent to the actor."""
message = messages._upgrade_internal_message(message)
if isinstance(message, messages._ActorStop):
return self._stop()
if isinstance(message, messages.ProxyCall):
callee = self._get_attribute_from_path(message.attr_path)
return callee(*message.args, **message.kwargs)
if isinstance(message, messages.ProxyGetAttr):
attr = self._get_attribute_from_path(message.attr_path)
return attr
if isinstance(message, messages.ProxySetAttr):
parent_attr = self._get_attribute_from_path(message.attr_path[:-1])
attr_name = message.attr_path[-1]
return setattr(parent_attr, attr_name, message.value)
return self.on_receive(message)
def on_receive(self, message):
"""
May be implemented for the actor to handle regular non-proxy messages.
:param message: the message to handle
:type message: any
:returns: anything that should be sent as a reply to the sender
"""
logger.warning(
'Unexpected message received by {}: {}'.format(self, message)
)
def _get_attribute_from_path(self, attr_path):
"""
Traverses the path and returns the attribute at the end of the path.
"""
attr = self
for attr_name in attr_path:
attr = getattr(attr, attr_name)
return attr
def _introspect_attribute_from_path(self, attr_path):
"""Get attribute information from ``__dict__`` on the container."""
if not attr_path:
return self
parent = self._get_attribute_from_path(attr_path[:-1])
parent_attrs = self._introspect_attributes(parent)
attr_name = attr_path[-1]
try:
return parent_attrs[attr_name]
except KeyError:
raise AttributeError(
'type object {!r} has no attribute {!r}'.format(
parent.__class__.__name__, attr_name
)
)
def _introspect_attributes(self, obj):
"""Combine ``__dict__`` from ``obj`` and all its superclasses."""
result = {}
for cls in reversed(obj.__class__.mro()):
result.update(cls.__dict__)
if hasattr(obj, '__dict__'):
result.update(obj.__dict__)
return result

View File

@@ -0,0 +1,36 @@
import sys
PY2 = sys.version_info[0] == 2
if PY2:
import Queue as queue # noqa
from collections import Callable, Iterable # noqa
string_types = basestring # noqa
def reraise(tp, value, tb=None):
exec('raise tp, value, tb')
await_dunder_future = None
await_keyword = None
else:
import queue # noqa
from collections.abc import Callable, Iterable # 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
# `async def` and return inside a generator are syntax errors on Python 2
# so these must be hidden behind a conditional import.
from pykka._compat.await_py3 import ( # noqa
await_dunder_future,
await_keyword,
)

View File

@@ -0,0 +1,8 @@
def await_dunder_future(self):
yield
value = self.get()
return value
async def await_keyword(val):
return await val

View File

@@ -0,0 +1,23 @@
class Envelope(object):
"""
Envelope to add metadata to a message.
This is an internal type and is not part of the public API.
:param message: the message to send
:type message: any
:param reply_to: the future to reply to if there is a response
:type reply_to: :class:`pykka.Future`
"""
# Using slots speeds up envelope creation with ~20%
__slots__ = ['message', 'reply_to']
def __init__(self, message, reply_to=None):
self.message = message
self.reply_to = reply_to
def __repr__(self):
return 'Envelope(message={!r}, reply_to={!r})'.format(
self.message, self.reply_to
)

View File

@@ -0,0 +1,13 @@
__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 File

@@ -0,0 +1,264 @@
import functools
from pykka import _compat
__all__ = ['Future', 'get_all']
class Future(object):
"""
A :class:`Future` is a handle to a value which is available or 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` or, if
using Python 3.5+, ``await`` the future.
"""
def __init__(self):
super(Future, self).__init__()
self._get_hook = None
self._get_hook_result = None
def get(self, timeout=None):
"""
Get the value encapsulated by the future.
If the encapsulated value is an exception, it is raised instead of
returned.
If ``timeout`` is :class:`None`, as default, the method will block
until it gets a reply, potentially forever. If ``timeout`` is an
integer or float, the method will wait for a reply for ``timeout``
seconds, and then raise :exc:`pykka.Timeout`.
The encapsulated value can be retrieved multiple times. The future will
only block the first time the value is accessed.
:param timeout: seconds to wait before timeout
:type timeout: float or :class:`None`
:raise: :exc:`pykka.Timeout` if timeout is reached
:raise: encapsulated value if it is an exception
:return: encapsulated value if it is not an exception
"""
if self._get_hook is not None:
if self._get_hook_result is None:
self._get_hook_result = self._get_hook(timeout)
return self._get_hook_result
raise NotImplementedError
def set(self, value=None):
"""
Set the encapsulated value.
:param value: the encapsulated value or nothing
:type value: any object or :class:`None`
:raise: an exception if set is called multiple times
"""
raise NotImplementedError
def set_exception(self, exc_info=None):
"""
Set an exception as the encapsulated value.
You can pass an ``exc_info`` three-tuple, as returned by
:func:`sys.exc_info`. If you don't pass ``exc_info``,
:func:`sys.exc_info` will be called and the value returned by it used.
In other words, if you're calling :meth:`set_exception`, without any
arguments, from an except block, the exception you're currently
handling will automatically be set on the future.
:param exc_info: the encapsulated exception
:type exc_info: three-tuple of (exc_class, exc_instance, traceback)
"""
raise NotImplementedError
def set_get_hook(self, func):
"""
Set a function to be executed when :meth:`get` is called.
The function will be called when :meth:`get` is called, with the
``timeout`` value as the only argument. The function's return value
will be returned from :meth:`get`.
.. versionadded:: 1.2
:param func: called to produce return value of :meth:`get`
:type func: function accepting a timeout value
"""
self._get_hook = func
def filter(self, func):
"""
Return a new future with only the items passing the predicate function.
If the future's value is an iterable, :meth:`filter` will return a new
future whose value is another iterable with only the items from the
first iterable for which ``func(item)`` is true. If the future's value
isn't an iterable, a :exc:`TypeError` will be raised when :meth:`get`
is called.
Example::
>>> import pykka
>>> f = pykka.ThreadingFuture()
>>> g = f.filter(lambda x: x > 10)
>>> g
<pykka.future.ThreadingFuture at ...>
>>> f.set(range(5, 15))
>>> f.get()
[5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
>>> g.get()
[11, 12, 13, 14]
.. versionadded:: 1.2
"""
future = self.__class__()
future.set_get_hook(
lambda timeout: list(filter(func, self.get(timeout)))
)
return future
def join(self, *futures):
"""
Return a new future with a list of the result of multiple futures.
One or more futures can be passed as arguments to :meth:`join`. The new
future returns a list with the results from all the joined futures.
Example::
>>> import pykka
>>> a = pykka.ThreadingFuture()
>>> b = pykka.ThreadingFuture()
>>> c = pykka.ThreadingFuture()
>>> f = a.join(b, c)
>>> a.set('def')
>>> b.set(123)
>>> c.set(False)
>>> f.get()
['def', 123, False]
.. versionadded:: 1.2
"""
future = self.__class__()
future.set_get_hook(
lambda timeout: [f.get(timeout) for f in [self] + list(futures)]
)
return future
def map(self, func):
"""
Return a new future with the result of the future passed through a
function.
Example::
>>> import pykka
>>> f = pykka.ThreadingFuture()
>>> g = f.map(lambda x: x + 10)
>>> f.set(30)
>>> g.get()
40
>>> f = pykka.ThreadingFuture()
>>> g = f.map(lambda x: x['foo'])
>>> f.set({'foo': 'bar'}})
>>> g.get()
'bar'
.. versionadded:: 1.2
.. versionchanged:: 2.0
Previously, if the future's result was an iterable (except a
string), the function was applied to each item in the iterable.
This behavior is unpredictable and makes regular use cases like
extracting a single field from a dict difficult, thus the
behavior has been simplified. Now, the entire result value is
passed to the function.
"""
future = self.__class__()
future.set_get_hook(lambda timeout: func(self.get(timeout)))
return future
def reduce(self, func, *args):
"""
reduce(func[, initial])
Return a new future with the result of reducing the future's iterable
into a single value.
The function of two arguments is applied cumulatively to the items of
the iterable, from left to right. The result of the first function call
is used as the first argument to the second function call, and so on,
until the end of the iterable. If the future's value isn't an iterable,
a :exc:`TypeError` is raised.
:meth:`reduce` accepts an optional second argument, which will be used
as an initial value in the first function call. If the iterable is
empty, the initial value is returned.
Example::
>>> import pykka
>>> f = pykka.ThreadingFuture()
>>> g = f.reduce(lambda x, y: x + y)
>>> f.set(['a', 'b', 'c'])
>>> g.get()
'abc'
>>> f = pykka.ThreadingFuture()
>>> g = f.reduce(lambda x, y: x + y)
>>> f.set([1, 2, 3])
>>> (1 + 2) + 3
6
>>> g.get()
6
>>> f = pykka.ThreadingFuture()
>>> g = f.reduce(lambda x, y: x + y, 5)
>>> f.set([1, 2, 3])
>>> ((5 + 1) + 2) + 3
11
>>> g.get()
11
>>> f = pykka.ThreadingFuture()
>>> g = f.reduce(lambda x, y: x + y, 5)
>>> f.set([])
>>> g.get()
5
.. versionadded:: 1.2
"""
future = self.__class__()
future.set_get_hook(
lambda timeout: functools.reduce(func, self.get(timeout), *args)
)
return future
__await__ = _compat.await_dunder_future
__iter__ = __await__
def get_all(futures, timeout=None):
"""
Collect all values encapsulated in the list of futures.
If ``timeout`` is not :class:`None`, the method will wait for a reply for
``timeout`` seconds, and then raise :exc:`pykka.Timeout`.
:param futures: futures for the results to collect
:type futures: list of :class:`pykka.Future`
:param timeout: seconds to wait before timeout
:type timeout: float or :class:`None`
:raise: :exc:`pykka.Timeout` if timeout is reached
:returns: list of results
"""
return [future.get(timeout=timeout) for future in futures]

View File

@@ -0,0 +1,357 @@
from __future__ import absolute_import
import logging
from pykka import ActorDeadError, _compat, messages
__all__ = ['ActorProxy']
logger = logging.getLogger('pykka')
class ActorProxy(object):
"""
An :class:`ActorProxy` wraps an :class:`ActorRef <pykka.ActorRef>`
instance. The proxy allows the referenced actor to be used through regular
method calls and field access.
You can create an :class:`ActorProxy` from any :class:`ActorRef
<pykka.ActorRef>`::
actor_ref = MyActor.start()
actor_proxy = ActorProxy(actor_ref)
You can also get an :class:`ActorProxy` by using :meth:`proxy()
<pykka.ActorRef.proxy>`::
actor_proxy = MyActor.start().proxy()
**Attributes and method calls**
When reading an attribute or getting a return value from a method, you get
a :class:`Future <pykka.Future>` object back. To get the enclosed value
from the future, you must call :meth:`get() <pykka.Future.get>` on the
returned future::
print(actor_proxy.string_attribute.get())
print(actor_proxy.count().get() + 1)
If you call a method just for it's side effects and do not care about the
return value, you do not need to accept the returned future or call
:meth:`get() <pykka.Future.get>` on the future. Simply call the method, and
it will be executed concurrently with your own code::
actor_proxy.method_with_side_effect()
If you want to block your own code from continuing while the other method
is processing, you can use :meth:`get() <pykka.Future.get>` to block until
it completes::
actor_proxy.method_with_side_effect().get()
If you're using Python 3.5+, you can also use the ``await`` keyword to
block until the method completes::
await actor_proxy.method_with_side_effect()
If you access a proxied method as an attribute, without calling it, you
get an :class:`CallableProxy`.
**Proxy to itself**
An actor can use a proxy to itself to schedule work for itself. The
scheduled work will only be done after the current message and all messages
already in the inbox are processed.
For example, if an actor can split a time consuming task into multiple
parts, and after completing each part can ask itself to start on the next
part using proxied calls or messages to itself, it can react faster to
other incoming messages as they will be interleaved with the parts of the
time consuming task. This is especially useful for being able to stop the
actor in the middle of a time consuming task.
To create a proxy to yourself, use the actor's :attr:`actor_ref
<pykka.Actor.actor_ref>` attribute::
proxy_to_myself_in_the_future = self.actor_ref.proxy()
If you create a proxy in your actor's constructor or :meth:`on_start
<pykka.Actor.on_start>` method, you can create a nice API for deferring
work to yourself in the future::
def __init__(self):
...
self._in_future = self.actor_ref.proxy()
...
def do_work(self):
...
self._in_future.do_more_work()
...
def do_more_work(self):
...
To avoid infinite loops during proxy introspection, proxies to self
should be kept as private instance attributes by prefixing the attribute
name with ``_``.
**Examples**
An example of :class:`ActorProxy` usage:
.. literalinclude:: ../../examples/counter.py
:param actor_ref: reference to the actor to proxy
:type actor_ref: :class:`pykka.ActorRef`
:raise: :exc:`pykka.ActorDeadError` if actor is not available
"""
#: The actor's :class:`pykka.ActorRef` instance.
actor_ref = None
def __init__(self, actor_ref, attr_path=None):
if not actor_ref.is_alive():
raise ActorDeadError('{} not found'.format(actor_ref))
self.actor_ref = actor_ref
self._actor = actor_ref._actor
self._attr_path = attr_path or tuple()
self._known_attrs = self._introspect_attributes()
self._actor_proxies = {}
self._callable_proxies = {}
def _introspect_attributes(self):
"""Introspects the actor's attributes."""
result = {}
attr_paths_to_visit = [[attr_name] for attr_name in dir(self._actor)]
while attr_paths_to_visit:
attr_path = attr_paths_to_visit.pop(0)
if not self._is_exposable_attribute(attr_path[-1]):
continue
attr = self._actor._introspect_attribute_from_path(attr_path)
if self._is_self_proxy(attr):
logger.warning(
(
'{} attribute {!r} is a proxy to itself. '
'Consider making it private by renaming it to {!r}.'
).format(
self._actor, '.'.join(attr_path), '_' + attr_path[-1]
)
)
continue
traversable = self._is_traversable_attribute(attr)
result[tuple(attr_path)] = {
'callable': self._is_callable_attribute(attr),
'traversable': traversable,
}
if traversable:
for attr_name in dir(attr):
attr_paths_to_visit.append(attr_path + [attr_name])
return result
def _is_exposable_attribute(self, attr_name):
"""
Returns true for any attribute name that may be exposed through
:class:`ActorProxy`.
"""
return not attr_name.startswith('_')
def _is_self_proxy(self, attr):
"""Returns true if attribute is an equivalent actor proxy."""
return attr == self
def _is_callable_attribute(self, attr):
"""Returns true for any attribute that is callable."""
return isinstance(attr, _compat.Callable)
def _is_traversable_attribute(self, attr):
"""
Returns true for any attribute that may be traversed from another
actor through a proxy.
"""
return (
getattr(attr, '_pykka_traversable', False) is True
or getattr(attr, 'pykka_traversable', False) is True
)
def __eq__(self, other):
if not isinstance(other, ActorProxy):
return False
if self._actor != other._actor:
return False
if self._attr_path != other._attr_path:
return False
return True
def __hash__(self):
return hash((self._actor, self._attr_path))
def __repr__(self):
return '<ActorProxy for {}, attr_path={!r}>'.format(
self.actor_ref, self._attr_path
)
def __dir__(self):
result = ['__class__']
result += list(self.__class__.__dict__.keys())
result += list(self.__dict__.keys())
result += [attr_path[0] for attr_path in list(self._known_attrs.keys())]
return sorted(result)
def __getattr__(self, name):
"""Get a field or callable from the actor."""
attr_path = self._attr_path + (name,)
if attr_path not in self._known_attrs:
self._known_attrs = self._introspect_attributes()
attr_info = self._known_attrs.get(attr_path)
if attr_info is None:
raise AttributeError('{} has no attribute {!r}'.format(self, name))
if attr_info['callable']:
if attr_path not in self._callable_proxies:
self._callable_proxies[attr_path] = CallableProxy(
self.actor_ref, attr_path
)
return self._callable_proxies[attr_path]
elif attr_info['traversable']:
if attr_path not in self._actor_proxies:
self._actor_proxies[attr_path] = ActorProxy(
self.actor_ref, attr_path
)
return self._actor_proxies[attr_path]
else:
message = messages.ProxyGetAttr(attr_path=attr_path)
return self.actor_ref.ask(message, block=False)
def __setattr__(self, name, value):
"""
Set a field on the actor.
Blocks until the field is set to check if any exceptions was raised.
"""
if name == 'actor_ref' or name.startswith('_'):
return super(ActorProxy, self).__setattr__(name, value)
attr_path = self._attr_path + (name,)
message = messages.ProxySetAttr(attr_path=attr_path, value=value)
return self.actor_ref.ask(message)
class CallableProxy(object):
"""Proxy to a single method.
:class:`CallableProxy` instances are returned when accessing methods on a
:class:`ActorProxy` without calling them.
Example::
proxy = AnActor.start().proxy()
# Ask semantics returns a future. See `__call__()` docs.
future = proxy.do_work()
# Tell semantics are fire and forget. See `defer()` docs.
proxy.do_work.defer()
"""
def __init__(self, actor_ref, attr_path):
self.actor_ref = actor_ref
self._attr_path = attr_path
def __call__(self, *args, **kwargs):
"""Call with :meth:`~pykka.ActorRef.ask` semantics.
Returns a future which will yield the called method's return value.
If the call raises an exception is set on the future, and will be
reraised by :meth:`~pykka.Future.get`. If the future is left unused,
the exception will not be reraised. Either way, the exception will
also be logged. See :ref:`logging` for details.
"""
message = messages.ProxyCall(
attr_path=self._attr_path, args=args, kwargs=kwargs
)
return self.actor_ref.ask(message, block=False)
def defer(self, *args, **kwargs):
"""Call with :meth:`~pykka.ActorRef.tell` semantics.
Does not create or return a future.
If the call raises an exception, there is no future to set the
exception on. Thus, the actor's :meth:`~pykka.Actor.on_failure` hook
is called instead.
.. versionadded:: 2.0
"""
message = messages.ProxyCall(
attr_path=self._attr_path, args=args, kwargs=kwargs
)
return self.actor_ref.tell(message)
def traversable(obj):
"""Marks an actor attribute as traversable.
The traversable marker makes the actor attribute's own methods and
attributes available to users of the actor through an
:class:`~pykka.ActorProxy`.
Used as a function to mark a single attribute::
class AnActor(pykka.ThreadingActor):
playback = pykka.traversable(Playback())
class Playback(object):
def play(self):
return True
This function can also be used as a class decorator, making all instances
of the class traversable::
class AnActor(pykka.ThreadingActor):
playback = Playback()
@pykka.traversable
class Playback(object):
def play(self):
return True
The third alternative, and the only way in Pykka < 2.0, is to manually
mark a class as traversable by setting the ``pykka_traversable`` attribute
to :class:`True`::
class AnActor(pykka.ThreadingActor):
playback = Playback()
class Playback(object):
pykka_traversable = True
def play(self):
return True
When the attribute is marked as traversable, its methods can be executed
in the context of the actor through an actor proxy::
proxy = AnActor.start().proxy()
assert proxy.playback.play().get() is True
.. versionadded:: 2.0
"""
if hasattr(obj, '__slots__'):
raise Exception(
'pykka.traversable() cannot be used to mark '
'an object using slots as traversable.'
)
obj._pykka_traversable = True
return obj

View File

@@ -0,0 +1,171 @@
from pykka import ActorDeadError, ActorProxy
from pykka._envelope import Envelope
from pykka.messages import _ActorStop
__all__ = ['ActorRef']
class ActorRef(object):
"""
Reference to a running actor which may safely be passed around.
:class:`ActorRef` instances are returned by :meth:`Actor.start` and the
lookup methods in :class:`ActorRegistry <pykka.ActorRegistry>`. You should
never need to create :class:`ActorRef` instances yourself.
:param actor: the actor to wrap
:type actor: :class:`Actor`
"""
#: The class of the referenced actor.
actor_class = None
#: See :attr:`Actor.actor_urn`.
actor_urn = None
#: See :attr:`Actor.actor_inbox`.
actor_inbox = None
#: See :attr:`Actor.actor_stopped`.
actor_stopped = None
def __init__(self, actor):
self._actor = actor
self.actor_class = actor.__class__
self.actor_urn = actor.actor_urn
self.actor_inbox = actor.actor_inbox
self.actor_stopped = actor.actor_stopped
def __repr__(self):
return '<ActorRef for {}>'.format(self)
def __str__(self):
return '{} ({})'.format(self.actor_class.__name__, self.actor_urn)
def is_alive(self):
"""
Check if actor is alive.
This is based on the actor's stopped flag. The actor is not guaranteed
to be alive and responding even though :meth:`is_alive` returns
:class:`True`.
:return:
Returns :class:`True` if actor is alive, :class:`False` otherwise.
"""
return not self.actor_stopped.is_set()
def tell(self, message):
"""
Send message to actor without waiting for any response.
Will generally not block, but if the underlying queue is full it will
block until a free slot is available.
:param message: message to send
:type message: any
:raise: :exc:`pykka.ActorDeadError` if actor is not available
:return: nothing
"""
if not self.is_alive():
raise ActorDeadError('{} not found'.format(self))
self.actor_inbox.put(Envelope(message))
def ask(self, message, block=True, timeout=None):
"""
Send message to actor and wait for the reply.
The message can be of any type.
If ``block`` is :class:`False`, it will immediately return a
:class:`Future <pykka.Future>` instead of blocking.
If ``block`` is :class:`True`, and ``timeout`` is :class:`None`, as
default, the method will block until it gets a reply, potentially
forever. If ``timeout`` is an integer or float, the method will wait
for a reply for ``timeout`` seconds, and then raise
:exc:`pykka.Timeout`.
:param message: message to send
:type message: any
:param block: whether to block while waiting for a reply
:type block: boolean
:param timeout: seconds to wait before timeout if blocking
:type timeout: float or :class:`None`
:raise: :exc:`pykka.Timeout` if timeout is reached if blocking
:raise: any exception returned by the receiving actor if blocking
:return: :class:`pykka.Future`, or response if blocking
"""
future = self.actor_class._create_future()
try:
if not self.is_alive():
raise ActorDeadError('{} not found'.format(self))
except ActorDeadError:
future.set_exception()
else:
self.actor_inbox.put(Envelope(message, reply_to=future))
if block:
return future.get(timeout=timeout)
else:
return future
def stop(self, block=True, timeout=None):
"""
Send a message to the actor, asking it to stop.
Returns :class:`True` if actor is stopped or was being stopped at the
time of the call. :class:`False` if actor was already dead. If
``block`` is :class:`False`, it returns a future wrapping the result.
Messages sent to the actor before the actor is asked to stop will
be processed normally before it stops.
Messages sent to the actor after the actor is asked to stop will
be replied to with :exc:`pykka.ActorDeadError` after it stops.
The actor may not be restarted.
``block`` and ``timeout`` works as for :meth:`ask`.
:return: :class:`pykka.Future`, or a boolean result if blocking
"""
ask_future = self.ask(_ActorStop(), block=False)
def _stop_result_converter(timeout):
try:
ask_future.get(timeout=timeout)
return True
except ActorDeadError:
return False
converted_future = ask_future.__class__()
converted_future.set_get_hook(_stop_result_converter)
if block:
return converted_future.get(timeout=timeout)
else:
return converted_future
def proxy(self):
"""
Wraps the :class:`ActorRef` in an :class:`ActorProxy
<pykka.ActorProxy>`.
Using this method like this::
proxy = AnActor.start().proxy()
is analogous to::
proxy = ActorProxy(AnActor.start())
:raise: :exc:`pykka.ActorDeadError` if actor is not available
:return: :class:`pykka.ActorProxy`
"""
return ActorProxy(self)

View File

@@ -0,0 +1,171 @@
from __future__ import absolute_import
import logging
import threading
from pykka import _compat
logger = logging.getLogger('pykka')
__all__ = ['ActorRegistry']
class ActorRegistry(object):
"""
Registry which provides easy access to all running actors.
Contains global state, but should be thread-safe.
"""
_actor_refs = []
_actor_refs_lock = threading.RLock()
@classmethod
def broadcast(cls, message, target_class=None):
"""
Broadcast ``message`` to all actors of the specified ``target_class``.
If no ``target_class`` is specified, the message is broadcasted to all
actors.
:param message: the message to send
:type message: any
:param target_class: optional actor class to broadcast the message to
:type target_class: class or class name
"""
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)
else:
targets = cls.get_all()
for ref in targets:
ref.tell(message)
@classmethod
def get_all(cls):
"""
Get :class:`ActorRef <pykka.ActorRef>` for all running actors.
:returns: list of :class:`pykka.ActorRef`
"""
with cls._actor_refs_lock:
return cls._actor_refs[:]
@classmethod
def get_by_class(cls, actor_class):
"""
Get :class:`ActorRef` for all running actors of the given class, or of
any subclass of the given class.
:param actor_class: actor class, or any superclass of the actor
:type actor_class: class
:returns: list of :class:`pykka.ActorRef`
"""
with cls._actor_refs_lock:
return [
ref
for ref in cls._actor_refs
if issubclass(ref.actor_class, actor_class)
]
@classmethod
def get_by_class_name(cls, actor_class_name):
"""
Get :class:`ActorRef` for all running actors of the given class
name.
:param actor_class_name: actor class name
:type actor_class_name: string
:returns: list of :class:`pykka.ActorRef`
"""
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):
"""
Get an actor by its universally unique URN.
:param actor_urn: actor URN
:type actor_urn: string
:returns: :class:`pykka.ActorRef` or :class:`None` if not found
"""
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_ref):
"""
Register an :class:`ActorRef` in the registry.
This is done automatically when an actor is started, e.g. by calling
:meth:`Actor.start() <pykka.Actor.start>`.
:param actor_ref: reference to the actor to register
:type actor_ref: :class:`pykka.ActorRef`
"""
with cls._actor_refs_lock:
cls._actor_refs.append(actor_ref)
logger.debug('Registered {}'.format(actor_ref))
@classmethod
def stop_all(cls, block=True, timeout=None):
"""
Stop all running actors.
``block`` and ``timeout`` works as for
:meth:`ActorRef.stop() <pykka.ActorRef.stop>`.
If ``block`` is :class:`True`, the actors are guaranteed to be stopped
in the reverse of the order they were started in. This is helpful if
you have simple dependencies in between your actors, where it is
sufficient to shut down actors in a LIFO manner: last started, first
stopped.
If you have more complex dependencies in between your actors, you
should take care to shut them down in the required order yourself, e.g.
by stopping dependees from a dependency's
:meth:`on_stop() <pykka.Actor.on_stop>` method.
:returns: If not blocking, a list with a future for each stop action.
If blocking, a list of return values from
:meth:`pykka.ActorRef.stop`.
"""
return [ref.stop(block, timeout) for ref in reversed(cls.get_all())]
@classmethod
def unregister(cls, actor_ref):
"""
Remove an :class:`ActorRef <pykka.ActorRef>` from the registry.
This is done automatically when an actor is stopped, e.g. by calling
:meth:`Actor.stop() <pykka.Actor.stop>`.
:param actor_ref: reference to the actor to unregister
:type actor_ref: :class:`pykka.ActorRef`
"""
removed = False
with cls._actor_refs_lock:
if actor_ref in cls._actor_refs:
cls._actor_refs.remove(actor_ref)
removed = True
if removed:
logger.debug('Unregistered {}'.format(actor_ref))
else:
logger.debug(
'Unregistered {} (not found in registry)'.format(actor_ref)
)

View File

@@ -0,0 +1,97 @@
from __future__ import absolute_import
import sys
import threading
from pykka import Actor, Future, Timeout, _compat
__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('{} seconds'.format(timeout))
def set(self, value=None):
self._queue.put({'value': value}, block=False)
def set_exception(self, exc_info=None):
assert exc_info is None or len(exc_info) == 3
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 File

@@ -0,0 +1,67 @@
from __future__ import absolute_import
import logging
import sys
import threading
import traceback
logger = logging.getLogger('pykka')
__all__ = ['log_thread_tracebacks']
def log_thread_tracebacks(*args, **kwargs):
"""Logs at :attr:`logging.CRITICAL` level a traceback for each running
thread.
This can be a convenient tool for debugging deadlocks.
The function accepts any arguments so that it can easily be used as e.g. a
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.CRITICAL` threshold or lower and make your main thread
register this with the :mod:`signal` module::
import logging
import signal
import pykka.debug
logging.basicConfig(level=logging.DEBUG)
signal.signal(signal.SIGUSR1, pykka.debug.log_thread_tracebacks)
If your application deadlocks, send the `SIGUSR1` signal to the process::
kill -SIGUSR1 <pid of your process>
Signal handler caveats:
- The function *must* be registered as a signal handler by your main
thread. If not, :func:`signal.signal` will raise a :exc:`ValueError`.
- All signals in Python are handled by the main thread. Thus, the signal
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
:meth:`pykka.Future.get` will break signal handling, and thus you won't
be able to signal your process to print the thread tracebacks.
The morale is: setup signals using your main thread, start your actors,
then let your main thread relax for the rest of your application's life
cycle.
.. versionadded:: 1.1
"""
thread_names = dict((t.ident, t.name) for t in threading.enumerate())
for ident, frame in sys._current_frames().items():
name = thread_names.get(ident, '?')
stack = ''.join(traceback.format_stack(frame))
logger.critical(
'Current state of {} (ident: {}):\n{}'.format(name, ident, stack)
)

View File

@@ -0,0 +1,107 @@
from __future__ import absolute_import
import sys
import eventlet
import eventlet.event
import eventlet.queue
from pykka import Actor, Future, Timeout
__all__ = ['EventletActor', 'EventletEvent', 'EventletFuture']
class EventletEvent(eventlet.event.Event):
"""
:class:`EventletEvent` adapts :class:`eventlet.event.Event` to
:class:`threading.Event` interface.
"""
def set(self):
if self.ready():
self.reset()
self.send()
def is_set(self):
return self.ready()
isSet = is_set
def clear(self):
if self.ready():
self.reset()
def wait(self, timeout):
if timeout is not None:
wait_timeout = eventlet.Timeout(timeout)
try:
with wait_timeout:
super(EventletEvent, self).wait()
except eventlet.Timeout as t:
if t is not wait_timeout:
raise
return False
else:
self.event.wait()
return True
class EventletFuture(Future):
"""
:class:`EventletFuture` implements :class:`pykka.Future` for use with
:class:`EventletActor`.
"""
event = None
def __init__(self):
super(EventletFuture, self).__init__()
self.event = eventlet.event.Event()
def get(self, timeout=None):
try:
return super(EventletFuture, self).get(timeout=timeout)
except NotImplementedError:
pass
if timeout is not None:
wait_timeout = eventlet.Timeout(timeout)
try:
with wait_timeout:
return self.event.wait()
except eventlet.Timeout as t:
if t is not wait_timeout:
raise
raise Timeout(t)
else:
return self.event.wait()
def set(self, value=None):
self.event.send(value)
def set_exception(self, exc_info=None):
assert exc_info is None or len(exc_info) == 3
self.event.send_exception(*(exc_info or sys.exc_info()))
class EventletActor(Actor):
"""
:class:`EventletActor` implements :class:`pykka.Actor` using the `eventlet
<https://eventlet.net/>`_ library.
This implementation uses eventlet green threads.
"""
@staticmethod
def _create_actor_inbox():
return eventlet.queue.Queue()
@staticmethod
def _create_future():
return EventletFuture()
def _start_actor_loop(self):
eventlet.greenthread.spawn(self._actor_loop)

View File

@@ -0,0 +1,73 @@
from __future__ import absolute_import
import sys
import gevent
import gevent.event
import gevent.queue
from pykka import Actor, Future, Timeout
__all__ = ['GeventActor', 'GeventFuture']
class GeventFuture(Future):
"""
:class:`GeventFuture` implements :class:`pykka.Future` for use with
:class:`GeventActor`.
It encapsulates a :class:`gevent.event.AsyncResult` object which may be
used directly, though it will couple your code with gevent.
"""
#: The encapsulated :class:`gevent.event.AsyncResult`
async_result = None
def __init__(self, async_result=None):
super(GeventFuture, self).__init__()
if async_result is None:
async_result = gevent.event.AsyncResult()
self.async_result = async_result
def get(self, timeout=None):
try:
return super(GeventFuture, self).get(timeout=timeout)
except NotImplementedError:
pass
try:
return self.async_result.get(timeout=timeout)
except gevent.Timeout as e:
raise Timeout(e)
def set(self, value=None):
assert not self.async_result.ready(), 'value has already been set'
self.async_result.set(value)
def set_exception(self, exc_info=None):
assert exc_info is None or len(exc_info) == 3
exc_info = exc_info or sys.exc_info()
self.async_result.set_exception(exc_info[1], exc_info=exc_info)
class GeventActor(Actor):
"""
:class:`GeventActor` implements :class:`pykka.Actor` using the `gevent
<http://www.gevent.org/>`_ library. gevent is a coroutine-based Python
networking library that uses greenlet to provide a high-level synchronous
API on top of libevent event loop.
This is a very fast implementation.
"""
@staticmethod
def _create_actor_inbox():
return gevent.queue.Queue()
@staticmethod
def _create_future():
return GeventFuture()
def _start_actor_loop(self):
gevent.Greenlet.spawn(self._actor_loop)

View File

@@ -0,0 +1,77 @@
"""
The :mod:`pykka.messages` module contains Pykka's own actor messages.
In general, you should not need to use any of these classes. However, they have
been made part of the public API so that certain optimizations can be done
without touching Pykka's internals.
An example is to combine :meth:`~pykka.ActorRef.ask` and :class:`ProxyCall`
to call a method on an actor without having to spend any resources on creating
a proxy object::
reply = actor_ref.ask(
ProxyCall(
attr_path=['my_method'],
args=['foo'],
kwargs={'bar': 'baz'}
)
)
Another example is to use :meth:`~pykka.ActorRef.tell` instead of
:meth:`~pykka.ActorRef.ask` for the proxy method call, and thus avoid the
creation of a future for the return value if you don't need it.
It should be noted that these optimizations should only be necessary in very
special circumstances.
"""
import warnings
from collections import namedtuple
# Internal actor messages
_ActorStop = namedtuple('ActorStop', [])
# Public proxy messages
# TODO Add docstrings to classes and attributes once we drop Python 2.7 support
ProxyCall = namedtuple('ProxyCall', ['attr_path', 'args', 'kwargs'])
ProxyGetAttr = namedtuple('ProxyGetAttr', ['attr_path'])
ProxySetAttr = namedtuple('ProxySetAttr', ['attr_path', 'value'])
def _upgrade_internal_message(message):
"""Filter that upgrades dict-based internal messages to the new format.
This is needed for a transitional period because Mopidy < 3 uses
the old internal message format directly, and maybe others.
"""
if not isinstance(message, dict):
return message
if not message.get('command', '').startswith('pykka_'):
return message
warnings.warn(
'Pykka received a dict-based internal message. '
'This is deprecated and will be unsupported in the future. '
'Message: {!r}'.format(message),
DeprecationWarning,
)
command = message.get('command')
if command == 'pykka_stop':
return _ActorStop()
elif command == 'pykka_call':
return ProxyCall(
attr_path=message['attr_path'],
args=message['args'],
kwargs=message['kwargs'],
)
elif command == 'pykka_getattr':
return ProxyGetAttr(attr_path=message['attr_path'])
elif command == 'pykka_setattr':
return ProxySetAttr(
attr_path=message['attr_path'], value=message['value']
)
else:
raise ValueError('Unknown internal message: {!r}'.format(message))