jenkins-bot has submitted this change and it was merged.
Change subject: [FEAT] Handle API warnings
......................................................................
[FEAT] Handle API warnings
Some code parses the warnings returned by the API separately like
Siteinfo. This option adds the possibility to handle warnings
returned by the api via "_warning_handler". If it's not a method
or that method returns not True the warning is still output like
before (and is by default). The method must accept two parameters
and is executed for each warning.
This also shows warnings when CachedRequest are used and not only
for the first actually submitted request.
Change-Id: I62d13dca5b592bddf72dbbdcc0b8edf77c0059ed
---
M pywikibot/data/api.py
M pywikibot/site.py
2 files changed, 44 insertions(+), 24 deletions(-)
Approvals:
John Vandenberg: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/data/api.py b/pywikibot/data/api.py
index c36f88e..56c42c0 100644
--- a/pywikibot/data/api.py
+++ b/pywikibot/data/api.py
@@ -160,6 +160,7 @@
if "action" not in kwargs:
raise ValueError("'action' specification missing from Request.")
self.update(**kwargs)
+ self._warning_handler = None
# Actions that imply database updates on the server, used for various
# things like throttling or skipping actions when we're in simulation
# mode
@@ -342,6 +343,25 @@
submsg.set_payload(content)
return submsg
+ def _handle_warnings(self, result):
+ if 'warnings' in result:
+ for mod, warning in result['warnings'].items():
+ if mod == 'info':
+ continue
+ if '*' in warning:
+ text = warning['*']
+ elif 'html' in warning:
+ # Bugzilla 49978
+ text = warning['html']['*']
+ else:
+ pywikibot.warning(
+ u'API warning ({0})of unknown format: {1}'.
+ format(mod, warning))
+ continue
+ if (not callable(self._warning_handler) or
+ not self._warning_handler(mod, text)):
+ pywikibot.warning(u"API warning (%s): %s" % (mod, text))
+
def submit(self):
"""Submit a query and parse the response.
@@ -470,21 +490,7 @@
self.site.login(status)
# retry the previous query
continue
- if "warnings" in result:
- modules = [k for k in result["warnings"] if k != "info"]
- for mod in modules:
- if '*' in result["warnings"][mod]:
- text = result["warnings"][mod]['*']
- elif 'html' in result["warnings"][mod]:
- # Bugzilla 49978
- text = result["warnings"][mod]['html']['*']
- else:
- # This is just a warning, we shouldn't raise an
- # exception because of it
- continue
- pywikibot.warning(
- u"API warning (%s): %s"
- % (mod, text))
+ self._handle_warnings(result)
if "error" not in result:
return result
if "*" in result["error"]:
@@ -639,6 +645,8 @@
if not cached_available:
self._data = super(CachedRequest, self).submit()
self._write_cache(self._data)
+ else:
+ self._handle_warnings(self._data)
return self._data
diff --git a/pywikibot/site.py b/pywikibot/site.py
index 46995af..4b4848d 100644
--- a/pywikibot/site.py
+++ b/pywikibot/site.py
@@ -907,6 +907,9 @@
All values of the siteinfo property 'general' are directly available.
"""
+ WARNING_REGEX = re.compile(u"^Unrecognized values? for parameter "
+ u"'siprop': ([^,]+(?:, [^,]+)*)$")
+
def __init__(self, site):
"""Initialise it with an empty cache."""
self._site = site
@@ -957,19 +960,33 @@
@rtype: dict (the values)
@see: U{https://www.mediawiki.org/wiki/API:Meta#siteinfo_.2F_si}
"""
+ def warn_handler(mod, message):
+ """Return True if the warning is handled."""
+ matched = Siteinfo.WARNING_REGEX.match(message)
+ if mod == 'siteinfo' and matched:
+ invalid_properties.extend(
+ prop.strip() for prop in matched.group(1).split(','))
+ return True
+ else:
+ return False
+
if isinstance(prop, basestring):
props = [prop]
else:
props = prop
if len(props) == 0:
raise ValueError('At least one property name must be provided.')
+ invalid_properties = []
try:
- data = pywikibot.data.api.CachedRequest(
+ request = pywikibot.data.api.CachedRequest(
expiry=pywikibot.config.API_config_expiry if expiry is False else expiry,
site=self._site,
action='query',
meta='siteinfo',
- siprop='|'.join(props)).submit()
+ siprop='|'.join(props))
+ # warnings are handled later
+ request._warning_handler = warn_handler
+ data = request.submit()
except api.APIError as e:
if e.code == 'siunknown_siprop':
if len(props) == 1:
@@ -987,13 +1004,8 @@
raise
else:
result = {}
- if 'warnings' in data and 'siteinfo' in data['warnings']:
- invalid_properties = []
- for prop in re.match(u"^Unrecognized values? for parameter "
- u"'siprop': ([^,]+(?:, [^,]+)*)$",
- data['warnings']['siteinfo']['*']).group(1).split(','):
- prop = prop.strip()
- invalid_properties += [prop]
+ if invalid_properties:
+ for prop in invalid_properties:
result[prop] = (Siteinfo._get_default(prop), False)
pywikibot.log(u"Unable to get siprop(s) '{0}'".format(
u"', '".join(invalid_properties)))
--
To view, visit https://gerrit.wikimedia.org/r/155235
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I62d13dca5b592bddf72dbbdcc0b8edf77c0059ed
Gerrit-PatchSet: 6
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: Mpaa <mpaa.wiki(a)gmail.com>
Gerrit-Reviewer: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged.
Change subject: Require login for two site methods
......................................................................
Require login for two site methods
Also purgepages was recently added as a API 'write' action
which asserts it must be performed by a logged in user.
is_uploaddisabled added recently for bug 69090 only
works if the user is logged in, otherwise the assert
module returns an error before the upload module can
respond. Re-raise any API error that is_uploaddisabled
is not expecting.
Use test projects to test is_uploaddisabled, as the
travis config already has username's for those sites.
Change-Id: I009260c9476852cc8ee818aa791fd15f1dc05ec1
---
M pywikibot/site.py
M tests/site_tests.py
2 files changed, 11 insertions(+), 3 deletions(-)
Approvals:
John Vandenberg: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/site.py b/pywikibot/site.py
index 6825a26..bcead58 100644
--- a/pywikibot/site.py
+++ b/pywikibot/site.py
@@ -3885,6 +3885,7 @@
return ((unwatch and "unwatched" in watched)
or (not unwatch and "watched" in result))
+ @must_be(group='user')
def purgepages(self, pages, **kwargs):
"""Purge the server's cache for one or multiple pages.
@@ -3936,6 +3937,7 @@
def getImagesFromAnHash(self, hash_found=None):
return self.getFilesFromAnHash(hash_found)
+ @must_be(group='user')
def is_uploaddisabled(self):
"""Return True if upload is disabled on site.
@@ -3959,8 +3961,14 @@
except api.APIError as error:
if error.code == u'uploaddisabled':
self._uploaddisabled = True
- else:
+ elif error.code == u'missingparam':
+ # If the upload module is enabled, the above dummy request
+ # does not have sufficient parameters and will cause a
+ # 'missingparam' error.
self._uploaddisabled = False
+ else:
+ # Unexpected error
+ raise
return self._uploaddisabled
@deprecate_arg('imagepage', 'filepage')
diff --git a/tests/site_tests.py b/tests/site_tests.py
index 5f72381..f3d8899 100644
--- a/tests/site_tests.py
+++ b/tests/site_tests.py
@@ -1184,10 +1184,10 @@
class TestUploadEnabledSite(SiteTestCase):
def test_is_uploaddisabled(self):
- site = pywikibot.Site('commons', 'commons')
+ site = pywikibot.Site('test', 'wikipedia')
self.assertFalse(site.is_uploaddisabled())
- site = pywikibot.Site('wikidata', 'wikidata')
+ site = pywikibot.Site('test', 'wikidata')
self.assertTrue(site.is_uploaddisabled())
--
To view, visit https://gerrit.wikimedia.org/r/158918
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I009260c9476852cc8ee818aa791fd15f1dc05ec1
Gerrit-PatchSet: 2
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: Mpaa <mpaa.wiki(a)gmail.com>
Gerrit-Reviewer: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged.
Change subject: site.py: check if site is enabled for uploads
......................................................................
site.py: check if site is enabled for uploads
Fix Bug 69090: upload.py attempts uploads on wikis that
don't allow uploads.
Check APIError code to determine if Site is enabled for uploading.
Currently it is done and cached at first upload attempt.
Added also a helper function is_uploaddisabled() to determine if a site
has uploads enabled, based on a dummy upload attempt.
Throttle has been disabled for such request to speed up the query
(implementation taken from I80b2bba9e63832173d5b697db1f4ea419ca1122f).
Change-Id: Id8ae0cd51582d76b7bdd15c53e39b4105995b540
---
M pywikibot/site.py
M scripts/upload.py
M tests/site_tests.py
3 files changed, 60 insertions(+), 7 deletions(-)
Approvals:
John Vandenberg: Looks good to me, approved
XZise: Looks good to me, but someone else must approve
jenkins-bot: Verified
diff --git a/pywikibot/site.py b/pywikibot/site.py
index cfbd128..214f638 100644
--- a/pywikibot/site.py
+++ b/pywikibot/site.py
@@ -3897,6 +3897,33 @@
def getImagesFromAnHash(self, hash_found=None):
return self.getFilesFromAnHash(hash_found)
+ def is_uploaddisabled(self):
+ """Return True if upload is disabled on site.
+
+ If not called directly, it is cached by the first attempted
+ upload action.
+
+ """
+ if hasattr(self, '_uploaddisabled'):
+ return self._uploaddisabled
+ else:
+ # attempt a fake upload; on enabled sites will fail for:
+ # missingparam: One of the parameters
+ # filekey, file, url, statuskey is required
+ # TODO: is there another way?
+ try:
+ dummy = pywikibot.FilePage(self, title='dummy')
+ token = self.token(dummy, 'edit')
+ req = api.Request(site=self, action="upload",
+ token=token, throttle=False)
+ req.submit()
+ except api.APIError as error:
+ if error.code == u'uploaddisabled':
+ self._uploaddisabled = True
+ else:
+ self._uploaddisabled = False
+ return self._uploaddisabled
+
@deprecate_arg('imagepage', 'filepage')
def upload(self, filepage, source_filename=None, source_url=None,
comment=None, text=None, watch=False, ignore_warnings=False,
@@ -3923,6 +3950,7 @@
and the chunk size is positive but lower than the file size.
@type chunk_size: int
"""
+
upload_warnings = {
# map API warning codes to user error messages
# %(msg)s will be replaced by message string from API responsse
@@ -3980,8 +4008,14 @@
req.mime_params['chunk'] = (chunk, None, {'filename': req.params['filename']})
if file_key:
req['filekey'] = file_key
- # TODO: Proper error and warning handling
- data = req.submit()['upload']
+ try:
+ data = req.submit()['upload']
+ self._uploaddisabled = False
+ except api.APIError as error:
+ # TODO: catch and process foreseeable errors
+ if error.code == u'uploaddisabled':
+ self._uploaddisabled = True
+ raise error
if 'warnings' in data:
result = data
break
@@ -4016,11 +4050,15 @@
req["ignorewarnings"] = ""
try:
result = req.submit()
- except api.APIError:
+ self._uploaddisabled = False
+ except api.APIError as error:
# TODO: catch and process foreseeable errors
- raise
+ if error.code == u'uploaddisabled':
+ self._uploaddisabled = True
+ raise error
result = result["upload"]
- pywikibot.debug(result, _logger)
+ pywikibot.debug(result, _logger)
+
if "warnings" in result:
warning = list(result["warnings"].keys())[0]
message = result["warnings"][warning]
diff --git a/scripts/upload.py b/scripts/upload.py
index 5ab1607..2b6436e 100755
--- a/scripts/upload.py
+++ b/scripts/upload.py
@@ -270,7 +270,12 @@
else:
pywikibot.output(u"Upload aborted.")
return
-
+ except pywikibot.data.api.APIError as error:
+ if error.code == u'uploaddisabled':
+ pywikibot.error("Upload error: Local file uploads are disabled on %s."
+ % site)
+ else:
+ pywikibot.error("Upload error: ", exc_info=True)
except Exception:
pywikibot.error("Upload error: ", exc_info=True)
diff --git a/tests/site_tests.py b/tests/site_tests.py
index 125b6aa..df636c2 100644
--- a/tests/site_tests.py
+++ b/tests/site_tests.py
@@ -16,7 +16,7 @@
import pywikibot
from pywikibot.exceptions import Error, NoPage
-from tests.utils import PywikibotTestCase, unittest
+from tests.utils import SiteTestCase, PywikibotTestCase, unittest
if sys.version_info[0] > 2:
basestring = (str, )
@@ -1179,6 +1179,16 @@
self.assertEqual(ll.site.family.name, 'wikipedia')
+class TestUploadEnabledSite(SiteTestCase):
+
+ def test_is_uploaddisabled(self):
+ site = pywikibot.Site('commons', 'commons')
+ self.assertFalse(site.is_uploaddisabled())
+
+ site = pywikibot.Site('wikidata', 'wikidata')
+ self.assertTrue(site.is_uploaddisabled())
+
+
if __name__ == '__main__':
try:
unittest.main()
--
To view, visit https://gerrit.wikimedia.org/r/156465
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Id8ae0cd51582d76b7bdd15c53e39b4105995b540
Gerrit-PatchSet: 6
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: Mpaa <mpaa.wiki(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: Mpaa <mpaa.wiki(a)gmail.com>
Gerrit-Reviewer: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: jenkins-bot <>
XZise has submitted this change and it was merged.
Change subject: Page save related exception hierarchy
......................................................................
Page save related exception hierarchy
All page save related exceptions are now subclasses of the new
PageSaveRelatedError, and PageNotSaved now is an alias of
PageSaveRelatedError.
Exceptions have been added for the known API error codes encountered
by Site.editpage that are errors specific to the page being edited,
and all page related save exceptions are re-raised to be handled by
scripts.
Scripts may continue to catch specific exceptions to customise
actions or errors, however scripts now only need to catch one exception
(PageNotSaved) to avoid halting on issues relating to a specific page.
PageNotSaved has been moved to last in the exception handling
of four scripts when it was previously before another exception
which is now a subclass of PageNotSaved.
Added edit_failure_tests to check exceptions are raised.
Bug: 55264
Bug: 67276
Change-Id: Id8b8c8055100329e7844b5db6500f71bbb1673f7
---
M pywikibot/__init__.py
M pywikibot/exceptions.py
M pywikibot/page.py
M pywikibot/site.py
M scripts/add_text.py
M scripts/blockpageschecker.py
M scripts/reflinks.py
M scripts/replace.py
A tests/edit_failure_tests.py
9 files changed, 236 insertions(+), 68 deletions(-)
Approvals:
XZise: Looks good to me, approved
diff --git a/pywikibot/__init__.py b/pywikibot/__init__.py
index 0228e79..184d875 100644
--- a/pywikibot/__init__.py
+++ b/pywikibot/__init__.py
@@ -37,7 +37,9 @@
Error, InvalidTitle, BadTitle, NoPage, SectionError,
NoSuchSite, NoUsername, UserBlocked,
PageRelatedError, IsRedirectPage, IsNotRedirectPage,
- PageNotSaved, LockedPage, EditConflict,
+ PageSaveRelatedError, PageNotSaved, OtherPageSaveError,
+ LockedPage, CascadeLockedPage, LockedNoPage,
+ EditConflict, PageDeletedConflict, PageCreatedConflict,
ServerError, FatalServerError, Server504Error,
CaptchaError, SpamfilterError, CircularRedirect,
WikiBaseError, CoordinateGlobeUnknownException,
@@ -69,7 +71,10 @@
'Error', 'InvalidTitle', 'BadTitle', 'NoPage', 'SectionError',
'NoSuchSite', 'NoUsername', 'UserBlocked',
'PageRelatedError', 'IsRedirectPage', 'IsNotRedirectPage',
- 'PageNotSaved', 'UploadWarning', 'LockedPage', 'EditConflict',
+ 'PageSaveRelatedError', 'PageNotSaved', 'OtherPageSaveError',
+ 'LockedPage', 'CascadeLockedPage', 'LockedNoPage',
+ 'EditConflict', 'PageDeletedConflict', 'PageCreatedConflict',
+ 'UploadWarning',
'ServerError', 'FatalServerError', 'Server504Error',
'CaptchaError', 'SpamfilterError', 'CircularRedirect',
'WikiBaseError', 'CoordinateGlobeUnknownException',
diff --git a/pywikibot/exceptions.py b/pywikibot/exceptions.py
index 35f7d2d..661fcf5 100644
--- a/pywikibot/exceptions.py
+++ b/pywikibot/exceptions.py
@@ -1,6 +1,43 @@
# -*- coding: utf-8 -*-
"""
Exception classes used throughout the framework.
+
+Error: Base class, all exceptions should the subclass of this class.
+ - NoUsername: Username is not in user-config.py
+ - UserBlockedY: our username or IP has been blocked
+ - AutoblockUser: requested action on a virtual autoblock user not valid
+ - UserActionRefuse
+ - NoSuchSite: Site does not exist
+ - BadTitle: Server responded with BadTitle
+ - InvalidTitle: Invalid page title
+ - PageNotFound: Page not found in list
+ - CaptchaError: Captcha is asked and config.solve_captcha == False
+ - Server504Error: Server timed out with HTTP 504 code
+
+PageRelatedError: any exception which is caused by an operation on a Page.
+ - NoPage: Page does not exist
+ - IsRedirectPage: Page is a redirect page
+ - IsNotRedirectPage: Page is not a redirect page
+ - CircularRedirect: Page is a circular redirect
+ - SectionError: The section specified by # does not exist
+ - LockedPage: Page is locked
+ - LockedNoPage: Title is locked against creation
+ - CascadeLockedPage: Page is locked due to cascading protection
+
+PageSaveRelatedError: page exceptions within the save operation on a Page.
+ (alias: PageNotSaved)
+ - SpamfilterError: MediaWiki spam filter detected a blacklisted URL
+ - OtherPageSaveError: misc. other save related exception.
+ - EditConflict: Edit conflict while uploading the page
+ - PageDeletedConflict: Page was deleted since being retrieved
+ - PageCreatedConflict: Page was created by another user
+
+ServerError: a problem with the server.
+ - FatalServerError: A fatal/non-recoverable server error
+
+WikiBaseError: any issue specific to Wikibase.
+ - CoordinateGlobeUnknownException: globe is not implemented yet.
+
"""
#
# (C) Pywikibot team, 2008
@@ -29,25 +66,72 @@
class PageRelatedError(Error):
- """Abstract Exception, used when the Exception concerns a particular
- Page, and when a generic message can be written once for all"""
+ """
+ Abstract Exception, used when the exception concerns a particular Page.
+ """
+
# Preformated UNICODE message where the page title will be inserted
# Override this in subclasses.
# u"Oh noes! Page %s is too funky, we should not delete it ;("
message = None
- def __init__(self, page):
+ def __init__(self, page, message=None):
"""
- @param page
+ Constructor.
+
+ @param page: Page that caused the exception
@type page: Page object
"""
+ if message:
+ self.message = message
+
if self.message is None:
raise Error("PageRelatedError is abstract. Can't instantiate it!")
- super(PageRelatedError, self).__init__(self.message % page)
- self._page = page
+
+ self.page = page
+ self.title = page.title(asLink=True)
+ self.site = page.site
+
+ if '%(' in self.message and ')s' in self.message:
+ super(PageRelatedError, self).__init__(self.message % self.__dict__)
+ else:
+ super(PageRelatedError, self).__init__(self.message % page)
def getPage(self):
return self._page
+
+
+class PageSaveRelatedError(PageRelatedError):
+
+ """Saving the page has failed"""
+ message = u"Page %s was not saved."
+
+ # This property maintains backwards compatibility with
+ # the old PageNotSaved which inherited from Error
+ # (not PageRelatedError) and exposed the normal 'args'
+ # which could be printed
+ @property
+ def args(self):
+ return unicode(self)
+
+
+class OtherPageSaveError(PageSaveRelatedError):
+
+ """Saving the page has failed due to uncatchable error."""
+ message = "Edit to page %(title)s failed:\n%(reason)s"
+
+ def __init__(self, page, reason):
+ """ Constructor.
+
+ @param reason: Details of the problem
+ @type reason: Exception or basestring
+ """
+ self.reason = reason
+ super(OtherPageSaveError, self).__init__(page)
+
+ @property
+ def args(self):
+ return unicode(self.reason)
class NoUsername(Error):
@@ -95,10 +179,22 @@
"""Invalid page title"""
-class LockedPage(PageRelatedError):
+class LockedPage(PageSaveRelatedError):
"""Page is locked"""
message = u"Page %s is locked."
+
+
+class LockedNoPage(LockedPage):
+
+ """Title is locked against creation"""
+ message = u"Page %s does not exist and is locked preventing creation."
+
+
+class CascadeLockedPage(LockedPage):
+
+ """Page is locked due to cascading protection"""
+ message = u"Page %s is locked due to cascading protection."
class SectionError(Error):
@@ -106,26 +202,38 @@
"""The section specified by # does not exist"""
-class PageNotSaved(Error):
-
- """Saving the page has failed"""
+PageNotSaved = PageSaveRelatedError
-class EditConflict(PageNotSaved):
+class EditConflict(PageSaveRelatedError):
"""There has been an edit conflict while uploading the page"""
+ message = u"Page %s could not be saved due to an edit conflict"
-class SpamfilterError(PageNotSaved):
+class PageDeletedConflict(EditConflict):
+
+ """Page was deleted since being retrieved"""
+ message = u"Page %s has been deleted since last retrieved."
+
+
+class PageCreatedConflict(EditConflict):
+
+ """Page was created by another user"""
+ message = u"Page %s has been created since last retrieved."
+
+
+class SpamfilterError(PageSaveRelatedError):
"""Saving the page has failed because the MediaWiki spam filter detected a
blacklisted URL.
"""
- def __init__(self, arg):
- super(SpamfilterError, self).__init__(
- u'MediaWiki spam filter has been triggered')
- self.url = arg
- self.args = arg,
+
+ message = "Edit to page %(title)s rejected by spam filter due to content:\n%(url)s"
+
+ def __init__(self, page, url):
+ self.url = url
+ super(SpamfilterError, self).__init__(page)
class ServerError(Error):
diff --git a/pywikibot/page.py b/pywikibot/page.py
index d30e649..e81aae5 100644
--- a/pywikibot/page.py
+++ b/pywikibot/page.py
@@ -960,9 +960,8 @@
else:
watchval = "unwatch"
if not force and not self.botMayEdit():
- raise pywikibot.PageNotSaved(
- "Page %s not saved; editing restricted by {{bots}} template"
- % self.title(asLink=True))
+ raise pywikibot.OtherPageSaveError(
+ self, "Editing restricted by {{bots}} template")
if botflag is None:
botflag = ("bot" in self.site.userinfo["rights"])
if async:
@@ -986,20 +985,17 @@
watch=watchval, bot=botflag, **kwargs)
if not done:
pywikibot.warning(u"Page %s not saved" % link)
- raise pywikibot.PageNotSaved(link)
+ raise pywikibot.PageNotSaved(self)
else:
pywikibot.output(u"Page %s saved" % link)
- except pywikibot.LockedPage as err:
- # re-raise the LockedPage exception so that calling program
- # can re-try if appropriate
- if not callback and not async:
- raise
# TODO: other "expected" error types to catch?
except pywikibot.Error as err:
pywikibot.log(u"Error saving page %s (%s)\n" % (link, err),
exc_info=True)
if not callback and not async:
- raise pywikibot.PageNotSaved("%s: %s" % (link, err))
+ if isinstance(err, pywikibot.PageSaveRelatedError):
+ raise err
+ raise pywikibot.OtherPageSaveError(self, err)
if callback:
callback(self, err)
diff --git a/pywikibot/site.py b/pywikibot/site.py
index 07a280c..d47fda3 100644
--- a/pywikibot/site.py
+++ b/pywikibot/site.py
@@ -33,9 +33,14 @@
from pywikibot.throttle import Throttle
from pywikibot.data import api
from pywikibot.exceptions import (
- EditConflict,
Error,
+ PageSaveRelatedError,
+ EditConflict,
+ PageCreatedConflict,
+ PageDeletedConflict,
LockedPage,
+ CascadeLockedPage,
+ LockedNoPage,
NoPage,
NoSuchSite,
NoUsername,
@@ -3373,23 +3378,26 @@
rngen.request["grnredirect"] = ""
return rngen
- # catalog of editpage error codes, for use in generating messages
+ # Catalog of editpage error codes, for use in generating messages.
+ # The block at the bottom are page related errors.
_ep_errors = {
"noapiwrite": "API editing not enabled on %(site)s wiki",
"writeapidenied": "User %(user)s is not authorized to edit on %(site)s wiki",
- "protectedtitle": "Title %(title)s is protected against creation on %(site)s",
"cantcreate": "User %(user)s not authorized to create new pages on %(site)s wiki",
"cantcreate-anon": """Bot is not logged in, and anon users are not authorized to create new pages on %(site)s wiki""",
- "articleexists": "Page %(title)s already exists on %(site)s wiki",
"noimageredirect-anon": """Bot is not logged in, and anon users are not authorized to create image redirects on %(site)s wiki""",
"noimageredirect": "User %(user)s not authorized to create image redirects on %(site)s wiki",
- "spamdetected": "Edit to page %(title)s rejected by spam filter due to content:\n",
"filtered": "%(info)s",
"contenttoobig": "%(info)s",
"noedit-anon": """Bot is not logged in, and anon users are not authorized to edit on %(site)s wiki""",
"noedit": "User %(user)s not authorized to edit pages on %(site)s wiki",
- "pagedeleted": "Page %(title)s has been deleted since last retrieved from %(site)s wiki",
- "editconflict": "Page %(title)s not saved due to edit conflict.",
+
+ "editconflict": EditConflict,
+ "articleexists": PageCreatedConflict,
+ "pagedeleted": PageDeletedConflict,
+ "protectedpage": LockedPage,
+ "protectedtitle": LockedNoPage,
+ "cascadeprotected": CascadeLockedPage,
}
@must_be(group='user')
@@ -3437,8 +3445,7 @@
# before the page is saved.
self.lock_page(page)
if lastrev is not None and page.latestRevision() != lastrev:
- raise EditConflict(
- "editpage: Edit conflict detected; saving aborted.")
+ raise EditConflict(page)
params = dict(action="edit",
title=page.title(withSection=False),
text=text, token=token, summary=summary)
@@ -3477,23 +3484,17 @@
u"editpage: received '%s' even though bot is logged in"
% err.code,
_logger)
- errdata = {
- 'site': self,
- 'title': page.title(withSection=False),
- 'user': self.user(),
- 'info': err.info
- }
- if err.code == "spamdetected":
- raise SpamfilterError(
- self._ep_errors[err.code] % errdata
- + err.info[err.info.index("fragment: ") + 9:])
-
- if err.code == "editconflict":
- raise EditConflict(self._ep_errors[err.code] % errdata)
- if err.code in ("protectedpage", "cascadeprotected"):
- raise LockedPage(errdata['title'])
if err.code in self._ep_errors:
- raise Error(self._ep_errors[err.code] % errdata)
+ if issubclass(self._ep_errors[err.code], PageSaveRelatedError):
+ raise self._ep_errors[err.code](page)
+ else:
+ errdata = {
+ 'site': self,
+ 'title': page.title(withSection=False),
+ 'user': self.user(),
+ 'info': err.info
+ }
+ raise Error(self._ep_errors[err.code] % errdata)
pywikibot.debug(
u"editpage: Unexpected error code '%s' received."
% err.code,
@@ -3537,6 +3538,8 @@
u"page not saved"
% captcha)
return False
+ elif 'spamblacklist' in result['edit']:
+ raise SpamfilterError(page, result['edit']['spamblacklist'])
else:
self.unlock_page(page)
pywikibot.error(u"editpage: unknown failure reason %s"
@@ -3600,8 +3603,9 @@
raise Error("Cannot move page %s to its own title."
% oldtitle)
if not page.exists():
- raise NoPage("Cannot move page %s because it does not exist on %s."
- % (oldtitle, self))
+ raise NoPage(page,
+ "Cannot move page %(page)s because it "
+ "does not exist on %(site)s.")
token = self.tokens['move']
self.lock_page(page)
req = api.Request(site=self, action="move", to=newtitle,
diff --git a/scripts/add_text.py b/scripts/add_text.py
index b3cd488..1d104e1 100644
--- a/scripts/add_text.py
+++ b/scripts/add_text.py
@@ -260,13 +260,13 @@
u'Cannot change %s because of blacklist entry %s'
% (page.title(), e.url))
return (False, False, always)
- except pywikibot.PageNotSaved as error:
- pywikibot.output(u'Error putting page: %s' % error.args)
- return (False, False, always)
except pywikibot.LockedPage:
pywikibot.output(u'Skipping %s (locked page)'
% page.title())
return (False, False, always)
+ except pywikibot.PageNotSaved as error:
+ pywikibot.output(u'Error putting page: %s' % error.args)
+ return (False, False, always)
else:
# Break only if the errors are one after the other...
errorCount = 0
diff --git a/scripts/blockpageschecker.py b/scripts/blockpageschecker.py
index 0417458..70c5fdf 100755
--- a/scripts/blockpageschecker.py
+++ b/scripts/blockpageschecker.py
@@ -474,14 +474,14 @@
u'blacklist entry %s'
% (page.title(), e.url))
break
- except pywikibot.PageNotSaved as error:
- pywikibot.output(u'Error putting page: %s'
- % (error.args,))
- break
except pywikibot.LockedPage:
pywikibot.output(u'The page is still protected. '
u'Skipping...')
break
+ except pywikibot.PageNotSaved as error:
+ pywikibot.output(u'Error putting page: %s'
+ % (error.args,))
+ break
else:
# Break only if the errors are one after the other
errorCount = 0
diff --git a/scripts/reflinks.py b/scripts/reflinks.py
index 2d35316..b22636a 100644
--- a/scripts/reflinks.py
+++ b/scripts/reflinks.py
@@ -744,11 +744,11 @@
pywikibot.output(
u'Cannot change %s because of blacklist entry %s'
% (page.title(), e.url))
- except pywikibot.PageNotSaved as error:
- pywikibot.error(u'putting page: %s' % (error.args,))
except pywikibot.LockedPage:
pywikibot.output(u'Skipping %s (locked page)'
% page.title())
+ except pywikibot.PageNotSaved as error:
+ pywikibot.error(u'putting page: %s' % (error.args,))
except pywikibot.ServerError as e:
pywikibot.output(u'Server Error : %s' % e)
diff --git a/scripts/replace.py b/scripts/replace.py
index a8e6b46..9d92a1e 100755
--- a/scripts/replace.py
+++ b/scripts/replace.py
@@ -412,12 +412,12 @@
pywikibot.output(
u'Cannot change %s because of blacklist entry %s'
% (page.title(), e.url))
- except pywikibot.PageNotSaved as error:
- pywikibot.output(u'Error putting page: %s'
- % (error.args,))
except pywikibot.LockedPage:
pywikibot.output(u'Skipping %s (locked page)'
% (page.title(),))
+ except pywikibot.PageNotSaved as error:
+ pywikibot.output(u'Error putting page: %s'
+ % (error.args,))
def prepareRegexForMySQL(pattern):
diff --git a/tests/edit_failure_tests.py b/tests/edit_failure_tests.py
new file mode 100644
index 0000000..50010b8
--- /dev/null
+++ b/tests/edit_failure_tests.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+"""
+Tests for edit failures.
+
+These tests should never write to the wiki,
+unless something has broken badly.
+"""
+#
+# (C) Pywikibot team, 2014
+#
+# Distributed under the terms of the MIT license.
+#
+__version__ = '$Id$'
+
+import pywikibot
+from pywikibot import (
+ LockedPage,
+ SpamfilterError,
+ OtherPageSaveError,
+)
+from tests.utils import SiteTestCase, unittest
+
+
+class TestSaveFailure(SiteTestCase):
+ """Test cases for edits which should fail to save."""
+
+ write = True
+
+ def setUp(self):
+ super(TestSaveFailure, self).setUp()
+ self.site = pywikibot.Site('test', 'wikipedia')
+
+ def test_protected(self):
+ """Test that protected titles raise the appropriate exception."""
+ if self.site._username[1]:
+ raise unittest.SkipTest('Testing failure of edit protected with a sysop account')
+ page = pywikibot.Page(self.site, 'Wikipedia:Create a new page')
+ self.assertRaises(LockedPage, page.save)
+
+ def test_spam(self):
+ """Test that spam in content raise the appropriate exception."""
+ page = pywikibot.Page(self.site, 'Wikipedia:Sandbox')
+ page.text = 'http://badsite.com'
+ self.assertRaisesRegexp(SpamfilterError, 'badsite.com', page.save)
+
+ def test_nobots(self):
+ """Test that {{nobots}} raise the appropriate exception."""
+ page = pywikibot.Page(self.site, 'User:John Vandenberg/nobots')
+ self.assertRaisesRegexp(OtherPageSaveError, 'nobots', page.save)
+
+if __name__ == '__main__':
+ try:
+ unittest.main()
+ except SystemExit:
+ pass
--
To view, visit https://gerrit.wikimedia.org/r/141398
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Id8b8c8055100329e7844b5db6500f71bbb1673f7
Gerrit-PatchSet: 11
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: Mpaa <mpaa.wiki(a)gmail.com>
Gerrit-Reviewer: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged.
Change subject: [FIX] Category: Only dump cache if loaded
......................................................................
[FIX] Category: Only dump cache if loaded
This also removes the 'loaded' variable as it's basically defined
if both the attributes are present. This also set the content to
'loaded' after loading the file.
Change-Id: I788166b45f5644a23d6d3dad858f8d32b6e16e38
---
M scripts/category.py
1 file changed, 7 insertions(+), 4 deletions(-)
Approvals:
John Vandenberg: Looks good to me, approved
jenkins-bot: Verified
diff --git a/scripts/category.py b/scripts/category.py
index 68dfc9d..246d637 100755
--- a/scripts/category.py
+++ b/scripts/category.py
@@ -141,12 +141,16 @@
if not os.path.isabs(filename):
filename = config.datafilepath(filename)
self.filename = filename
- self.loaded = False
if rebuild:
self.rebuild()
+ @property
+ def is_loaded(self):
+ """Return whether the contents have been loaded."""
+ return hasattr(self, 'catContentDB') and hasattr(self, 'superclassDB')
+
def _load(self):
- if not self.loaded:
+ if not self.is_loaded():
try:
f = bz2.BZ2File(self.filename, 'r')
pywikibot.output(u'Reading dump from %s'
@@ -166,7 +170,6 @@
def rebuild(self):
self.catContentDB = {}
self.superclassDB = {}
- self.loaded = True
def getSubcats(self, supercat):
"""Return the list of subcategories for a given supercategory.
@@ -226,7 +229,7 @@
filename = self.filename
elif not os.path.isabs(filename):
filename = config.datafilepath(filename)
- if self.catContentDB or self.superclassDB:
+ if self.is_loaded and (self.catContentDB or self.superclassDB):
pywikibot.output(u'Dumping to %s, please wait...'
% config.shortpath(filename))
f = bz2.BZ2File(filename, 'w')
--
To view, visit https://gerrit.wikimedia.org/r/158844
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I788166b45f5644a23d6d3dad858f8d32b6e16e38
Gerrit-PatchSet: 2
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged.
Change subject: movepages.py unable to login without password.lwp
......................................................................
movepages.py unable to login without password.lwp
Site.movepage must have user rights.
Added missing @must_be(group='user'), which forces login().
Bug 70447
Change-Id: I09061c0a8e4fb80362eccddf00cb713fe83aabe4
---
M pywikibot/site.py
1 file changed, 1 insertion(+), 0 deletions(-)
Approvals:
John Vandenberg: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/site.py b/pywikibot/site.py
index 07a280c..ad3ae8d 100644
--- a/pywikibot/site.py
+++ b/pywikibot/site.py
@@ -3576,6 +3576,7 @@
"[[%(newtitle)s]] file extension does not match content of [[%(oldtitle)s]]"
}
+ @must_be(group='user')
def movepage(self, page, newtitle, summary, movetalk=True,
noredirect=False):
"""Move a Page to a new title.
--
To view, visit https://gerrit.wikimedia.org/r/158805
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I09061c0a8e4fb80362eccddf00cb713fe83aabe4
Gerrit-PatchSet: 1
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: Mpaa <mpaa.wiki(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: jenkins-bot <>