Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement PluginManager class #212

Closed
wants to merge 15 commits into from
Closed

Conversation

unode
Copy link
Collaborator

@unode unode commented May 5, 2021

Fixes #165 and some of the issues discussed in Discord.

Highlights:

  • Introduces annotations={} in @listen_to - examples and better documentation to be added
  • Introduces mmpy_bot.plugins.base.PluginManager to allow customizing help messages across all enabled plugins.
  • Introduces driver.direct_message and driver.create_post(..., direct=bool) to simplify sending direct messages.
  • Introduces PluginManager.get_help as alternative to PluginManager.get_help_string. This interface should probably be delegated to all classes that implement get_help_string to avoid crossing interface boundaries in PluginManager.get_help but I didn't want to introduce more changes in this PR.

Changes:

  • @bot help and !help are no longer registered by default for every enabled plugin. Instead only @bot help is enabled and provided by PluginManager. Old behavior still available with Plugin(help_trigger=True, help_trigger_bang=True, direct_help=False), options also available to PluginManager().
  • @bot help doesn't currently make use of help text generated by click. I found this output to be overwhelming and very noisy, particularly when many commands or plugins are enabled.

Apologies for the noisy PR but when touching the docs where were several issues that made the linters unhappy.

@unode
Copy link
Collaborator Author

unode commented May 5, 2021

Any suggestion on how to workaround the type checking failures in PluginMixin?
I can get rid of it if we are ok with having that code duplicated.

@attzonko
Copy link
Owner

attzonko commented May 6, 2021

@unode thanks for putting in this effort to improve the bot help functionality in 2.x
Do you have a before and after screenshot to show how your changes affect the help seen by the user?
Do you need help resolving the linting/code climate issues?

@unode
Copy link
Collaborator Author

unode commented May 6, 2021

I'd like to push a few more changes in this direction to make the code more robust but wanted to get some early feedback if we are all on board with this direction.

Will try to add a screenshot of the default plugins. The default is likely going to be too lengthy so unlikely to fit one screen.

@unode
Copy link
Collaborator Author

unode commented May 6, 2021

Before

screenshot_2021-05-06_22-52-37_253198397

  • Notice the show more button on this one.
    screenshot_2021-05-06_22-52-56_948594996

After

screenshot_2021-05-06_22-53-34_338804572

@unode
Copy link
Collaborator Author

unode commented May 6, 2021

click functions. Currently the help message gets lost.
We'll be able to support them once we modify the help methods in all the relevant objects.

Copy link
Owner

@attzonko attzonko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general looks good to me I like the condensed version myself, and it is closer to what we had in 1.x

mmpy_bot/plugins/base.py Show resolved Hide resolved
Copy link
Collaborator

@jneeven jneeven left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me 👍 I think moving the Plugin management logic out of the EventHandler makes a lot of sense. I just have some questions and suggestions

mmpy_bot/bot.py Outdated Show resolved Hide resolved
Comment on lines +44 to +46
# PluginManager also has listeners for "help"
for matcher, functions in self.plug_manager.message_listeners.items():
self.message_listeners[matcher].extend(functions)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is how I already did it, but since any message listener needs to be defined on a Plugin and you've now created a nice place to handle all Plugin logic, maybe it makes more sense not to have a self.message_listeners on the EventHandler at all? We can simply forward any message to the PluginManager and it can then take the appropriate action itself.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried implementing this on my first try but there was some code that needed to access message_listeners when only EventHandler was available. Need to give it another try now that I understand the code better.

mmpy_bot/function.py Outdated Show resolved Hide resolved
Comment on lines +66 to +72
if help_trigger:
self.help = listen_to("^help$", needs_mention=True)(Plugin.help)
if help_trigger_bang:
if not help_trigger:
self.help = listen_to("^!help$")(Plugin.help)
else:
self.help = listen_to("^!help$")(self.help)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if help_trigger:
self.help = listen_to("^help$", needs_mention=True)(Plugin.help)
if help_trigger_bang:
if not help_trigger:
self.help = listen_to("^!help$")(Plugin.help)
else:
self.help = listen_to("^!help$")(self.help)
help_func = Plugin.help if (help_trigger_bang and help_trigger) else self.help
self.help = listen_to("^!help$", needs_mention=help_trigger)(help_func)

I have no idea what the distinction means here; just simplifying the logic

Copy link
Collaborator Author

@unode unode May 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your suggestion doesn't implement the same logic as mine but this code needs a better solution either way.
These conditionals are mostly there to allow the user to not register help or !help for every plugin. Doing it for every plugin makes the help output unnecessarily verbose.
For backwards compatibility I kept both behind independent boolean flags.

Comment on lines -78 to -90
async def call_function(
self,
function: Function,
event: EventWrapper,
groups: Optional[Sequence[str]] = [],
):
if function.is_coroutine:
await function(event, *groups) # type:ignore
else:
# By default, we use the global threadpool of the driver, but we could use
# a plugin-specific thread or process pool if we wanted.
self.driver.threadpool.add_task(function, event, *groups)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain this? This already had access to the driver, so what problem does moving it out of the class solve?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code duplication mostly. At the moment, this code needs to be used in Plugin and PluginManager due to how call_function is triggered in the driver. If we move the listeners out of the event handler class and make PluginManager less transparent this may no longer be necessary. I didn't get that far.

mmpy_bot/plugins/base.py Outdated Show resolved Hide resolved
Comment on lines +173 to +179
# This code is a bit hairy because the function signature of an
# instance is (message) not (self, message) causing failures later
if help_trigger:
self.help = listen_to("^help$", needs_mention=True)(Plugin.help)
if help_trigger_bang:
if not help_trigger:
self.help = listen_to("^!help$")(Plugin.help)
else:
self.help = listen_to("^!help$")(self.help)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this code identical to the code in Plugin? Why does it even need these arguments?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment above.

@unode unode force-pushed the plugin-manager branch 2 times, most recently from 3d55d58 to 00c9acd Compare May 28, 2021 00:04
@unode
Copy link
Collaborator Author

unode commented May 28, 2021

Clarifying a little more about the help and !help listeners in both Plugin and PluginManager.
In the original Plugin code, both behaviors are registered by default without any option for customization.
I prefer to have plugins only reacting to mentions so I started by optionally disabling the !help option.

However since both listeners were registered for every plugin, having multiple plugins meant that each had its own set of listeners and each would respond independently, sending a rather large wall of text to the user.

My solution, while trying to keep backwards compatibility, was to introduce two booleans help_trigger (register on mention) and help_trigger_bang (register on !help) to allow omitting help if desired.

If we are ok with breaking backwards compatibility, I would remove the help/!help listeners from Plugin and keep them only on PluginManager. This ensures that there's only one listener per bot instance providing aggregated help.

@attzonko attzonko closed this May 28, 2021
@attzonko attzonko deleted the branch attzonko:master May 28, 2021 16:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

How to include metadata annotations in decorated functions
3 participants