How does the Delegator work exactly in TelePot?

I'm trying to learn the python library by Telepot

looking at the example counter.py

available here: https://github.com/nickoala/telepot/blob/master/examples/chat/counter.py .
I am having a hard time understanding how the class works DelegatorBot

.

This is what I think I have figured out so far:

1.

I see that initially this class (derived from the "ChatHandler" class) is defined:

class MessageCounter(telepot.helper.ChatHandler):

    def __init__(self, *args, **kwargs):
        super(MessageCounter, self).__init__(*args, **kwargs)
        self._count = 0

    def on_chat_message(self, msg):
        self._count += 1
        self.sender.sendMessage(self._count)

      

2.

Then the bot is created by initializing the class DelegatorBot

:

bot = telepot.DelegatorBot(TOKEN, [
    pave_event_space()(
        per_chat_id(), create_open, MessageCounter, timeout=10
    ),
])

      

3.

I understand that a new instance is created DelegatorBot

and put into a variable bot

. The first parameter is the token required by the telegram to authenticate this bot, the second parameter is a list that contains what I don't understand.

I mean this part:

pave_event_space()(
    per_chat_id(), create_open, MessageCounter, timeout=10
)

      

And then my question is:

Is a pave_event_space()

method called that returns a reference to another method? And then this return method is called with parameters (per_chat_id(), create_open, MessageCounter, timeout=10)

?

+3


source to share


1 answer


Short answer

Yes, it pave_event_space()

returns a function. Let's call it fn

. Then fn

called with fn(per_chat_id(), create_open, ...)

, which returns a 2-tuple (seeder function, delegate-producing function)

.

If you want to explore the code further, this short answer probably doesn't help much ...

Longer answer

To understand what pave_event_space()

this series of arguments does and what it means, we must go back to basics and understand what it DelegatorBot

takes as arguments.

DelegatorBot

the constructor is explained here . Simply put, it takes a list of 2 tuples (seeder function, delegate-producing function)

. To reduce verbosity, I'm going to name the first planter item and the second item delegate-producer .

The seeder has this signature seeder(msg) -> number

. For each message received, it is seeder(msg)

called to createnumber



... If thisnumber



is new, a companion producer delegate (the one that shares the same tuple with the seed) will be called to create a thread that is used to process the new message. If thisnumber



busy with a running thread, nothing is done. Essentially, the planter "classifies" the message. It spawns a new thread if it sees that a message belongs to a new "category".

The Producer Delegate has this signature producer(cls, *args, **kwargs) -> Thread

. It calls cls(*args, **kwargs)

to create a handler object ( MessageCounter

in your case) and streams it, so the handler methods execute independently.

(Note: the planter does not actually return number



, and the producer delegate does not necessarily return Thread

. I have simplified the above for clarity. See link for full explanation.)

In earlier days, telecasting was DelegatorBot

usually done by providing a transparent seeder and a producer delegate:

bot = DelegatorBot(TOKEN, [
        (per_chat_id(), create_open(MessageCounter, ...))])

      

Later, I added the handlers (for example ChatHandler

) the ability to generate their own events (for example, a timeout event). Each handler class gets its own event space, so events from different classes will not mix. Within each event space, the event objects themselves also have a source ID to identify which handler emitted it. This architecture places additional demands on planters and producer delegates.

Separators must be able to "classify" events (in addition to external messages) and return the same number



which results in the event emitter (because we don't want to create a thread for that event; for the event emitter itself to handle). The producer delegate must also pass the appropriate event space to the Handler class (since each Handler class receives a unique externally generated event space).

For everything to work properly, the same event space must be provided to the planter and its companion producer delegate. And each pair (seeder, delegate-producer)

should receive a globally unique event space. pave_event_space()

enforces these two conditions, basically fixes some additional operations and parameters on per_chat_id()

and create_open()

and ensures they are consistent.

Deeper still

How exactly does the "fix" happen? Why am I forcing you to do pave_event_space()(...)

instead of being more direct pave_event_space(...)

?

First, recall that our ultimate goal is to have a 2-tuple (per_chat_id(), create_open(MessageCounter, ...))

. For a "patch", this usually means (1) adding additional operations to per_chat_id()

and (2) adding additional parameters to the call create_open(... more arguments here ...)

. This means that I cannot allow the user to call create_open(...)

directly, because once it is called, I cannot insert additional parameters. I need a more abstract construct where the user specifies create_open

, but the call is create_open(...)

actually made by me.

Imagine a function with a name pair

that is signed pair(per_chat_id(), create_open, ...) -> (per_chat_id(), create_open(...))

. In other words, it passes the first argument as the first element of the tuple and creates the second element of the tuple, making the actual call create_open(...)

with the remaining arguments.

Now it reaches the point where I cannot explain the source code in words (I thought for 30 minutes). The pseudocode pave_event_space

looks like this:

def pave_event_space(fn=pair):
    def p(s, d, *args, **kwargs):
        return fn(append_event_space_seeder(s), 
                  d, *args, event_space=event_space, **kwargs)
    return p

      

It takes a function pair

and returns a pair

-like function (signature, identical pair

), but with a more complex planter and more parameters marked with. This is what I mean by "fix".

pave_event_space

- the most common "patcher". Other patches include include_callback_query_chat_id

and intercept_callback_query_origin

. They all do the same thing: do a function pair

, return a different function pair

, with a more complex planter and more parameters labeled. Since the input and output are similar, they can be mapped to apply multiple patches. If you look at the callback examples , you will see something like this:

bot = DelegatorBot(TOKEN, [
    include_callback_query_chat_id(
        pave_event_space())(
            per_chat_id(), create_open, Lover, timeout=10),
])

      

It breaks up the event space stuff, then fixes the callback request stuff to enable planter ( per_chat_id()

) and handler ( Lover

) to work together.

What can I say now. Hope this sheds some light on the code. Good luck.

+7


source







All Articles