jenkins-bot merged this change.

View Change

Approvals: Dalba: Looks good to me, approved jenkins-bot: Verified
[IMPR] enable any LogEntry subclass for each log type

- Each logtype has its own LogEntry subclass either declared in logtype module
or as a subclass of OtherLogEntry created by get_entry_class class method
- A new instance method get_valid_entry_class was introduced in
LogEntryFactory to verify that logtype is valid for a given site. This
method is used by the initializer.
- The new site method logtypes returns all available log types. The result
is a set to speed up any searching inside that collection
- Deprecate logtypes class property
- Define ProtectEntry, DeleteEntry, ImportEntry NewUsersEntry and
ThanksEntry by the LogEntryFactory.get_entry_class constructor method
for backward compatibility
- pagegenerators uses new site.logtypes to verify the -logevents option.
This enables all other logtypes to be processed by pagegenerators.
- The old -<logtype>log pg options are no longer supported and ignored now.
The old options where deprecated 40 months ago which should be enough.
- update tests accordingly
- update HISTORY.rst
- fix spelling mistake in api.py

Bug: T199013
Change-Id: I0ba3ec7be0d0270ebc1930b149463ece3b53ea2b
---
M HISTORY.rst
M pywikibot/data/api.py
M pywikibot/logentries.py
M pywikibot/pagegenerators.py
M pywikibot/site.py
M tests/logentry_tests.py
M tests/pagegenerators_tests.py
7 files changed, 79 insertions(+), 165 deletions(-)

diff --git a/HISTORY.rst b/HISTORY.rst
index ff5bd84..28b87ea 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -4,6 +4,8 @@
Current release
---------------

+* Enable any LogEntry subclass for each logevent type (T199013)
+* Deprecated pagegenerators options -<logtype>log aren't supported any longer (T199013)
* Open RotatingFileHandler with utf-8 encoding (T188231)
* Fix occasional failure of TestLogentries due to hidden namespace (T197506)
* Remove multiple empty sections at once in cosmetic_changes (T196324)
diff --git a/pywikibot/data/api.py b/pywikibot/data/api.py
index 0d1785c..be03877 100644
--- a/pywikibot/data/api.py
+++ b/pywikibot/data/api.py
@@ -3121,7 +3121,7 @@
self.entryFactory = logentries.LogEntryFactory(self.site, logtype)

def result(self, pagedata):
- """Instatiate LogEntry from data from api."""
+ """Instantiate LogEntry from data from api."""
return self.entryFactory.create(pagedata)

def _check_result_namespace(self, result):
diff --git a/pywikibot/logentries.py b/pywikibot/logentries.py
index d0a93be..f60b3ff 100644
--- a/pywikibot/logentries.py
+++ b/pywikibot/logentries.py
@@ -157,6 +157,13 @@
return self.data['comment']


+class OtherLogEntry(LogEntry):
+
+ """A log entry class for unspecified log events."""
+
+ pass
+
+
class UserTargetLogEntry(LogEntry):

"""A log entry whose target is a user page."""
@@ -260,13 +267,6 @@
return self._expiry


-class ProtectEntry(LogEntry):
-
- """Protection log entry."""
-
- _expectedType = 'protect'
-
-
class RightsEntry(LogEntry):

"""Rights log entry."""
@@ -288,13 +288,6 @@
return self._params['newgroups']


-class DeleteEntry(LogEntry):
-
- """Deletion log entry."""
-
- _expectedType = 'delete'
-
-
class UploadEntry(LogEntry):

"""Upload log entry."""
@@ -361,13 +354,6 @@
return 'suppressedredirect' in self._params


-class ImportEntry(LogEntry):
-
- """Import log entry."""
-
- _expectedType = 'import'
-
-
class PatrolEntry(LogEntry):

"""Patrol log entry."""
@@ -396,22 +382,6 @@
return 'auto' in self._params and self._params['auto'] != 0


-class NewUsersEntry(UserTargetLogEntry):
-
- """New user log entry."""
-
- _expectedType = 'newusers'
-
-
-class ThanksEntry(UserTargetLogEntry):
-
- """Thanks log entry."""
-
- _expectedType = 'thanks'
-
-# TODO entries for merge,suppress,makebot,gblblock,renameuser,globalauth,gblrights ?
-
-
class LogEntryFactory(object):

"""
@@ -420,17 +390,12 @@
Only available method is create()
"""

