Conversation
622dbbe to
8865fb0
Compare
For ease of use, Sopel provides a SopelWrapper to plugin callable so
they can use ``bot.say(message)`` without having to specify the
destination: the wrapper will look at the trigger's sender and send the
message back to it (be it a channel or a nick).
However, this has the disavantage of having to 1. maintain a separate
class with dunder methods (boo, magic!), and 2. it makes static typing
more complex.
Welcome Python 3.7 and its new built-in module: contextvars. This
wonderful utility module provides a thread-safe and asyncio-safe object
that can have a context specific value.
Sopel now uses this feature to bind itself to:
* a trigger that matches a rule
* and the triggered rule
so now it can be used instead of a SopelWrapper instance.
The biggest changes are on ``call_rule``, which doesn't require anymore
a SopelWrapper instance, and on the tests, in particular on the trigger
factory's wrapped method, which now returns a Sopel instance.
The second side-effect on test is that now it is not possible to bind a
trigger multiple times on a test bot, and tests have to rely on
Sopel.sopel_wrapper context manager (as Sopel.call_rule does), which
requires a Rule object. This is not perfect, albeit manageable.
In the future, it would be nice to have a better testing utility, maybe
a context manager to do someething like:
```
def some_test(mockbot, triggerfactory):
with triggerfactory.wraps(mockbot, "PRIVMSG #channel Nick :text") as wrapped:
wrapped.say('something')
```
Wouldn't it be cool?
8865fb0 to
ccbcfa7
Compare
|
@SnoopJ @dgw I'd love to have your input on the feature itself, i.e. the possibility to get rid of SopelWrapper. The code probably has its flaw, and I definitively want to write more documentation. But first, I'd really like to know if you are OK with the idea behind this PR. I can vet for contextvar as a pretty safe way to handle that. However, I'm not sure I really like how to handle the state of the reset token itself, and maybe I'd like something easier for tests. Making the tests easier is something that can be achieved with better factories as well, so there are various options here! |
SnoopJ
left a comment
There was a problem hiding this comment.
Not a lot to say about this WIP except that this is exactly the kind of thing ContextVar is for, so I'm in favor of it, especially if it means killing off the SopelWrapper class.
I asked on IRC if this introduces any concerns with thread safety, and the short version of the ensuing discussion is that this changeset doesn't introduce any new thread safety issues, but it doesn't resolve existing ones either.
| def sopel_wrapper( | ||
| self, | ||
| trigger: Trigger, | ||
| rule: plugin_rules.AbstractRule, | ||
| ) -> Generator[Sopel, None, None]: | ||
| """Context manager to bind and unbind the bot to a trigger and a rule. |
There was a problem hiding this comment.
I have mixed feelings about the name sopel_wrapper. On one hand, it matches the old SopelWrapper API, but on the other, it seems like there could be a name that better communicates that we're binding to a particular combination of trigger and rule. Is there a Sopel-ese word for that pairing? If so, maybe bind_thatword() would be a good name. My intuition would be bind_event() but that name is probably misleading.
There was a problem hiding this comment.
Oh yes! I should have added a "TODO: find a good name", because I don't like it either! Something like bind_context or wraps_context would probably be better I guess? Or maybe bind_response? I used sopel_wrapper for exactly the reason you talk about (it matches SopelWrapper), and I also think it doesn't properly communicate what we want to achieve with that.
There was a problem hiding this comment.
Other names:
wraps_messagewraps_trigger_and_rule- just
wraps(because why not?)
Description
(based on #2441 for simplicity)
For ease of use, Sopel provides a SopelWrapper to plugin callable so they can use
bot.say(message)without having to specify the destination: the wrapper will look at the trigger's sender and send the message back to it (be it a channel or a nick).However, this has the disavantage of having to 1. maintain a separate class with dunder methods (boo, magic!), and 2. it makes static typing more complex.
Welcome Python 3.7 and its new built-in module: contextvars. This wonderful utility module provides a thread-safe and asyncio-safe object that can have a context specific value.
Sopel now uses this feature to bind itself to:
and can be used in place of a
SopelWrapperinstance.The biggest change are on
call_rule, which doesn't require anymore aSopelWrapperinstance, and on the tests, in particular on the trigger factory'swrappedmethod, which now returns aSopelinstance.The second side-effect on test is that now it is not possible to bind a trigger multiple times on a test bot, and tests have to rely on
Sopel.sopel_wrappercontext manager (asSopel.call_ruledoes), which requires a Rule object. This is not perfect, albeit manageable.I've updated the documentation a bit to reflect that, however I think more should be done to explain all that. The change that matters is in
anatomy.rst, that makes this possible:Checklist
make qa(runsmake qualityandmake test)