diff --git a/zulip_bots/zulip_bots/finder.py b/zulip_bots/zulip_bots/finder.py index 41cba98..0f8ee88 100644 --- a/zulip_bots/zulip_bots/finder.py +++ b/zulip_bots/zulip_bots/finder.py @@ -32,7 +32,7 @@ class DuplicateRegisteredBotName(Exception): pass -def import_module_from_zulip_bot_registry(name: str) -> Optional[ModuleType]: +def import_module_from_zulip_bot_registry(name: str) -> Tuple[str, Optional[ModuleType]]: # Prior to Python 3.10, calling importlib.metadata.entry_points returns a # SelectableGroups object when no parameters is given. Currently we use # the importlib_metadata library for compatibility, but we need to migrate @@ -42,12 +42,38 @@ def import_module_from_zulip_bot_registry(name: str) -> Optional[ModuleType]: matching_bots = [bot for bot in registered_bots if bot.name == name] if len(matching_bots) == 1: # Unique matching entrypoint - return matching_bots[0].load() + """We expect external bots to be registered using entry_points in the + group "zulip_bots.registry", where the name of the entry point should + match the name of the module containing the bot handler and the value + of it should be the package containing the bot handler module. + + E.g, an Python package for a bot called "packaged_bot" should have an + `entry_points` setup like the following: + + setup( + ... + entry_points={ + "zulip_bots.registry":[ + "packaged_bot=packaged_bot.packaged_bot" + ] + } + ... + ) + """ + bot = matching_bots[0] + bot_name = bot.name + bot_module = bot.load() + bot_version = bot_module.__version__ + + if bot_version is not None: + return f"{bot_name}: {bot_version}", bot_module + else: + return f"editable package: {bot_name}", bot_module if len(matching_bots) > 1: raise DuplicateRegisteredBotName(name) - return None # no matches in registry + return "", None # no matches in registry def resolve_bot_path(name: str) -> Optional[Tuple[Path, str]]: diff --git a/zulip_bots/zulip_bots/lib.py b/zulip_bots/zulip_bots/lib.py index d6d79f7..1e526d8 100644 --- a/zulip_bots/zulip_bots/lib.py +++ b/zulip_bots/zulip_bots/lib.py @@ -443,6 +443,7 @@ def run_message_handler_for_bot( config_file: str, bot_config_file: str, bot_name: str, + bot_source: str, ) -> Any: """ lib_module is of type Any, since it can contain any bot's @@ -473,7 +474,7 @@ def run_message_handler_for_bot( message_handler = prepare_message_handler(bot_name, restricted_client, lib_module) if not quiet: - print("Running {} Bot:".format(bot_details["name"])) + print("Running {} Bot (from {}):".format(bot_details["name"], bot_source)) if bot_details["description"] != "": print("\n\t{}".format(bot_details["description"])) if hasattr(message_handler, "usage"): diff --git a/zulip_bots/zulip_bots/run.py b/zulip_bots/zulip_bots/run.py index d0abc0f..e269223 100755 --- a/zulip_bots/zulip_bots/run.py +++ b/zulip_bots/zulip_bots/run.py @@ -118,7 +118,7 @@ def main() -> None: if args.registry: try: - lib_module = finder.import_module_from_zulip_bot_registry(args.bot) + bot_source, lib_module = finder.import_module_from_zulip_bot_registry(args.bot) except finder.DuplicateRegisteredBotName as error: print( f'ERROR: Found duplicate entries for "{error}" in zulip bots registry.\n' @@ -151,10 +151,12 @@ def main() -> None: ) print(dep_err_msg.format(bot_name=bot_name, deps_list=deps_list)) sys.exit(1) + bot_source = "source" else: lib_module = finder.import_module_by_name(args.bot) if lib_module: bot_name = lib_module.__name__ + bot_source = "named module" if args.provision: print("ERROR: Could not load bot's module for '{}'. Exiting now.") sys.exit(1) @@ -179,6 +181,7 @@ def main() -> None: bot_config_file=args.bot_config_file, quiet=args.quiet, bot_name=bot_name, + bot_source=bot_source, ) except NoBotConfigException: print( diff --git a/zulip_bots/zulip_bots/tests/test_lib.py b/zulip_bots/zulip_bots/tests/test_lib.py index e60e77b..e83ba54 100644 --- a/zulip_bots/zulip_bots/tests/test_lib.py +++ b/zulip_bots/zulip_bots/tests/test_lib.py @@ -197,6 +197,7 @@ class LibTest(TestCase): config_file=None, bot_config_file=None, bot_name="testbot", + bot_source="bot code location", ) def test_upload_file(self): diff --git a/zulip_bots/zulip_bots/tests/test_run.py b/zulip_bots/zulip_bots/tests/test_run.py index ffcf9e0..a549c30 100644 --- a/zulip_bots/zulip_bots/tests/test_run.py +++ b/zulip_bots/zulip_bots/tests/test_run.py @@ -2,13 +2,13 @@ import os import sys import unittest -from importlib.metadata import EntryPoint from pathlib import Path from typing import Optional from unittest import TestCase, mock -from unittest.mock import patch +from unittest.mock import MagicMock, patch import zulip_bots.run +from zulip_bots.finder import metadata from zulip_bots.lib import extract_query_without_mention @@ -16,7 +16,10 @@ class TestDefaultArguments(TestCase): our_dir = os.path.dirname(__file__) path_to_bot = os.path.abspath(os.path.join(our_dir, "../bots/giphy/giphy.py")) - packaged_bot_entrypoint = EntryPoint("packaged_bot", "module_name", "zulip_bots.registry") + packaged_bot_module = MagicMock(__version__="1.0.0") + packaged_bot_entrypoint = metadata.EntryPoint( + "packaged_bot", "module_name", "zulip_bots.registry" + ) @patch("sys.argv", ["zulip-run-bot", "giphy", "--config-file", "/foo/bar/baz.conf"]) @patch("zulip_bots.run.run_message_handler_for_bot") @@ -31,6 +34,7 @@ class TestDefaultArguments(TestCase): config_file="/foo/bar/baz.conf", bot_config_file=None, lib_module=mock.ANY, + bot_source="source", quiet=False, ) @@ -47,6 +51,7 @@ class TestDefaultArguments(TestCase): config_file="/foo/bar/baz.conf", bot_config_file=None, lib_module=mock.ANY, + bot_source="source", quiet=False, ) @@ -58,7 +63,8 @@ class TestDefaultArguments(TestCase): self, mock_run_message_handler_for_bot: mock.Mock ) -> None: with patch("zulip_bots.run.exit_gracefully_if_zulip_config_is_missing"), patch( - "zulip_bots.finder.metadata.EntryPoint.load" + "zulip_bots.finder.metadata.EntryPoint.load", + return_value=self.packaged_bot_module, ), patch( "zulip_bots.finder.metadata.entry_points", return_value=(self.packaged_bot_entrypoint,), @@ -70,6 +76,7 @@ class TestDefaultArguments(TestCase): config_file="/foo/bar/baz.conf", bot_config_file=None, lib_module=mock.ANY, + bot_source="packaged_bot: 1.0.0", quiet=False, )