diff --git a/.gitignore b/.gitignore index 385160b014..de435d109e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ pymongo.egg-info/ *.egg .tox mongocryptd.pid +.idea/ diff --git a/pymongo/errors.py b/pymongo/errors.py index cf32546850..973719dbd1 100644 --- a/pymongo/errors.py +++ b/pymongo/errors.py @@ -100,6 +100,14 @@ class NetworkTimeout(AutoReconnect): """ +def _format_detailed_error(message, details): + if details is not None: + message = "%s, full error: %s" % (message, details) + if sys.version_info[0] == 2 and isinstance(message, unicode): + message = message.encode('utf-8', errors='replace') + return message + + class NotMasterError(AutoReconnect): """The server responded "not master" or "node is recovering". @@ -113,11 +121,10 @@ class NotMasterError(AutoReconnect): Subclass of :exc:`~pymongo.errors.AutoReconnect`. """ - def __str__(self): - output_str = "%s, full error: %s" % (self._message, self.__details) - if sys.version_info[0] == 2 and isinstance(output_str, unicode): - return output_str.encode('utf-8', errors='replace') - return output_str + def __init__(self, message='', errors=None): + super(NotMasterError, self).__init__( + _format_detailed_error(message, errors), errors=errors) + class ServerSelectionTimeoutError(AutoReconnect): """Thrown when no MongoDB server is available for an operation @@ -149,7 +156,7 @@ def __init__(self, error, code=None, details=None): if details is not None: error_labels = details.get('errorLabels') super(OperationFailure, self).__init__( - error, error_labels=error_labels) + _format_detailed_error(error, details), error_labels=error_labels) self.__code = code self.__details = details @@ -171,11 +178,6 @@ def details(self): """ return self.__details - def __str__(self): - output_str = "%s, full error: %s" % (self._message, self.__details) - if sys.version_info[0] == 2 and isinstance(output_str, unicode): - return output_str.encode('utf-8', errors='replace') - return output_str class CursorNotFound(OperationFailure): """Raised while iterating query results if the cursor is diff --git a/test/test_database.py b/test/test_database.py index 43fac30b0e..6feb1a84e9 100644 --- a/test/test_database.py +++ b/test/test_database.py @@ -959,7 +959,7 @@ def test_command_response_without_ok(self): try: helpers._check_command_response({'$err': 'foo'}) except OperationFailure as e: - self.assertEqual(e.args[0], 'foo') + self.assertEqual(e.args[0], "foo, full error: {'$err': 'foo'}") else: self.fail("_check_command_response didn't raise OperationFailure") diff --git a/test/test_errors.py b/test/test_errors.py new file mode 100644 index 0000000000..32d7af3284 --- /dev/null +++ b/test/test_errors.py @@ -0,0 +1,73 @@ +# Copyright 2020-present MongoDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import traceback + +sys.path[0:0] = [""] + +from pymongo.errors import (NotMasterError, + OperationFailure) +from test import (PyMongoTestCase, + unittest) + + +class TestErrors(PyMongoTestCase): + def test_not_master_error(self): + exc = NotMasterError("not master test", {"errmsg": "error"}) + self.assertIn("full error", str(exc)) + try: + raise exc + except NotMasterError: + self.assertIn("full error", traceback.format_exc()) + + def test_operation_failure(self): + exc = OperationFailure("operation failure test", 10, + {"errmsg": "error"}) + self.assertIn("full error", str(exc)) + try: + raise exc + except OperationFailure: + self.assertIn("full error", traceback.format_exc()) + + def _test_unicode_strs(self, exc): + if sys.version_info[0] == 2: + self.assertEqual("unicode \xf0\x9f\x90\x8d, full error: {" + "'errmsg': u'unicode \\U0001f40d'}", str(exc)) + elif 'PyPy' in sys.version: + # PyPy displays unicode in repr differently. + self.assertEqual("unicode \U0001f40d, full error: {" + "'errmsg': 'unicode \\U0001f40d'}", str(exc)) + else: + self.assertEqual("unicode \U0001f40d, full error: {" + "'errmsg': 'unicode \U0001f40d'}", str(exc)) + try: + raise exc + except Exception: + self.assertIn("full error", traceback.format_exc()) + + def test_unicode_strs_operation_failure(self): + exc = OperationFailure(u'unicode \U0001f40d', 10, + {"errmsg": u'unicode \U0001f40d'}) + self._test_unicode_strs(exc) + + def test_unicode_strs_not_master_error(self): + exc = NotMasterError(u'unicode \U0001f40d', + {"errmsg": u'unicode \U0001f40d'}) + self._test_unicode_strs(exc) + + + +if __name__ == "__main__": + unittest.main()