jenkins-bot has submitted this change and it was merged.

Change subject: [FEAT] upload: Support ignore warnings callback
......................................................................


[FEAT] upload: Support ignore warnings callback

This supports a callback method which is executed whenever an upload caused a
warning. This fixes the issue when multiple warnings are reported that the
upload method basically returned only one warning randomly.

It also provides a way to remove the success message as the method now returns
a boolean to indicate whether the upload succeeded.

Change-Id: I391bb2b7fd4a0f318c5b4537d4b615569413f447
---
M pywikibot/site.py
M scripts/upload.py
2 files changed, 141 insertions(+), 41 deletions(-)

Approvals:
  John Vandenberg: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/pywikibot/site.py b/pywikibot/site.py
index c09f399..3666f03 100644
--- a/pywikibot/site.py
+++ b/pywikibot/site.py
@@ -5180,7 +5180,7 @@
     @deprecate_arg('imagepage', 'filepage')
     def upload(self, filepage, source_filename=None, source_url=None,
                comment=None, text=None, watch=False, ignore_warnings=False,
-               chunk_size=0, _file_key=None, _offset=0):
+               chunk_size=0, _file_key=None, _offset=0, report_success=None):
         """Upload a file to the wiki.
 
         Either source_filename or source_url, but not both, must be provided.
@@ -5195,8 +5195,16 @@
         @param text: Initial page text; if this is not set, then
             filepage.text will be used, or comment.
         @param watch: If true, add filepage to the bot user's watchlist
-        @param ignore_warnings: if true, ignore API warnings and force
-            upload (for example, to overwrite an existing file); default False
+        @param ignore_warnings: It may be a static boolean, a callable 
returning
+            a boolean or an iterable. The callable gets a list of UploadWarning
+            instances and the iterable should contain the warning codes for
+            which an equivalent callable would return True if all UploadWarning
+            codes are in thet list. If the result is False it'll not continuing
+            uploading the file and otherwise disable any warning and
+            reattempting to upload the file. NOTE: If report_success is True or
+            None it'll raise an UploadWarning exception if the static boolean 
is
+            False.
+        @type ignore_warnings: bool or callable or iterable of str
         @param chunk_size: The chunk size in bytesfor chunked uploading (see
             U{https://www.mediawiki.org/wiki/API:Upload#Chunked_uploading}). It
             will only upload in chunks, if the version number is 1.20 or higher
@@ -5209,7 +5217,23 @@
             continue a previously canceled chunked upload. If False it treats
             that as a finished upload. By default starts at 0.
         @type _offset: int or bool
+        @param report_success: If the upload was successful it'll print a
+            success message and if ignore_warnings is set to False it'll
+            raise an UploadWarning if a warning occurred. If it's None 
(default)
+            it'll be True if ignore_warnings is a bool and False otherwise. If
+            it's True or None ignore_warnings must be a bool.
+        @return: It returns True if the upload was successful and False
+            otherwise.
+        @rtype: bool
         """
+        def create_warnings_list(response):
+            return [
+                api.UploadWarning(
+                    warning,
+                    upload_warnings.get(warning, '%(msg)s') % {'msg': data},
+                    _file_key, response.get('offset', 0))
+                for warning, data in response['warnings'].items()]
+
         upload_warnings = {
             # map API warning codes to user error messages
             # %(msg)s will be replaced by message string from API responsse
@@ -5240,6 +5264,22 @@
         if not comment:
             raise ValueError("APISite.upload: cannot upload file without "
                              "a summary/description.")
+        if report_success is None:
+            report_success = isinstance(ignore_warnings, bool)
+        if report_success is True:
+            if not isinstance(ignore_warnings, bool):
+                raise ValueError('report_success may only be set to True when '
+                                 'ignore_warnings is a boolean')
+            issue_deprecation_warning('"ignore_warnings" as a boolean and '
+                                      '"report_success" is True or None',
+                                      '"report_success=False" or define '
+                                      '"ignore_warnings" as callable/iterable',
+                                      2)
+        if isinstance(ignore_warnings, Iterable):
+            ignored_warnings = ignore_warnings
+            ignore_warnings = lambda warnings: all(w.code in ignored_warnings
+                                                   for w in warnings)
+        ignore_all_warnings = not callable(ignore_warnings) and ignore_warnings
         if text is None:
             text = filepage.text
         if not text:
@@ -5289,7 +5329,7 @@
                                 'filesize': filesize,
                                 'offset': offset,
                                 'filename': file_page_title,
-                                'ignorewarnings': ignore_warnings})
+                                'ignorewarnings': ignore_all_warnings})
                         req.mime_params['chunk'] = (chunk,
                                                     ("application", 
"octet-stream"),
                                                     {'filename': 
mime_filename})
@@ -5303,7 +5343,16 @@
                             if error.code == u'uploaddisabled':
                                 self._uploaddisabled = True
                             raise error
