jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/364993 )
Change subject: Decommission rcstream
......................................................................
Decommission rcstream
rcstream is offline since 2017-07-10
- remove comms/rcstream.py
- change docs/api_ref/pywikibot.comms.rst which solves T168831
- change docs and README.rst
- add usage documentation to EventStreams
- update documentation in pagegenerators.py
- remove rcstreams_port and rcstreams_path methods from WikimediaFamily
and rename rcstreams_host to eventstreams_host
- remove dependency from setup.py
- skip doctest for eventstreams.py
- Add numpydoc and autosummary to enable section headers in docs
Bug: T170534
Bug: T168831
Change-Id: Ic5de5d07c5065c6c2759c7eef4fdb83ab10b8b6f
---
M docs/api_ref/pywikibot.comms.rst
M docs/conf.py
M docs/requirements-py3.txt
M pywikibot/README.rst
M pywikibot/comms/eventstreams.py
D pywikibot/comms/rcstream.py
M pywikibot/family.py
M pywikibot/pagegenerators.py
M setup.py
M tox.ini
10 files changed, 57 insertions(+), 262 deletions(-)
Approvals:
Krinkle: Looks good to me, but someone else must approve
Dalba: Looks good to me, approved
jenkins-bot: Verified
diff --git a/docs/api_ref/pywikibot.comms.rst b/docs/api_ref/pywikibot.comms.rst
index 2c25c5d..39ccc28 100644
--- a/docs/api_ref/pywikibot.comms.rst
+++ b/docs/api_ref/pywikibot.comms.rst
@@ -17,10 +17,10 @@
:undoc-members:
:show-inheritance:
-pywikibot.comms.rcstream module
+pywikibot.comms.eventstreams module
-------------------------------
-.. automodule:: pywikibot.comms.rcstream
+.. automodule:: pywikibot.comms.eventstreams
:members:
:undoc-members:
:show-inheritance:
diff --git a/docs/conf.py b/docs/conf.py
index 17cf9ac..04d20f7 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""Configuration file for Sphinx."""
#
-# (C) Pywikibot team, 2015-2016
+# (C) Pywikibot team, 2015-2017
#
# Distributed under the terms of the MIT license.
#
@@ -36,7 +36,9 @@
'sphinx_epytext',
'sphinx.ext.todo',
'sphinx.ext.coverage',
- 'sphinx.ext.viewcode']
+ 'sphinx.ext.viewcode',
+ 'sphinx.ext.autosummary',
+ 'numpydoc']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
diff --git a/docs/requirements-py3.txt b/docs/requirements-py3.txt
index c99a6f2..7403a65 100644
--- a/docs/requirements-py3.txt
+++ b/docs/requirements-py3.txt
@@ -4,4 +4,5 @@
sphinx==1.3.1
sphinx-epytext>=0.0.4
+numpydoc
diff --git a/pywikibot/README.rst b/pywikibot/README.rst
index 702f1a1..181ddd3 100644
--- a/pywikibot/README.rst
+++ b/pywikibot/README.rst
@@ -106,11 +106,9 @@
+---------------------------+-------------------------------------------------------+
| comms | Communication layer. |
+===========================+=======================================================+
- | eventstreams.py | rcstream client for server sent events |
+ | eventstreams.py | stream client for server sent events |
+---------------------------+-------------------------------------------------------+
| http.py | Basic HTTP access interface |
- +---------------------------+-------------------------------------------------------+
- | rcstream.py | SocketIO-based rcstream client (deprecated) |
+---------------------------+-------------------------------------------------------+
| threadedhttp.py | Httplib2 threaded cookie layer extending httplib2 |
+---------------------------+-------------------------------------------------------+
diff --git a/pywikibot/comms/eventstreams.py b/pywikibot/comms/eventstreams.py
index 2236481..5d4db87 100644
--- a/pywikibot/comms/eventstreams.py
+++ b/pywikibot/comms/eventstreams.py
@@ -38,6 +38,28 @@
It provides access to arbitrary streams of data including recent changes.
It replaces rcstream.py implementation.
+
+ Usage:
+
+ >>> stream = EventStreams(stream='recentchange')
+ >>> change = iter(stream).next()
+ >>> change
+ {'comment': '/* wbcreateclaim-create:1| */ [[Property:P31]]: [[Q4167836]]',
+ 'wiki': 'wikidatawiki', 'type': 'edit', 'server_name': 'www.wikidata.org',
+ 'server_script_path': '/w', 'namespace': 0, 'title': 'Q32857263',
+ 'bot': True, 'server_url': 'https://www.wikidata.org',
+ 'length': {'new': 1223, 'old': 793},
+ 'meta': {'domain': 'www.wikidata.org', 'partition': 0,
+ 'uri': 'https://www.wikidata.org/wiki/Q32857263',
+ 'offset': 288986585, 'topic': 'eqiad.mediawiki.recentchange',
+ 'request_id': '1305a006-8204-4f51-a27b-0f2df58289f4',
+ 'schema_uri': 'mediawiki/recentchange/1',
+ 'dt': '2017-07-13T10:55:31+00:00',
+ 'id': 'ca13742b-67b9-11e7-935d-141877614a33'},
+ 'user': 'XXN-bot', 'timestamp': 1499943331, 'patrolled': True,
+ 'id': 551158959, 'minor': False,
+ 'revision': {'new': 518751558, 'old': 517180066}}
+ >>> del stream
"""
def __init__(self, **kwargs):
@@ -80,7 +102,7 @@
raise NotImplementedError(
'No stream specified for class {0}'
.format(self.__class__.__name__))
- self._url = ('{0}{1}/{2}'.format(self._site.rcstream_host(),
+ self._url = ('{0}{1}/{2}'.format(self._site.eventstreams_host(),
self._site.eventstreams_path(),
self._stream))
return self._url
@@ -106,6 +128,7 @@
Filter types
============
+
There are 3 types of filter: 'all', 'any' and 'none'.
The filter type must be given with the keyword argument 'ftype'
(see below). If no 'ftype' keyword argument is given, 'all' is
@@ -120,6 +143,7 @@
Filter functions
================
+
Filter may be specified as external function methods given as
positional argument like::
@@ -134,6 +158,7 @@
Filter keys and values
======================
+
Another method to register a filter is to pass pairs of keys and values
as keyword arguments to this method. The key must be a key of the event
data dict and the value must be any value or an iterable of values the
@@ -247,20 +272,11 @@
@type total: int
@return: pywikibot.comms.eventstream.rc_listener configured for given site
+ @raises ImportError: sseclient installation is required
"""
if isinstance(EventSource, Exception):
- warning('sseclient is required for EventStreams;\n'
- 'install it with "pip install sseclient"\n')
- # fallback to old rcstream method
- # NOTE: this will be deprecated soon
- from pywikibot.comms.rcstream import rc_listener
- return rc_listener(
- wikihost=site.hostname(),
- rchost=site.rcstream_host(),
- rcport=site.rcstream_port(),
- rcpath=site.rcstream_path(),
- total=total,
- )
+ raise ImportError('sseclient is required for EventStreams;\n'
+ 'install it with "pip install sseclient"\n')
stream = EventStreams(stream='recentchange', site=site)
stream.set_maximum_items(total)
diff --git a/pywikibot/comms/rcstream.py b/pywikibot/comms/rcstream.py
deleted file mode 100644
index a235f90..0000000
--- a/pywikibot/comms/rcstream.py
+++ /dev/null
@@ -1,226 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-SocketIO-based rcstream client.
-
-This file is part of the Pywikibot framework.
-
-This module requires socketIO_client to be installed:
- pip install socketIO_client
-"""
-#
-# (C) 2014 Merlijn van Deen
-# (C) Pywikibot team, 2014-2017
-#
-# Distributed under the terms of the MIT license.
-#
-from __future__ import absolute_import, unicode_literals
-
-__version__ = '$Id$'
-#
-
-import sys
-import threading
-
-if sys.version_info[0] > 2:
- from queue import Queue, Empty
-else:
- from Queue import Queue, Empty
-
-try:
- import socketIO_client
-except ImportError as e:
- socketIO_client = e
-
-from pywikibot.bot import debug, warning
-from pywikibot.tools import deprecated
-
-_logger = 'pywikibot.rcstream'
-
-
-class RcListenerThread(threading.Thread):
-
- """
- Low-level RC Listener Thread, pushing RC stream events into a queue.
-
- @param wikihost: the hostname of the wiki we want to get changes for. This
- is passed to rcstream using a 'subscribe' command. Pass
- '*' to listen to all wikis for a given rc host.
- @param rchost: the recent changes stream host to connect to. For Wikimedia
- wikis, this is 'https://stream.wikimedia.org'
- @param rcport: the port to connect to (default: 80)
- @param rcpath: the sockets.io path. For Wikimedia wikis, this is '/rc'.
- (default: '/rc')
- @param total: the maximum number of entries to return. The underlying
- thread is shut down then this number is reached.
-
- This part of the rc listener runs in a Thread. It makes the actual
- socketIO/websockets connection to the rc stream server, subscribes
- to a single site and pushes those entries into a queue.
-
- Usage:
-
- >>> t = RcListenerThread('en.wikipedia.org', 'https://stream.wikimedia.org')
- >>> t.start()
- >>> change = t.queue.get()
- >>> change
- {'server_name': 'en.wikipedia.org', 'wiki': 'enwiki', 'minor': True,
- 'length': {'new': 2038, 'old': 2009}, 'timestamp': 1419964350,
- 'server_script_path': '/w', 'bot': False, 'user': 'Od Mishehu',
- 'comment': 'stub sorting', 'title': 'Bradwell Bay Wilderness',
- 'server_url': 'http://en.wikipedia.org', 'id': 703158386,
- 'revision': {'new': 640271171, 'old': 468264850},
- 'type': 'edit', 'namespace': 0}
- >>> t.stop() # optional, the thread will shut down on exiting python
- """
-
- def __init__(self, wikihost, rchost, rcport=80, rcpath='/rc', total=None):
- """Constructor for RcListenerThread."""
- super(RcListenerThread, self).__init__()
- self.rchost = rchost
- self.rcport = rcport
- self.rcpath = rcpath
- self.wikihost = wikihost
-
- self.daemon = True
- self.running = False
- self.queue = Queue()
-
- self.warn_queue_length = 100
-
- self.total = total
- self.count = 0
-
- debug('Opening connection to %r' % self, _logger)
- self.client = socketIO_client.SocketIO(rchost, rcport)
-
- thread = self
-
- class RCListener(socketIO_client.BaseNamespace):
- def on_change(self, change):
- debug('Received change %r' % change, _logger)
- if not thread.running:
- debug('Thread in shutdown mode; ignoring change.', _logger)
- return
-
- thread.count += 1
- thread.queue.put(change)
- if thread.queue.qsize() > thread.warn_queue_length:
- warning('%r queue length exceeded %i'
- % (thread,
- thread.warn_queue_length),
- _logger=_logger)
- thread.warn_queue_length = thread.warn_queue_length + 100
-
- if thread.total is not None and thread.count >= thread.total:
- thread.stop()
- return
-
- def on_connect(self):
- debug('Connected to %r; subscribing to %s'
- % (thread, thread.wikihost),
- _logger)
- self.emit('subscribe', thread.wikihost)
- debug('Subscribed to %s' % thread.wikihost, _logger)
-
- def on_reconnect(self):
- debug('Reconnected to %r' % (thread,), _logger)
- self.on_connect()
-
- class GlobalListener(socketIO_client.BaseNamespace):
- def on_heartbeat(self):
- self._transport.send_heartbeat()
-
- self.client.define(RCListener, rcpath)
- self.client.define(GlobalListener)
-
- def __repr__(self):
- """Return representation."""
- return "<rcstream for socketio://%s@%s:%s%s>" % (
- self.wikihost, self.rchost, self.rcport, self.rcpath
- )
-
- def run(self):
- """
- Threaded function.
-
- Runs inside the thread when started with .start().
- """
- self.running = True
- while self.running:
- self.client.wait(seconds=0.1)
- debug('Shut down event loop for %r' % self, _logger)
- self.client.disconnect()
- debug('Disconnected %r' % self, _logger)
- self.queue.put(None)
-
- def stop(self):
- """Stop the thread."""
- self.running = False
-
-
-def rc_listener(wikihost, rchost, rcport=80, rcpath='/rc', total=None):
- """Yield changes received from RCstream.
-
- @param wikihost: the hostname of the wiki we want to get changes for. This
- is passed to rcstream using a 'subscribe' command. Pass
- '*' to listen to all wikis for a given rc host.
- @param rchost: the recent changes stream host to connect to. For Wikimedia
- wikis, this is 'https://stream.wikimedia.org'
- @param rcport: the port to connect to (default: 80)
- @param rcpath: the sockets.io path. For Wikimedia wikis, this is '/rc'.
- (default: '/rc')
- @param total: the maximum number of entries to return. The underlying thread
- is shut down then this number is reached.
-
- @return: yield dict as formatted by MediaWiki's
- MachineReadableRCFeedFormatter, which consists of at least id
- (recent changes id), type ('edit', 'new', 'log' or 'external'),
- namespace, title, comment, timestamp, user and bot (bot flag for the
- change).
- @see: U{MachineReadableRCFeedFormatter<https://doc.wikimedia.org/
- mediawiki-core/master/php/classMachineReadableRCFeedFormatter.html>}
- @rtype: generator
- @raises ImportError
- """
- if isinstance(socketIO_client, Exception):
- raise ImportError('socketIO_client is required for the rc stream;\n'
- 'install it with pip install "socketIO_client==0.5.6"')
-
- rc_thread = RcListenerThread(
- wikihost=wikihost,
- rchost=rchost, rcport=rcport, rcpath=rcpath,
- total=total
- )
-
- debug('Starting rcstream thread %r' % rc_thread,
- _logger)
- rc_thread.start()
-
- while True:
- try:
- element = rc_thread.queue.get(timeout=0.1)
- except Empty:
- continue
- if element is None:
- return
- yield element
-
-
-@deprecated('eventstreams.site_rc_listener')
-def site_rc_listener(site, total=None):
- """Yield changes received from RCstream.
-
- @param site: the Pywikibot.Site object to yield live recent changes for
- @type site: Pywikibot.BaseSite
- @param total: the maximum number of changes to return
- @type total: int
-
- @return: pywikibot.comms.rcstream.rc_listener configured for the given site
- """
- return rc_listener(
- wikihost=site.hostname(),
- rchost=site.rcstream_host(),
- rcport=site.rcstream_port(),
- rcpath=site.rcstream_path(),
- total=total,
- )
diff --git a/pywikibot/family.py b/pywikibot/family.py
index 1ff3d7f..d5b8d6f 100644
--- a/pywikibot/family.py
+++ b/pywikibot/family.py
@@ -1144,12 +1144,19 @@
def rcstream_host(self, code):
"""Hostname for RCStream."""
- raise NotImplementedError(
- 'This family does support neither RCStream nor EventStreams')
+ raise NotImplementedError('This family does not support RCStream')
def rcstream_path(self, code):
"""Return path for RCStream."""
raise NotImplementedError("This family does not support RCStream")
+
+ def rcstream_port(self, code):
+ """Return port for RCStream."""
+ raise NotImplementedError('This family does not support RCStream')
+
+ def eventstreams_host(self, code):
+ """Hostname for EventStreams."""
+ raise NotImplementedError('This family does not support EventStreams')
def eventstreams_path(self, code):
"""Return path for EventStreams."""
@@ -1639,17 +1646,14 @@
"""Return 'https' as the protocol."""
return 'https'
+ @deprecated('eventstreams_host')
def rcstream_host(self, code):
- """Return 'https://stream.wikimedia.org' as the RCStream hostname."""
+ """DEPRECATED: use eventstreams_host instead."""
+ return self.eventstreams_host(code)
+
+ def eventstreams_host(self, code):
+ """Return 'https://stream.wikimedia.org' as the stream hostname."""
return 'https://stream.wikimedia.org'
-
- def rcstream_port(self, code):
- """Return 443 as the RCStream port number."""
- return 443
-
- def rcstream_path(self, code):
- """Return path for RCStream."""
- return '/rc'
def eventstreams_path(self, code):
"""Return path for EventStreams."""
diff --git a/pywikibot/pagegenerators.py b/pywikibot/pagegenerators.py
index 32c1c22..f125ab8 100644
--- a/pywikibot/pagegenerators.py
+++ b/pywikibot/pagegenerators.py
@@ -2403,10 +2403,12 @@
"""
Yield pages from a socket.io RC stream.
- Generates pages based on the socket.io recent changes stream.
+ Generates pages based on the EventStreams Server-Sent-Event (SSE) recent
+ changes stream.
The Page objects will have an extra property ._rcinfo containing the
literal rc data. This can be used to e.g. filter only new pages. See
- `pywikibot.comms.rcstream.rc_listener` for details on the .rcinfo format.
+ `pywikibot.comms.eventstreams.rc_listener` for details on the .rcinfo
+ format.
@param site: site to return recent changes for
@type site: pywikibot.BaseSite
diff --git a/setup.py b/setup.py
index cb74bca..1657ad5 100644
--- a/setup.py
+++ b/setup.py
@@ -61,8 +61,6 @@
'IRC': [irc_dep],
'mwparserfromhell': ['mwparserfromhell>=0.3.3'],
'Tkinter': ['Pillow<3.5.0' if PY26 else 'Pillow'],
- # 0.6.1 supports socket.io 1.0, but WMF is using 0.9 (T91393 and T85716)
- 'rcstream': ['socketIO-client<0.6.1'],
'security': ['requests[security]', 'pycparser!=2.14'],
'mwoauth': ['mwoauth>=0.2.4,!=0.3.1'],
'html': ['BeautifulSoup4'],
diff --git a/tox.ini b/tox.ini
index 724aa80..0b652a8 100644
--- a/tox.ini
+++ b/tox.ini
@@ -12,7 +12,7 @@
envlist = flake8,pyflakes-{py3,pypy}
[params]
-doctest_skip = --ignore-files=(gui\.py|botirc\.py|rcstream\.py)
+doctest_skip = --ignore-files=(gui\.py|botirc\.py|eventstreams\.py)
[testenv]
setenv =
--
To view, visit https://gerrit.wikimedia.org/r/364993
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ic5de5d07c5065c6c2759c7eef4fdb83ab10b8b6f
Gerrit-PatchSet: 12
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
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: Krinkle <krinklemail(a)gmail.com>
Gerrit-Reviewer: Lokal Profil <lokal.profil(a)gmail.com>
Gerrit-Reviewer: Magul <tomasz.magulski(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
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/371697 )
Change subject: Add retry logic to timeouted requests
......................................................................
Add retry logic to timeouted requests
Reuses the same basic logic as api.py. The retry logic is needed
for sparql queries which time out (http-wise) but actually terminate
successfully so are cached the next time you make the same query.
Change-Id: I2e4feff5338eef3c669ec4f0e5bef8412f12bbfb
---
M pywikibot/data/api.py
M pywikibot/data/sparql.py
M pywikibot/exceptions.py
3 files changed, 57 insertions(+), 18 deletions(-)
Approvals:
jenkins-bot: Verified
Xqt: Looks good to me, approved
diff --git a/pywikibot/data/api.py b/pywikibot/data/api.py
index 94be83c..6dc86d6 100644
--- a/pywikibot/data/api.py
+++ b/pywikibot/data/api.py
@@ -35,8 +35,7 @@
from pywikibot.comms import http
from pywikibot.exceptions import (
Server504Error, Server414Error, FatalServerError, NoUsername,
- Error,
- InvalidTitle
+ Error, TimeoutError, InvalidTitle
)
from pywikibot.tools import (
MediaWikiVersion, deprecated, itergroup, ip, PY2, getargspec,
@@ -1241,11 +1240,6 @@
def __len__(self):
"""Return the number of enabled and disabled options."""
return len(self._enabled) + len(self._disabled)
-
-
-class TimeoutError(Error):
-
- """API request failed with a timeout error."""
class EnableSSLSiteWrapper(object):
diff --git a/pywikibot/data/sparql.py b/pywikibot/data/sparql.py
index b577bb0..392f8a8 100644
--- a/pywikibot/data/sparql.py
+++ b/pywikibot/data/sparql.py
@@ -9,14 +9,18 @@
import json
import sys
+import time
if sys.version_info[0] > 2:
from urllib.parse import quote
else:
from urllib2 import quote
-from pywikibot import Site, Error
+from requests.exceptions import Timeout
+
+from pywikibot import config, warning, Site
from pywikibot.comms import http
from pywikibot.tools import UnicodeMixin, py2_encode_utf_8
+from pywikibot.exceptions import Error, TimeoutError
DEFAULT_HEADERS = {'cache-control': 'no-cache',
'Accept': 'application/sparql-results+json'}
@@ -29,7 +33,8 @@
This class allows to run SPARQL queries against any SPARQL endpoint.
"""
- def __init__(self, endpoint=None, entity_url=None, repo=None):
+ def __init__(self, endpoint=None, entity_url=None, repo=None,
+ max_retries=None, retry_wait=None):
"""
Create endpoint.
@@ -38,9 +43,16 @@
@param entity_url: URL prefix for any entities returned in a query.
@type entity_url: string
@param repo: The Wikibase site which we want to run queries on. If
- provided this overrides any value in endpoint and entity_url.
- Defaults to Wikidata.
+ provided this overrides any value in endpoint and entity_url.
+ Defaults to Wikidata.
@type repo: pywikibot.site.DataSite
+ @param max_retries: (optional) Maximum number of times to retry after
+ errors, defaults to config.max_retries.
+ @type max_retries: int
+ @param retry_wait: (optional) Minimum time in seconds to wait after an
+ error, defaults to config.retry_wait seconds (doubles each retry
+ until max of 120 seconds is reached).
+ @type retry_wait: float
"""
# default to Wikidata
if not repo and not endpoint:
@@ -67,6 +79,15 @@
self.entity_url = entity_url
self.last_response = None
+
+ if max_retries is None:
+ self.max_retries = config.max_retries
+ else:
+ self.max_retries = max_retries
+ if retry_wait is None:
+ self.retry_wait = config.retry_wait
+ else:
+ self.retry_wait = retry_wait
def get_last_response(self):
"""
@@ -120,13 +141,28 @@
@type query: string
"""
url = '%s?query=%s' % (self.endpoint, quote(query))
- self.last_response = http.fetch(url, headers=headers)
- if not self.last_response.content:
- return None
- try:
- return json.loads(self.last_response.content)
- except ValueError:
- return None
+ while True:
+ try:
+ self.last_response = http.fetch(url, headers=headers)
+ if not self.last_response.content:
+ return None
+ try:
+ return json.loads(self.last_response.content)
+ except ValueError:
+ return None
+ except Timeout:
+ self.wait()
+ continue
+
+ def wait(self):
+ """Determine how long to wait after a failed request."""
+ self.max_retries -= 1
+ if self.max_retries < 0:
+ raise TimeoutError('Maximum retries attempted without success.')
+ warning('Waiting {0} seconds before retrying.'.format(self.retry_wait))
+ time.sleep(self.retry_wait)
+ # double the next wait, but do not exceed 120 seconds
+ self.retry_wait = min(120, self.retry_wait * 2)
def ask(self, query, headers=DEFAULT_HEADERS):
"""
diff --git a/pywikibot/exceptions.py b/pywikibot/exceptions.py
index aa249c7..d9dff19 100644
--- a/pywikibot/exceptions.py
+++ b/pywikibot/exceptions.py
@@ -58,6 +58,8 @@
- CoordinateGlobeUnknownException: globe is not implemented yet.
- EntityTypeUnknownException: entity type is not available on the site.
+TimeoutError: request failed with a timeout
+
DeprecationWarning: old functionality replaced by new functionality
PendingDeprecationWarning: problematic code which has not yet been
@@ -549,6 +551,13 @@
pass
+class TimeoutError(Error):
+
+ """Request failed with a timeout error."""
+
+ pass
+
+
@__deprecated
class DeprecatedPageNotFoundError(Error):
--
To view, visit https://gerrit.wikimedia.org/r/371697
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I2e4feff5338eef3c669ec4f0e5bef8412f12bbfb
Gerrit-PatchSet: 2
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: Lokal Profil <lokal.profil(a)gmail.com>
Gerrit-Reviewer: Dalba <dalba.wiki(a)gmail.com>
Gerrit-Reviewer: Jean-Frédéric <jeanfrederic.wiki(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Lokal Profil <lokal.profil(a)gmail.com>
Gerrit-Reviewer: Smalyshev <smalyshev(a)wikimedia.org>
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/371719 )
Change subject: [bugfix] rename the mainpage image because "image" is already in use
......................................................................
[bugfix] rename the mainpage image because "image" is already in use
Bug: T172770
Change-Id: I9e5729c803ed6d6b763dc1fe25eba47945038b47
---
M tests/page_tests.py
1 file changed, 3 insertions(+), 4 deletions(-)
Approvals:
Dalba: Looks good to me, approved
jenkins-bot: Verified
diff --git a/tests/page_tests.py b/tests/page_tests.py
index 17c69ef..3c74326 100644
--- a/tests/page_tests.py
+++ b/tests/page_tests.py
@@ -563,10 +563,9 @@
if MediaWikiVersion(site.version()) < MediaWikiVersion('1.20'):
self.assertRaises(NotImplementedError, mainpage.page_image)
elif site.has_extension('PageImages'):
- image = mainpage.page_image()
- if image is not None:
- self.assertIsInstance(mainpage.page_image(),
- pywikibot.FilePage)
+ mainpage_image = mainpage.page_image()
+ if mainpage_image is not None:
+ self.assertIsInstance(mainpage_image, pywikibot.FilePage)
# for file pages, the API should return the file itself
self.assertEqual(image.page_image(), image)
else:
--
To view, visit https://gerrit.wikimedia.org/r/371719
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I9e5729c803ed6d6b763dc1fe25eba47945038b47
Gerrit-PatchSet: 1
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
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: Strainu <wiki(a)strainu.ro>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/368192 )
Change subject: family.py: Get the stable MW version number from mediawiki.org
......................................................................
family.py: Get the stable MW version number from mediawiki.org
Use mediawiki.org's {{Template:MW stable release number}} to retrieve the
latest stable MediaWiki version number.
The template is used on https://www.mediawiki.org/wiki/Download and looks
to be reliable enough.
Change-Id: I158e4d01ca668d4569a65f54bee719dc2e05d7db
---
M pywikibot/family.py
1 file changed, 5 insertions(+), 3 deletions(-)
Approvals:
jenkins-bot: Verified
Xqt: Looks good to me, approved
diff --git a/pywikibot/family.py b/pywikibot/family.py
index 1ff3d7f..fd9ea4e 100644
--- a/pywikibot/family.py
+++ b/pywikibot/family.py
@@ -29,9 +29,7 @@
from warnings import warn
import pywikibot
-
from pywikibot import config
-
from pywikibot.exceptions import UnknownFamily, FamilyMaintenanceWarning
from pywikibot.tools import (
deprecated, deprecated_args, issue_deprecation_warning,
@@ -1268,7 +1266,11 @@
Use L{pywikibot.tools.MediaWikiVersion} to compare version strings.
"""
# Here we return the latest mw release for downloading
- return '1.28.1'
+ if not hasattr(self, '_version'):
+ self._version = \
+ pywikibot.Site('mediawiki', 'mediawiki').expand_text(
+ '{{MW stable release number}}')
+ return self._version
def force_version(self, code):
"""
--
To view, visit https://gerrit.wikimedia.org/r/368192
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I158e4d01ca668d4569a65f54bee719dc2e05d7db
Gerrit-PatchSet: 10
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
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: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/371704 )
Change subject: [doc] max_retries and retry_wait defaults is set in config2.py
......................................................................
[doc] max_retries and retry_wait defaults is set in config2.py
- defaults for max_retries and retry_wait are set in config file
(and config.max_retries is now 15 btw.)
- fix spelling mistake in doc string
- keep lines beneath 80 chars
Change-Id: I72b291b69bebd009aa5a822b06496461c3479ee8
---
M pywikibot/data/api.py
1 file changed, 19 insertions(+), 18 deletions(-)
Approvals:
Lokal Profil: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/data/api.py b/pywikibot/data/api.py
index f905bc6..3abfb7f 100644
--- a/pywikibot/data/api.py
+++ b/pywikibot/data/api.py
@@ -1339,16 +1339,16 @@
"""
Create a new Request instance with the given parameters.
- The parameters for the request can be defined via eiter the 'parameters'
- parameter or the keyword arguments. The keyword arguments were the
- previous implementation but could cause problems when there are
- arguments to the API named the same as normal arguments to this class.
- So the second parameter 'parameters' was added which just contains all
- parameters. When a Request instance is created it must use either one
- of them and not both at the same time. To have backwards compatibility
- it adds a parameter named 'parameters' to kwargs when both parameters
- are set as that indicates an old call and 'parameters' was originally
- supplied as a keyword parameter.
+ The parameters for the request can be defined via either the
+ 'parameters' parameter or the keyword arguments. The keyword arguments
+ were the previous implementation but could cause problems when there
+ are arguments to the API named the same as normal arguments to this
+ class. So the second parameter 'parameters' was added which just
+ contains all parameters. When a Request instance is created it must use
+ either one of them and not both at the same time. To have backwards
+ compatibility it adds a parameter named 'parameters' to kwargs when
+ both parameters are set as that indicates an old call and 'parameters'
+ was originally supplied as a keyword parameter.
If undefined keyword arguments were given AND the 'parameters'
parameter was supplied as a positional parameter it still assumes
@@ -1365,17 +1365,18 @@
@param site: The Site to which the request will be submitted. If not
supplied, uses the user's configured default Site.
@param mime: If true, send in "multipart/form-data" format (default
- False). Parameters which should only be transferred via mime mode
- can be defined via that parameter too (an empty dict acts like
- 'True' not like 'False'!).
+ False). Parameters which should only be transferred via mime
+ mode can be defined via that parameter too (an empty dict acts
+ like 'True' not like 'False'!).
@type mime: bool or dict
@param mime_params: DEPRECATED! A dictionary of parameter which should
- only be transferred via mime mode. If not None sets mime to True.
+ only be transferred via mime mode. If not None sets mime to
+ True.
@param max_retries: (optional) Maximum number of times to retry after
- errors, defaults to 25
- @param retry_wait: (optional) Minimum time to wait after an error,
- defaults to 5 seconds (doubles each retry until max of 120 is
- reached)
+ errors, defaults to config.max_retries.
+ @param retry_wait: (optional) Minimum time in seconds to wait after an
+ error, defaults to config.retry_wait seconds (doubles each retry
+ until max of 120 seconds is reached).
@param use_get: (optional) Use HTTP GET request if possible. If False
it uses a POST request. If None, it'll try to determine via
action=paraminfo if the action requires a POST.
--
To view, visit https://gerrit.wikimedia.org/r/371704
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I72b291b69bebd009aa5a822b06496461c3479ee8
Gerrit-PatchSet: 1
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: Xqt <info(a)gno.de>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Lokal Profil <lokal.profil(a)gmail.com>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged. ( https://gerrit.wikimedia.org/r/362324 )
Change subject: Add ability for Pywikibot to thank Flow posts
......................................................................
Add ability for Pywikibot to thank Flow posts
This change adds a thank method to Post, which will allow bots to
thank the creators of Flow posts. A property was added to Post
which returns a User object corresponding to the creator of that
post. Two tests, adapted from the revision thanking tests, have
been added and pass locally.
Bug: T135411
Change-Id: Ie629aaa46aa007a39e6d5dce6400504bf73e5980
---
M pywikibot/data/api.py
M pywikibot/flow.py
M pywikibot/site.py
A tests/flow_thanks_tests.py
4 files changed, 96 insertions(+), 2 deletions(-)
Approvals:
jenkins-bot: Verified
Xqt: Looks good to me, approved
diff --git a/pywikibot/data/api.py b/pywikibot/data/api.py
index 7f4c80b..9a19f8a 100644
--- a/pywikibot/data/api.py
+++ b/pywikibot/data/api.py
@@ -1442,7 +1442,7 @@
"wbcreateclaim", "wbremoveclaims", "wbsetclaimvalue",
"wbsetreference", "wbremovereferences", "wbsetclaim",
'wbcreateredirect',
- 'thank',
+ 'thank', 'flowthank'
)
# Client side verification that the request is being performed
# by a logged in user, and warn if it isn't a config username.
diff --git a/pywikibot/flow.py b/pywikibot/flow.py
index 5f8a006..e5920a3 100644
--- a/pywikibot/flow.py
+++ b/pywikibot/flow.py
@@ -12,7 +12,7 @@
import logging
from pywikibot.exceptions import NoPage, UnknownExtension, LockedPage
-from pywikibot.page import BasePage
+from pywikibot.page import BasePage, User
from pywikibot.tools import PY2
if not PY2:
@@ -428,6 +428,16 @@
self._load()
return self._current_revision['isModerated']
+ @property
+ def creator(self):
+ """The creator of this post."""
+ if not hasattr(self, '_current_revision'):
+ self._load()
+ if not hasattr(self, '_creator'):
+ self._creator = User(self.site,
+ self._current_revision['creator']['name'])
+ return self._creator
+
def get(self, format='wikitext', force=False, sysop=False):
"""Return the contents of the post in the given format.
@@ -535,3 +545,7 @@
"""
self.site.restore_post(self, reason)
self._load()
+
+ def thank(self):
+ """Thank the user who made this post."""
+ self.site.thank_post(self)
diff --git a/pywikibot/site.py b/pywikibot/site.py
index e2bc6cc..08b7b7a 100644
--- a/pywikibot/site.py
+++ b/pywikibot/site.py
@@ -6775,6 +6775,7 @@
comparison = data['compare']['*']
return comparison
+ # Thanks API calls
@need_extension('Thanks')
def thank_revision(self, revid, source=None):
"""Corresponding method to the 'action=thank' API action.
@@ -6794,6 +6795,25 @@
raise api.APIError('Thanking unsuccessful')
return data
+ @need_extension('Flow')
+ @need_extension('Thanks')
+ def thank_post(self, post):
+ """Corresponding method to the 'action=flowthank' API action.
+
+ @param post: The post to be thanked for.
+ @type post: Post
+ @raise APIError: On thanking oneself or other API errors.
+ @return: The API response.
+ """
+ post_id = post.uuid
+ token = self.tokens['csrf']
+ req = self._simple_request(action='flowthank',
+ postid=post_id, token=token)
+ data = req.submit()
+ if data['result']['success'] != 1:
+ raise api.APIError('Thanking unsuccessful')
+ return data
+
# Flow API calls
@need_extension('Flow')
def load_board(self, page):
diff --git a/tests/flow_thanks_tests.py b/tests/flow_thanks_tests.py
new file mode 100644
index 0000000..00710c9
--- /dev/null
+++ b/tests/flow_thanks_tests.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+"""Tests for thanks-related code."""
+#
+# (C) Pywikibot team, 2016-17
+#
+# Distributed under the terms of the MIT license.
+#
+from __future__ import absolute_import, unicode_literals
+
+from pywikibot.flow import Topic
+
+from tests.aspects import TestCase
+
+
+NO_THANKABLE_POSTS = 'There is no recent post which can be test thanked.'
+
+
+class TestThankFlowPost(TestCase):
+
+ """Test thanks for Flow posts."""
+
+ family = 'test'
+ code = 'test'
+
+ write = True
+
+ @classmethod
+ def setUpClass(cls):
+ """Set up class."""
+ super(TestThankFlowPost, cls).setUpClass()
+ cls._topic_title = 'Topic:Tvkityksg1ukyrrw'
+
+ def test_thank_post(self):
+ """Test thanks for Flow posts."""
+ found_log = False
+ site = self.get_site()
+ topic = Topic(site, self._topic_title)
+ for post in reversed(topic.replies()):
+ user = post.creator
+ if site.user() == user.username:
+ continue
+ if user.is_thankable:
+ break
+ else:
+ self.skipTest(NO_THANKABLE_POSTS)
+ before_time = site.getcurrenttimestamp()
+ post.thank()
+ log_entries = site.logevents(logtype='thanks', total=5, page=user,
+ start=before_time, reverse=True)
+ for __ in log_entries:
+ found_log = True
+ break
+ self.assertTrue(found_log)
+
+ def test_self_thank(self):
+ """Test that thanking one's own Flow post causes an error."""
+ site = self.get_site()
+ topic = Topic(site, self._topic_title)
+ my_reply = topic.reply('My attempt to thank myself.')
+ self.assertAPIError('invalidrecipient', None, my_reply.thank)
--
To view, visit https://gerrit.wikimedia.org/r/362324
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ie629aaa46aa007a39e6d5dce6400504bf73e5980
Gerrit-PatchSet: 8
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: Happy5214 <happy5214(a)gmail.com>
Gerrit-Reviewer: Framawiki <framawiki(a)tools.wmflabs.org>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Magul <tomasz.magulski(a)gmail.com>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot <>