Python3 Migrate
This commit is contained in:
32
venv/lib/python3.7/site-packages/pykka/__init__.py
Normal file
32
venv/lib/python3.7/site-packages/pykka/__init__.py
Normal 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())
|
||||
356
venv/lib/python3.7/site-packages/pykka/_actor.py
Normal file
356
venv/lib/python3.7/site-packages/pykka/_actor.py
Normal 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
|
||||
36
venv/lib/python3.7/site-packages/pykka/_compat/__init__.py
Normal file
36
venv/lib/python3.7/site-packages/pykka/_compat/__init__.py
Normal 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,
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
def await_dunder_future(self):
|
||||
yield
|
||||
value = self.get()
|
||||
return value
|
||||
|
||||
|
||||
async def await_keyword(val):
|
||||
return await val
|
||||
23
venv/lib/python3.7/site-packages/pykka/_envelope.py
Normal file
23
venv/lib/python3.7/site-packages/pykka/_envelope.py
Normal 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
|
||||
)
|
||||
13
venv/lib/python3.7/site-packages/pykka/_exceptions.py
Normal file
13
venv/lib/python3.7/site-packages/pykka/_exceptions.py
Normal 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
|
||||
264
venv/lib/python3.7/site-packages/pykka/_future.py
Normal file
264
venv/lib/python3.7/site-packages/pykka/_future.py
Normal 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]
|
||||
357
venv/lib/python3.7/site-packages/pykka/_proxy.py
Normal file
357
venv/lib/python3.7/site-packages/pykka/_proxy.py
Normal 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
|
||||
171
venv/lib/python3.7/site-packages/pykka/_ref.py
Normal file
171
venv/lib/python3.7/site-packages/pykka/_ref.py
Normal 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)
|
||||
171
venv/lib/python3.7/site-packages/pykka/_registry.py
Normal file
171
venv/lib/python3.7/site-packages/pykka/_registry.py
Normal 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)
|
||||
)
|
||||
97
venv/lib/python3.7/site-packages/pykka/_threading.py
Normal file
97
venv/lib/python3.7/site-packages/pykka/_threading.py
Normal 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()
|
||||
67
venv/lib/python3.7/site-packages/pykka/debug.py
Normal file
67
venv/lib/python3.7/site-packages/pykka/debug.py
Normal 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)
|
||||
)
|
||||
107
venv/lib/python3.7/site-packages/pykka/eventlet.py
Normal file
107
venv/lib/python3.7/site-packages/pykka/eventlet.py
Normal 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)
|
||||
73
venv/lib/python3.7/site-packages/pykka/gevent.py
Normal file
73
venv/lib/python3.7/site-packages/pykka/gevent.py
Normal 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)
|
||||
77
venv/lib/python3.7/site-packages/pykka/messages.py
Normal file
77
venv/lib/python3.7/site-packages/pykka/messages.py
Normal 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))
|
||||
Reference in New Issue
Block a user