Skip to content

Commit eaf5d88

Browse files
committed
Adopted py2neo-style error hydration
1 parent 261a689 commit eaf5d88

File tree

8 files changed

+131
-32
lines changed

8 files changed

+131
-32
lines changed

neo4j/bolt/connection.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,6 @@ def fetch(self):
274274
elif summary_signature == FAILURE:
275275
log_info("S: FAILURE (%r)", summary_metadata)
276276
response.on_failure(summary_metadata or {})
277-
self.reset()
278277
else:
279278
raise ProtocolError("Unexpected response message with signature %02X" % summary_signature)
280279

neo4j/exceptions.py

Lines changed: 120 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,140 @@
1919
# limitations under the License.
2020

2121

22+
"""
23+
This module contains the core driver exceptions.
24+
"""
25+
26+
2227
class AddressError(Exception):
2328
""" Raised when a network address is invalid.
2429
"""
2530

2631

32+
class ProtocolError(Exception):
33+
""" Raised when an unexpected or unsupported protocol event occurs.
34+
"""
35+
36+
37+
class ServiceUnavailable(Exception):
38+
""" Raised when no database service is available.
39+
"""
40+
41+
2742
class SecurityError(Exception):
2843
""" Raised when an action is denied due to security settings.
2944
"""
3045

3146

32-
class AuthError(SecurityError):
33-
""" Raised when authentication failure occurs.
47+
class CypherError(Exception):
48+
""" Raised when the Cypher engine returns an error to the client.
3449
"""
3550

51+
message = None
52+
code = None
53+
classification = None
54+
category = None
55+
title = None
56+
metadata = None
3657

37-
class ProtocolError(Exception):
38-
""" Raised when an unexpected or unsupported protocol event occurs.
58+
@classmethod
59+
def hydrate(cls, message=None, code=None, **metadata):
60+
message = message or "An unknown error occurred."
61+
code = code or "Neo.DatabaseError.General.UnknownError"
62+
try:
63+
_, classification, category, title = code.split(".")
64+
except ValueError:
65+
classification = "DatabaseError"
66+
category = "General"
67+
title = "UnknownError"
68+
if classification == "ClientError":
69+
try:
70+
error_class = client_errors[code]
71+
except KeyError:
72+
error_class = ClientError
73+
elif classification == "DatabaseError":
74+
error_class = DatabaseError
75+
elif classification == "TransientError":
76+
error_class = TransientError
77+
else:
78+
error_class = cls
79+
inst = error_class(message)
80+
inst.message = message
81+
inst.code = code
82+
inst.classification = classification
83+
inst.category = category
84+
inst.title = title
85+
inst.metadata = metadata
86+
return inst
87+
88+
89+
class ClientError(CypherError):
90+
""" The Client sent a bad request - changing the request might yield a successful outcome.
3991
"""
4092

4193

