jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/835198 )
Change subject: [cleanup] Drop support for MediaWiki < 1.27 ......................................................................
[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(-)
Approvals: JJMC89: Looks good to me, approved jenkins-bot: Verified
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):
pywikibot-commits@lists.wikimedia.org