jenkins-bot submitted this change.

View Change

Approvals: JJMC89: Looks good to me, approved jenkins-bot: Verified
[cleanup] Drop support for MediaWiki < 1.27

Drop support for MediaWiki < 1.27 in several modules.

Bug: T306637
Change-Id: I6a0e8d360b1eeda089a38c2680eed21f4ee18438
---
M README.rst
M docs/index.rst
M pywikibot/data/api/_requests.py
M pywikibot/logentries.py
M pywikibot/page/_page.py
M pywikibot/site/_apisite.py
M pywikibot/site/_generators.py
M pywikibot/site_detect.py
M tests/dry_api_tests.py
M tests/logentries_tests.py
M tests/page_tests.py
M tests/site_detect_tests.py
12 files changed, 77 insertions(+), 196 deletions(-)

diff --git a/README.rst b/README.rst
index bcd87a7..81734a5 100644
--- a/README.rst
+++ b/README.rst
@@ -41,7 +41,7 @@

The Pywikibot framework is a Python library that interfaces with the
`MediaWiki API <https://www.mediawiki.org/wiki/API:Main_page>`_
-version 1.23 or higher.
+version 1.27 or higher.

Also included are various general function scripts that can be adapted for
different tasks.
diff --git a/docs/index.rst b/docs/index.rst
index b460247..4384161 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -11,7 +11,8 @@
The project started in 2003 and is currently on core version |version|.
It features full API usage and is up-to-date with new MediaWiki features and
a Pythonic package layout. But it also works with older installations of
-MediaWiki 1.23 or higher.
+MediaWiki 1.27 or higher. For older MediaWiki versions you have to use older
+Pywikibot releases; refer :manpage:`Compatibility`.

Pywikibot supports Microsoft Windows, macOS and Linux when used with a
compatible version of Python. It should also work on any other operating
diff --git a/pywikibot/data/api/_requests.py b/pywikibot/data/api/_requests.py
index 70d1207..f8dce8c 100644
--- a/pywikibot/data/api/_requests.py
+++ b/pywikibot/data/api/_requests.py
@@ -440,7 +440,8 @@
if ('tokens' not in meta and 'continue' not in self._params
and self.site.mw_version >= '1.25wmf5'):
self._params.setdefault('rawcontinue', [''])
- elif self.action == 'help' and self.site.mw_version > '1.24':
+
+ elif self.action == 'help':
self['wrap'] = ''

if config.maxlag:
diff --git a/pywikibot/logentries.py b/pywikibot/logentries.py
index 2eb9ff2..9208ec0 100644
--- a/pywikibot/logentries.py
+++ b/pywikibot/logentries.py
@@ -6,7 +6,6 @@
#
import datetime
from collections import UserDict
-from contextlib import suppress
from typing import Any, Optional, Type, Union

import pywikibot
@@ -97,8 +96,6 @@
@property
def _params(self) -> Dict[str, Any]:
"""Additional data for some log entry types."""
- with suppress(KeyError):
- return self[self._expected_type] # old behaviour
return self.get('params', {})

@cached
@@ -185,11 +182,7 @@
if self.action() == 'unblock':
return []

- flags = self._params.get('flags', [])
- # pre mw 1.25 returned a delimited string.
- if isinstance(flags, str):
- flags = flags.split(',') if flags else []
- return flags
+ return self._params.get('flags', [])

@cached
def duration(self) -> Optional[datetime.timedelta]:
@@ -224,11 +217,7 @@
LogEntry has no additional data e.g. due to hidden data and
insufficient rights.
"""
- params = self._params
- if 'old' in params: # old mw style (mw < 1.25)
- return params['old'].split(',') if params['old'] else []
-
- return params.get('oldgroups', [])
+ return self._params.get('oldgroups', [])

@property
def newgroups(self) -> List[str]:
@@ -239,11 +228,7 @@
LogEntry has no additional data e.g. due to hidden data and
insufficient rights.
"""
- params = self._params
- if 'new' in params: # old mw style (mw < 1.25)
- return params['new'].split(',') if params['new'] else []
-
- return params.get('newgroups', [])
+ return self._params.get('newgroups', [])


class UploadEntry(LogEntry):
@@ -267,18 +252,12 @@
@property
def target_ns(self) -> 'pywikibot.site._namespace.Namespace':
"""Return namespace object of target page."""
- # key has been changed in mw 1.25 to 'target_ns'
- return self.site.namespaces[self._params['target_ns']
- if 'target_ns' in self._params
- else self._params['new_ns']]
+ return self.site.namespaces[self._params['target_ns']]