- logtypes = {
+ _logtypes = {
'block': BlockEntry,
- 'protect': ProtectEntry,
'rights': RightsEntry,
- 'delete': DeleteEntry,
'upload': UploadEntry,
'move': MoveEntry,
- 'import': ImportEntry,
'patrol': PatrolEntry,
- 'newusers': NewUsersEntry,
- 'thanks': ThanksEntry,
}

def __init__(self, site, logtype=None):
@@ -450,14 +415,14 @@
else:
# Bind a Class object to self._creator:
# When called, it will initialize a new object of that class
- logclass = LogEntryFactory._getEntryClass(logtype)
+ logclass = self.get_valid_entry_class(logtype)
self._creator = lambda data: logclass(data, self._site)

@classproperty
- @deprecated('LogEntryFactory.logtypes')
- def _logtypes(cls): # noqa: N805
+ @deprecated('Site.logtypes or LogEntryFactory.get_entry_class(logtype)')
+ def logtypes(cls): # noqa: N805
"""DEPRECATED LogEntryFactory class attribute of log types."""
- return cls.logtypes
+ return cls._logtypes

def create(self, logdata):
"""
@@ -470,20 +435,40 @@
"""
return self._creator(logdata)

- @classmethod
- def _getEntryClass(cls, logtype):
+ def get_valid_entry_class(self, logtype):
"""
Return the class corresponding to the @logtype string parameter.

- @return: specified subclass of LogEntry, or LogEntry
- @rtype: class
+ @return: specified subclass of LogEntry
+ @rtype: LogEntry
+ @raise KeyError: logtype is not valid
"""
- try:
- return cls.logtypes[logtype]
- except KeyError:
- pywikibot.warning(
- 'Log entry key {0} is not known.'.format(logtype))
- return LogEntry
+ if logtype not in self._site.logtypes:
+ raise KeyError('{} is not a valid logtype'.format(logtype))
+ return LogEntryFactory.get_entry_class(logtype)
+
+ @classmethod
+ def get_entry_class(cls, logtype):
+ """
+ Return the class corresponding to the @logtype string parameter.
+
+ @return: specified subclass of LogEntry
+ @rtype: LogEntry
+ @note: this class method cannot verify whether the given logtype
+ already exits for a given site; to verify use Site.logtypes
+ or use the get_valid_entry_class instance method instead.
+ """
+ if logtype not in cls._logtypes:
+ if logtype in ('newusers', 'thanks'):
+ bases = (UserTargetLogEntry, OtherLogEntry)
+ else:
+ bases = (OtherLogEntry, )
+ classname = str(logtype.capitalize() + 'Entry'
+ if logtype is not None
+ else OtherLogEntry.__name__)
+ cls._logtypes[logtype] = type(
+ classname, bases, {'_expectedType': logtype})
+ return cls._logtypes[logtype]

def _createFromData(self, logdata):
"""
@@ -499,4 +484,12 @@
pywikibot.debug('API log entry received:\n{0}'.format(logdata),
_logger)
raise Error("Log entry has no 'type' key")
- return LogEntryFactory._getEntryClass(logtype)(logdata, self._site)
+ return LogEntryFactory.get_entry_class(logtype)(logdata, self._site)
+
+
+# For backward compatibility
+ProtectEntry = LogEntryFactory.get_entry_class('protect')
+DeleteEntry = LogEntryFactory.get_entry_class('delete')
+ImportEntry = LogEntryFactory.get_entry_class('import')
+NewUsersEntry = LogEntryFactory.get_entry_class('newusers')
+ThanksEntry = LogEntryFactory.get_entry_class('thanks')
diff --git a/pywikibot/pagegenerators.py b/pywikibot/pagegenerators.py
index bceae51..6e73d33 100644
--- a/pywikibot/pagegenerators.py
+++ b/pywikibot/pagegenerators.py
@@ -53,7 +53,6 @@
ServerError,
UnknownExtension,
)
-from pywikibot.logentries import LogEntryFactory
from pywikibot.proofreadpage import ProofreadPage
from pywikibot.tools import MediaWikiVersion

@@ -1125,34 +1124,11 @@
def _handle_logevents(self, value):
"""Handle `-logevents` argument."""
params = value.split(',')
- if params[0] not in LogEntryFactory.logtypes:
+ if params[0] not in self.site.logtypes:
raise NotImplementedError(
'Invalid -logevents parameter "{0}"'.format(params[0]))
return self._parse_log_events(*params)

- def _old_eventlog_handler(self, logtype, value):
- """Help in handling arguments LogEntryFactory.logtypes."""
- total = 500
- if value:
- try:
- total = int(value)
- except ValueError:
- params = value.split(';')
- if len(params) == 2:
- value, total = params
- else:
- value = params[0]
- else:
- value = None
- else:
- value = None
- issue_deprecation_warning(
- 'The usage of "{0}"'.format('-%slog' % logtype),
- '-logevents:"{0}"'.format(
- ','.join((logtype, value or '', str(total)))),
- 4, ArgumentDeprecationWarning)
- return self._parse_log_events(logtype, value, total)
-
def handleArg(self, arg):
"""Parse one argument at a time.

@@ -1191,22 +1167,6 @@
return False


-def _old_eventlog_handler_method_factory(log_type):
- """Return an old type handler method to be assigned to LogEntryFactory."""
- # Using closure is a workaround for the lack of functools.partialmethod in
- # Python 2.7.
- def method(self, vaule):
- return self._old_eventlog_handler(log_type, vaule)
- return method
-
-
-for _log_type in LogEntryFactory.logtypes:
- setattr(
- GeneratorFactory,
- '_handle_' + _log_type + 'log',
- _old_eventlog_handler_method_factory(_log_type))
-
-
def _int_none(v):
"""Return None if v is None or '' else return int(v)."""
return v if (v is None or v is '') else int(v)
diff --git a/pywikibot/site.py b/pywikibot/site.py
index 0959fa5..6df1114 100644
--- a/pywikibot/site.py
+++ b/pywikibot/site.py
@@ -4539,6 +4539,12 @@
namespaces=namespaces,
total=total, g_content=content, **iuargs)

