From a075eb798f97379cec2d7d1cc971a774ccaeb9b7 Mon Sep 17 00:00:00 2001 From: Julius Park Date: Thu, 2 Jul 2020 13:53:08 -0400 Subject: [PATCH] PYTHON-1787: fix NotMasterError no attribute error (#450) --- .gitignore | 1 + pymongo/errors.py | 24 +++++++------- test/test_database.py | 2 +- test/test_errors.py | 73 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 12 deletions(-) create mode 100644 test/test_errors.py diff --git a/.gitignore b/.gitignore index 385160b01..de435d109 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 e5d52bfe3..0dfa1f237 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 @@ class OperationFailure(PyMongoError): 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 self.__max_wire_version = max_wire_version @@ -176,11 +183,6 @@ class OperationFailure(PyMongoError): """ 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): diff --git a/test/test_database.py b/test/test_database.py index 18eb322ea..349f09005 100644 --- a/test/test_database.py +++ b/test/test_database.py @@ -959,7 +959,7 @@ class TestDatabase(IntegrationTest): try: helpers._check_command_response({'$err': 'foo'}, None) 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 000000000..32d7af328 --- /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()