jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/425128 )
Change subject: [cleanup] use content parameter instead of getText in loadrevisions
......................................................................
[cleanup] use content parameter instead of getText in loadrevisions
deprecate "getText" parameter of Site.loadrevisions and use "content" instead.
"content" is used on all other methods to preload text.
Change-Id: I6125fd1f1aec429548f2335e2d052dbccbb222f1
---
M pywikibot/page.py
M pywikibot/site.py
M tests/basepage_tests.py
M tests/site_tests.py
4 files changed, 14 insertions(+), 12 deletions(-)
Approvals:
Dalba: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/page.py b/pywikibot/page.py
index ddef974..46a43e1 100644
--- a/pywikibot/page.py
+++ b/pywikibot/page.py
@@ -508,7 +508,7 @@
# If not already stored, fetch revision
if self._latest_cached_revision() is None:
try:
- self.site.loadrevisions(self, getText=True, sysop=sysop)
+ self.site.loadrevisions(self, content=True, sysop=sysop)
except (pywikibot.NoPage, pywikibot.SectionError) as e:
self._getexception = e
raise
@@ -530,7 +530,7 @@
if force or oldid not in self._revisions \
or self._revisions[oldid].text is None:
self.site.loadrevisions(self,
- getText=True,
+ content=True,
revids=oldid,
sysop=sysop)
# TODO: what about redirects, errors?
@@ -1697,7 +1697,7 @@
rollback=False, starttime=None, endtime=None):
"""Generator which loads the version history as Revision instances."""
# TODO: Only request uncached revisions
- self.site.loadrevisions(self, getText=content, rvdir=reverse,
+ self.site.loadrevisions(self, content=content, rvdir=reverse,
starttime=starttime, endtime=endtime,
total=total, rollback=rollback)
return (self._revisions[rev] for rev in
diff --git a/pywikibot/site.py b/pywikibot/site.py
index e5bc29a..20c6d62 100644
--- a/pywikibot/site.py
+++ b/pywikibot/site.py
@@ -3981,7 +3981,8 @@
total=total, g_content=content, **cmargs)
return cmgen
- def loadrevisions(self, page, getText=False, revids=None,
+ @deprecated_args(getText='content')
+ def loadrevisions(self, page, content=False, revids=None,
startid=None, endid=None, starttime=None,
endtime=None, rvdir=None, user=None, excludeuser=None,
section=None, sysop=False, step=None, total=None,
@@ -4000,10 +4001,11 @@
@param page: retrieve revisions of this Page (required unless ids
is specified)
- @param getText: if True, retrieve the wiki-text of each revision;
+ @param content: if True, retrieve the wiki-text of each revision;
otherwise, only retrieve the revision metadata (default)
+ @type content: bool
@param section: if specified, retrieve only this section of the text
- (getText must be True); section must be given by number (top of
+ (content must be True); section must be given by number (top of
the article is section 0), not name
@type section: int
@param revids: retrieve only the specified revision ids (raise
@@ -4058,7 +4060,7 @@
rvargs['rvprop'].append('contentmodel')
if MediaWikiVersion(self.version()) >= MediaWikiVersion('1.19'):
rvargs['rvprop'].append('sha1')
- if getText:
+ if content:
rvargs['rvprop'].append('content')
if section is not None:
rvargs[u"rvsection"] = unicode(section)
diff --git a/tests/basepage_tests.py b/tests/basepage_tests.py
index 99449de..58bbde9 100644
--- a/tests/basepage_tests.py
+++ b/tests/basepage_tests.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""Tests for BasePage subclasses."""
#
-# (C) Pywikibot team, 2015
+# (C) Pywikibot team, 2015-2018
#
# Distributed under the terms of the MIT license.
#
@@ -65,7 +65,7 @@
self.assertFalse(hasattr(page, '_text'))
self.assertIsNone(page._latest_cached_revision())
- self.site.loadrevisions(page, total=1, getText=True)
+ self.site.loadrevisions(page, total=1, content=True)
self.assertFalse(hasattr(page, '_text'))
self.assertIsNotNone(page._latest_cached_revision())
diff --git a/tests/site_tests.py b/tests/site_tests.py
index e42c3eb..667011e 100644
--- a/tests/site_tests.py
+++ b/tests/site_tests.py
@@ -2384,9 +2384,9 @@
# The revision content will be loaded by .text
self.assertIsNotNone(self.mainpage.text)
- def testLoadRevisions_getText(self):
- """Test the site.loadrevisions() method with getText=True."""
- self.mysite.loadrevisions(self.mainpage, getText=True, total=5)
+ def test_loadrevisions_content(self):
+ """Test the site.loadrevisions() method with content=True."""
+ self.mysite.loadrevisions(self.mainpage, content=True, total=5)
self.assertFalse(hasattr(self.mainpage, '_text'))
self.assertIn(self.mainpage._revid, self.mainpage._revisions)
self.assertIsNotNone(self.mainpage._revisions[self.mainpage._revid].text)
--
To view, visit https://gerrit.wikimedia.org/r/425128
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-MessageType: merged
Gerrit-Change-Id: I6125fd1f1aec429548f2335e2d052dbccbb222f1
Gerrit-Change-Number: 425128
Gerrit-PatchSet: 2
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Dalba <dalba.wiki(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: Zoranzoki21 <zorandori4444(a)gmail.com>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/425465 )
Change subject: [IMPR] use context manager to open a file
......................................................................
[IMPR] use context manager to open a file
also remove hidden comment because it is unclear what to do with it
Change-Id: I88814540b446aceb138fdf0b335125e022c3013c
---
M scripts/editarticle.py
1 file changed, 2 insertions(+), 7 deletions(-)
Approvals:
Dalba: Looks good to me, approved
jenkins-bot: Verified
diff --git a/scripts/editarticle.py b/scripts/editarticle.py
index 938bde2..f89e58a 100755
--- a/scripts/editarticle.py
+++ b/scripts/editarticle.py
@@ -45,10 +45,6 @@
"""Edit a wiki page."""
- # join lines if line starts with this ones
- # TODO: No apparent usage
- # joinchars = string.letters + '[]' + string.digits
-
def __init__(self, *args):
"""Constructor."""
self.set_options(*args)
@@ -85,9 +81,8 @@
def handle_edit_conflict(self, new):
"""When an edit conflict occures save the new text to a file."""
fn = os.path.join(tempfile.gettempdir(), self.page.title())
- fp = open(fn, 'w')
- fp.write(new)
- fp.close()
+ with open(fn, 'w') as fp:
+ fp.write(new)
pywikibot.output(
"An edit conflict has arisen. Your edit has been saved to %s. "
"Please try again." % fn)
--
To view, visit https://gerrit.wikimedia.org/r/425465
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-MessageType: merged
Gerrit-Change-Id: I88814540b446aceb138fdf0b335125e022c3013c
Gerrit-Change-Number: 425465
Gerrit-PatchSet: 1
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Dalba <dalba.wiki(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Zoranzoki21 <zorandori4444(a)gmail.com>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/425121 )
Change subject: [tox] Disable flake8-string-format rule P101
......................................................................
[tox] Disable flake8-string-format rule P101
P101 rule "format string does contain unindexed parameters" was introduced
for python 2.6 compatibility. Since py 2.6 was dropped, the implicit index
arguments of format strings could be enabled which is by the wax closer to
the modulo format string.
Change-Id: I131c760881da3ae6b67b03b6976cc6653b6f7d77
---
M tox.ini
1 file changed, 2 insertions(+), 1 deletion(-)
Approvals:
Dalba: Looks good to me, approved
jenkins-bot: Verified
diff --git a/tox.ini b/tox.ini
index 700e857..bd0cf03 100644
--- a/tox.ini
+++ b/tox.ini
@@ -150,6 +150,7 @@
# H405: docstring summary line
# H301,I100,I101,202: import order rules; Pywikibot uses rules H306 and I201
# W503: line break before binary operator; against current PEP 8 recommendation
+# P101: format string does contain unindexed parameters
# The following are to be fixed
# D102: Missing docstring in public method
@@ -163,7 +164,7 @@
# D413: Missing blank line after last section
# D412: No blank lines allowed between a section header and its content
-ignore = E402,D105,D211,FI10,FI12,FI13,FI15,FI16,FI17,FI5,H101,H236,H301,H404,H405,H903,I100,I101,I202,N802,N803,N806,D401,D413,D103,D412,W503
+ignore = E402,D105,D211,FI10,FI12,FI13,FI15,FI16,FI17,FI5,H101,H236,H301,H404,H405,H903,I100,I101,I202,N802,N803,N806,D401,D413,D103,D412,P101,W503
exclude = .tox,.git,./*.egg,ez_setup.py,build,externals,user-config.py,./scripts/i18n/*,scripts/userscripts/*
min-version = 2.7
max_line_length = 100
--
To view, visit https://gerrit.wikimedia.org/r/425121
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-MessageType: merged
Gerrit-Change-Id: I131c760881da3ae6b67b03b6976cc6653b6f7d77
Gerrit-Change-Number: 425121
Gerrit-PatchSet: 1
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Dalba <dalba.wiki(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Zoranzoki21 <zorandori4444(a)gmail.com>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/423884 )
Change subject: [IMPR] reduce complexity of pywikibot.__init__.Site
......................................................................
[IMPR] reduce complexity of pywikibot.__init__.Site
- Reduce code complexity measures by radon from 26 to 19 using a private
procedure to store and retrieve code and family pair for a given url.
- Simplify matching_sites conditions. Don't save None for an invalid url
because it is raising an exception already. Check for code and fam with
url later in the code.
- Shorten the try clause when importing interface part
Change-Id: Ice41f5dcb2080eebe1eacf44ac88204949588fcc
---
M pywikibot/__init__.py
1 file changed, 40 insertions(+), 32 deletions(-)
Approvals:
Dalba: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/__init__.py b/pywikibot/__init__.py
index 0d612f3..62080b9 100644
--- a/pywikibot/__init__.py
+++ b/pywikibot/__init__.py
@@ -1176,6 +1176,35 @@
_url_cache = {} # The code/fam pair for each URL
+def _code_fam_from_url(url):
+ """Set url to cache and get code and family from cache.
+
+ Site helper method.
+ @param url: The site URL to get code and family
+ @type url: string
+ @raises SiteDefinitionError: Unknown URL
+ """
+ if url not in _url_cache:
+ matched_sites = []
+ # Iterate through all families and look, which does apply to
+ # the given URL
+ for fam in config.family_files:
+ family = Family.load(fam)
+ code = family.from_url(url)
+ if code is not None:
+ matched_sites.append((code, family))
+
+ if not matched_sites:
+ # TODO: As soon as AutoFamily is ready, try and use an
+ # AutoFamily
+ raise SiteDefinitionError("Unknown URL '{0}'.".format(url))
+ if len(matched_sites) > 1:
+ warning('Found multiple matches for URL "{0}": {1} (use first)'
+ .format(url, ', '.join(str(s) for s in matched_sites)))
+ _url_cache[url] = matched_sites[0]
+ return _url_cache[url]
+
+
def Site(code=None, fam=None, user=None, sysop=None, interface=None, url=None):
"""A factory method to obtain a Site object.
@@ -1199,41 +1228,19 @@
URL. Still requires that the family supporting that URL exists.
@type url: string
@rtype: pywikibot.site.APISite
-
+ @raises ValueError: URL and pair of code and family given
+ @raises ValueError: Invalid interface name
+ @raises SiteDefinitionError: Unknown URL
"""
- # Either code and fam or only url
- if url and (code or fam):
- raise ValueError('URL to the wiki OR a pair of code and family name '
- 'should be provided')
_logger = "wiki"
if url:
- if url not in _url_cache:
- matched_sites = []
- # Iterate through all families and look, which does apply to
- # the given URL
- for fam in config.family_files:
- family = Family.load(fam)
- code = family.from_url(url)
- if code is not None:
- matched_sites += [(code, family)]
-
- if matched_sites:
- if len(matched_sites) > 1:
- warning(
- 'Found multiple matches for URL "{0}": {1} (use first)'
- .format(url, ', '.join(str(s) for s in matched_sites)))
- _url_cache[url] = matched_sites[0]
- else:
- # TODO: As soon as AutoFamily is ready, try and use an
- # AutoFamily
- _url_cache[url] = None
-
- cached = _url_cache[url]
- if cached:
- code, fam = cached
- else:
- raise SiteDefinitionError("Unknown URL '{0}'.".format(url))
+ # Either code and fam or only url
+ if code or fam:
+ raise ValueError(
+ 'URL to the wiki OR a pair of code and family name '
+ 'should be provided')
+ code, fam = _code_fam_from_url(url)
else:
# Fallback to config defaults
code = code or config.mylang
@@ -1259,9 +1266,10 @@
# If it isnt a class, assume it is a string
try:
tmp = __import__('pywikibot.site', fromlist=[interface])
- interface = getattr(tmp, interface)
except ImportError:
raise ValueError('Invalid interface name: {0}'.format(interface))
+ else:
+ interface = getattr(tmp, interface)
if not issubclass(interface, BaseSite):
warning('Site called with interface=%s' % interface.__name__)
--
To view, visit https://gerrit.wikimedia.org/r/423884
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-MessageType: merged
Gerrit-Change-Id: Ice41f5dcb2080eebe1eacf44ac88204949588fcc
Gerrit-Change-Number: 423884
Gerrit-PatchSet: 3
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Dalba <dalba.wiki(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: Zoranzoki21 <zorandori4444(a)gmail.com>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/403906 )
Change subject: [cleanup] use assert instead of raising AssertionError
......................................................................
[cleanup] use assert instead of raising AssertionError
Change-Id: I9ae1af2cb2557169239052a3bb5f7b23e6283fb1
---
M pywikibot/date.py
M pywikibot/page.py
2 files changed, 22 insertions(+), 26 deletions(-)
Approvals:
Framawiki: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/date.py b/pywikibot/date.py
index b6c45ab..bf01478 100644
--- a/pywikibot/date.py
+++ b/pywikibot/date.py
@@ -7,7 +7,7 @@
# (C) Andre Engels, 2004-2005
# (C) Yuri Astrakhan, 2005-2006 (<Firstname><Lastname>@gmail.com)
# (years/decades/centuries/millenniums str <=> int conversions)
-# (C) Pywikibot team, 2004-2017
+# (C) Pywikibot team, 2004-2018
#
# Distributed under the terms of the MIT license.
#
@@ -384,10 +384,9 @@
if isinstance(dec, basestring):
# Special case for strings that are replaced instead of
# decoded
- if len(s) == 3:
- raise AssertionError(
- "Invalid pattern %s: Cannot use zero padding size "
- "in %s!" % (pattern, s))
+ assert len(s) < 3, (
+ 'Invalid pattern {0}: Cannot use zero padding size '
+ 'in {1}!'.format(pattern, s))
newPattern += re.escape(dec)
strPattern += s # Keep the original text
else:
@@ -451,8 +450,8 @@
for i in range(len(decoders))]
decValue = decf(values)
- if isinstance(decValue, basestring):
- raise AssertionError("Decoder must not return a string!")
+ assert not isinstance(decValue, basestring), \
+ 'Decoder must not return a string!'
# recursive call to re-encode and see if we get the original
# (may through filter exception)
@@ -473,19 +472,17 @@
MakeParameter = _make_parameter
if type(params) in _listTypes:
- if len(params) != len(decoders):
- raise AssertionError(
- "parameter count (%d) does not match decoder count (%d)"
- % (len(params), len(decoders)))
+ assert len(params) == len(decoders), (
+ 'parameter count ({0}) does not match decoder count ({1})'
+ .format(len(params), len(decoders)))
# convert integer parameters into their textual representation
params = [MakeParameter(decoders[i], params[i])
for i in range(len(params))]
return strPattern % tuple(params)
else:
- if 1 != len(decoders):
- raise AssertionError(
- "A single parameter does not match %d decoders."
- % len(decoders))
+ assert len(decoders) == 1, (
+ 'A single parameter does not match {0} decoders.'
+ .format(len(decoders)))
# convert integer parameter into its textual representation
return strPattern % MakeParameter(decoders[0], params)
@@ -1959,15 +1956,16 @@
def addFmt1(lang, isMnthOfYear, patterns):
- """Add 12 month formats for a specific type ('January','Feb..), for a given language.
+ """Add 12 month formats for a specific type ('January', 'Feb.').
The function must accept one parameter for the ->int or ->string
conversions, just like everywhere else in the formats map.
The patterns parameter is a list of 12 elements to be used for each month.
+ @param lang: language code
+ @type lang: str
"""
- if len(patterns) != 12:
- raise AssertionError(u'pattern %s does not have 12 elements' % lang)
+ assert len(patterns) == 12, 'pattern %s does not have 12 elements' % lang
for i in range(12):
if patterns[i] is not None:
diff --git a/pywikibot/page.py b/pywikibot/page.py
index 6cff93b..22438fd 100644
--- a/pywikibot/page.py
+++ b/pywikibot/page.py
@@ -5219,10 +5219,9 @@
@rtype: int or long
@raises AssertionError: parent id not supplied to the constructor
"""
- if self._parent_id is None:
- raise AssertionError(
- 'Revision %d was instantiated without a parent id'
- % self.revid)
+ assert self._parent_id is not None, (
+ 'Revision {0} was instantiated without a parent id'
+ .format(self.revid))
return self._parent_id
@@ -5237,10 +5236,9 @@
which always occurs for MediaWiki versions lower than 1.21.
"""
# TODO: T102735: Add a sane default of 'wikitext' and others for <1.21
- if self._content_model is None:
- raise AssertionError(
- 'Revision %d was instantiated without a content model'
- % self.revid)
+ assert self._content_model is not None, (
+ 'Revision {0} was instantiated without a content model'
+ .format(self.revid))
return self._content_model
--
To view, visit https://gerrit.wikimedia.org/r/403906
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-MessageType: merged
Gerrit-Change-Id: I9ae1af2cb2557169239052a3bb5f7b23e6283fb1
Gerrit-Change-Number: 403906
Gerrit-PatchSet: 5
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Dalba <dalba.wiki(a)gmail.com>
Gerrit-Reviewer: Framawiki <framawiki(a)tools.wmflabs.org>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: Zoranzoki21 <zorandori4444(a)gmail.com>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/421805 )
Change subject: [IMPR] LogDict: Raise HiddenKeyError when a key is hidden
......................................................................
[IMPR] LogDict: Raise HiddenKeyError when a key is hidden
When part of a log event (key) is hidden 'actionhidden', 'commenthidden',
and/or 'userhidden' keys are included in the API response. If the desired
key is missing and the corresponsing hidden key is present, then raise
HiddenKeyError.
Bug: T187635
Change-Id: Iba46f5b17057ce825d6d1a94d9ccacb2a6a051dc
---
M pywikibot/exceptions.py
M pywikibot/logentries.py
M tests/logentry_tests.py
3 files changed, 25 insertions(+), 11 deletions(-)
Approvals:
Xqt: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/exceptions.py b/pywikibot/exceptions.py
index 929c1a4..07d47b7 100644
--- a/pywikibot/exceptions.py
+++ b/pywikibot/exceptions.py
@@ -542,6 +542,13 @@
pass
+class HiddenKeyError(UserRightsError, KeyError):
+
+ """Insufficient user rights to view the hidden key."""
+
+ pass
+
+
class NotEmailableError(PageRelatedError):
"""This user is not emailable."""
diff --git a/pywikibot/logentries.py b/pywikibot/logentries.py
index c12ff73..01f2246 100644
--- a/pywikibot/logentries.py
+++ b/pywikibot/logentries.py
@@ -10,7 +10,7 @@
import sys
import pywikibot
-from pywikibot.exceptions import Error
+from pywikibot.exceptions import Error, HiddenKeyError
from pywikibot.tools import deprecated, classproperty
if sys.version_info[0] > 2:
@@ -22,7 +22,10 @@
class LogDict(dict):
"""
- Simple custom dict that raises a custom KeyError when a key is missing.
+ Simple custom dict that raises custom Errors when a key is missing.
+
+ HiddenKeyError is raised when the user does not have permission.
+ KeyError is raised otherwise.
It also logs debugging information when a key is missing.
"""
@@ -31,6 +34,13 @@
"""Debug when the key is missing."""
pywikibot.debug(u"API log entry received:\n" + repr(self),
_logger)
+ if ((key in ('ns', 'title', 'pageid', 'logpage', 'params', 'action')
+ and 'actionhidden' in self)
+ or (key == 'comment' and 'commenthidden' in self)
+ or (key == 'user' and 'userhidden' in self)):
+ raise HiddenKeyError(
+ "Log entry ({0}) has a hidden '{1}' key and you don't have "
+ 'permission to view it.'.format(self._type, key))
raise KeyError("Log entry (%s) has no '%s' key" % (self._type, key))
@@ -112,12 +122,8 @@
"""
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
-
@return: page on action was performed
@rtype: pywikibot.Page
- @raise: KeyError: title was missing from log entry
"""
if not hasattr(self, '_page'):
self._page = pywikibot.Page(self.site, self.data['title'])
@@ -146,7 +152,6 @@
def comment(self):
"""Return the logentry's comment.
- @raise KeyError: The log entry has no 'comment' key.
@rtype: str
"""
return self.data['comment']
@@ -300,11 +305,7 @@
"""
Return FilePage 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
-
@rtype: pywikibot.FilePage
- @raise: KeyError: title was missing from log entry
"""
if not hasattr(self, '_page'):
self._page = pywikibot.FilePage(self.site, self.data['title'])
diff --git a/tests/logentry_tests.py b/tests/logentry_tests.py
index 50f900a..05c734f 100644
--- a/tests/logentry_tests.py
+++ b/tests/logentry_tests.py
@@ -11,6 +11,7 @@
import pywikibot
+from pywikibot.exceptions import HiddenKeyError
from pywikibot.logentries import LogEntryFactory, UserTargetLogEntry
from pywikibot.tools import (
MediaWikiVersion,
@@ -77,6 +78,11 @@
self.assertIsInstance(logentry.action(), unicode)
try:
self.assertIsInstance(logentry.comment(), unicode)
+ except HiddenKeyError as e:
+ self.assertRegex(
+ str(e),
+ "Log entry ([^)]+) has a hidden 'comment' key, and you don't "
+ 'have permission to view it.')
except KeyError as e:
self.assertRegex(str(e), "Log entry ([^)]+) has no 'comment' key")
self.assertIsInstance(logentry.logid(), int)
--
To view, visit https://gerrit.wikimedia.org/r/421805
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-MessageType: merged
Gerrit-Change-Id: Iba46f5b17057ce825d6d1a94d9ccacb2a6a051dc
Gerrit-Change-Number: 421805
Gerrit-PatchSet: 11
Gerrit-Owner: JJMC89 <JJMC89.Wikimedia(a)gmail.com>
Gerrit-Reviewer: Framawiki <framawiki(a)tools.wmflabs.org>
Gerrit-Reviewer: JJMC89 <JJMC89.Wikimedia(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: Zhuyifei1999 <zhuyifei1999(a)gmail.com>
Gerrit-Reviewer: Zoranzoki21 <zorandori4444(a)gmail.com>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/364423 )
Change subject: site.categorymembers: Support cmstartsortkeyprefix API parameter
......................................................................
site.categorymembers: Support cmstartsortkeyprefix API parameter
site.py:
- Fix the docstring for `startsort` and `endsort`. They are in binary
string format and cannot be compared with a title directly. Also mention
that they are deprecated in MW 1.24.
- Add two new parameters, `startprefix` and `endprefix`, to be passed to
API as `cmstartsortkeyprefix` and `cmendsortkeyprefix`.
The old `startsort` is passed to API as `cmstartsortkey` which is
deprecated and is not compatible with `cmstartsortkeyprefix`. Also
working with `startsort` is difficult as it should be a "binary string"
usually obtained via API or directly from the database. (See T74101)
page.py:
- Add the new parameters to `Category.articles`.
Bug: T170265
Change-Id: I08f4a7a4bb1bbe5c10fe678cc7a951a273f61d07
---
M pywikibot/page.py
M pywikibot/site.py
2 files changed, 64 insertions(+), 15 deletions(-)
Approvals:
Xqt: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/page.py b/pywikibot/page.py
index 6b4d8cf..97f03e1 100644
--- a/pywikibot/page.py
+++ b/pywikibot/page.py
@@ -2802,7 +2802,9 @@
def articles(self, recurse=False, total=None,
content=False, namespaces=None, sortby=None,
reverse=False, starttime=None, endtime=None,
- startsort=None, endsort=None):
+ startsort=None, endsort=None,
+ startprefix=None, endprefix=None,
+ ):
"""
Yield all articles in the current category.
@@ -2833,12 +2835,22 @@
@param endtime: if provided, only generate pages added before this
time; not valid unless sortby="timestamp"
@type endtime: pywikibot.Timestamp
- @param startsort: if provided, only generate pages >= this title
- lexically; not valid if sortby="timestamp"
+ @param startsort: if provided, only generate pages that have a
+ sortkey >= startsort; not valid if sortby="timestamp"
+ (Deprecated in MW 1.24)
@type startsort: str
- @param endsort: if provided, only generate pages <= this title
- lexically; not valid if sortby="timestamp"
+ @param endsort: if provided, only generate pages that have a
+ sortkey <= endsort; not valid if sortby="timestamp"
+ (Deprecated in MW 1.24)
@type endsort: str
+ @param startprefix: if provided, only generate pages >= this title
+ lexically; not valid if sortby="timestamp"; overrides "startsort"
+ (requires MW 1.18+)
+ @type startprefix: str
+ @param endprefix: if provided, only generate pages < this title
+ lexically; not valid if sortby="timestamp"; overrides "endsort"
+ (requires MW 1.18+)
+ @type endprefix: str
"""
for member in self.site.categorymembers(self,
namespaces=namespaces,
@@ -2849,6 +2861,8 @@
endtime=endtime,
startsort=startsort,
endsort=endsort,
+ startprefix=startprefix,
+ endprefix=endprefix,
member_type=['page', 'file']
):
yield member
@@ -2868,7 +2882,9 @@
starttime=starttime,
endtime=endtime,
startsort=startsort,
- endsort=endsort
+ endsort=endsort,
+ startprefix=startprefix,
+ endprefix=endprefix,
):
yield article
if total is not None:
diff --git a/pywikibot/site.py b/pywikibot/site.py
index 88f48eb..e5bc29a 100644
--- a/pywikibot/site.py
+++ b/pywikibot/site.py
@@ -3811,7 +3811,9 @@
def categorymembers(self, category, namespaces=None, sortby=None,
reverse=False, starttime=None, endtime=None,
startsort=None, endsort=None, total=None,
- content=False, member_type=None):
+ content=False, member_type=None,
+ startprefix=None, endprefix=None,
+ ):
"""Iterate members of specified category.
@param category: The Category to iterate.
@@ -3833,13 +3835,22 @@
@type starttime: pywikibot.Timestamp
@param endtime: if provided, only generate pages added before this
time; not valid unless sortby="timestamp"
- @type endtime: pywikibot.Timestamp
- @param startsort: if provided, only generate pages >= this title
- lexically; not valid if sortby="timestamp"
+ @param startsort: if provided, only generate pages that have a
+ sortkey >= startsort; not valid if sortby="timestamp"
+ (Deprecated in MW 1.24)
@type startsort: str
- @param endsort: if provided, only generate pages <= this title
- lexically; not valid if sortby="timestamp"
+ @param endsort: if provided, only generate pages that have a
+ sortkey <= endsort; not valid if sortby="timestamp"
+ (Deprecated in MW 1.24)
@type endsort: str
+ @param startprefix: if provided, only generate pages >= this title
+ lexically; not valid if sortby="timestamp"; overrides "startsort"
+ (requires MW 1.18+)
+ @type startprefix: str
+ @param endprefix: if provided, only generate pages < this title
+ lexically; not valid if sortby="timestamp"; overrides "endsort"
+ (requires MW 1.18+)
+ @type endprefix: str
@param content: if True, load the current content of each iterated page
(default False)
@type content: bool
@@ -3849,6 +3860,8 @@
@type member_type: str or iterable of str; values: page, subcat, file
@raises KeyError: a namespace identifier was not resolved
+ @raises NotImplementedError: startprefix or endprefix parameters are
+ given but site.version is less than 1.18.
@raises TypeError: a namespace identifier has an inappropriate
type such as NoneType or bool
"""
@@ -3868,7 +3881,10 @@
if starttime and endtime and starttime > endtime:
raise ValueError(
"categorymembers: starttime must be before endtime")
- if startsort and endsort and startsort > endsort:
+ if startprefix and endprefix and startprefix > endprefix:
+ raise ValueError(
+ 'categorymembers: startprefix must be less than endprefix')
+ elif startsort and endsort and startsort > endsort:
raise ValueError(
"categorymembers: startsort must be less than endsort")
@@ -3923,6 +3939,7 @@
# sort; we take care of this reversal for the user
(starttime, endtime) = (endtime, starttime)
(startsort, endsort) = (endsort, startsort)
+ (startprefix, endprefix) = (endprefix, startprefix)
if starttime and sortby == "timestamp":
cmargs["gcmstart"] = starttime
elif starttime:
@@ -3933,12 +3950,28 @@
elif endtime:
raise ValueError("categorymembers: "
"invalid combination of 'sortby' and 'endtime'")
- if startsort and sortby != "timestamp":
+ if startprefix and sortby != 'timestamp':
+ if self.version() < MediaWikiVersion('1.18'):
+ raise NotImplementedError(
+ 'categorymembers: "startprefix" requires MW 1.18+')
+ cmargs['gcmstartsortkeyprefix'] = startprefix
+ elif startprefix:
+ raise ValueError('categorymembers: invalid combination of '
+ "'sortby' and 'startprefix'")
+ elif startsort and sortby != 'timestamp':
cmargs["gcmstartsortkey"] = startsort
elif startsort:
raise ValueError("categorymembers: "
"invalid combination of 'sortby' and 'startsort'")
- if endsort and sortby != "timestamp":
+ if endprefix and sortby != 'timestamp':
+ if self.version() < MediaWikiVersion('1.18'):
+ raise NotImplementedError(
+ 'categorymembers: "endprefix" requires MW 1.18+')
+ cmargs['cmendsortkeyprefix'] = endprefix
+ elif endprefix:
+ raise ValueError('categorymembers: '
+ "invalid combination of 'sortby' and 'endprefix'")
+ elif endsort and sortby != 'timestamp':
cmargs["gcmendsortkey"] = endsort
elif endsort:
raise ValueError("categorymembers: "
--
To view, visit https://gerrit.wikimedia.org/r/364423
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-MessageType: merged
Gerrit-Change-Id: I08f4a7a4bb1bbe5c10fe678cc7a951a273f61d07
Gerrit-Change-Number: 364423
Gerrit-PatchSet: 12
Gerrit-Owner: Dalba <dalba.wiki(a)gmail.com>
Gerrit-Reviewer: Dalba <dalba.wiki(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Magul <tomasz.magulski(a)gmail.com>
Gerrit-Reviewer: Mpaa <mpaa.wiki(a)gmail.com>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/423829 )
Change subject: appveyor.yml: Use a shallow clone instead of cloning the whole repository
......................................................................
appveyor.yml: Use a shallow clone instead of cloning the whole repository
A depth of 50 is chosen as it would be very unusual for pywikibot to have
more than 50 builds in Appveyor queue.
See https://www.appveyor.com/docs/how-to/repository-shallow-clone/ for
more info.
Change-Id: Ic9959ffb5f75592eb78fdcea9c41bf8812aae79a
---
M .appveyor.yml
1 file changed, 1 insertion(+), 0 deletions(-)
Approvals:
Xqt: Looks good to me, approved
jenkins-bot: Verified
diff --git a/.appveyor.yml b/.appveyor.yml
index 85ccee8..2755449 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -1,3 +1,4 @@
+clone_depth: 50
environment:
global:
--
To view, visit https://gerrit.wikimedia.org/r/423829
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-MessageType: merged
Gerrit-Change-Id: Ic9959ffb5f75592eb78fdcea9c41bf8812aae79a
Gerrit-Change-Number: 423829
Gerrit-PatchSet: 1
Gerrit-Owner: Dalba <dalba.wiki(a)gmail.com>
Gerrit-Reviewer: Dalba <dalba.wiki(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: Zoranzoki21 <zorandori4444(a)gmail.com>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/423704 )
Change subject: [doc] Prepare next release
......................................................................
[doc] Prepare next release
Change-Id: I2d95732d235d27652ed44ffc4a1f61acfe0b08a6
---
M HISTORY.rst
M docs/conf.py
2 files changed, 8 insertions(+), 1 deletion(-)
Approvals:
Dalba: Looks good to me, approved
jenkins-bot: Verified
diff --git a/HISTORY.rst b/HISTORY.rst
index 525006e..067510d 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -3,6 +3,13 @@
Current release
---------------
+
+* Bugfixes and improvements
+* Localisation updates
+
+3.0.20180403
+------------
+
* Deprecation warning: support for py2.7.2 and py2.7.3 will be dropped
* Dropped support for Python 2.6 (T154771)
* Dropped support for Python 3.3 (T184508)
diff --git a/docs/conf.py b/docs/conf.py
index 9ead2b0..2c140a4 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -67,7 +67,7 @@
# The short X.Y version.
version = '3.0'
# The full version, including alpha/beta/rc tags.
-release = '3.0.20180304'
+release = '3.0.20180403'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
--
To view, visit https://gerrit.wikimedia.org/r/423704
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-MessageType: merged
Gerrit-Change-Id: I2d95732d235d27652ed44ffc4a1f61acfe0b08a6
Gerrit-Change-Number: 423704
Gerrit-PatchSet: 2
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Dalba <dalba.wiki(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Zoranzoki21 <zorandori4444(a)gmail.com>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/423702 )
Change subject: [cleanup] Cleanup TODO list
......................................................................
[cleanup] Cleanup TODO list
remove "Rewrite help parser to support earlier releases" from TODO list.
MW 1.10 seems early enough.
Change-Id: Idaae1e38cf2234dfbb8a994cf48a790602e824ed
---
M pywikibot/data/api.py
1 file changed, 0 insertions(+), 2 deletions(-)
Approvals:
Dalba: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/data/api.py b/pywikibot/data/api.py
index 278ff81..29030fe 100644
--- a/pywikibot/data/api.py
+++ b/pywikibot/data/api.py
@@ -172,8 +172,6 @@
It does not support the format modules.
- TODO: Rewrite help parser to support earlier releases.
-
TODO: establish a data structure in the class which prefills
the param information available for a site given its
version, using the API information available for each
--
To view, visit https://gerrit.wikimedia.org/r/423702
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-MessageType: merged
Gerrit-Change-Id: Idaae1e38cf2234dfbb8a994cf48a790602e824ed
Gerrit-Change-Number: 423702
Gerrit-PatchSet: 1
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Dalba <dalba.wiki(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Zoranzoki21 <zorandori4444(a)gmail.com>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/423483 )
Change subject: [cleanup] Remove old for dropped python versions
......................................................................
[cleanup] Remove old for dropped python versions
- tools module:
python 2.7.0 and python 2.6 aren't supported anymore
Therefore the ContextManagerWrapper code part
for bz2 and gzip files could be removed
- bot module:
total_seconds() was introduced with 2.7
- deprecate ContextManagerWrapper
Change-Id: I50cc95bc7b9a8845b11d2515a97313511668cdcd
---
M pywikibot/bot.py
M pywikibot/tools/__init__.py
M tests/tools_tests.py
3 files changed, 56 insertions(+), 61 deletions(-)
Approvals:
Dalba: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/bot.py b/pywikibot/bot.py
index ceb9459..802ab04 100644
--- a/pywikibot/bot.py
+++ b/pywikibot/bot.py
@@ -96,7 +96,7 @@
)
from pywikibot.logging import critical
from pywikibot.tools import (
- deprecated, deprecate_arg, deprecated_args, PY2, PYTHON_VERSION,
+ deprecated, deprecate_arg, deprecated_args, PY2,
)
from pywikibot.tools._logging import (
LoggingFormatter as _LoggingFormatter,
@@ -1380,10 +1380,7 @@
% (self._treat_counter, self._save_counter))
if hasattr(self, '_start_ts'):
delta = (pywikibot.Timestamp.now() - self._start_ts)
- if PYTHON_VERSION >= (2, 7):
- seconds = int(delta.total_seconds())
- else:
- seconds = delta.seconds + delta.days * 86400
+ seconds = int(delta.total_seconds())
if delta.days:
pywikibot.output("Execution time: %d days, %d seconds"
% (delta.days, delta.seconds))
diff --git a/pywikibot/tools/__init__.py b/pywikibot/tools/__init__.py
index 95eae41..4df67fc 100644
--- a/pywikibot/tools/__init__.py
+++ b/pywikibot/tools/__init__.py
@@ -988,48 +988,6 @@
raise StopIteration
-class ContextManagerWrapper(object):
-
- """
- Wraps an object in a context manager.
-
- It is redirecting all access to the wrapped object and executes 'close' when
- used as a context manager in with-statements. In such statements the value
- set via 'as' is directly the wrapped object. For example:
-
- >>> class Wrapper(object):
- ... def close(self): pass
- >>> an_object = Wrapper()
- >>> wrapped = ContextManagerWrapper(an_object)
- >>> with wrapped as another_object:
- ... assert another_object is an_object
-
- It does not subclass the object though, so isinstance checks will fail
- outside a with-statement.
- """
-
- def __init__(self, wrapped):
- """Create a new wrapper."""
- super(ContextManagerWrapper, self).__init__()
- super(ContextManagerWrapper, self).__setattr__('_wrapped', wrapped)
-
- def __enter__(self):
- """Enter a context manager and use the wrapped object directly."""
- return self._wrapped
-
- def __exit__(self, exc_type, exc_value, traceback):
- """Call close on the wrapped object when exiting a context manager."""
- self._wrapped.close()
-
- def __getattr__(self, name):
- """Get the attribute from the wrapped object."""
- return getattr(self._wrapped, name)
-
- def __setattr__(self, name, value):
- """Set the attribute in the wrapped object."""
- setattr(self._wrapped, name, value)
-
-
def open_archive(filename, mode='rb', use_extension=True):
"""
Open a file and uncompress it if needed.
@@ -1060,17 +1018,8 @@
It is also raised by bz2 when its content is invalid. gzip does not
immediately raise that error but only on reading it.
@return: A file-like object returning the uncompressed data in binary mode.
- Before Python 2.7 the GzipFile object and before 2.7.1 the BZ2File are
- wrapped in a ContextManagerWrapper with its advantages/disadvantages.
@rtype: file-like object
"""
- def wrap(wrapped, sub_ver):
- """Wrap in a wrapper when this is below Python version 2.7."""
- if PYTHON_VERSION < (2, 7, sub_ver):
- return ContextManagerWrapper(wrapped)
- else:
- return wrapped
-
if mode in ('r', 'a', 'w'):
mode += 'b'
elif mode not in ('rb', 'ab', 'wb'):
@@ -1097,9 +1046,9 @@
if extension == 'bz2':
if isinstance(bz2, ImportError):
raise bz2
- return wrap(bz2.BZ2File(filename, mode), 1)
+ return bz2.BZ2File(filename, mode)
elif extension == 'gz':
- return wrap(gzip.open(filename, mode), 0)
+ return gzip.open(filename, mode)
elif extension == '7z':
if mode != 'rb':
raise NotImplementedError('It is not possible to write a 7z file.')
@@ -1785,8 +1734,53 @@
sha.update(read_bytes)
return sha.hexdigest()
+# deprecated parts ############################################################
+
+
+class ContextManagerWrapper(object):
+
+ """
+ DEPRECATED. Wraps an object in a context manager.
+
+ It is redirecting all access to the wrapped object and executes 'close'
+ when used as a context manager in with-statements. In such statements the
+ value set via 'as' is directly the wrapped object. For example:
+
+ >>> class Wrapper(object):
+ ... def close(self): pass
+ >>> an_object = Wrapper()
+ >>> wrapped = ContextManagerWrapper(an_object)
+ >>> with wrapped as another_object:
+ ... assert another_object is an_object
+
+ It does not subclass the object though, so isinstance checks will fail
+ outside a with-statement.
+ """
+
+ def __init__(self, wrapped):
+ """Create a new wrapper."""
+ super(ContextManagerWrapper, self).__init__()
+ super(ContextManagerWrapper, self).__setattr__('_wrapped', wrapped)
+
+ def __enter__(self):
+ """Enter a context manager and use the wrapped object directly."""
+ return self._wrapped
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ """Call close on the wrapped object when exiting a context manager."""
+ self._wrapped.close()
+
+ def __getattr__(self, name):
+ """Get the attribute from the wrapped object."""
+ return getattr(self._wrapped, name)
+
+ def __setattr__(self, name, value):
+ """Set the attribute in the wrapped object."""
+ setattr(self._wrapped, name, value)
+
wrapper = ModuleDeprecationWrapper(__name__)
wrapper._add_deprecated_attr('Counter', collections.Counter)
wrapper._add_deprecated_attr('OrderedDict', collections.OrderedDict)
wrapper._add_deprecated_attr('count', itertools.count)
+wrapper._add_deprecated_attr('ContextManagerWrapper', replacement_name='')
diff --git a/tests/tools_tests.py b/tests/tools_tests.py
index ab58205..e0429c0 100644
--- a/tests/tools_tests.py
+++ b/tests/tools_tests.py
@@ -16,7 +16,7 @@
import warnings
from pywikibot import tools
-from pywikibot.tools import classproperty
+from pywikibot.tools import classproperty, suppress_warnings
from tests import join_xml_data_path, mock
from tests.aspects import (
@@ -49,7 +49,9 @@
def test_wrapper(self):
"""Create a test instance and verify the wrapper redirects."""
obj = self.DummyClass()
- wrapped = tools.ContextManagerWrapper(obj)
+ with suppress_warnings(
+ 'pywikibot.tools.ContextManagerWrapper is deprecated.'):
+ wrapped = tools.ContextManagerWrapper(obj)
self.assertIs(wrapped.class_var, obj.class_var)
self.assertIs(wrapped.instance_var, obj.instance_var)
self.assertIs(wrapped._wrapped, obj)
@@ -63,7 +65,9 @@
def test_exec_wrapper(self):
"""Check that the wrapper permits exceptions."""
- wrapper = tools.ContextManagerWrapper(self.DummyClass())
+ with suppress_warnings(
+ 'pywikibot.tools.ContextManagerWrapper is deprecated.'):
+ wrapper = tools.ContextManagerWrapper(self.DummyClass())
self.assertFalse(wrapper.closed)
with self.assertRaisesRegex(ZeroDivisionError,
'(integer division or modulo by zero|division by zero)'):
--
To view, visit https://gerrit.wikimedia.org/r/423483
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-MessageType: merged
Gerrit-Change-Id: I50cc95bc7b9a8845b11d2515a97313511668cdcd
Gerrit-Change-Number: 423483
Gerrit-PatchSet: 5
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: Dalba <dalba.wiki(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Zoranzoki21 <zorandori4444(a)gmail.com>
Gerrit-Reviewer: jenkins-bot <>