-                        if 'warnings' in data and not ignore_warnings:
+                        if 'warnings' in data and not ignore_all_warnings:
+                            if callable(ignore_warnings):
+                                if ignore_warnings(create_warnings_list(data)):
+                                    # Future warnings of this run can be 
ignored
+                                    ignore_warnings = True
+                                    ignore_all_warnings = True
+                                    offset = result.get('offset', 0)
+                                    continue
+                                else:
+                                    return False
                             result = data
                             if 'offset' not in result:
                                 result['offset'] = 0
@@ -5345,7 +5394,7 @@
                 url=source_url, comment=comment, text=text, token=token)
         if not result:
             final_request['watch'] = watch
-            final_request['ignorewarnings'] = ignore_warnings
+            final_request['ignorewarnings'] = ignore_all_warnings
             try:
                 result = final_request.submit()
                 self._uploaddisabled = False
@@ -5357,10 +5406,7 @@
             result = result["upload"]
             pywikibot.debug(result, _logger)
 
-        if "warnings" in result and not ignore_warnings:
-            # TODO: Handle multiple warnings at the same time
-            warning = list(result["warnings"].keys())[0]
-            message = result["warnings"][warning]
+        if 'warnings' in result and not ignore_all_warnings:
             if 'filekey' in result:
                 _file_key = result['filekey']
             elif 'sessionkey' in result:
@@ -5370,6 +5416,24 @@
             else:
                 _file_key = None
                 pywikibot.warning('No filekey defined.')