@property
def target_title(self) -> str:
"""Return the target title."""
- # key has been changed in mw 1.25 to 'target_title'
- return (self._params['target_title']
- if 'target_title' in self._params
- else self._params['new_title'])
+ return self._params['target_title']

@property
@cached
@@ -301,18 +280,12 @@
@property
def current_id(self) -> int:
"""Return the current id."""
- # key has been changed in mw 1.25; try the new mw style first
- # sometimes it returns strs sometimes ints
- return int(self._params['curid']
- if 'curid' in self._params else self._params['cur'])
+ return int(self._params['curid'])

@property
def previous_id(self) -> int:
"""Return the previous id."""
- # key has been changed in mw 1.25; try the new mw style first
- # sometimes it returns strs sometimes ints
- return int(self._params['previd']
- if 'previd' in self._params else self._params['prev'])
+ return int(self._params['previd'])

@property
def auto(self) -> bool:
diff --git a/pywikibot/page/_page.py b/pywikibot/page/_page.py
index 94239b8..07de029 100644
--- a/pywikibot/page/_page.py
+++ b/pywikibot/page/_page.py
@@ -31,7 +31,7 @@

import pywikibot
from pywikibot import Timestamp, config, date, i18n, textlib
-from pywikibot.backports import Generator, Iterable, Iterator, List
+from pywikibot.backports import Generator, Iterable, Iterator, List, Set
from pywikibot.cosmetic_changes import CANCEL, CosmeticChangesToolkit
from pywikibot.exceptions import (
Error,
@@ -1026,31 +1026,10 @@
"""Return a dictionary reflecting page protections."""
return self.site.page_restrictions(self)

- def applicable_protections(self) -> set:
- """
- Return the protection types allowed for that page.
-
- If the page doesn't exist it only returns "create". Otherwise it
- returns all protection types provided by the site, except "create".
- It also removes "upload" if that page is not in the File namespace.
-
- It is possible, that it returns an empty set, but only if original
- protection types were removed.
-
- :return: set of str
- """
- # New API since commit 32083235eb332c419df2063cf966b3400be7ee8a
- if self.site.mw_version >= '1.25wmf14':
- self.site.loadpageinfo(self)
- return self._applicable_protections
-
- p_types = set(self.site.protection_types())
- if not self.exists():
- return {'create'} if 'create' in p_types else set()
- p_types.remove('create') # no existing page allows that
- if not self.is_filepage(): # only file pages allow upload
- p_types.remove('upload')
- return p_types
+ def applicable_protections(self) -> Set[str]:
+ """Return the protection types allowed for that page."""
+ self.site.loadpageinfo(self)
+ return self._applicable_protections

def has_permission(self, action: str = 'edit') -> bool:
"""Determine whether the page can be modified.
diff --git a/pywikibot/site/_apisite.py b/pywikibot/site/_apisite.py
index 2050db3..b2af8da 100644
--- a/pywikibot/site/_apisite.py
+++ b/pywikibot/site/_apisite.py
@@ -439,10 +439,8 @@
"""
if self.is_oauth_token_available():
pywikibot.warning('Using OAuth suppresses logout function')
- req_params = {'action': 'logout'}
- # csrf token introduced in MW 1.24
- with suppress(Error):
- req_params['token'] = self.tokens['csrf']
+
+ req_params = {'action': 'logout', 'token': self.tokens['csrf']}
uirequest = self.simple_request(**req_params)
uirequest.submit()
self._loginstatus = _LoginStatus.NOT_LOGGED_IN
@@ -931,19 +929,18 @@
"""
if not isinstance(text, str):
raise ValueError('text must be a string')
+
if not text:
return ''
- req = self.simple_request(action='expandtemplates', text=text)
+
+ req = self.simple_request(action='expandtemplates',
+ text=text, prop='wikitext')
if title is not None:
req['title'] = title
if includecomments is True:
req['includecomments'] = ''
- if self.mw_version > '1.24wmf7':
- key = 'wikitext'
- req['prop'] = key
- else:
- key = '*'
- return req.submit()['expandtemplates'][key]
+
+ return req.submit()['expandtemplates']['wikitext']

def getcurrenttimestamp(self) -> str:
"""
@@ -1087,10 +1084,10 @@
pywikibot.error(msg)
raise

- if MediaWikiVersion(version) < '1.23':
+ if MediaWikiVersion(version) < '1.27':
raise RuntimeError(
'Pywikibot "{}" does not support MediaWiki "{}".\n'
- 'Use Pywikibot prior to "6.0" branch instead.'
+ 'Use Pywikibot prior to "8.0" branch instead.'
.format(pywikibot.__version__, version))
return version

@@ -1496,12 +1493,8 @@
return page._redirtarget

def validate_tokens(self, types: List[str]) -> List[str]:
- """Validate if requested tokens are acceptable.
-
- Valid tokens depend on mw version.
- """
- query = 'tokens' if self.mw_version < '1.24wmf19' else 'query+tokens'
- data = self._paraminfo.parameter(query, 'type')
+ """Validate if requested tokens are acceptable."""
+ data = self._paraminfo.parameter('query+tokens', 'type')
assert data is not None
return [token for token in types if token in data['type']]

@@ -1512,7 +1505,6 @@
) -> Dict[str, str]:
"""Preload one or multiple tokens.

- For MediaWiki version 1.23, only one token can be retrieved at once.
For MediaWiki versions since 1.24wmfXXX a new token
system was introduced which reduced the amount of tokens available.
Most of them were merged into the 'csrf' token. If the token type in
@@ -1543,21 +1535,13 @@
r'Action \'\w+\' is not allowed for the current user', text)

user_tokens = {}
- if self.mw_version < '1.24wmf19':
- if all is not False:
- pdata = self._paraminfo.parameter('tokens', 'type')
- assert pdata is not None
- types.extend(pdata['type'])
- req = self.simple_request(action='tokens',
- type=self.validate_tokens(types))
- else:
- if all is not False:
- pdata = self._paraminfo.parameter('query+tokens', 'type')
- assert pdata is not None
- types.extend(pdata['type'])
+ if all is not False:
+ pdata = self._paraminfo.parameter('query+tokens', 'type')
+ assert pdata is not None
+ types.extend(pdata['type'])

- req = self.simple_request(action='query', meta='tokens',
- type=self.validate_tokens(types))
+ req = self.simple_request(action='query', meta='tokens',
+ type=self.validate_tokens(types))

req._warning_handler = warn_handler
data = req.submit()
@@ -2665,7 +2649,6 @@
When the version is at least 1.27wmf9, uses general siteinfo.
If not called directly, it is cached by the first attempted
upload action.
-
"""
if self.mw_version >= '1.27wmf9':
return not self._siteinfo.get('general')['uploadsenabled']
@@ -2677,10 +2660,10 @@
# missingparam: One of the parameters
# filekey, file, url, statuskey is required
# TODO: is there another way?
+ req = self._request(throttle=False,
+ parameters={'action': 'upload',
+ 'token': self.tokens['edit']})
try:
- req = self._request(throttle=False,
- parameters={'action': 'upload',
- 'token': self.tokens['edit']})
req.submit()
except APIError as error:
if error.code == 'uploaddisabled':
@@ -2694,6 +2677,7 @@
# Unexpected error
raise
return self._uploaddisabled
+
raise RuntimeError(
'Unexpected success of upload action without parameters.')

