mirror of
https://github.com/agessaman/meshcore-bot.git
synced 2026-10-23 14:22:22 +00:00
refactor(commands): direct help call only when the method also accepts the message
Space bunny on d60e7cf1: operators may install their own command files inside modules/commands or modules/commands/alternatives (documented, and preserved across upgrades), so a class under modules. is not necessarily bundled. Such a command written as get_help_text(self) was treated as bundled, called with the message and raised, so help returned nothing where dev returned its text. help_text_for now calls once with the message only when the method comes from a class under modules. and its signature binds the message; otherwise, including when the signature can't be read, it uses dev's call-then-retry. New test: an operator command in the modules.commands namespace with get_help_text(self) gets its help.
This commit is contained in:
1 parent
dabb59b823
commit
f0649558e8
2 files changed
+36
-5
No files matched your search
@@ -4,6 +4,7 @@ Base command class for all MeshCore Bot commands
|
||||
Provides common functionality and interface for command implementations
|
||||
"""
|
||||
|
||||
import inspect
|
||||
import re
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Callable, Iterator
|
||||
@@ -71,17 +72,28 @@ def _defined_by_bundled_class(command: Any, method: Any) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _accepts_message(method: Any, message: Any) -> bool:
|
||||
"""Whether ``method(message)`` binds, per its signature (False when that can't be read)."""
|
||||
try:
|
||||
inspect.signature(method).bind(message)
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def help_text_for(command: Any, message: Any) -> str:
|
||||
"""``command.get_help_text(message)``, retrying without the message for local plugins.
|
||||
|
||||
Every bundled command's get_help_text takes the message, so when the
|
||||
method comes from a bundled class it is called once and an error from its
|
||||
body propagates. Anything else (a local plugin or subclass override, which
|
||||
may implement ``get_help_text(self)``, or any other callable) keeps the old
|
||||
contract: a TypeError from the message call is retried without the message.
|
||||
method comes from a class under ``modules.`` and its signature accepts the
|
||||
message, it is called once and an error from its body propagates. Anything
|
||||
else keeps the old contract, where a TypeError from the message call is
|
||||
retried without the message: local plugins and overrides, operator-installed
|
||||
commands inside modules/commands written as ``get_help_text(self)``, and
|
||||
any other callable.
|
||||
"""
|
||||
get_help_text = command.get_help_text
|
||||
if _defined_by_bundled_class(command, get_help_text):
|
||||
if _defined_by_bundled_class(command, get_help_text) and _accepts_message(get_help_text, message):
|
||||
return get_help_text(message)
|
||||
try:
|
||||
return get_help_text(message)
|
||||
|
||||
@@ -252,6 +252,25 @@ class TestGetSpecificHelp:
|
||||
cmd = HelpCommand(bot)
|
||||
cmd.get_specific_help("ping", Mock()) # must not raise
|
||||
|
||||
def test_operator_command_inside_modules_without_message_param_gets_help(self):
|
||||
"""An installed-only command file in modules/commands may use get_help_text(self)."""
|
||||
from modules.commands.base_command import BaseCommand
|
||||
|
||||
class OperatorWeatherCommand(BaseCommand):
|
||||
name = "operatorweather"
|
||||
|
||||
def get_help_text(self):
|
||||
return "operator help"
|
||||
|
||||
async def execute(self, message):
|
||||
return True
|
||||
|
||||
OperatorWeatherCommand.__module__ = "modules.commands.operatorweather"
|
||||
bot = _make_bot()
|
||||
bot.command_manager.commands = {"operatorweather": object.__new__(OperatorWeatherCommand)}
|
||||
cmd = HelpCommand(bot)
|
||||
cmd.get_specific_help("operatorweather", Mock()) # must not raise
|
||||
|
||||
def test_local_plugin_help_keeps_the_typeerror_retry(self):
|
||||
"""A local get_help_text(message=None) that raises TypeError is retried without it."""
|
||||
bot = _make_bot()
|
||||
|
||||
Reference in new issue
Block a user