Skip to content

Commit

Permalink
#211: Implement error handling for MediaWiki warnings on existing files
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
marcfrederick committed Feb 17, 2024
1 parent 390fd4d commit fb44e3b
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 2 deletions.
9 changes: 9 additions & 0 deletions mwclient/client.py
Expand Up @@ -982,7 +982,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
Expand Down
13 changes: 12 additions & 1 deletion mwclient/errors.py
Expand Up @@ -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):
Expand Down
28 changes: 27 additions & 1 deletion test/test_client.py
Expand Up @@ -12,7 +12,6 @@

import unittest.mock as mock


if __name__ == "__main__":
print()
print("Note: Running in stand-alone mode. Consult the README")
Expand Down Expand Up @@ -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):

Expand Down

0 comments on commit fb44e3b

Please sign in to comment.