An actor has the following characteristics:
In Pykka, we have two different ways to use actors: plain actors and typed actors.
Pykka’s plain actors get all incoming messages delivered to the on_receive() method. This method can decide what action is needed in response to the message. The messages are expected to be Python dictionaries, containing anything that can be serialized.
#! /usr/bin/env python
from pykka.actor import ThreadingActor
class PlainActor(ThreadingActor):
def __init__(self):
self.stored_messages = []
def on_receive(self, message):
if message.get('command') == 'get_messages':
return self.stored_messages
else:
self.stored_messages.append(message)
if __name__ == '__main__':
actor = PlainActor.start()
actor.tell({'no': 'Norway', 'se': 'Sweden'})
actor.tell({'a': 3, 'b': 4, 'c': 5})
print actor.ask({'command': 'get_messages'})
actor.stop()
We get the following output:
$ PYTHONPATH=. python examples/plain_actor.py
[{'se': 'Sweden', 'no': 'Norway'}, {'a': 3, 'c': 5, 'b': 4}]
If you wrap a plain actor in a pykka.proxy.ActorProxy, Pykka let you call methods on the actor like you would on a regular object, but it runs the code in the actor. Similarly, when you access the actor’s fields, they are read in the actor, serialized and copied to the reader.
Both method calling and attribute reads immediately returns future objects. This means that your code can continue while the result is calculated in some other actor, and that you’re code will not block until you actually use the returned value.
Here is a small example of two actors wrapped in pykka.proxy.ActorProxy objects. It may look like they communicate with each other by calling regular methods, but–under the hood–the calls are serialized and sent to the other actor. Meanwhile, the first actor can continue executing its own code.
#! /usr/bin/env python
from pykka.actor import ThreadingActor
from pykka.registry import ActorRegistry
class Adder(ThreadingActor):
def add_one(self, i):
print '%s is increasing %d' % (self, i)
return i + 1
class Bookkeeper(ThreadingActor):
def __init__(self, adder):
self.adder = adder
def count_to(self, target):
i = 0
while i < target:
i = self.adder.add_one(i).get()
print '%s got %d back' % (self, i)
if __name__ == '__main__':
adder = Adder.start().proxy()
bookkeeper = Bookkeeper.start(adder).proxy()
bookkeeper.count_to(10).get()
ActorRegistry.stop_all()
When we run the above example with Pykka on the PYTHONPATH, we get the following output:
$ PYTHONPATH=. python examples/counter.py
Adder (urn:uuid:35d5216f-332b-4c04-97bb-a02016ba4121) is increasing 0
Bookkeeper (urn:uuid:fd8df21d-8a58-451b-a1b8-77bd19d868b8) got 1 back
Adder (urn:uuid:35d5216f-332b-4c04-97bb-a02016ba4121) is increasing 1
Bookkeeper (urn:uuid:fd8df21d-8a58-451b-a1b8-77bd19d868b8) got 2 back
Adder (urn:uuid:35d5216f-332b-4c04-97bb-a02016ba4121) is increasing 2
Bookkeeper (urn:uuid:fd8df21d-8a58-451b-a1b8-77bd19d868b8) got 3 back
Adder (urn:uuid:35d5216f-332b-4c04-97bb-a02016ba4121) is increasing 3
Bookkeeper (urn:uuid:fd8df21d-8a58-451b-a1b8-77bd19d868b8) got 4 back
Adder (urn:uuid:35d5216f-332b-4c04-97bb-a02016ba4121) is increasing 4
Bookkeeper (urn:uuid:fd8df21d-8a58-451b-a1b8-77bd19d868b8) got 5 back
Adder (urn:uuid:35d5216f-332b-4c04-97bb-a02016ba4121) is increasing 5
Bookkeeper (urn:uuid:fd8df21d-8a58-451b-a1b8-77bd19d868b8) got 6 back
Adder (urn:uuid:35d5216f-332b-4c04-97bb-a02016ba4121) is increasing 6
Bookkeeper (urn:uuid:fd8df21d-8a58-451b-a1b8-77bd19d868b8) got 7 back
Adder (urn:uuid:35d5216f-332b-4c04-97bb-a02016ba4121) is increasing 7
Bookkeeper (urn:uuid:fd8df21d-8a58-451b-a1b8-77bd19d868b8) got 8 back
Adder (urn:uuid:35d5216f-332b-4c04-97bb-a02016ba4121) is increasing 8
Bookkeeper (urn:uuid:fd8df21d-8a58-451b-a1b8-77bd19d868b8) got 9 back
Adder (urn:uuid:35d5216f-332b-4c04-97bb-a02016ba4121) is increasing 9
Bookkeeper (urn:uuid:fd8df21d-8a58-451b-a1b8-77bd19d868b8) got 10 back
See the examples/ dir for more runnable examples.
Sometimes you don’t care about the attribute of an actor, but you want to access the attributes of the attribute itself, or call methods on the attribute. For this case, Pykka supports traversable attributes. By marking an actor attribute as traversable, Pykka will not return the attribute when accessed, but wrap it in a new pykka.proxy.ActorProxy. When the wrapped attribute is used, Pykka will get/set attributes or call methods on the actor attribute, just as it normally would on the actor, if wrapped in an actor proxy.
To mark an attribute as traversable, simply set the pykka_traversable attribute to something, like e.g. True:
class AnActor(GeventActor):
an_attribute = SomeOtherObject()
an_attribute.pykka_traversable = True
You can mark the attributes of attributes of the actor as traversable, and so on, as long as all objects in the path from the actor to the deepest nested attribute is marked as traversable.
Pykka uses Python’s standard logging module for logging debug statements and any unhandled exceptions in the actors. All log records emitted by Pykka are issued to the logger named “pykka”, or a sublogger of it.
Out of the box, Pykka is set up with logging.NullHandler as the only log record handler. This is the recommended approach for logging in libraries, so that the application developer using the library will have full control over how the log records from the library will be exposed to the application’s users. In other words, if you want to see the log records from Pykka anywhere, you need to add a useful handler to the root logger or the logger named “pykka” to get any log output from Pykka. The defaults provided by logging.basicConfig() is enough to get debug log statements out of Pykka:
import logging
logging.basicConfig()
If your application is already using logging, and you want debug log output from your own application, but not from Pykka, you can ignore debug log messages from Pykka by increasing the threshold on the Pykka logger to “info” level or higher:
import logging
logging.getLogger('pykka').setLevel(logging.INFO)
For more details on how to use logging, please refer to the Python standard library documentation.
Pykka is licensed under the Apache License, Version 2.0.
Install Pykka’s dependencies:
To install Pykka you can use pip:
pip install pykka
To upgrade your Pykka installation to the latest released version:
pip install --upgrade pykka
To install the latest development snapshot:
pip install pykka==dev