facebookarchive / pyaib

An easy to use Python Framework (that uses gevent) for writing IRC Bots.
Apache License 2.0
337 stars 71 forks source link

Passing data between greenlets #24

Open rossjrw opened 4 years ago

rossjrw commented 4 years ago

pyaib's eponymous asynchronous nature makes it difficult - if not outright impossible - to pass data between greenlets, or pause a greenlet to wait for some event.

Not good enough for me - I had a situation where I needed a keyword command to request a fresh NAMES response from the server and compare that to the cached NAMES response in my database. How could I possibly do that? In a function decorated with @keyword, I can't stop it in the middle and wait for @observe('IRC_MSG_353'). They would both have to be handled by different functions - different, completely separate, asynchronous functions.

This PR introduces a new concept: Signals, which are pretty much a fork of Events. Signals are messages broadcasted from one greenlet to all others. They have a name/ID and can carry data with them.

This PR introduces a new decorator and two new functions:

Example usage (a truncated examples/plugins/signals.py):

from pyiab.plugins import plugin_class
from pyaib.components import observe, awaits_signal
from pyaib.signals import emit_signal, await_signal
import re

@plugin_class('names')
class Names:
    @keyword('names')
    def get_list_of_names(self, irc_c, message, trigger, args, kwargs):
        irc_c.RAW("NAMES %s" % message.channel)
        try:
            response = await_signal(irc_c, 'NAMES_RESPONSE', timeout=10.0)
        except TimeoutError:
            message.reply("The request timed out.")
            return
        channel = response[0]
        names = response[1]
        assert channel == message.channel
        message.reply("List of channel members: %s" % ", ".join(names))

    @observe('IRC_MSG_353') # 353 indicates a NAMES response
    def recieve_names(self, irc_c, message):
        response = re.split(r"\s:?", message.args.strip())[2:]
        channel = response[0]
        names = response[1:]
        emit_signal(irc_c, 'NAMES_RESPONSE', data=(channel, names))

The development process can be found here: https://github.com/rossjrw/pyaib/pull/1