+            if not report_success:
+                if ignore_warnings(create_warnings_list(result)):
+                    return self.upload(
+                        filepage, source_filename, source_url, comment, text,
+                        watch, True, chunk_size, _file_key,
+                        result.get('offset', False), report_success=False)
+                else:
+                    return False
+            warn('When ignore_warnings=False in APISite.upload will change '
+                 'from raising an UploadWarning into behaving like being a '
+                 'callable returning False.', DeprecationWarning, 2)
+            if len(result['warnings']) > 1:
+                warn('The upload returned {0} warnings: '
+                     '{1}'.format(len(result['warnings']),
+                                  ', '.join(result['warnings'])),
+                     UserWarning, 2)
+            warning = list(result["warnings"].keys())[0]
+            message = result["warnings"][warning]
             raise pywikibot.UploadWarning(warning, upload_warnings[warning]
                                           % {'msg': message},
                                           file_key=_file_key,
@@ -5378,12 +5442,13 @@
         elif "result" not in result:
             pywikibot.output(u"Upload: unrecognized response: %s" % result)
         if result["result"] == "Success":
-            pywikibot.output(u"Upload successful.")
+            if report_success:
+                pywikibot.output(u"Upload successful.")
             # If we receive a nochange, that would mean we're in simulation
             # mode, don't attempt to access imageinfo
             if "nochange" not in result:
                 filepage._load_file_revisions([result["imageinfo"]])
-            return
+        return result['result'] == 'Success'
 
     @deprecated_args(number='total',
                      repeat=None,
diff --git a/scripts/upload.py b/scripts/upload.py
index abf0ff7..5c50ab0 100755
--- a/scripts/upload.py
+++ b/scripts/upload.py
@@ -23,6 +23,13 @@
                   'Mi': Mebibytes (1024x1024 B)
                 The suffixes are case insensitive.
 
+It is possible to combine -abortonwarn and -ignorewarn so that if the specific
+warning is given it won't apply the general one but more specific one. So if it
+should ignore specific warnings and abort on the rest it's possible by defining
+no warning for -abortonwarn and the specific warnings for -ignorewarn. The 
order
+does not matter. If both are unspecific or a warning is specified by both, 
it'll
+prefer aborting.
+
 If any other arguments are given, the first is either URL, filename or 
directory
 to upload, and the rest is a proposed description to go with the upload. If 
none
 of these are given, the user is asked for the directory, file or URL to upload.
@@ -197,6 +204,44 @@
         t.close()
         return tempname
 
+    def _handle_warning(self, warning):
+        """
+        Return whether the warning cause an abort or be ignored.
+
+        @param warning: The warning name
+        @type warning: str
+        @return: False if this warning should cause an abort, True if it should
+            be ignored or None if this warning has no default handler.
+        @rtype: bool or None
+        """
+        if self.aborts is not True:
+            if warning in self.aborts:
+                return False
+        if self.ignoreWarning is True or (self.ignoreWarning is not False and
+                                          warning in self.ignoreWarning):
+            return True
+        return None if self.aborts is not True else False
+
+    def _handle_warnings(self, warnings):
+        messages = '\n'.join('{0.code}: {0.info}'.format(warning)
+                             for warning in sorted(warnings,
+                                                   key=lambda w: w.code))
+        if len(warnings) > 1:
+            messages = '\n' + messages
+        pywikibot.output('We got the following warning(s): ' + messages)
+        answer = True
+        for warning in warnings:
+            this_answer = self._handle_warning(warning.code)
+            if this_answer is False:
+                answer = False
+                break
+            elif this_answer is None:
+                answer = None
+        if answer is None:
+            answer = pywikibot.input_yn(u"Do you want to ignore?",
+                                        default=False, automatic_quit=False)
+        return answer
+
     def process_filename(self, file_url=None):
         """Return base filename portion of file_url."""
         if not file_url:
@@ -351,54 +396,44 @@
 
         pywikibot.output(u'Uploading file to %s via API...' % site)
 
+        success = False
         try:
-            apiIgnoreWarnings = False
             if self.ignoreWarning is True:
                 apiIgnoreWarnings = True
+            else:
+                apiIgnoreWarnings = self._handle_warnings
             if self.uploadByUrl:
-                site.upload(imagepage, source_url=file_url,
-                            ignore_warnings=apiIgnoreWarnings,
-                            _file_key=_file_key, _offset=_offset)
+                success = site.upload(imagepage, source_url=file_url,
+                                      ignore_warnings=apiIgnoreWarnings,
+                                      _file_key=_file_key, _offset=_offset)
             else:
                 if "://" in file_url:
                     temp = self.read_file_content(file_url)
                 else:
                     temp = file_url
-                site.upload(imagepage, source_filename=temp,
-                            ignore_warnings=apiIgnoreWarnings,
-                            chunk_size=self.chunk_size,
-                            _file_key=_file_key, _offset=_offset)
+                success = site.upload(imagepage, source_filename=temp,
+                                      ignore_warnings=apiIgnoreWarnings,
+                                      chunk_size=self.chunk_size,
+                                      _file_key=_file_key, _offset=_offset)
 
-        except pywikibot.data.api.UploadWarning as warn:
-            pywikibot.output(
-                u'We got a warning message: {0} - {1}'.format(warn.code, 
warn.message))
-            if self.abort_on_warn(warn.code):
-                answer = False
-            elif self.ignore_on_warn(warn.code):
-                answer = True
-            else:
-                answer = pywikibot.input_yn(u"Do you want to ignore?",
-                                            default=False, 
automatic_quit=False)
-            if answer:
-                self.ignoreWarning = True
-                self.keepFilename = True
-                return self.upload_file(file_url, debug, warn.file_key, 
warn.offset)
-            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)
+            return None
         except Exception:
             pywikibot.error("Upload error: ", exc_info=True)
-
+            return None
         else:
-            # No warning, upload complete.
-            pywikibot.output(u"Upload of %s successful." % filename)
-            return filename  # data['filename']
+            if success:
+                # No warning, upload complete.
+                pywikibot.output(u"Upload of %s successful." % filename)
+                return filename  # data['filename']
+            else:
+                pywikibot.output(u"Upload aborted.")
+                return None
 
     def run(self):
         """Run bot."""

-- 
To view, visit https://gerrit.wikimedia.org/r/233102
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I391bb2b7fd4a0f318c5b4537d4b615569413f447
Gerrit-PatchSet: 7
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: XZise <[email protected]>
Gerrit-Reviewer: John Vandenberg <[email protected]>
Gerrit-Reviewer: Ladsgroup <[email protected]>
Gerrit-Reviewer: XZise <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
Pywikibot-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/pywikibot-commits

Reply via email to