Xqt has submitted this change. (
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/789897 )
Change subject: [IMPR] Print counter statistic for all counters
......................................................................
[IMPR] Print counter statistic for all counters
BaseBot provides a counter which holds 'read', 'write' and 'skip'
by default but other counters can be added easily.
- rewrite exit() method that all counter values are printed
- increase 'read' counter before processing the page to add the 'read'
counter first. BotPage.counter keeps the order of insertion (with
Python 3.6+; the order of Python 3.5 is not deterministic)
- add tests accordingly
- add counters in touch.py when a page is touched or purged
- use 'upload' counter in specialbots/_upload.py
- Update BaseBot documentation
Bug: T307834
Change-Id: I567bae073e49eb3bde083b82a30e9f2a76044950
---
M docs/api_ref/pywikibot.rst
M pywikibot/bot.py
M pywikibot/specialbots/_upload.py
M scripts/touch.py
M tests/bot_tests.py
5 files changed, 80 insertions(+), 59 deletions(-)
Approvals:
Xqt: Verified; Looks good to me, approved
diff --git a/docs/api_ref/pywikibot.rst b/docs/api_ref/pywikibot.rst
index 02bd83a..cd8f47c 100644
--- a/docs/api_ref/pywikibot.rst
+++ b/docs/api_ref/pywikibot.rst
@@ -29,6 +29,7 @@
--------------------
.. automodule:: pywikibot.bot
+ :member-order: bysource
pywikibot.bot\_choice module
----------------------------
diff --git a/pywikibot/bot.py b/pywikibot/bot.py
index 274e4bf..e343643 100644
--- a/pywikibot/bot.py
+++ b/pywikibot/bot.py
@@ -1174,16 +1174,14 @@
'Option opt.bar is 4711'
"""
- # Handler configuration.
- # Only the keys of the dict can be passed as init options
- # The values are the default values
- # Overwrite this in subclasses!
-
available_options = {} # type: Dict[str, Any]
+ """ Handler configuration attribute.
+ Only the keys of the dict can be passed as `__init__` options.
+ The values are the default values. Overwrite this in subclasses!
+ """
def __init__(self, **kwargs: Any) -> None:
- """
- Only accept options defined in available_options.
+ """Only accept options defined in available_options.
:param kwargs: bot options
"""
@@ -1206,8 +1204,10 @@
class BaseBot(OptionHandler):
- """
- Generic Bot to be subclassed.
+ """Generic Bot to be subclassed.
+
+ Only accepts `generator` and options defined in
+ :attr:`available_options`.
This class provides a :meth:`run` method for basic processing of a
generator one page at a time.
@@ -1225,13 +1225,12 @@
properties.
If the subclass does not set a generator, or does not override
- :meth:`treat` or :meth:`run`, NotImplementedError is raised.
+ :meth:`treat` or :meth:`run`, `NotImplementedError` is raised.
For bot options handling refer :class:`OptionHandler` class above.
.. versionchanged:: 7.0
- A counter attribute is provided which is a `collections.Counter`;
- The default counters are 'read', 'write' and 'skip'.
+ A :attr:`counter` instance variable is provided.
"""
use_disambigs = None # type: Optional[bool]
@@ -1250,18 +1249,14 @@
.. versionadded:: 7.2
"""
- # Handler configuration.
- # The values are the default values
- # Extend this in subclasses!
-
available_options = {
'always': False, # By default ask for confirmation when putting a page
}
update_options = {} # type: Dict[str, Any]
- """update_options can be used to update available_options;
+ """`update_options` can be used to update :attr:`available_options`;
do not use it if the bot class is to be derived but use
- self.available_options.update(<dict>) initializer in such case.
+ `self.available_options.update(<dict>)` initializer in such case.
.. versionadded:: 6.4
"""
@@ -1269,7 +1264,7 @@
_current_page = None # type: Optional[pywikibot.page.BasePage]
def __init__(self, **kwargs: Any) -> None:
- """Only accept 'generator' and options defined in available_options.
+ """Initializer.
:param kwargs: bot options
:keyword generator: a :attr:`generator` processed by :meth:`run` method
@@ -1279,14 +1274,26 @@
pywikibot.warn('{} has a generator already. Ignoring argument.'
.format(self.__class__.__name__))
else:
- #: generator processed by :meth:`run` method
+ #: instance variable to hold the generator processed by
+ #: :meth:`run` method
self.generator = kwargs.pop('generator')
self.available_options.update(self.update_options)
super().__init__(**kwargs)
- self.counter = Counter()
+ self.counter = Counter() # type: Counter
+ """Instance variable which holds counters. The default counters
+ are 'read', 'write' and 'skip'. You can use your own counters like::
+
+ self.counter['delete'] += 1
+
+ .. versionadded:: 7.0
+ .. versionchanged:: 7.3
+ Your additional counters are also printed during :meth:`exit`
+ """
+
self._generator_completed = False
+
#: instance variable to hold the default page type
self.treat_page_type = pywikibot.page.BasePage # type: Any
@@ -1378,21 +1385,18 @@
"""
Save a new revision of a page, with user confirmation as required.
- Print differences, ask user for confirmation,
- and puts the page if needed.
+ Print differences, ask user for confirmation, and puts the page
+ if needed.
Option used:
* 'always'
- Keyword args used:
-
- * 'asynchronous' - passed to page.save
- * 'summary' - passed to page.save
- * 'show_diff' - show changes between oldtext and newtext (enabled)
- * 'ignore_save_related_errors' - report and ignore (disabled)
- * 'ignore_server_errors' - report and ignore (disabled)
-
+ :keyword asynchronous: passed to page.save
+ :keyword summary: passed to page.save
+ :keyword show_diff: show changes between oldtext and newtext (enabled)
+ :keyword ignore_save_related_errors: report and ignore (disabled)
+ :keyword ignore_server_errors: report and ignore (disabled)
:return: whether the page was saved successfully
"""
if oldtext.rstrip() == newtext.rstrip():
@@ -1403,7 +1407,6 @@
self.current_page = page
show_diff = kwargs.pop('show_diff', True)
-
if show_diff:
pywikibot.showDiff(oldtext, newtext)
@@ -1419,6 +1422,8 @@
"""
Helper function to handle page save-related option error handling.
+ .. note:: Do no use it directly. Use :meth:`userPut` instead.
+
:param page: currently edited page
:param func: the function to call
:param args: passed to the function
@@ -1430,6 +1435,8 @@
page save will be reported and ignored (default: False)
:kwtype ignore_save_related_errors: bool
:return: whether the page was saved successfully
+
+ :meta public:
"""
if not self.user_confirm('Do you want to accept these changes?'):
return False
@@ -1473,13 +1480,17 @@
raise QuitKeyboardInterrupt
def exit(self) -> None:
- """
- Cleanup and exit processing.
+ """Cleanup and exit processing.
- Invoked when Bot.run() is finished.
- Prints treat and save counters and informs whether the script
+ Invoked when :meth:`run` is finished. Waits for pending threads,
+ prints counter statistics and informs whether the script
terminated gracefully or was halted by exception.
- May be overridden by subclasses.
+
+ .. note:: Do not overwrite it by subclasses; :meth:`teardown`
+ should be used instead.
+
+ .. versionchanged:: 7.3
+ Statistics are printed for all entries in :attr:`counter`
"""
self.teardown()
if hasattr(self, '_start_ts'):
@@ -1489,10 +1500,10 @@
# wait until pending threads finished but don't close the queue
pywikibot.stopme()
- pywikibot.output('\n{read} pages read'
- '\n{write} pages written'
- '\n{skip} pages skipped'
- .format_map(self.counter))
+ pywikibot.info()
+ for op, count in self.counter.items():
+ pywikibot.info('{} {} operation{}'
+ .format(count, op, 's' if count > 1 else ''))
if hasattr(self, '_start_ts'):
write_delta = pywikibot.Timestamp.now() - self._start_ts
@@ -1508,10 +1519,12 @@
if self.counter['read']:
pywikibot.output('Read operation time: {:.1f} seconds'
.format(read_seconds / self.counter['read']))
- if self.counter['write']:
- pywikibot.output(
- 'Write operation time: {:.1f} seconds'
- .format(write_seconds / self.counter['write']))
+
+ for op, count in self.counter.items():
+ if not count or op == 'read':
+ continue
+ pywikibot.info('{} operation time: {:.1f} seconds'
+ .format(op.capitalize(), write_seconds / count))
# exc_info contains exception from self.run() while terminating
exc_info = sys.exc_info()
@@ -1525,13 +1538,15 @@
def init_page(self, item: Any) -> 'pywikibot.page.BasePage':
"""Initialize a generator item before treating.
- Ensure that the result of init_page is always a pywikibot.Page object
- even when the generator returns something else.
+ Ensure that the result of `init_page` is always a
+ pywikibot.Page object or any other type given by the
+ :attr:`treat_page_type` even when the generator returns
+ something else.
- Also used to set the arrange the current site. This is called before
- skip_page and treat.
+ Also used to set the arrange the current site. This is called
+ before :meth:`skip_page` and :meth:`treat`.
- :param item: any item from self.generator
+ :param item: any item from :attr:`generator`
:return: return the page object to be processed further
"""
return item
@@ -1576,17 +1591,18 @@
.format(self.__class__.__name__))
def setup(self) -> None:
- """Some initial setup before run operation starts.
+ """Some initial setup before :meth:`run` operation starts.
This can be used for reading huge parts from life wiki or file
operation which is more than just initialize the instance.
- Invoked by run() before running through generator loop.
+ Invoked by :meth:`run` before running through :attr:`generator`
+ loop.
.. versionadded:: 3.0
"""
def teardown(self) -> None:
- """Some cleanups after run operation. Invoked by exit().
+ """Some cleanups after :meth:`run` operation. Invoked by :meth:`exit`.
.. versionadded:: 3.0
"""
@@ -1621,8 +1637,8 @@
continue
# Process the page
- self.treat(page)
self.counter['read'] += 1
+ self.treat(page)
self._generator_completed = True
except QuitKeyboardInterrupt:
diff --git a/pywikibot/specialbots/_upload.py b/pywikibot/specialbots/_upload.py
index 52409eb..743f38e 100644
--- a/pywikibot/specialbots/_upload.py
+++ b/pywikibot/specialbots/_upload.py
@@ -431,7 +431,7 @@
# No warning, upload complete.
pywikibot.output('Upload of {} successful.'
.format(filename))
- self.counter['write'] += 1
+ self.counter['upload'] += 1
return filename # data['filename']
pywikibot.output('Upload aborted.')
break
diff --git a/scripts/touch.py b/scripts/touch.py
index 292f8ad..48386dd 100755
--- a/scripts/touch.py
+++ b/scripts/touch.py
@@ -61,6 +61,8 @@
.format(page.title(as_link=True)))
except PageSaveRelatedError as e:
pywikibot.error('Page {} not saved:\n{}'.format(page, e.args))
+ else:
+ self.counter['touch'] += 1
class PurgeBot(MultipleSitesBot):
@@ -76,9 +78,11 @@
def treat(self, page) -> None:
"""Purge the given page."""
+ done = page.purge(**self.opt)
+ if done:
+ self.counter['purge'] += 1
pywikibot.output('Page {}{} purged'
- .format(page,
- '' if page.purge(**self.opt) else ' not'))
+ .format(page, '' if done else ' not'))
def main(*args: str) -> None:
diff --git a/tests/bot_tests.py b/tests/bot_tests.py
index 12c89de..8b51092 100755
--- a/tests/bot_tests.py
+++ b/tests/bot_tests.py
@@ -195,7 +195,7 @@
pywikibot.Page(self.en, 'Page 2'),
pywikibot.Page(self.de, 'Page 3')],
post_treat)
- self.bot.exit = self._exit(2, exception=ValueError)
+ self.bot.exit = self._exit(3, exception=ValueError)
with self.assertRaisesRegex(ValueError, 'Whatever'):
self.bot.run()
@@ -212,7 +212,7 @@
pywikibot.Page(self.de, 'Page 3')],
post_treat)
- self.bot.exit = self._exit(2, exception=None)
+ self.bot.exit = self._exit(3, exception=None)
self.bot.run()
--
To view, visit https://gerrit.wikimedia.org/r/c/pywikibot/core/+/789897
To unsubscribe, or for help writing mail filters, visit
https://gerrit.wikimedia.org/r/settings
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: I567bae073e49eb3bde083b82a30e9f2a76044950
Gerrit-Change-Number: 789897
Gerrit-PatchSet: 6
Gerrit-Owner: Xqt <[email protected]>
Gerrit-Reviewer: D3r1ck01 <[email protected]>
Gerrit-Reviewer: Xqt <[email protected]>
Gerrit-Reviewer: jenkins-bot
Gerrit-MessageType: merged
_______________________________________________
Pywikibot-commits mailing list -- [email protected]
To unsubscribe send an email to [email protected]