jenkins-bot has submitted this change and it was merged.
Change subject: [FIX] Re-enable BlockEntry and MoveEntry methods ......................................................................
[FIX] Re-enable BlockEntry and MoveEntry methods
- added new methods for PatrolEntry and RightsEntry classes
- Parts of BlockEntry and MoveEntry are broken since MW 1.19 as the log entry specific data was accessed using log type while with MW 1.19 it's always 'params' - new generic hidden method _params() because the data key may depend on the entry type. - use data['params'] for newer mw versions else data[_expectedType] - flags must not be splitted, it is a list of flag strings now - data['expiry'] may not exists for infinite blocks. Return None here - isAutoblockRemoval could never be called as method. I removed it. - deprecate new_ns and new_title methods, use the newer names. - use target_ns instead of new_ns() - use target_page instead of new_title(). It was renamed because the method returns a page object - target_title just returns the title string.
Tests are added.
bug: T100424 bug: T99516 Change-Id: I610540df99fd6f5a77f0cfc8702555c2a082307a --- M pywikibot/logentries.py A tests/logentry_tests.py 2 files changed, 247 insertions(+), 36 deletions(-)
Approvals: John Vandenberg: Looks good to me, approved jenkins-bot: Verified
diff --git a/pywikibot/logentries.py b/pywikibot/logentries.py index c4919cb..238f0b2 100644 --- a/pywikibot/logentries.py +++ b/pywikibot/logentries.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Objects representing Mediawiki log entries.""" # -# (C) Pywikibot team, 2007-2013 +# (C) Pywikibot team, 2007-2015 # # Distributed under the terms of the MIT license. # @@ -10,8 +10,14 @@ __version__ = '$Id$' #
-from pywikibot.exceptions import Error +import sys + import pywikibot +from pywikibot.exceptions import Error +from pywikibot.tools import deprecated + +if sys.version_info[0] > 2: + basestring = (str, )
_logger = "wiki"
@@ -51,6 +57,22 @@ def __hash__(self): return self.logid()
+ @property + def _params(self): + """ + Additional data for some log entry types. + + @rtype: dict or None + """ + if 'params' in self.data: + return self.data['params'] + else: # try old mw style preceding mw 1.19 + try: + return self.data[self._expectedType] + except KeyError: + raise Error("action='%s': this log entry has no params details " + "for type %s." % (self.action(), self.type)) + def logid(self): return self.data['logid']
@@ -61,7 +83,12 @@ return self.data['ns']
def title(self): - """Page on which action was performed.""" + """ + Page on which action was performed. + + Note: title may be missing in data dict e.g. by oversight action to + hide the title. In that case a KeyError exception will raise + """ if not hasattr(self, '_title'): self._title = pywikibot.Page(self.site, self.data['title']) return self._title @@ -79,7 +106,8 @@ def timestamp(self): """Timestamp object corresponding to event timestamp.""" if not hasattr(self, '_timestamp'): - self._timestamp = pywikibot.Timestamp.fromISOformat(self.data['timestamp']) + self._timestamp = pywikibot.Timestamp.fromISOformat( + self.data['timestamp']) return self._timestamp
def comment(self): @@ -118,27 +146,19 @@ else: return super(BlockEntry, self).title()
- def isAutoblockRemoval(self): - return self.isAutoblockRemoval - - def _getBlockDetails(self): - try: - return self.data['block'] - except KeyError: - # No 'block' key means this is an unblocking log entry - if self.action() == 'unblock': - raise Error("action='unblock': this log entry has no block details such as flags, duration, or expiry!") - raise - def flags(self): """ Return a list of (str) flags associated with the block entry.
It raises an Error if the entry is an unblocking log entry. + + @rtype: list of flag strings """ - if hasattr(self, '_flags'): - return self._flags - self._flags = self._getBlockDetails()['flags'].split(',') + if not hasattr(self, '_flags'): + self._flags = self._params['flags'] + # pre mw 1.19 returned a delimited string. + if self._flags and isinstance(self._flags, basestring): + self._flags = self._flags.split(',') return self._flags
def duration(self): @@ -146,26 +166,27 @@ Return a datetime.timedelta representing the block duration.
@return: datetime.timedelta, or None if block is indefinite. - @raises Error: the entry is an unblocking log entry. """ - if hasattr(self, '_duration'): - return self._duration - if self._getBlockDetails()['duration'] == 'indefinite': - self._duration = None - else: - # Doing the difference is easier than parsing the string - self._duration = self.expiry() - self.timestamp() + if not hasattr(self, '_duration'): + if self.expiry() is None: + self._duration = None + else: + # Doing the difference is easier than parsing the string + self._duration = self.expiry() - self.timestamp() return self._duration
def expiry(self): """ Return a Timestamp representing the block expiry date.
- @raises Error: the entry is an unblocking log entry. + @rtype: pywikibot.Timestamp or None """ - if hasattr(self, '_expiry'): - return self._expiry - self._expiry = pywikibot.Timestamp.fromISOformat(self._getBlockDetails()['expiry']) + if not hasattr(self, '_expiry'): + details = self._params.get('expiry') + if details: + self._expiry = pywikibot.Timestamp.fromISOformat(details) + else: + self._expiry = None # for infinite blocks return self._expiry
@@ -181,6 +202,20 @@ """Rights log entry."""
_expectedType = 'rights' + + @property + def oldgroups(self): + """Return old rights groups.""" + if 'old' in self._params: # old mw style + return self._params['old'].split(',') if self._params['old'] else [] + return self._params['oldgroups'] + + @property + def newgroups(self): + """Return new rights groups.""" + if 'new' in self._params: # old mw style + return self._params['new'].split(',') if self._params['new'] else [] + return self._params['newgroups']
class DeleteEntry(LogEntry): @@ -203,14 +238,38 @@
_expectedType = 'move'
+ @deprecated('target_ns.id') def new_ns(self): - return self.data['move']['new_ns'] + """Return namespace id of target page.""" + return self.target_ns.id
+ @property + def target_ns(self): + """Return namespace object of target page.""" + # key has been changed in mw 1.19 + return self.site.namespaces[self._params['target_ns'] + if 'target_ns' in self._params + else self._params['new_ns']] + + @deprecated('target_page') def new_title(self): """Return page object of the new title.""" - if not hasattr(self, '_new_title'): - self._new_title = pywikibot.Page(self.site, self.data['move']['new_title']) - return self._new_title + return self.target_page + + @property + def target_title(self): + """Return the target title.""" + # key has been changed in mw 1.19 + return (self._params['target_title'] + if 'target_title' in self._params + else self._params['new_title']) + + @property + def target_page(self): + """Return target page object.""" + if not hasattr(self, '_target_page'): + self._target_page = pywikibot.Page(self.site, self.target_title) + return self._target_page
def suppressedredirect(self): """ @@ -219,7 +278,7 @@ @rtype: bool """ # Introduced in MW r47901 - return 'suppressedredirect' in self.data['move'] + return 'suppressedredirect' in self._params
class ImportEntry(LogEntry): @@ -235,6 +294,25 @@
_expectedType = 'patrol'
+ @property + def current_id(self): + """Return the current id.""" + # key has been changed in mw 1.19; try the new mw style first + return (self._params['curid'] + if 'curid' in self._params else self._params['cur']) + + @property + def previous_id(self): + # key has been changed in mw 1.19; try the new mw style first + """Return the previous id.""" + return (self._params['previd'] + if 'previd' in self._params else self._params['prev']) + + @property + def auto(self): + """Return auto patrolled.""" + return 'auto' in self._params and self._params['auto'] != 0 +
class NewUsersEntry(LogEntry):
diff --git a/tests/logentry_tests.py b/tests/logentry_tests.py new file mode 100644 index 0000000..772b652 --- /dev/null +++ b/tests/logentry_tests.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +"""Test logentries module.""" +# +# (C) Pywikibot team, 2015 +# +# Distributed under the terms of the MIT license. +# +from __future__ import unicode_literals + +__version__ = '$Id$' + +import datetime +import sys + +import pywikibot +from pywikibot.logentries import LogEntryFactory + +from tests.aspects import ( + unittest, MetaTestCaseClass, TestCase, DeprecationTestCase +) + +if sys.version_info[0] > 2: + unicode = str + + +def get_logentry(site, logtype): + """Global method to retriev a single log entry.""" + return next(iter(site.logevents(logtype=logtype, total=1))) + + +class TestLogentriesMeta(MetaTestCaseClass): + + """Test meta class for TestLogentries.""" + + def __new__(cls, name, bases, dct): + """Create the new class.""" + cls.site = pywikibot.Site('de', 'wikipedia') + + def test_method(logtype): + + def test_logevent(self): + """Test a single logtype entry.""" + logentry = get_logentry(cls.site, logtype) + self.assertEqual(logtype, logentry._expectedType) + self.assertIsInstance(logentry.action(), unicode) + self.assertIsInstance(logentry.comment(), unicode) + self.assertIsInstance(logentry.logid(), int) + self.assertIsInstance(logentry.ns(), int) + self.assertIsInstance(logentry.pageid(), int) + self.assertIsInstance(logentry.timestamp(), pywikibot.Timestamp) + if 'title' in logentry.data: # title may be missing + self.assertIsInstance(logentry.title(), pywikibot.Page) + self.assertEqual(logentry.type(), logtype) + self.assertIsInstance(logentry.user(), unicode) + self.assertGreaterEqual(logentry.logid(), 0) + self.assertGreaterEqual(logentry.ns(), -2) + self.assertGreaterEqual(logentry.pageid(), 0) + return test_logevent + + # create test methods for package messages processed by unittest + for logtype in LogEntryFactory._logtypes: + test_name = str('test_%sEntry' % logtype.title()) + dct[test_name] = test_method(logtype) + + return super(MetaTestCaseClass, cls).__new__(cls, name, bases, dct) + + +class TestLogentries(TestCase): + + """Test TestLogentries processed by unittest.""" + + __metaclass__ = TestLogentriesMeta + + +class TestLogentryParams(TestCase): + + """Test Logentry params.""" + + family = 'wikipedia' + code = 'de' + + def test_BlockEntry(self): + """Test BlockEntry methods.""" + logentry = get_logentry(self.site, 'block') + if logentry.action() == 'block': + self.assertIsInstance(logentry.flags(), list) + if logentry.expiry() is not None: + self.assertIsInstance(logentry.expiry(), pywikibot.Timestamp) + self.assertIsInstance(logentry.duration(), datetime.timedelta) + + def test_RightsEntry(self): + """Test MoveEntry methods.""" + logentry = get_logentry(self.site, 'rights') + self.assertIsInstance(logentry.oldgroups, list) + self.assertIsInstance(logentry.newgroups, list) + + def test_MoveEntry(self): + """Test MoveEntry methods.""" + logentry = get_logentry(self.site, 'move') + self.assertIsInstance(logentry.target_ns, pywikibot.site.Namespace) + self.assertEqual(logentry.target_page.namespace(), + logentry.target_ns.id) + self.assertIsInstance(logentry.target_title, unicode) + self.assertIsInstance(logentry.target_page, pywikibot.Page) + self.assertIsInstance(logentry.suppressedredirect(), bool) + + def test_PatrolEntry(self): + """Test MoveEntry methods.""" + logentry = get_logentry(self.site, 'patrol') + self.assertIsInstance(logentry.current_id, int) + self.assertIsInstance(logentry.previous_id, int) + self.assertIsInstance(logentry.auto, bool) + + +class TestDeprecatedMethods(DeprecationTestCase): + + """Test cases for deprecated logentry methods.""" + + family = 'wikipedia' + code = 'de' + + def test_MoveEntry(self): + """Test MoveEntry methods.""" + logentry = get_logentry(self.site, 'move') + self.assertIsInstance(logentry.new_ns(), int) + self.assertEqual(logentry.new_title(), logentry.target_page) + + +if __name__ == '__main__': + try: + unittest.main() + except SystemExit: + pass
pywikibot-commits@lists.wikimedia.org