From c184060d1180ef553a4a81a89980842db33f756a Mon Sep 17 00:00:00 2001 From: holybiber Date: Mon, 15 Jun 2026 01:01:45 +0200 Subject: [PATCH] Add modules= config option for ResourcesBot. Allow default post-processing modules to be set in config.ini while keeping -m as a command-line override. Co-authored-by: Cursor --- config.example.ini | 4 ++ pywikitools/resourcesbot/bot.py | 37 ++++++++++++++++++ pywikitools/test/test_resourcesbot.py | 56 ++++++++++++++++++++++++++- resourcesbot.py | 30 +++++++------- 4 files changed, 112 insertions(+), 15 deletions(-) diff --git a/config.example.ini b/config.example.ini index 32eaa30..4c248e9 100644 --- a/config.example.ini +++ b/config.example.ini @@ -56,6 +56,10 @@ username = ResourcesBot password = MySecretPassword # resourcesbot writes language reports to [Paths:languagereports] +# Post-processing modules to run (abbreviations, space-separated). +# Use "all" to run every module (default). Overridden by -m on the command line. +# Run `python3 resourcesbot.py -h` to see available module abbreviations. +modules = all # Optionally: log to files (will be relative to path defined in [Paths:logs] ) # Three different verbosity levels (warning, info, debug) logfile = resourcesbot.log diff --git a/pywikitools/resourcesbot/bot.py b/pywikitools/resourcesbot/bot.py index 131f7ac..383cc03 100755 --- a/pywikitools/resourcesbot/bot.py +++ b/pywikitools/resourcesbot/bot.py @@ -57,6 +57,43 @@ def load_module(module_name: str) -> Callable: raise RuntimeError(f"Couldn't load module {module_name}. Giving up") +def build_module_choices() -> Dict[str, str]: + """Return module abbreviations mapped to full module names.""" + modules: Dict[str, str] = {} + for selected_module in AVAILABLE_MODULES: + module = load_module(selected_module) + modules[module.abbreviation()] = selected_module + return modules + + +def resolve_run_modules( + modules: Dict[str, str], + config: ConfigParser, + cli_modules: Optional[List[str]] = None, +) -> list[str]: + """ + Resolve which post-processing modules to run. + + Command-line ``-m`` overrides ``[resourcesbot] modules`` in config.ini. + """ + if cli_modules is not None: + return [modules[abbr] for abbr in cli_modules] + + config_value = config.get("resourcesbot", "modules", fallback="all").strip() + if config_value.lower() == "all": + return AVAILABLE_MODULES + + selected = config_value.split() + unknown = [abbr for abbr in selected if abbr not in modules] + if unknown: + available = ", ".join(sorted(modules.keys())) + raise ValueError( + "Unknown module(s) in config.ini [resourcesbot] modules: " + f"{', '.join(unknown)}. Available: {available}" + ) + return [modules[abbr] for abbr in selected] + + class ResourcesBot: """Contains all the logic of our bot""" diff --git a/pywikitools/test/test_resourcesbot.py b/pywikitools/test/test_resourcesbot.py index c8103fb..7b781c5 100644 --- a/pywikitools/test/test_resourcesbot.py +++ b/pywikitools/test/test_resourcesbot.py @@ -14,7 +14,13 @@ import pywikibot -from pywikitools.resourcesbot.bot import ResourcesBot, load_module +from pywikitools.resourcesbot.bot import ( + AVAILABLE_MODULES, + ResourcesBot, + build_module_choices, + load_module, + resolve_run_modules, +) from pywikitools.resourcesbot.data_structures import TranslationProgress, WorksheetInfo from pywikitools.test.test_data_structures import TEST_PROGRESS, TEST_TIME, TEST_URL @@ -341,5 +347,53 @@ def factory(*args, **kwargs): # TODO: test_run_with_limit_lang +class TestResolveRunModules(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.modules = build_module_choices() + + def _config_with_modules(self, modules_value: str) -> ConfigParser: + config = ConfigParser() + config.read_dict( + { + "resourcesbot": { + "site": "test", + "username": "TestBotName", + "modules": modules_value, + } + } + ) + return config + + def test_default_is_all_modules(self): + config = ConfigParser() + config.read_dict({"resourcesbot": {"site": "test", "username": "TestBotName"}}) + self.assertEqual(resolve_run_modules(self.modules, config), AVAILABLE_MODULES) + + def test_config_all(self): + config = self._config_with_modules("all") + self.assertEqual(resolve_run_modules(self.modules, config), AVAILABLE_MODULES) + + def test_config_specific_modules(self): + config = self._config_with_modules("list report") + self.assertEqual( + resolve_run_modules(self.modules, config), + ["write_lists", "write_report"], + ) + + def test_cli_overrides_config(self): + config = self._config_with_modules("list") + self.assertEqual( + resolve_run_modules(self.modules, config, cli_modules=["report"]), + ["write_report"], + ) + + def test_config_unknown_module_raises(self): + config = self._config_with_modules("list unknown") + with self.assertRaises(ValueError) as context: + resolve_run_modules(self.modules, config) + self.assertIn("unknown", str(context.exception)) + + if __name__ == "__main__": unittest.main() diff --git a/resourcesbot.py b/resourcesbot.py index cbf050d..84f330f 100644 --- a/resourcesbot.py +++ b/resourcesbot.py @@ -65,9 +65,14 @@ import sys import traceback from configparser import ConfigParser -from typing import Dict, List +from typing import List -from pywikitools.resourcesbot.bot import AVAILABLE_MODULES, ResourcesBot, load_module +from pywikitools.resourcesbot.bot import ( + ResourcesBot, + build_module_choices, + load_module, + resolve_run_modules, +) def parse_arguments() -> ResourcesBot: @@ -85,16 +90,17 @@ def parse_arguments() -> ResourcesBot: log_levels: List[str] = ["debug", "info", "warning", "error"] rewrite_options: List[str] = ["all", "json", "summary"] - modules: Dict[str, str] = {} # abbreviation -> full name + modules = build_module_choices() modules_help = "Select the modules to be run. Available options are:\n" - # Read module information from the module classes - for selected_module in AVAILABLE_MODULES: + for abbr, selected_module in modules.items(): module = load_module(selected_module) - modules_help += f" - {module.abbreviation()}: {module.help_summary()}\n" - modules[module.abbreviation()] = selected_module + modules_help += f" - {abbr}: {module.help_summary()}\n" if module.can_be_rewritten(): - rewrite_options.append(module.abbreviation()) - modules_help += "Default: run all modules" + rewrite_options.append(abbr) + modules_help += ( + "Default: all modules, or set [resourcesbot] modules= in config.ini " + "(overridden by -m)" + ) parser.add_argument( "--read-from-cache", @@ -133,11 +139,7 @@ def parse_arguments() -> ResourcesBot: assert isinstance(numeric_level, int) set_loglevel(config, numeric_level) - # Map abbreviations to full module names - if args.m is None: - run_modules = AVAILABLE_MODULES - else: - run_modules = [modules[abbr] for abbr in args.m] + run_modules = resolve_run_modules(modules, config, args.m) return ResourcesBot( config=config,