Short answer
Yes, pave_event_space()
returns a function. Let's call that fn
. fn
is then invoked with fn(per_chat_id(), create_open, ...)
, which returns a 2-tuple (seeder function, delegate-producing function)
.
If you want to study the code further, this short answer probably is not very helpful ...
Longer answer
To understand what pave_event_space()
does and what that series of arguments means, we have to go back to basics and understand what DelegatorBot
accepts as arguments.
DelegatorBot
's constructor is explained here. Simply put, it accepts a list of 2-tuples (seeder function, delegate-producing function)
. To reduce verbosity, I am going to call the first element seeder and the second element delegate-producer.
A seeder has this signature seeder(msg) -> number
. For every message received, seeder(msg)
gets called to produce a number
. If that number
is new, the companion delegate-producer (the one that shares the same tuple with the seeder) will get called to produce a thread, which is used to handle the new message. If that number
has been occupied by a running thread, nothing is done. In essence, the seeder "categorizes" the message. It spawns a new thread if it sees a message belong to a new "category".
A delegate-producer has this signature producer(cls, *args, **kwargs) -> Thread
. It calls cls(*args, **kwargs)
to instantiate a handler object (MessageCounter
in your case) and wrap it in a thread, so the handler's methods are executed independently.
(Note: In reality, a seeder does not necessarily returns a number
and a delegate-producer does not necessarily returns a Thread
. I have simplified above for clarity. See the reference for a full explanation.)
In earlier days of telepot, a DelegatorBot
was usually made by supplying a seeder and a delegate-producer transparently:
bot = DelegatorBot(TOKEN, [
(per_chat_id(), create_open(MessageCounter, ...))])
Later, I added to handlers (e.g. ChatHandler
) a capability to generate its own events (say, a timeout event). Each class of handlers get their own event space, so different classes' events won't mix. Within each event space, the event objects themselves also have a source id to identify which handler has emitted it. This architecture puts some extra requirements on seeders and delegate-producers.
Seeders have to be able to "categorize" events (in additional to external messages) and returns the same number
that leads to the event emitter (because we don't want to spawn a thread for this event; it's supposed to be handled by the event emitter itself). Delegate-producers also have to pass the appropriate event space to the Handler class (because each Handler class gets a unique event space, generated externally).
For everything to work properly, the same event space has to be supplied to the seeder and its companion delegate-producer. And every pair of (seeder, delegate-producer)
has to get a globally unique event space. pave_event_space()
ensures these two conditions, basically patches some extra operations and parameters onto per_chat_id()
and create_open()
and making sure they are consistent.
Deeper still
Exactly how the "patching" is done? Why do I make you do pave_event_space()(...)
instead of the more straight-forward pave_event_space(...)
?
First, recall that our ultimate goal is to have a 2-tuple (per_chat_id(), create_open(MessageCounter, ...))
. To "patch" it usually means (1) appending some extra operations to per_chat_id()
, and (2) inserting some extra parameters to the call create_open(... more arguments here ...)
. That means I cannot let the user call create_open(...)
directly because, once it is called, I cannot insert extra parameters. I need a more abstract construct in which the user specifies create_open
but the call create_open(...)
is actually made by me.
Imagine a function named pair
, whose signature being pair(per_chat_id(), create_open, ...) -> (per_chat_id(), create_open(...))
. In other words, it passes the first argument as the first tuple element, and creates the second tuple element by making an actual call to create_open(...)
with remaining arguments.
Now, it reaches a point where I am unable to explain source code in words (I have been thinking for 30 minutes). The pseudo-code of 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 the function pair
, and returns a pair
-like function (signature identical to pair
), but with a more complex seeder and more parameters tagged on. That's what I meant by "patching".
pave_event_space
is the most often-seen "patcher". Other patchers include include_callback_query_chat_id
and intercept_callback_query_origin
. They all do basically the same kind of things: takes a pair
-like function, returns another pair
-like function, with a more complex seeder and more parameters tagged on. Because the input and output are alike, they can be chained to apply multiple patches. If you look into 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 patches event space stuff, then patches callback query stuff, to enable the seeder (per_chat_id()
) and handler (Lover
) to work cohesively.
That's all I can say for now. I hope this throws some light on the code. Good luck.