42-
class ServiceUnavailable(Exception):
43-
""" Raised when no database service is available.
94+
class DatabaseError(CypherError):
95+
""" The database failed to service the request.
96+
"""
97+
98+
99+
class TransientError(CypherError):
100+
""" The database cannot service the request right now, retrying later might yield a successful outcome.
101+
"""
102+
103+
104+
class ConstraintError(ClientError):
44105
"""
106+
"""
107+
108+
109+
class CypherSyntaxError(ClientError):
110+
"""
111+
"""
112+
113+
114+
class CypherTypeError(ClientError):
115+
"""
116+
"""
117+
118+
119+
class Forbidden(ClientError, SecurityError):
120+
"""
121+
"""
122+
123+
124+
class AuthError(ClientError, SecurityError):
125+
""" Raised when authentication failure occurs.
126+
"""
127+
128+
129+
client_errors = {
130+
131+
# ConstraintError
132+
"Neo.ClientError.Schema.ConstraintValidationFailed": ConstraintError,
133+
"Neo.ClientError.Schema.ConstraintViolation": ConstraintError,
134+
"Neo.ClientError.Statement.ConstraintVerificationFailed": ConstraintError,
135+
"Neo.ClientError.Statement.ConstraintViolation": ConstraintError,
136+
137+
# CypherSyntaxError
138+
"Neo.ClientError.Statement.InvalidSyntax": CypherSyntaxError,
139+
"Neo.ClientError.Statement.SyntaxError": CypherSyntaxError,
140+
141+
# CypherTypeError
142+
"Neo.ClientError.Procedure.TypeError": CypherTypeError,
143+
"Neo.ClientError.Statement.InvalidType": CypherTypeError,
144+
"Neo.ClientError.Statement.TypeError": CypherTypeError,
145+
146+
# Forbidden
147+
"Neo.ClientError.General.ForbiddenOnReadOnlyDatabase": Forbidden,
148+
"Neo.ClientError.General.ReadOnly": Forbidden,
149+
"Neo.ClientError.Schema.ForbiddenOnConstraintIndex": Forbidden,
150+
"Neo.ClientError.Schema.IndexBelongsToConstraint": Forbidden,
151+
"Neo.ClientError.Security.Forbidden": Forbidden,
152+
"Neo.ClientError.Transaction.ForbiddenDueToTransactionType": Forbidden,
153+
154+
# AuthError
155+
"Neo.ClientError.Security.AuthorizationFailed": AuthError,
156+
"Neo.ClientError.Security.Unauthorized": AuthError,
157+
158+
}

neo4j/v1/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
# limitations under the License.
2020

2121

22+
from neo4j.exceptions import *
23+
2224
from .api import *
2325
from .direct import *
2426
from .exceptions import *

neo4j/v1/api.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@
2727

2828
from neo4j.bolt import ProtocolError, ServiceUnavailable
2929
from neo4j.compat import urlparse
30+
from neo4j.exceptions import CypherError
3031

31-
from .exceptions import DriverError, SessionError, SessionExpired, TransactionError, CypherError
32+
from .exceptions import DriverError, SessionError, SessionExpired, TransactionError
3233

3334

3435
_warned_about_transaction_bookmarks = False
@@ -558,6 +559,7 @@ def close(self):
558559
""" Close this transaction, triggering either a COMMIT or a ROLLBACK.
559560
"""
560561
if not self.closed():
562+
self.sync()
561563
try:
562564
self.sync()
563565
except CypherError:

neo4j/v1/exceptions.py

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@
1919
# limitations under the License.
2020

2121

22-
from neo4j.exceptions import *
23-
24-
2522
class DriverError(Exception):
2623
""" Raised when an error occurs while using a driver.
2724
"""
@@ -42,7 +39,7 @@ def __init__(self, session, *args, **kwargs):
4239

4340
class SessionExpired(SessionError):
4441
""" Raised when no a session is no longer able to fulfil
45-
its purpose, as defined by its original session parameters.
42+
the purpose described by its original parameters.
4643
"""
4744

4845
def __init__(self, session, *args, **kwargs):
@@ -56,17 +53,3 @@ class TransactionError(Exception):
5653
def __init__(self, transaction, *args, **kwargs):
5754
super(TransactionError, self).__init__(*args, **kwargs)
5855
self.transaction = transaction
59-
60-
61-
class CypherError(Exception):
62-
""" Raised when the Cypher engine returns an error to the client.
63-
"""
64-
65-
code = None
66-
message = None
67-
68-
def __init__(self, data):
69-
super(CypherError, self).__init__(data.get("message"))
70-
for key, value in data.items():
71-
if not key.startswith("_"):
72-
setattr(self, key, value)

neo4j/v1/result.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121

2222
from collections import namedtuple
2323

24+
from neo4j.exceptions import CypherError
2425
from neo4j.v1.api import GraphDatabase, StatementResult
25-
from neo4j.v1.exceptions import CypherError
2626
from neo4j.v1.types import Record
2727

2828

@@ -36,8 +36,6 @@ class BoltStatementResult(StatementResult):
3636
""" A handler for the result of Cypher statement execution.
3737
"""
3838

39-
error_class = CypherError
40-
4139
value_system = GraphDatabase.value_systems["packstream"]
4240

4341
zipper = Record
@@ -68,7 +66,7 @@ def on_failure(metadata):
6866
# Called on execution failure.
6967
self._session._connection.acknowledge_failure()
7068
on_footer(metadata)
71-
raise self.error_class(metadata)
69+
raise CypherError.hydrate(**metadata)
7270

7371
run_response.on_success = on_header
7472
run_response.on_failure = on_failure

neo4j/v1/routing.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@
2525
from neo4j.addressing import SocketAddress, resolve
2626
from neo4j.bolt import ConnectionPool, ServiceUnavailable, ProtocolError, DEFAULT_PORT, connect
2727
from neo4j.compat.collections import MutableSet, OrderedDict
28+
from neo4j.exceptions import CypherError
2829
from neo4j.v1.api import Driver, READ_ACCESS, WRITE_ACCESS
29-
from neo4j.v1.exceptions import SessionExpired, CypherError
30+
from neo4j.v1.exceptions import SessionExpired
3031
from neo4j.v1.security import SecurityPlan
3132
from neo4j.v1.session import BoltSession
3233

test/stub/scripts/broken_router.script

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
C: RUN "CALL dbms.cluster.routing.getServers" {}
55
PULL_ALL
6-
S: FAILURE {"code": "Neo.X", "message": "X"}
6+
S: FAILURE {"code": "Neo.DatabaseError.General.UnknownError", "message": "An unknown error occurred."}
77
IGNORED
88
C: ACK_FAILURE
99
S: SUCCESS {}

0 commit comments

Comments
 (0)