From 20175c14494e468b4af999f6644ba404df1e7a8e Mon Sep 17 00:00:00 2001 From: Robsdedude Date: Fri, 16 Sep 2022 14:25:29 +0200 Subject: [PATCH 1/2] Improve Noe4jError representation When the error is not received from the DBMS, but instead originates from somewhere in the driver, it might not have a code and a message. In that case, we fall back to the default Exception representation. Related: * https://github.com/neo4j/neo4j-python-driver/issues/796 --- neo4j/exceptions.py | 8 +++++--- tests/unit/test_exceptions.py | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/neo4j/exceptions.py b/neo4j/exceptions.py index aba256539..5a0ce2585 100644 --- a/neo4j/exceptions.py +++ b/neo4j/exceptions.py @@ -146,14 +146,16 @@ def is_fatal_during_discovery(self): return False def __str__(self): - return "{{code: {code}}} {{message: {message}}}".format(code=self.code, message=self.message) + if self.code and self.message: + return "{{code: {code}}} {{message: {message}}}".format( + code=self.code, message=self.message + ) + return super().__str__() class ClientError(Neo4jError): """ The Client sent a bad request - changing the request might yield a successful outcome. """ - def __str__(self): - return super(Neo4jError, self).__str__() class DatabaseError(Neo4jError): diff --git a/tests/unit/test_exceptions.py b/tests/unit/test_exceptions.py index 2152a5b6d..d11dc7011 100644 --- a/tests/unit/test_exceptions.py +++ b/tests/unit/test_exceptions.py @@ -237,3 +237,20 @@ def test_transient_error_is_retriable_case_3(): assert isinstance(error, TransientError) assert error.is_retriable() is True + + +def test_neo4j_error_from_server_as_str(): + error = Neo4jError.hydrate(message="Test error message", + code="Neo.ClientError.General.UnknownError") + + assert isinstance(error, ClientError) + assert str(error) == ("{code: Neo.ClientError.General.UnknownError} " + "{message: Test error message}") + + +@pytest.mark.parametrize("cls", (Neo4jError, ClientError)) +def test_neo4j_error_from_code_as_str(cls): + error = cls("Generated somewhere in the driver") + + assert isinstance(error, cls) + assert str(error) == "Generated somewhere in the driver" From 59dc3666d8d5f1a5b05b5bd2cfbcd73aaf5366f7 Mon Sep 17 00:00:00 2001 From: Robsdedude Date: Mon, 19 Sep 2022 09:21:57 +0200 Subject: [PATCH 2/2] Make Neo4jError representation more robust Even though, all errors that come from the server go through `Noej4jError.hydrate` which (currently) makes sure that `code` *and* `message` are set, this might change in the future. --- neo4j/exceptions.py | 2 +- tests/unit/test_exceptions.py | 53 ++++++++++++++++++++++++++++++----- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/neo4j/exceptions.py b/neo4j/exceptions.py index 5a0ce2585..a9309d6c2 100644 --- a/neo4j/exceptions.py +++ b/neo4j/exceptions.py @@ -146,7 +146,7 @@ def is_fatal_during_discovery(self): return False def __str__(self): - if self.code and self.message: + if self.code or self.message: return "{{code: {code}}} {{message: {message}}}".format( code=self.code, message=self.message ) diff --git a/tests/unit/test_exceptions.py b/tests/unit/test_exceptions.py index d11dc7011..81475c949 100644 --- a/tests/unit/test_exceptions.py +++ b/tests/unit/test_exceptions.py @@ -239,18 +239,57 @@ def test_transient_error_is_retriable_case_3(): assert error.is_retriable() is True -def test_neo4j_error_from_server_as_str(): - error = Neo4jError.hydrate(message="Test error message", - code="Neo.ClientError.General.UnknownError") +@pytest.mark.parametrize( + ("code", "message", "expected_cls", "expected_str"), + ( + ( + "Neo.ClientError.General.UnknownError", + "Test error message", + ClientError, + "{code: Neo.ClientError.General.UnknownError} " + "{message: Test error message}" + ), + ( + None, + "Test error message", + DatabaseError, + "{code: Neo.DatabaseError.General.UnknownError} " + "{message: Test error message}" + ), + ( + "", + "Test error message", + DatabaseError, + "{code: Neo.DatabaseError.General.UnknownError} " + "{message: Test error message}" + ), + ( + "Neo.ClientError.General.UnknownError", + None, + ClientError, + "{code: Neo.ClientError.General.UnknownError} " + "{message: An unknown error occurred}" + ), + ( + "Neo.ClientError.General.UnknownError", + "", + ClientError, + "{code: Neo.ClientError.General.UnknownError} " + "{message: An unknown error occurred}" + ), + ) +) +def test_neo4j_error_from_server_as_str(code, message, expected_cls, + expected_str): + error = Neo4jError.hydrate(code=code, message=message) - assert isinstance(error, ClientError) - assert str(error) == ("{code: Neo.ClientError.General.UnknownError} " - "{message: Test error message}") + assert type(error) == expected_cls + assert str(error) == expected_str @pytest.mark.parametrize("cls", (Neo4jError, ClientError)) def test_neo4j_error_from_code_as_str(cls): error = cls("Generated somewhere in the driver") - assert isinstance(error, cls) + assert type(error)== cls assert str(error) == "Generated somewhere in the driver"