jenkins-bot has submitted this change. (
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1326256?usp=email )
Change subject: category: Extract bot construction
......................................................................
category: Extract bot construction
Move action-specific bot setup and dispatch into focused helpers while
preserving category option routing.
Bug: T434526
Change-Id: Ie768fe5ce95731f7dc9dd409f8aba73b108afc4e
---
M scripts/category.py
M tests/category_bot_tests.py
2 files changed, 384 insertions(+), 103 deletions(-)
Approvals:
Xqt: Looks good to me, approved
jenkins-bot: Verified
diff --git a/scripts/category.py b/scripts/category.py
index 8878707..ac9d451 100755
--- a/scripts/category.py
+++ b/scripts/category.py
@@ -169,6 +169,7 @@
from itertools import chain
from operator import methodcaller
from textwrap import fill
+from typing import Union
import pywikibot
from pywikibot import config, i18n, pagegenerators, textlib
@@ -1669,6 +1670,176 @@
return generator_factory
+def _build_add_bot(
+ settings: _CategorySettings,
+ generator_factory: pagegenerators.GeneratorFactory,
+) -> CategoryAddBot:
+ """Build a bot for the add action."""
+ generator = generator_factory.getCombinedGenerator(preload=True)
+ if not generator:
+ # default for backwards compatibility
+ generator_factory.handle_arg('-links')
+ generator = generator_factory.getCombinedGenerator(preload=True)
+ return CategoryAddBot(
+ generator,
+ newcat=settings.options.get('to'),
+ sort_by_last_name=settings.sort_by_last_name,
+ create=settings.create_pages,
+ comment=settings.summary,
+ follow_redirects=settings.follow_redirects,
+ )
+
+
+def _build_remove_bot(
+ settings: _CategorySettings,
+ generator_factory: pagegenerators.GeneratorFactory,
+) -> CategoryMoveRobot:
+ """Build a bot for the remove action."""
+ if 'from' not in settings.options:
+ settings.options['from'] = pywikibot.input(
+ 'Please enter the name of the category that should be removed:')
+ generator = generator_factory.getCombinedGenerator()
+ return CategoryMoveRobot(
+ oldcat=settings.options.get('from'),
+ batch=settings.batch,
+ comment=settings.summary,
+ inplace=settings.inplace,
+ delete_oldcat=settings.delete_empty_cat,
+ title_regex=settings.title_regex,
+ history=settings.history,
+ pagesonly=settings.pagesonly,
+ deletion_comment=settings.use_deletion_summary,
+ generator=generator,
+ )
+
+
+def _resolve_move_categories(settings: _CategorySettings) -> bool:
+ """Prompt until distinct move categories are available."""
+ while True:
+ if 'from' not in settings.options:
+ settings.options['from'] = pywikibot.input(
+ 'Please enter the old name of the category:')
+ if not settings.options['from']:
+ return False
+
+ if 'to' not in settings.options:
+ settings.options['to'] = pywikibot.input(
+ 'Please enter the new name of the category:')
+
+ if settings.options['from'] != settings.options['to']:
+ return True
+
+ pywikibot.error('-from and -to arguments are equal, please retry.')
+ del settings.options['from']
+ del settings.options['to']
+
+
+def _build_move_bot(
+ settings: _CategorySettings,
+ generator_factory: pagegenerators.GeneratorFactory,
+) -> CategoryMoveRobot | None:
+ """Build a bot for the move action."""
+ if not _resolve_move_categories(settings):
+ return None
+
+ if settings.use_deletion_summary:
+ deletion_comment = (
+ CategoryMoveRobot.DELETION_COMMENT_SAME_AS_EDIT_COMMENT
+ )
+ else:
+ deletion_comment = CategoryMoveRobot.DELETION_COMMENT_AUTOMATIC
+
+ generator = generator_factory.getCombinedGenerator()
+ return CategoryMoveRobot(
+ oldcat=settings.options.get('from'),
+ newcat=settings.options.get('to'),
+ batch=settings.batch,
+ comment=settings.summary,
+ inplace=settings.inplace,
+ delete_oldcat=settings.delete_empty_cat,
+ title_regex=settings.title_regex,
+ history=settings.history,
+ pagesonly=settings.pagesonly,
+ deletion_comment=deletion_comment,
+ wikibase=settings.wikibase,
+ allow_split=settings.allow_split,
+ move_together=settings.move_together,
+ keep_sortkey=settings.keep_sortkey,
+ generator=generator,
+ )
+
+
+def _build_tidy_bot(
+ settings: _CategorySettings,
+ generator_factory: pagegenerators.GeneratorFactory,
+ category_database: CategoryDatabase,
+) -> CategoryTidyRobot:
+ """Build a bot for the tidy action."""
+ return CategoryTidyRobot(
+ settings.options.get('from'), category_database,
+ generator_factory.namespaces, settings.summary)
+
+
+def _build_tree_bot(
+ settings: _CategorySettings,
+ category_database: CategoryDatabase,
+) -> CategoryTreeRobot:
+ """Build a bot for the tree action."""
+ return CategoryTreeRobot(
+ settings.options.get('from'), category_database,
+ settings.options.get('to'), max_depth=settings.depth)
+
+
+def _build_listify_bot(
+ settings: _CategorySettings,
+ generator_factory: pagegenerators.GeneratorFactory,
+) -> CategoryListifyRobot:
+ """Build a bot for the listify action."""
+ return CategoryListifyRobot(
+ settings.options.get('from'), settings.options.get('to'),
+ settings.summary, settings.append, settings.overwrite,
+ settings.showimages, talk_pages=settings.talkpages,
+ recurse=settings.options.get('recurse', False),
+ namespaces=generator_factory.namespaces, **settings.options)
+
+
+def _build_clean_bot(settings: _CategorySettings) -> CleanBot:
+ """Build a bot for the clean action."""
+ return CleanBot(**settings.options)
+
+
+_CategoryBot = Union[BaseBot, CategoryListifyRobot, CategoryTreeRobot]
+
+
+def _build_bot(
+ settings: _CategorySettings,
+ generator_factory: pagegenerators.GeneratorFactory | None,
+ category_database: CategoryDatabase,
+) -> _CategoryBot | None:
+ """Build the bot selected by the category action."""
+ if settings.action == 'add':
+ assert generator_factory is not None
+ return _build_add_bot(settings, generator_factory)
+ if settings.action == 'remove':
+ assert generator_factory is not None
+ return _build_remove_bot(settings, generator_factory)
+ if settings.action == 'move':
+ assert generator_factory is not None
+ return _build_move_bot(settings, generator_factory)
+ if settings.action == 'tidy':
+ assert generator_factory is not None
+ return _build_tidy_bot(
+ settings, generator_factory, category_database)
+ if settings.action == 'tree':
+ return _build_tree_bot(settings, category_database)
+ if settings.action == 'listify':
+ assert generator_factory is not None
+ return _build_listify_bot(settings, generator_factory)
+ if settings.action == 'clean':
+ return _build_clean_bot(settings)
+ return None
+
+
def main(*args: str) -> None:
"""Process command line arguments and invoke bot.
@@ -1681,110 +1852,19 @@
suggest_help(unknown_parameters=settings.unknown)
cat_db = CategoryDatabase(rebuild=settings.rebuild)
+ bot = _build_bot(settings, gen_factory, cat_db)
- if settings.action == 'add':
- assert gen_factory is not None
- gen = gen_factory.getCombinedGenerator(preload=True)
- if not gen:
- # default for backwards compatibility
- gen_factory.handle_arg('-links')
- gen = gen_factory.getCombinedGenerator(preload=True)
- bot = CategoryAddBot(gen,
- newcat=settings.options.get('to'),
- sort_by_last_name=settings.sort_by_last_name,
- create=settings.create_pages,
- comment=settings.summary,
- follow_redirects=settings.follow_redirects)
- elif settings.action == 'remove':
- assert gen_factory is not None
- if 'from' not in settings.options:
- settings.options['from'] = \
- pywikibot.input('Please enter the name of the '
- 'category that should be removed:')
- gen = gen_factory.getCombinedGenerator()
- bot = CategoryMoveRobot(oldcat=settings.options.get('from'),
- batch=settings.batch,
- comment=settings.summary,
- inplace=settings.inplace,
- delete_oldcat=settings.delete_empty_cat,
- title_regex=settings.title_regex,
- history=settings.history,
- pagesonly=settings.pagesonly,
- deletion_comment=(
- settings.use_deletion_summary),
- generator=gen)
- elif settings.action == 'move':
- assert gen_factory is not None
- while True:
- if 'from' not in settings.options:
- settings.options['from'] = pywikibot.input(
- 'Please enter the old name of the category:')
- if not settings.options['from']:
- return
+ if suggest_help(missing_action=not settings.action) or bot is None:
+ return
- if 'to' not in settings.options:
- settings.options['to'] = pywikibot.input(
- 'Please enter the new name of the category:')
-
- if settings.options['from'] != settings.options['to']:
- break
-
- pywikibot.error('-from and -to arguments are equal, please retry.')
- del settings.options['from']
- del settings.options['to']
-
- if settings.use_deletion_summary:
- deletion_comment = \
- CategoryMoveRobot.DELETION_COMMENT_SAME_AS_EDIT_COMMENT
- else:
- deletion_comment = CategoryMoveRobot.DELETION_COMMENT_AUTOMATIC
- gen = gen_factory.getCombinedGenerator()
- bot = CategoryMoveRobot(oldcat=settings.options.get('from'),
- newcat=settings.options.get('to'),
- batch=settings.batch,
- comment=settings.summary,
- inplace=settings.inplace,
- delete_oldcat=settings.delete_empty_cat,
- title_regex=settings.title_regex,
- history=settings.history,
- pagesonly=settings.pagesonly,
- deletion_comment=deletion_comment,
- wikibase=settings.wikibase,
- allow_split=settings.allow_split,
- move_together=settings.move_together,
- keep_sortkey=settings.keep_sortkey,
- generator=gen)
- elif settings.action == 'tidy':
- assert gen_factory is not None
- bot = CategoryTidyRobot(settings.options.get('from'), cat_db,
- gen_factory.namespaces, settings.summary)
- elif settings.action == 'tree':
- bot = CategoryTreeRobot(settings.options.get('from'), cat_db,
- settings.options.get('to'),
- max_depth=settings.depth)
- elif settings.action == 'listify':
- assert gen_factory is not None
- bot = CategoryListifyRobot(settings.options.get('from'),
- settings.options.get('to'),
- settings.summary, settings.append,
- settings.overwrite, settings.showimages,
- talk_pages=settings.talkpages,
- recurse=settings.options.get(
- 'recurse', False),
- namespaces=gen_factory.namespaces,
- **settings.options)
- elif settings.action == 'clean':
- bot = CleanBot(**settings.options)
-
- if not suggest_help(missing_action=not settings.action):
- pywikibot.Site().login()
- try:
- bot.run()
- except Error:
- pywikibot.exception('Fatal error:')
- finally:
- if cat_db:
- cat_db.dump()
+ pywikibot.Site().login()
+ try:
+ bot.run()
+ except Error:
+ pywikibot.exception('Fatal error:')
+ finally:
+ if cat_db:
+ cat_db.dump()
if __name__ == '__main__':
diff --git a/tests/category_bot_tests.py b/tests/category_bot_tests.py
index de56235..50d1221 100755
--- a/tests/category_bot_tests.py
+++ b/tests/category_bot_tests.py
@@ -9,7 +9,7 @@
import unittest
from contextlib import suppress
-from unittest.mock import Mock, patch
+from unittest.mock import Mock, call, patch
import pywikibot
from pywikibot.site import BaseSite
@@ -207,6 +207,207 @@
self.assertEqual(settings.unknown, ['-page:Example'])
+class TestCategoryBotConstruction(TestCase):
+
+ """Test construction of category action bots."""
+
+ net = False
+
+ @patch.object(category, 'CategoryAddBot')
+ def test_build_add_bot(self, bot_class) -> None:
+ """Test add bot construction and its default generator."""
+ generator = object()
+ generator_factory = Mock()
+ generator_factory.getCombinedGenerator.side_effect = [
+ None, generator]
+ settings = category._CategorySettings(
+ action='add',
+ options={'to': 'New'},
+ summary='summary',
+ sort_by_last_name=True,
+ create_pages=True,
+ follow_redirects=True,
+ )
+
+ result = category._build_add_bot(settings, generator_factory)
+
+ self.assertIs(result, bot_class.return_value)
+ self.assertEqual(
+ generator_factory.getCombinedGenerator.call_args_list,
+ [call(preload=True), call(preload=True)],
+ )
+ generator_factory.handle_arg.assert_called_once_with('-links')
+ bot_class.assert_called_once_with(
+ generator,
+ newcat='New',
+ sort_by_last_name=True,
+ create=True,
+ comment='summary',
+ follow_redirects=True,
+ )
+
+ @patch.object(category, 'CategoryMoveRobot')
+ @patch.object(category.pywikibot, 'input', return_value='Old')
+ def test_build_remove_bot(self, input_mock, bot_class) -> None:
+ """Test remove bot construction and its source prompt."""
+ generator = object()
+ generator_factory = Mock()
+ generator_factory.getCombinedGenerator.return_value = generator
+ settings = category._CategorySettings(
+ action='remove',
+ batch=True,
+ summary='summary',
+ inplace=True,
+ delete_empty_cat=False,
+ title_regex='title',
+ history=True,
+ pagesonly=True,
+ use_deletion_summary=False,
+ )
+
+ result = category._build_remove_bot(settings, generator_factory)
+
+ self.assertIs(result, bot_class.return_value)
+ input_mock.assert_called_once_with(
+ 'Please enter the name of the category that should be removed:')
+ bot_class.assert_called_once_with(
+ oldcat='Old',
+ batch=True,
+ comment='summary',
+ inplace=True,
+ delete_oldcat=False,
+ title_regex='title',
+ history=True,
+ pagesonly=True,
+ deletion_comment=False,
+ generator=generator,
+ )
+
+ @patch.object(category.pywikibot, 'error')
+ @patch.object(category.pywikibot, 'input', side_effect=['Old', 'New'])
+ def test_resolve_equal_move_categories(
+ self,
+ input_mock,
+ error_mock,
+ ) -> None:
+ """Test prompting again for equal move categories."""
+ settings = category._CategorySettings(
+ action='move', options={'from': 'Same', 'to': 'Same'})
+
+ result = category._resolve_move_categories(settings)
+
+ self.assertTrue(result)
+ self.assertEqual(settings.options, {'from': 'Old', 'to': 'New'})
+ error_mock.assert_called_once_with(
+ '-from and -to arguments are equal, please retry.')
+ self.assertEqual(input_mock.call_count, 2)
+
+ settings = category._CategorySettings(action='move')
+ with patch.object(category.pywikibot, 'input', return_value=''):
+ result = category._resolve_move_categories(settings)
+ self.assertFalse(result)
+
+ def test_build_move_bot(self) -> None:
+ """Test move bot construction."""
+ original_class = category.CategoryMoveRobot
+ bot = Mock()
+ bot_class = Mock(return_value=bot)
+ bot_class.DELETION_COMMENT_SAME_AS_EDIT_COMMENT = (
+ original_class.DELETION_COMMENT_SAME_AS_EDIT_COMMENT
+ )
+ bot_class.DELETION_COMMENT_AUTOMATIC = (
+ original_class.DELETION_COMMENT_AUTOMATIC
+ )
+ generator = object()
+ generator_factory = Mock()
+ generator_factory.getCombinedGenerator.return_value = generator
+ settings = category._CategorySettings(
+ action='move',
+ options={'from': 'Old', 'to': 'New'},
+ batch=True,
+ summary='summary',
+ inplace=True,
+ delete_empty_cat=False,
+ title_regex='title',
+ history=True,
+ pagesonly=True,
+ wikibase=False,
+ allow_split=True,
+ move_together=True,
+ keep_sortkey=True,
+ )
+
+ with patch.object(category, 'CategoryMoveRobot', bot_class):
+ result = category._build_move_bot(settings, generator_factory)
+
+ self.assertIs(result, bot)
+ bot_class.assert_called_once_with(
+ oldcat='Old',
+ newcat='New',
+ batch=True,
+ comment='summary',
+ inplace=True,
+ delete_oldcat=False,
+ title_regex='title',
+ history=True,
+ pagesonly=True,
+ deletion_comment=(
+ original_class.DELETION_COMMENT_SAME_AS_EDIT_COMMENT),
+ wikibase=False,
+ allow_split=True,
+ move_together=True,
+ keep_sortkey=True,
+ generator=generator,
+ )
+
+ bot_class.reset_mock()
+ settings.use_deletion_summary = False
+ result = category._build_move_bot(settings, generator_factory)
+
+ self.assertIs(result, bot)
+ self.assertEqual(
+ bot_class.call_args.kwargs['deletion_comment'],
+ original_class.DELETION_COMMENT_AUTOMATIC,
+ )
+
+ def test_build_bot_dispatch(self) -> None:
+ """Test dispatching every category action to its builder."""
+ generator_factory = Mock()
+ category_database = Mock()
+ builder_names = {
+ 'add': '_build_add_bot',
+ 'clean': '_build_clean_bot',
+ 'listify': '_build_listify_bot',
+ 'move': '_build_move_bot',
+ 'remove': '_build_remove_bot',
+ 'tidy': '_build_tidy_bot',
+ 'tree': '_build_tree_bot',
+ }
+
+ for action, builder_name in builder_names.items():
+ with (
+ self.subTest(action=action),
+ patch.object(category, builder_name) as builder,
+ ):
+ settings = category._CategorySettings(action=action)
+
+ result = category._build_bot(
+ settings, generator_factory, category_database)
+
+ self.assertIs(result, builder.return_value)
+ if action == 'tidy':
+ builder.assert_called_once_with(
+ settings, generator_factory, category_database)
+ elif action == 'tree':
+ builder.assert_called_once_with(
+ settings, category_database)
+ elif action in category._GENERATOR_ACTIONS:
+ builder.assert_called_once_with(
+ settings, generator_factory)
+ else:
+ builder.assert_called_once_with(settings)
+
+
if __name__ == '__main__':
with suppress(SystemExit):
unittest.main()
--
To view, visit
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1326256?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.wikimedia.org/r/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: Ie768fe5ce95731f7dc9dd409f8aba73b108afc4e
Gerrit-Change-Number: 1326256
Gerrit-PatchSet: 3
Gerrit-Owner: Mahveotm <[email protected]>
Gerrit-Reviewer: Xqt <[email protected]>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
Pywikibot-commits mailing list -- [email protected]
To unsubscribe send an email to [email protected]