Skip to content

Commit 1d07bb5

Browse files
authored
GQL Compliant Errors (#1096)
* Adds superclass `GqlError` for `Neo4jError` and `DriverError` * Use properties and setters for attributes on errors * Loosen constraint on utc_patch handling to enable hydration in `FAILURE` responses to early messages (e.g. `HELLO`).
1 parent 12f50b9 commit 1d07bb5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+3625
-265
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
See also https://github.com/neo4j/neo4j-python-driver/wiki for a full changelog.
44

55
## NEXT RELEASE
6-
- No breaking or major changes.
6+
- Deprecated setting attributes on `Neo4jError` like `message` and `code`.
7+
- Deprecated undocumented method `Neo4jError.hydrate`.
8+
It's internal and should not be used by client code.
79

810

911
## Version 5.25

docs/source/api.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1989,6 +1989,17 @@ Errors
19891989
******
19901990

19911991

1992+
GQL Errors
1993+
==========
1994+
.. autoexception:: neo4j.exceptions.GqlError()
1995+
:show-inheritance:
1996+
:members: gql_status, message, gql_status_description, gql_raw_classification, gql_classification, diagnostic_record, __cause__
1997+
1998+
.. autoclass:: neo4j.exceptions.GqlErrorClassification()
1999+
:show-inheritance:
2000+
:members:
2001+
2002+
19922003
Neo4j Errors
19932004
============
19942005

src/neo4j/_async/io/_bolt.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ def _to_auth_dict(cls, auth):
217217
try:
218218
return vars(auth)
219219
except (KeyError, TypeError) as e:
220+
# TODO: 6.0 - change this to be a DriverError (or subclass)
220221
raise AuthError(
221222
f"Cannot determine auth details from {auth!r}"
222223
) from e
@@ -306,6 +307,7 @@ def protocol_handlers(cls, protocol_version=None):
306307
AsyncBolt5x4,
307308
AsyncBolt5x5,
308309
AsyncBolt5x6,
310+
AsyncBolt5x7,
309311
)
310312

311313
handlers = {
@@ -322,6 +324,7 @@ def protocol_handlers(cls, protocol_version=None):
322324
AsyncBolt5x4.PROTOCOL_VERSION: AsyncBolt5x4,
323325
AsyncBolt5x5.PROTOCOL_VERSION: AsyncBolt5x5,
324326
AsyncBolt5x6.PROTOCOL_VERSION: AsyncBolt5x6,
327+
AsyncBolt5x7.PROTOCOL_VERSION: AsyncBolt5x7,
325328
}
326329

327330
if protocol_version is None:
@@ -458,7 +461,10 @@ async def open(
458461

459462
# avoid new lines after imports for better readability and conciseness
460463
# fmt: off
461-
if protocol_version == (5, 6):
464+
if protocol_version == (5, 7):
465+
from ._bolt5 import AsyncBolt5x7
466+
bolt_cls = AsyncBolt5x7
467+
elif protocol_version == (5, 6):
462468
from ._bolt5 import AsyncBolt5x6
463469
bolt_cls = AsyncBolt5x6
464470
elif protocol_version == (5, 5):
@@ -506,6 +512,7 @@ async def open(
506512
await AsyncBoltSocket.close_socket(s)
507513

508514
supported_versions = cls.protocol_handlers().keys()
515+
# TODO: 6.0 - raise public DriverError subclass instead
509516
raise BoltHandshakeError(
510517
"The neo4j server does not support communication with this "
511518
"driver. This driver has support for Bolt protocols "
@@ -909,6 +916,16 @@ def goodbye(self, dehydration_hooks=None, hydration_hooks=None):
909916
def new_hydration_scope(self):
910917
return self.hydration_handler.new_hydration_scope()
911918

919+
def _default_hydration_hooks(self, dehydration_hooks, hydration_hooks):
920+
if dehydration_hooks is not None and hydration_hooks is not None:
921+
return dehydration_hooks, hydration_hooks
922+
hydration_scope = self.new_hydration_scope()
923+
if dehydration_hooks is None:
924+
dehydration_hooks = hydration_scope.dehydration_hooks
925+
if hydration_hooks is None:
926+
hydration_hooks = hydration_scope.hydration_hooks
927+
return dehydration_hooks, hydration_hooks
928+
912929
def _append(
913930
self, signature, fields=(), response=None, dehydration_hooks=None
914931
):

src/neo4j/_async/io/_bolt3.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,9 @@ async def hello(self, dehydration_hooks=None, hydration_hooks=None):
215215
or self.notifications_disabled_classifications is not None
216216
):
217217
self.assert_notification_filtering_support()
218+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
219+
dehydration_hooks, hydration_hooks
220+
)
218221
headers = self.get_base_headers()
219222
headers.update(self.auth_dict)
220223
logged_headers = dict(headers)
@@ -275,6 +278,9 @@ async def route(
275278
f"{self.PROTOCOL_VERSION!r}. Trying to impersonate "
276279
f"{imp_user!r}."
277280
)
281+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
282+
dehydration_hooks, hydration_hooks
283+
)
278284

279285
metadata = {}
280286
records = []
@@ -337,6 +343,9 @@ def run(
337343
or notifications_disabled_classifications is not None
338344
):
339345
self.assert_notification_filtering_support()
346+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
347+
dehydration_hooks, hydration_hooks
348+
)
340349
if not parameters:
341350
parameters = {}
342351
extra = {}
@@ -379,6 +388,9 @@ def discard(
379388
**handlers,
380389
):
381390
# Just ignore n and qid, it is not supported in the Bolt 3 Protocol.
391+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
392+
dehydration_hooks, hydration_hooks
393+
)
382394
log.debug("[#%04X] C: DISCARD_ALL", self.local_port)
383395
self._append(
384396
b"\x2f",
@@ -396,6 +408,9 @@ def pull(
396408
**handlers,
397409
):
398410
# Just ignore n and qid, it is not supported in the Bolt 3 Protocol.
411+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
412+
dehydration_hooks, hydration_hooks
413+
)
399414
log.debug("[#%04X] C: PULL_ALL", self.local_port)
400415
self._append(
401416
b"\x3f",
@@ -435,6 +450,9 @@ def begin(
435450
or notifications_disabled_classifications is not None
436451
):
437452
self.assert_notification_filtering_support()
453+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
454+
dehydration_hooks, hydration_hooks
455+
)
438456
extra = {}
439457
if mode in {READ_ACCESS, "r"}:
440458
# It will default to mode "w" if nothing is specified
@@ -464,6 +482,9 @@ def begin(
464482
)
465483

466484
def commit(self, dehydration_hooks=None, hydration_hooks=None, **handlers):
485+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
486+
dehydration_hooks, hydration_hooks
487+
)
467488
log.debug("[#%04X] C: COMMIT", self.local_port)
468489
self._append(
469490
b"\x12",
@@ -475,6 +496,9 @@ def commit(self, dehydration_hooks=None, hydration_hooks=None, **handlers):
475496
def rollback(
476497
self, dehydration_hooks=None, hydration_hooks=None, **handlers
477498
):
499+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
500+
dehydration_hooks, hydration_hooks
501+
)
478502
log.debug("[#%04X] C: ROLLBACK", self.local_port)
479503
self._append(
480504
b"\x13",
@@ -490,6 +514,9 @@ async def reset(self, dehydration_hooks=None, hydration_hooks=None):
490514
Add a RESET message to the outgoing queue, send it and consume all
491515
remaining messages.
492516
"""
517+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
518+
dehydration_hooks, hydration_hooks
519+
)
493520
log.debug("[#%04X] C: RESET", self.local_port)
494521
response = ResetResponse(self, "reset", hydration_hooks)
495522
self._append(
@@ -499,6 +526,9 @@ async def reset(self, dehydration_hooks=None, hydration_hooks=None):
499526
await self.fetch_all()
500527

501528
def goodbye(self, dehydration_hooks=None, hydration_hooks=None):
529+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
530+
dehydration_hooks, hydration_hooks
531+
)
502532
log.debug("[#%04X] C: GOODBYE", self.local_port)
503533
self._append(b"\x02", (), dehydration_hooks=dehydration_hooks)
504534

src/neo4j/_async/io/_bolt4.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ async def hello(self, dehydration_hooks=None, hydration_hooks=None):
131131
or self.notifications_disabled_classifications is not None
132132
):
133133
self.assert_notification_filtering_support()
134+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
135+
dehydration_hooks, hydration_hooks
136+
)
134137
headers = self.get_base_headers()
135138
headers.update(self.auth_dict)
136139
logged_headers = dict(headers)
@@ -184,6 +187,9 @@ async def route(
184187
f"{self.PROTOCOL_VERSION!r}. Trying to impersonate "
185188
f"{imp_user!r}."
186189
)
190+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
191+
dehydration_hooks, hydration_hooks
192+
)
187193
metadata = {}
188194
records = []
189195

@@ -244,6 +250,9 @@ def run(
244250
or notifications_disabled_classifications is not None
245251
):
246252
self.assert_notification_filtering_support()
253+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
254+
dehydration_hooks, hydration_hooks
255+
)
247256
if not parameters:
248257
parameters = {}
249258
extra = {}
@@ -292,6 +301,9 @@ def discard(
292301
hydration_hooks=None,
293302
**handlers,
294303
):
304+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
305+
dehydration_hooks, hydration_hooks
306+
)
295307
extra = {"n": n}
296308
if qid != -1:
297309
extra["qid"] = qid
@@ -311,6 +323,9 @@ def pull(
311323
hydration_hooks=None,
312324
**handlers,
313325
):
326+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
327+
dehydration_hooks, hydration_hooks
328+
)
314329
extra = {"n": n}
315330
if qid != -1:
316331
extra["qid"] = qid
@@ -347,6 +362,9 @@ def begin(
347362
or notifications_disabled_classifications is not None
348363
):
349364
self.assert_notification_filtering_support()
365+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
366+
dehydration_hooks, hydration_hooks
367+
)
350368
extra = {}
351369
if mode in {READ_ACCESS, "r"}:
352370
# It will default to mode "w" if nothing is specified
@@ -379,6 +397,9 @@ def begin(
379397
)
380398

381399
def commit(self, dehydration_hooks=None, hydration_hooks=None, **handlers):
400+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
401+
dehydration_hooks, hydration_hooks
402+
)
382403
log.debug("[#%04X] C: COMMIT", self.local_port)
383404
self._append(
384405
b"\x12",
@@ -390,6 +411,9 @@ def commit(self, dehydration_hooks=None, hydration_hooks=None, **handlers):
390411
def rollback(
391412
self, dehydration_hooks=None, hydration_hooks=None, **handlers
392413
):
414+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
415+
dehydration_hooks, hydration_hooks
416+
)
393417
log.debug("[#%04X] C: ROLLBACK", self.local_port)
394418
self._append(
395419
b"\x13",
@@ -405,6 +429,9 @@ async def reset(self, dehydration_hooks=None, hydration_hooks=None):
405429
Add a RESET message to the outgoing queue, send it and consume all
406430
remaining messages.
407431
"""
432+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
433+
dehydration_hooks, hydration_hooks
434+
)
408435
log.debug("[#%04X] C: RESET", self.local_port)
409436
response = ResetResponse(self, "reset", hydration_hooks)
410437
self._append(
@@ -414,6 +441,9 @@ async def reset(self, dehydration_hooks=None, hydration_hooks=None):
414441
await self.fetch_all()
415442

416443
def goodbye(self, dehydration_hooks=None, hydration_hooks=None):
444+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
445+
dehydration_hooks, hydration_hooks
446+
)
417447
log.debug("[#%04X] C: GOODBYE", self.local_port)
418448
self._append(b"\x02", (), dehydration_hooks=dehydration_hooks)
419449

@@ -547,6 +577,9 @@ async def route(
547577
f"{self.PROTOCOL_VERSION!r}. Trying to impersonate "
548578
f"{imp_user!r}."
549579
)
580+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
581+
dehydration_hooks, hydration_hooks
582+
)
550583

551584
routing_context = self.routing_context or {}
552585
log.debug(
@@ -576,6 +609,9 @@ async def hello(self, dehydration_hooks=None, hydration_hooks=None):
576609
or self.notifications_disabled_classifications is not None
577610
):
578611
self.assert_notification_filtering_support()
612+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
613+
dehydration_hooks, hydration_hooks
614+
)
579615

580616
def on_success(metadata):
581617
self.configuration_hints.update(metadata.pop("hints", {}))
@@ -635,6 +671,9 @@ async def route(
635671
dehydration_hooks=None,
636672
hydration_hooks=None,
637673
):
674+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
675+
dehydration_hooks, hydration_hooks
676+
)
638677
routing_context = self.routing_context or {}
639678
db_context = {}
640679
if database is not None:
@@ -683,6 +722,9 @@ def run(
683722
or notifications_disabled_classifications is not None
684723
):
685724
self.assert_notification_filtering_support()
725+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
726+
dehydration_hooks, hydration_hooks
727+
)
686728
if not parameters:
687729
parameters = {}
688730
extra = {}
@@ -744,6 +786,9 @@ def begin(
744786
or notifications_disabled_classifications is not None
745787
):
746788
self.assert_notification_filtering_support()
789+
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
790+
dehydration_hooks, hydration_hooks
791+
)
747792
extra = {}
748793
if mode in {READ_ACCESS, "r"}:
749794
# It will default to mode "w" if nothing is specified

0 commit comments

Comments
 (0)