diff --git a/pywikibot/site/_generators.py b/pywikibot/site/_generators.py
index 49630e3..b3d6621 100644
--- a/pywikibot/site/_generators.py
+++ b/pywikibot/site/_generators.py
@@ -10,7 +10,6 @@
from contextlib import suppress
from itertools import zip_longest
from typing import Any, Optional, Union
-from warnings import warn

import pywikibot
from pywikibot.backports import Dict, Generator, Iterable, List # skipcq
@@ -23,7 +22,7 @@
NoPageError,
UserRightsError,
)
-from pywikibot.site._decorators import need_right, need_version
+from pywikibot.site._decorators import need_right
from pywikibot.site._namespace import NamespaceArgType
from pywikibot.tools import is_ip_address, issue_deprecation_warning
from pywikibot.tools.itertools import filter_unique, itergroup
@@ -293,7 +292,6 @@
namespaces=namespaces, total=total,
g_content=content, **eiargs)

- @need_version('1.24')
def page_redirects(
self,
page: 'pywikibot.Page',
@@ -1574,28 +1572,11 @@
:keyword prop: Which properties to get. Defaults are ids, user,
comment, flags and timestamp
"""
- def handle_props(props):
- """Translate deletedrev props to deletedrevisions props."""
- if isinstance(props, str):
- props = props.split('|')
- if self.mw_version >= '1.25':
- return props
-
- old_props = []
- for item in props:
- if item == 'ids':
- old_props += ['revid', 'parentid']
- elif item == 'flags':
- old_props.append('minor')
- elif item != 'timestamp':
- old_props.append(item)
- if item == 'content' and self.mw_version < '1.24':
- old_props.append('token')
- return old_props
-
# set default properties
prop = kwargs.pop('prop',
['ids', 'user', 'comment', 'flags', 'timestamp'])
+ if isinstance(prop, str):
+ prop = prop.split('|')
if content:
prop.append('content')

@@ -1608,46 +1589,26 @@
if not bool(titles) ^ (revids is not None):
raise Error('deletedrevs: either "titles" or "revids" parameter '
'must be given.')
- if revids and self.mw_version < '1.25':
- raise NotImplementedError(
- 'deletedrevs: "revid" is not implemented with MediaWiki {}'
- .format(self.mw_version))

- if self.mw_version >= '1.25':
- pre = 'drv'
- type_arg = 'deletedrevisions'
- generator = api.PropertyGenerator
- else:
- pre = 'dr'
- type_arg = 'deletedrevs'
- generator = api.ListGenerator
+ gen = self._generator(api.PropertyGenerator,
+ type_arg='deletedrevisions',
+ titles=titles, revids=revids, total=total)

- gen = self._generator(generator, type_arg=type_arg,
- titles=titles, revids=revids,
- total=total)
-
- gen.request[pre + 'start'] = start
- gen.request[pre + 'end'] = end
- gen.request[pre + 'prop'] = handle_props(prop)
+ gen.request['drvstart'] = start
+ gen.request['drvend'] = end
+ gen.request['drvprop'] = prop
+ if reverse:
+ gen.request['drvdir'] = 'newer'

# handle other parameters like user
for k, v in kwargs.items():
- gen.request[pre + k] = v
+ gen.request['drv' + k] = v

- if reverse:
- gen.request[pre + 'dir'] = 'newer'
+ for data in gen:
+ with suppress(KeyError):
+ data['revisions'] = data.pop('deletedrevisions')
+ yield data

- if self.mw_version < '1.25':
- yield from gen
-
- else:
- # The dict result is different for both generators
- for data in gen:
- with suppress(KeyError):
- data['revisions'] = data.pop('deletedrevisions')
- yield data
-
- @need_version('1.25')
def alldeletedrevisions(
self,
*,
@@ -1748,15 +1709,7 @@
redirects = mapping[redirects]
params = {}
if redirects is not None:
- if self.mw_version < '1.26':
- if redirects == 'all':
- warn("parameter redirects=None to retrieve 'all' random"
- 'page types is not supported by mw version {}. '
- 'Using default.'.format(self.mw_version),
- UserWarning)
- params['grnredirect'] = redirects == 'redirects'
- else:
- params['grnfilterredir'] = redirects
+ params['grnfilterredir'] = redirects
return self._generator(api.PageGenerator, type_arg='random',
namespaces=namespaces, total=total,
g_content=content, **params)
diff --git a/pywikibot/site_detect.py b/pywikibot/site_detect.py
index 5031518..b4b2f6b 100644
--- a/pywikibot/site_detect.py
+++ b/pywikibot/site_detect.py
@@ -29,7 +29,7 @@
SERVER_DB_ERROR_MSG = \
'<h1>Sorry! This site is experiencing technical difficulties.</h1>'

-MIN_VERSION = MediaWikiVersion('1.23')
+MIN_VERSION = MediaWikiVersion('1.27')


class MWSite:
@@ -43,7 +43,7 @@
:raises pywikibot.exceptions.ServerError: a server error occurred
while loading the site
:raises Timeout: a timeout occurred while loading the site
- :raises RuntimeError: Version not found or version less than 1.23
+ :raises RuntimeError: Version not found or version less than 1.27
"""
if fromurl.endswith('$1'):
fromurl = fromurl[:-2]
diff --git a/tests/dry_api_tests.py b/tests/dry_api_tests.py
index c170f3e..b42847c 100755
--- a/tests/dry_api_tests.py
+++ b/tests/dry_api_tests.py
@@ -160,7 +160,7 @@
self._siteinfo = DummySiteinfo({'case': 'first-letter'})

def version(self):
- return '1.23' # lowest supported release
+ return '1.27' # lowest supported release

def protocol(self):
return 'http'
diff --git a/tests/logentries_tests.py b/tests/logentries_tests.py
index 4798036..bbbf189 100755
--- a/tests/logentries_tests.py
+++ b/tests/logentries_tests.py
@@ -29,9 +29,9 @@

It uses the German Wikipedia for a current representation of the
log entries and the test Wikipedia for the future representation.
- It also tests on a wiki with MW < 1.25 to check that it can still
- read the older format. It currently uses portalwiki which as of this
- commit uses 1.23.16.
+ It also tests on a wiki with MW <= 1.27 to check that the module
+ works with older wikis. It currently uses infogalacticwiki which as
+ of this commit uses 1.27.1.
"""

sites = {
@@ -51,8 +51,8 @@
'target': None,
},
'old': {
- 'family': AutoFamily('portalwiki',
- 'https://theportalwiki.com/wiki/Main_Page'),
+ 'family': AutoFamily('infogalactic',
+ 'https://infogalactic.com/info/Main_Page'),
'code': 'en',
'target': None,
}
@@ -64,7 +64,7 @@
# This is an assertion as the tests don't make sense with newer
# MW versions and otherwise it might not be visible that the test
# isn't run on an older wiki.
- self.assertLess(self.site.mw_version, '1.25')
+ self.assertEqual(self.site.mw_version, '1.27.1')

with skipping(StopIteration,
msg=f'No entry found for {logtype!r}'):
@@ -80,11 +80,8 @@
if logtype not in LogEntryFactory._logtypes:
self.assertIsInstance(logentry, OtherLogEntry)

- if self.site_key == 'old':
- self.assertNotIn('params', logentry.data)
- else:
- self.assertNotIn(logentry.type(), logentry.data)
-
+ # check that we only have the new implementation
+ self.assertNotIn(logentry.type(), logentry.data)
self.assertIsInstance(logentry.action(), str)

try:
diff --git a/tests/page_tests.py b/tests/page_tests.py
index 32dfafb..47a0d07 100755
--- a/tests/page_tests.py
+++ b/tests/page_tests.py
@@ -1082,7 +1082,6 @@
p2 = pywikibot.Page(site, 'User:Unicodesnowman/ProtectTest')
p3 = pywikibot.Page(site, 'File:Wiki.png')

- # from the API, since 1.25wmf14
pp1 = p1.applicable_protections()
pp2 = p2.applicable_protections()
pp3 = p3.applicable_protections()
@@ -1093,12 +1092,6 @@
self.assertNotIn('upload', pp2)
self.assertIn('upload', pp3)

- # inferred
- site.version = lambda: '1.24'
- self.assertEqual(pp1, p1.applicable_protections())
- self.assertEqual(pp2, p2.applicable_protections())
- self.assertEqual(pp3, p3.applicable_protections())
-

class TestPageProtect(TestCase):

diff --git a/tests/site_detect_tests.py b/tests/site_detect_tests.py
index 2ac6ffc..fdbafb0 100755
--- a/tests/site_detect_tests.py
+++ b/tests/site_detect_tests.py
@@ -79,10 +79,6 @@
"""
self.assertSite('http://www.wikichristian.org/index.php?title=$1')

- def test_wikifur(self):
- """Test detection of MediaWiki sites for en.wikifur.com."""
- self.assertSite('https://en.wikifur.com/wiki/$1')
-

class NonStandardVersionSiteTestCase(SiteDetectionTestCase):

@@ -96,14 +92,10 @@
"""Test detection of MediaWiki sites for www.arabeyes.org."""
self.assertSite('https://www.arabeyes.org/$1')

- def test_tfwiki(self):
- """Test detection of MediaWiki sites for tfwiki.net."""
- self.assertNoSite('http://tfwiki.net/wiki/$1') # 1.19.5-1+deb7u1

+class UnsupportedSiteTestCase(SiteDetectionTestCase):

-class Pre119SiteTestCase(SiteDetectionTestCase):
-
- """Test pre 1.19 sites which should be detected as unsupported."""
+ """Test pre 1.27 sites which should be detected as unsupported."""

def test_hrwiki(self):
"""Test detection of MediaWiki sites for www.hrwiki.org."""
@@ -113,6 +105,14 @@
"""Test detection of MediaWiki sites for www.wikifon.org."""
self.assertNoSite('http://www.wikifon.org/$1') # v1.11.0

+ def test_tfwiki(self):
+ """Test detection of MediaWiki sites for tfwiki.net."""
+ self.assertNoSite('http://tfwiki.net/wiki/$1') # 1.19.5-1+deb7u1
+
+ def test_wikifur(self):
+ """Test detection of MediaWiki sites for en.wikifur.com."""
+ self.assertNoSite('https://en.wikifur.com/wiki/$1') # 1.23.16
+

class PreAPISiteTestCase(SiteDetectionTestCase):


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

Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: I6a0e8d360b1eeda089a38c2680eed21f4ee18438
Gerrit-Change-Number: 835198
Gerrit-PatchSet: 9
Gerrit-Owner: Xqt <info@gno.de>
Gerrit-Reviewer: JJMC89 <JJMC89.Wikimedia@gmail.com>
Gerrit-Reviewer: jenkins-bot
Gerrit-MessageType: merged