+ @property
+ def logtypes(self):
+ """Return a set of log types available on current site."""
+ return set(filter(None, self._paraminfo.parameter(
+ 'query+logevents', 'type')['type']))
+
@deprecated_args(step=None)
def logevents(self, logtype=None, user=None, page=None, namespace=None,
start=None, end=None, reverse=False, tag=None, total=None):
diff --git a/tests/logentry_tests.py b/tests/logentry_tests.py
index 2819f88..d1ecbde 100644
--- a/tests/logentry_tests.py
+++ b/tests/logentry_tests.py
@@ -12,7 +12,8 @@
import pywikibot

from pywikibot.exceptions import HiddenKeyError
-from pywikibot.logentries import LogEntryFactory, UserTargetLogEntry
+from pywikibot.logentries import (
+ LogEntryFactory, OtherLogEntry, UserTargetLogEntry)
from pywikibot.tools import (
MediaWikiVersion,
UnicodeType as unicode,
@@ -67,10 +68,10 @@
def _test_logevent(self, logtype):
"""Test a single logtype entry."""
logentry = self._get_logentry(logtype)
- if logtype in LogEntryFactory.logtypes:
- self.assertEqual(logentry._expectedType, logtype)
- else:
- self.assertIsNone(logentry._expectedType)
+ self.assertIn(logtype, logentry.__class__.__name__.lower())
+ self.assertEqual(logentry._expectedType, logtype)
+ if logtype not in LogEntryFactory._logtypes:
+ self.assertIsInstance(logentry, OtherLogEntry)
if self.site_key == 'old':
self.assertNotIn('params', logentry.data)
else:
@@ -123,7 +124,7 @@
return test_logevent

# create test methods for the support logtype classes
- for logtype in LogEntryFactory.logtypes:
+ for logtype in LogEntryFactory._logtypes:
cls.add_method(dct, 'test_%sEntry' % logtype.title(),
test_method(logtype))

@@ -147,9 +148,8 @@
# Unfortunately it's not possible to use the metaclass to create a
# bunch of test methods for this too as the site instances haven't been
# initialized yet.
- available_types = set(self.site._paraminfo.parameter(
- 'query+logevents', 'type')['type'])
- for simple_type in available_types - set(LogEntryFactory.logtypes):
+ for simple_type in (self.site.logtypes
+ - set(LogEntryFactory._logtypes)):
if not simple_type:
# paraminfo also reports an empty string as a type
continue
@@ -241,13 +241,20 @@
"""Test equality of LogEntry instances."""
site = self.get_site('dewp')
other_site = self.get_site('tewp')
- le1 = next(iter(site.logevents(reverse=True, total=1)))
- le2 = next(iter(site.logevents(reverse=True, total=1)))
+ gen1 = iter(site.logevents(reverse=True, total=2))
+ gen2 = iter(site.logevents(reverse=True, total=2))
+ le1 = next(gen1)
+ le2 = next(gen2)
le3 = next(iter(other_site.logevents(reverse=True, total=1)))
+ le4 = next(gen1)
+ le5 = next(gen2)
self.assertEqual(le1, le2)
self.assertFalse(le1 != le2) # __ne__ test
self.assertNotEqual(le1, le3)
self.assertNotEqual(le1, site)
+ self.assertIsInstance(le4, OtherLogEntry)
+ self.assertIsInstance(le5, OtherLogEntry)
+ self.assertEqual(type(le4), type(le5))


class TestDeprecatedMethods(TestLogentriesBase, DeprecationTestCase):
diff --git a/tests/pagegenerators_tests.py b/tests/pagegenerators_tests.py
index 8bcf3f7..4c6de1d 100755
--- a/tests/pagegenerators_tests.py
+++ b/tests/pagegenerators_tests.py
@@ -1325,60 +1325,6 @@
self.assertFalse(factory.handleArg(gf_mock, '-anotherlog'))
self.assertFalse(gf_mock.method_calls)

- def test_logevents_default(self):
- """Test old logevents option handling."""
- gf = pagegenerators.GeneratorFactory(site=self.site)
- self.assertTrue(gf.handleArg('-newuserslog'))
- self.assertOneDeprecationParts('The usage of "-newuserslog"',
- '-logevents:"newusers,,500"')
- gen = gf.getCombinedGenerator()
- self.assertIsNotNone(gen)
- pages = set(gen)
- self.assertLessEqual(len(pages), 500)
- self.assertTrue(all(isinstance(item, pywikibot.User)
- for item in pages))
-
- def test_logevents_default_multi(self):
- """Test old logevents option handling with limit argument."""
- gf = pagegenerators.GeneratorFactory(site=self.site)
- self.assertTrue(gf.handleArg('-newuserslog:10'))
- gen = gf.getCombinedGenerator()
- self.assertIsNotNone(gen)
- pages = set(gen)
- self.assertLessEqual(len(pages), 10)
- self.assertTrue(all(isinstance(item, pywikibot.User)
- for item in pages))
-
- def test_logevents_ns(self):
- """Test old logevents option with limit argument and namespace."""
- gf = pagegenerators.GeneratorFactory(site=self.site)
- gf.handleArg('-ns:1')
- gf.handleArg('-newuserslog:10')
- gen = gf.getCombinedGenerator()
- self.assertIsNotNone(gen)
- self.assertPagesInNamespaces(gen, 1)
- self.assertTrue(all(isinstance(item, pywikibot.User)
- for item in gen))
-
- def test_logevents_user_multi(self):
- """Test old logevents option for a given user."""
- gf = pagegenerators.GeneratorFactory(site=self.site)
- user = self.get_site().user()
- self.assertTrue(gf.handleArg('-newuserslog:' + user + ';10'))
- gen = gf.getCombinedGenerator()
- self.assertIsNotNone(gen)
- pages = set(gen)
-
- if not pages:
- raise unittest.SkipTest('No user creation log entries for ' + user)
-
- # TODO: Check if the pages generated correspond to the user
- # (no easy way of checking from pages)
-
- self.assertLessEqual(len(pages), 10)
- self.assertTrue(all(isinstance(item, pywikibot.User)
- for item in pages))
-
def test_logevents_with_start_timestamp(self):
"""Test -logevents which uses timestamp for start."""
gf = pagegenerators.GeneratorFactory(site=self.site)

To view, visit change 424586. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-MessageType: merged
Gerrit-Change-Id: I0ba3ec7be0d0270ebc1930b149463ece3b53ea2b
Gerrit-Change-Number: 424586
Gerrit-PatchSet: 21
Gerrit-Owner: Xqt <info@gno.de>
Gerrit-Reviewer: Dalba <dalba.wiki@gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb@gmail.com>
Gerrit-Reviewer: Xqt <info@gno.de>
Gerrit-Reviewer: Zhuyifei1999 <zhuyifei1999@gmail.com>
Gerrit-Reviewer: Zoranzoki21 <zorandori4444@gmail.com>
Gerrit-Reviewer: jenkins-bot