From 6aada3aefaaef54555e7435c478afc398128ca3a Mon Sep 17 00:00:00 2001 From: Marc Troelitzsch Date: Sun, 18 Feb 2024 00:31:39 +0100 Subject: [PATCH] #211: Implement error handling for MediaWiki warnings on existing files Raises an error when ignore=False and the MediaWiki response contains the `upload.warnings.exists` key. Previously, this warning was logged, but the call to `Site.upload` succeeded. This could be easily missed by users, especially when they haven't configured the log level. --- mwclient/client.py | 10 ++++++++++ mwclient/errors.py | 13 ++++++++++++- test/test_client.py | 28 +++++++++++++++++++++++++++- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/mwclient/client.py b/mwclient/client.py index bd1f7156..74ba99ae 100644 --- a/mwclient/client.py +++ b/mwclient/client.py @@ -904,6 +904,7 @@ def upload(self, file=None, filename=None, description='', ignore=False, Raises: errors.InsufficientPermission requests.exceptions.HTTPError + errors.FileExists: The file already exists and `ignore` is `False`. """ if file_size is not None: @@ -982,7 +983,16 @@ def upload(self, file=None, filename=None, description='', ignore=False, info = {} if self.handle_api_result(info, kwargs=predata, sleeper=sleeper): response = info.get('upload', {}) + # Workaround for https://github.com/mwclient/mwclient/issues/211 + # ---------------------------------------------------------------- + # Raise an error if the file already exists. This is necessary because + # MediaWiki returns a warning, not an error, leading to silent failure. + # The user must explicitly set ignore=True (ignorewarnings=True) to + # overwrite an existing file. + if ignore is False and 'exists' in response.get('warnings', {}): + raise errors.FileExists(filename) break + if file is not None: file.close() return response diff --git a/mwclient/errors.py b/mwclient/errors.py index 845cf315..5c87ccad 100644 --- a/mwclient/errors.py +++ b/mwclient/errors.py @@ -48,7 +48,18 @@ def __str__(self): class FileExists(EditError): - pass + """ + Raised when trying to upload a file that already exists. + + See also: https://www.mediawiki.org/wiki/API:Upload#Upload_warnings + """ + + def __init__(self, file_name): + self.file_name = file_name + + def __str__(self): + return ('The file "{0}" already exists. Set ignore=True to overwrite it.' + .format(self.file_name)) class LoginError(MwClientError): diff --git a/test/test_client.py b/test/test_client.py index c86077f7..757d346e 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -12,7 +12,6 @@ import unittest.mock as mock - if __name__ == "__main__": print() print("Note: Running in stand-alone mode. Consult the README") @@ -762,6 +761,33 @@ def test_upload_missing_upload_permission(self): with pytest.raises(mwclient.errors.InsufficientPermission): self.site.upload(filename='Test', file=StringIO('test')) + def test_upload_file_exists(self): + self.configure() + self.raw_call.side_effect = [ + self.makePageResponse(title='File:Test.jpg', imagerepository='local', + imageinfo=[{ + "comment": "", + "height": 1440, + "metadata": [], + "sha1": "69a764a9cf8307ea4130831a0aa0b9b7f9585726", + "size": 123, + "timestamp": "2013-12-22T07:11:07Z", + "user": "TestUser", + "width": 2160 + }]), + json.dumps({'query': {'tokens': {'csrftoken': self.vars['token']}}}), + json.dumps({ + 'upload': {'result': 'Warning', + 'warnings': {'duplicate': ['Test.jpg'], + 'exists': 'Test.jpg'}, + 'filekey': '1apyzwruya84.da2cdk.1.jpg', + 'sessionkey': '1apyzwruya84.da2cdk.1.jpg'} + }) + ] + + with pytest.raises(mwclient.errors.FileExists): + self.site.upload(file=StringIO('test'), filename='Test.jpg', ignore=False) + class TestClientGetTokens(TestCase):