jodal / pykka

🌀 Pykka makes it easier to build concurrent Python applications.

Home Page:https://pykka.readthedocs.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Nicer syntax for creating/comparing messages

drozzy opened this issue · comments

Is there any way we can create messages with some kind of class-inheriance, and have some tools for comparing them?

E.g.:

class Tick(Message):
   def __init__(self, param1, param2):
       self.param1 = param1
       self.param2 = param2

self.actor_ref.tell(Tick('1', '2'))

def on_receive(self, message):
      if message |match| Tick:
          pass

I tried doing this on my own, but doing "name" on a class does not seem to be thread safe! I got really weird errors of actors sending messages of the wrong type!

Issue #45 alludes to this approach. I am not sure inheritance is the right way to go.

Here is what I came up with, works quite nicely (same solution for issue #45):

Here is how you can patch the ThreadingActor. Of course, you would inherit from this class instead for creating all your actors:

from pykka import ThreadingActor, ActorRef


class Actor(ThreadingActor):
    """
    Actor class from which classes should inherit to create an actor.

    Adapter to the pykka actors that treats messages as instances
    of some class. If pykka's system message is encountered,
    this adapter simply passes the message on.
    """
    PAYLOAD_MARKER = '__actor.message.payload__'

    def __init__(self, *args, **kwargs):
        super(Actor, self).__init__(*args, **kwargs)
        # Override actor ref with our custom actor ref,
        # that creates a dict each time it sends a message
        self.actor_ref = ActorRefAdaptor(self)

    def _handle_receive(self, message_or_dict):
        if Actor.PAYLOAD_MARKER in message_or_dict:
            # This is our message with an instance of the object in it - pass it
            # straight to on receive function
            self.on_receive(message_or_dict[Actor.PAYLOAD_MARKER])
        else:
            # hand the message over to pykka
            super(Actor, self)._handle_receive(message_or_dict)


class ActorRefAdaptor(ActorRef):
    def tell(self, message):
        # If this is a dict message, it might be pykka's system message,
        # and we leave it as is.
        if isinstance(message, dict):
            super(ActorRefAdaptor, self).tell(message)
        else:
            # Our message is an instance of some class, but we need
            #to pass pykka a dict object - so we wrap it in one!
            super(ActorRefAdaptor, self).tell({Actor.PAYLOAD_MARKER: message})

Now use it simply as:

class MyActor(Actor):
    def on_receive(self, message):
       if isinstance(message, SomeCommand):
           print("Got command with x=%s, and y=%s!!!" % (message.x, message.y))

class SomeCommand(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

actor = MyActor.start()
actor.tell(SomeCommand(x=1, y=2))

Enjoy!

With the merge of #79 that will be part of the upcoming Pykka 2.0, the example here now works without having to wrap the actor and ref in adapters:

In [1]: import pykka

In [2]: class SomeCommand(object):
   ...:     def __init__(self, x, y):
   ...:         self.x = x
   ...:         self.y = y
   ...: 

In [3]: class MyActor(pykka.ThreadingActor):
   ...:     def on_receive(self, message):
   ...:         if isinstance(message, SomeCommand):
   ...:             print('Got command with x={} and y={}'.format(message.x, message.y))
   ...: 

In [4]: actor = MyActor.start()

In [5]: actor.tell(SomeCommand(x=1, y=2))

Got command with x=1 and y=2
In [6]: