Skip to content

GQL Compliant Errors #1096

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a815986
First API draft
robsdedude Jul 4, 2024
3a4f3d0
Update to reflect design changes
robsdedude Sep 25, 2024
c1b4973
Add docs, clean-up to-dos and commented out code
robsdedude Sep 25, 2024
ea89c7a
Improve resilience in diag. record enrichment for erroneous types
robsdedude Sep 25, 2024
a0632e3
Fix struct hydration in FAILURE responses
robsdedude Sep 27, 2024
ba3c2a2
Update GQL error fallback description and message
robsdedude Sep 27, 2024
21090f5
Small clean-ups
robsdedude Oct 2, 2024
ba3c97a
Fix GqlError cause handling + add tests for it
robsdedude Oct 3, 2024
2c1ffc1
More unit tests
robsdedude Oct 3, 2024
a9d055d
Merge branch '5.0' into 023-gql-errors
robsdedude Oct 3, 2024
acd01d4
`.. versionadded:: 5.xx` -> `.. versionadded:: 5.26`
robsdedude Oct 3, 2024
2d2aa6e
TestKit backend: only sent GQLError causes to TestKit
robsdedude Oct 4, 2024
205a167
Fix hydration/dehydration hooks confusion
robsdedude Oct 4, 2024
da30b5f
Merge branch '5.0' into 023-gql-errors
robsdedude Oct 4, 2024
48b395b
Loosen constraint on utc_patch handling
robsdedude Oct 4, 2024
6ef1812
TestKit backend: fix error traceback formatting
robsdedude Oct 4, 2024
2901768
TestKit backend: fix GqlError cause serialization
robsdedude Oct 4, 2024
44f356a
TestKit backend: improve general error cause serialization
robsdedude Oct 4, 2024
d74a1e7
TestKit backend: fix warning assertion
robsdedude Oct 7, 2024
129de58
TestKit backend: restore error msg formatting
robsdedude Oct 7, 2024
b4d47a0
TestKit backend: further error message serialization fixes
robsdedude Oct 7, 2024
9dc131d
TestKit backend: I swear, I'll get error serialization right... event…
robsdedude Oct 7, 2024
e0bca52
TestKit backend: surely this time the error formatting is right... ri…
robsdedude Oct 7, 2024
6580566
TestKit backend: are you feeling it now, Mr. Krabs?
robsdedude Oct 8, 2024
ed3908f
😭🙏
robsdedude Oct 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
See also https://github.com/neo4j/neo4j-python-driver/wiki for a full changelog.

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


## Version 5.25
Expand Down
11 changes: 11 additions & 0 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1989,6 +1989,17 @@ Errors
******


GQL Errors
==========
.. autoexception:: neo4j.exceptions.GqlError()
:show-inheritance:
:members: gql_status, message, gql_status_description, gql_raw_classification, gql_classification, diagnostic_record, __cause__

.. autoclass:: neo4j.exceptions.GqlErrorClassification()
:show-inheritance:
:members:


Neo4j Errors
============

Expand Down
19 changes: 18 additions & 1 deletion src/neo4j/_async/io/_bolt.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ def _to_auth_dict(cls, auth):
try:
return vars(auth)
except (KeyError, TypeError) as e:
# TODO: 6.0 - change this to be a DriverError (or subclass)
raise AuthError(
f"Cannot determine auth details from {auth!r}"
) from e
Expand Down Expand Up @@ -306,6 +307,7 @@ def protocol_handlers(cls, protocol_version=None):
AsyncBolt5x4,
AsyncBolt5x5,
AsyncBolt5x6,
AsyncBolt5x7,
)

handlers = {
Expand All @@ -322,6 +324,7 @@ def protocol_handlers(cls, protocol_version=None):
AsyncBolt5x4.PROTOCOL_VERSION: AsyncBolt5x4,
AsyncBolt5x5.PROTOCOL_VERSION: AsyncBolt5x5,
AsyncBolt5x6.PROTOCOL_VERSION: AsyncBolt5x6,
AsyncBolt5x7.PROTOCOL_VERSION: AsyncBolt5x7,
}

if protocol_version is None:
Expand Down Expand Up @@ -458,7 +461,10 @@ async def open(

# avoid new lines after imports for better readability and conciseness
# fmt: off
if protocol_version == (5, 6):
if protocol_version == (5, 7):
from ._bolt5 import AsyncBolt5x7
bolt_cls = AsyncBolt5x7
elif protocol_version == (5, 6):
from ._bolt5 import AsyncBolt5x6
bolt_cls = AsyncBolt5x6
elif protocol_version == (5, 5):
Expand Down Expand Up @@ -506,6 +512,7 @@ async def open(
await AsyncBoltSocket.close_socket(s)

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

def _default_hydration_hooks(self, dehydration_hooks, hydration_hooks):
if dehydration_hooks is not None and hydration_hooks is not None:
return dehydration_hooks, hydration_hooks
hydration_scope = self.new_hydration_scope()
if dehydration_hooks is None:
dehydration_hooks = hydration_scope.dehydration_hooks
if hydration_hooks is None:
hydration_hooks = hydration_scope.hydration_hooks
return dehydration_hooks, hydration_hooks

def _append(
self, signature, fields=(), response=None, dehydration_hooks=None
):
Expand Down
30 changes: 30 additions & 0 deletions src/neo4j/_async/io/_bolt3.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,9 @@ async def hello(self, dehydration_hooks=None, hydration_hooks=None):
or self.notifications_disabled_classifications is not None
):
self.assert_notification_filtering_support()
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)
headers = self.get_base_headers()
headers.update(self.auth_dict)
logged_headers = dict(headers)
Expand Down Expand Up @@ -275,6 +278,9 @@ async def route(
f"{self.PROTOCOL_VERSION!r}. Trying to impersonate "
f"{imp_user!r}."
)
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)

metadata = {}
records = []
Expand Down Expand Up @@ -337,6 +343,9 @@ def run(
or notifications_disabled_classifications is not None
):
self.assert_notification_filtering_support()
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)
if not parameters:
parameters = {}
extra = {}
Expand Down Expand Up @@ -379,6 +388,9 @@ def discard(
**handlers,
):
# Just ignore n and qid, it is not supported in the Bolt 3 Protocol.
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)
log.debug("[#%04X] C: DISCARD_ALL", self.local_port)
self._append(
b"\x2f",
Expand All @@ -396,6 +408,9 @@ def pull(
**handlers,
):
# Just ignore n and qid, it is not supported in the Bolt 3 Protocol.
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)
log.debug("[#%04X] C: PULL_ALL", self.local_port)
self._append(
b"\x3f",
Expand Down Expand Up @@ -435,6 +450,9 @@ def begin(
or notifications_disabled_classifications is not None
):
self.assert_notification_filtering_support()
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)
extra = {}
if mode in {READ_ACCESS, "r"}:
# It will default to mode "w" if nothing is specified
Expand Down Expand Up @@ -464,6 +482,9 @@ def begin(
)

def commit(self, dehydration_hooks=None, hydration_hooks=None, **handlers):
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)
log.debug("[#%04X] C: COMMIT", self.local_port)
self._append(
b"\x12",
Expand All @@ -475,6 +496,9 @@ def commit(self, dehydration_hooks=None, hydration_hooks=None, **handlers):
def rollback(
self, dehydration_hooks=None, hydration_hooks=None, **handlers
):
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)
log.debug("[#%04X] C: ROLLBACK", self.local_port)
self._append(
b"\x13",
Expand All @@ -490,6 +514,9 @@ async def reset(self, dehydration_hooks=None, hydration_hooks=None):
Add a RESET message to the outgoing queue, send it and consume all
remaining messages.
"""
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)
log.debug("[#%04X] C: RESET", self.local_port)
response = ResetResponse(self, "reset", hydration_hooks)
self._append(
Expand All @@ -499,6 +526,9 @@ async def reset(self, dehydration_hooks=None, hydration_hooks=None):
await self.fetch_all()

def goodbye(self, dehydration_hooks=None, hydration_hooks=None):
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)
log.debug("[#%04X] C: GOODBYE", self.local_port)
self._append(b"\x02", (), dehydration_hooks=dehydration_hooks)

Expand Down
45 changes: 45 additions & 0 deletions src/neo4j/_async/io/_bolt4.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ async def hello(self, dehydration_hooks=None, hydration_hooks=None):
or self.notifications_disabled_classifications is not None
):
self.assert_notification_filtering_support()
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)
headers = self.get_base_headers()
headers.update(self.auth_dict)
logged_headers = dict(headers)
Expand Down Expand Up @@ -184,6 +187,9 @@ async def route(
f"{self.PROTOCOL_VERSION!r}. Trying to impersonate "
f"{imp_user!r}."
)
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)
metadata = {}
records = []

Expand Down Expand Up @@ -244,6 +250,9 @@ def run(
or notifications_disabled_classifications is not None
):
self.assert_notification_filtering_support()
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)
if not parameters:
parameters = {}
extra = {}
Expand Down Expand Up @@ -292,6 +301,9 @@ def discard(
hydration_hooks=None,
**handlers,
):
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)
extra = {"n": n}
if qid != -1:
extra["qid"] = qid
Expand All @@ -311,6 +323,9 @@ def pull(
hydration_hooks=None,
**handlers,
):
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)
extra = {"n": n}
if qid != -1:
extra["qid"] = qid
Expand Down Expand Up @@ -347,6 +362,9 @@ def begin(
or notifications_disabled_classifications is not None
):
self.assert_notification_filtering_support()
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)
extra = {}
if mode in {READ_ACCESS, "r"}:
# It will default to mode "w" if nothing is specified
Expand Down Expand Up @@ -379,6 +397,9 @@ def begin(
)

def commit(self, dehydration_hooks=None, hydration_hooks=None, **handlers):
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)
log.debug("[#%04X] C: COMMIT", self.local_port)
self._append(
b"\x12",
Expand All @@ -390,6 +411,9 @@ def commit(self, dehydration_hooks=None, hydration_hooks=None, **handlers):
def rollback(
self, dehydration_hooks=None, hydration_hooks=None, **handlers
):
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)
log.debug("[#%04X] C: ROLLBACK", self.local_port)
self._append(
b"\x13",
Expand All @@ -405,6 +429,9 @@ async def reset(self, dehydration_hooks=None, hydration_hooks=None):
Add a RESET message to the outgoing queue, send it and consume all
remaining messages.
"""
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)
log.debug("[#%04X] C: RESET", self.local_port)
response = ResetResponse(self, "reset", hydration_hooks)
self._append(
Expand All @@ -414,6 +441,9 @@ async def reset(self, dehydration_hooks=None, hydration_hooks=None):
await self.fetch_all()

def goodbye(self, dehydration_hooks=None, hydration_hooks=None):
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)
log.debug("[#%04X] C: GOODBYE", self.local_port)
self._append(b"\x02", (), dehydration_hooks=dehydration_hooks)

Expand Down Expand Up @@ -547,6 +577,9 @@ async def route(
f"{self.PROTOCOL_VERSION!r}. Trying to impersonate "
f"{imp_user!r}."
)
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)

routing_context = self.routing_context or {}
log.debug(
Expand Down Expand Up @@ -576,6 +609,9 @@ async def hello(self, dehydration_hooks=None, hydration_hooks=None):
or self.notifications_disabled_classifications is not None
):
self.assert_notification_filtering_support()
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)

def on_success(metadata):
self.configuration_hints.update(metadata.pop("hints", {}))
Expand Down Expand Up @@ -635,6 +671,9 @@ async def route(
dehydration_hooks=None,
hydration_hooks=None,
):
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)
routing_context = self.routing_context or {}
db_context = {}
if database is not None:
Expand Down Expand Up @@ -683,6 +722,9 @@ def run(
or notifications_disabled_classifications is not None
):
self.assert_notification_filtering_support()
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)
if not parameters:
parameters = {}
extra = {}
Expand Down Expand Up @@ -744,6 +786,9 @@ def begin(
or notifications_disabled_classifications is not None
):
self.assert_notification_filtering_support()
dehydration_hooks, hydration_hooks = self._default_hydration_hooks(
dehydration_hooks, hydration_hooks
)
extra = {}
if mode in {READ_ACCESS, "r"}:
# It will default to mode "w" if nothing is specified
Expand Down
Loading