Skip to content

Commit 8420fc4

Browse files
authored
Fix Notification.description polyfill from GqlStatusObject (#1071)
Bolt 5.6 introduces the original notification description back at the protocol level. This avoids the `Notification.description` changing when connected to GQL aware servers. This issues was detected during homologation, so the problem won't happen with any released server since the bolt version missing the information will not be released.
1 parent d6e2fa6 commit 8420fc4

File tree

13 files changed

+1465
-35
lines changed

13 files changed

+1465
-35
lines changed

src/neo4j/_async/io/_bolt.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ def protocol_handlers(cls, protocol_version=None):
284284
AsyncBolt5x3,
285285
AsyncBolt5x4,
286286
AsyncBolt5x5,
287+
AsyncBolt5x6,
287288
)
288289

289290
handlers = {
@@ -299,6 +300,7 @@ def protocol_handlers(cls, protocol_version=None):
299300
AsyncBolt5x3.PROTOCOL_VERSION: AsyncBolt5x3,
300301
AsyncBolt5x4.PROTOCOL_VERSION: AsyncBolt5x4,
301302
AsyncBolt5x5.PROTOCOL_VERSION: AsyncBolt5x5,
303+
AsyncBolt5x6.PROTOCOL_VERSION: AsyncBolt5x6,
302304
}
303305

304306
if protocol_version is None:
@@ -413,7 +415,10 @@ async def open(
413415

414416
# Carry out Bolt subclass imports locally to avoid circular dependency
415417
# issues.
416-
if protocol_version == (5, 5):
418+
if protocol_version == (5, 6):
419+
from ._bolt5 import AsyncBolt5x6
420+
bolt_cls = AsyncBolt5x6
421+
elif protocol_version == (5, 5):
417422
from ._bolt5 import AsyncBolt5x5
418423
bolt_cls = AsyncBolt5x5
419424
elif protocol_version == (5, 4):

src/neo4j/_async/io/_bolt5.py

+35-3
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,7 @@ def telemetry(self, api: TelemetryAPI, dehydration_hooks=None,
685685
Response(self, "telemetry", hydration_hooks, **handlers),
686686
dehydration_hooks=dehydration_hooks)
687687

688+
688689
class AsyncBolt5x5(AsyncBolt5x4):
689690

690691
PROTOCOL_VERSION = Version(5, 5)
@@ -783,7 +784,7 @@ def begin(self, mode=None, bookmarks=None, metadata=None, timeout=None,
783784
("CURRENT_SCHEMA", "/"),
784785
)
785786

786-
def _make_enrich_diagnostic_record_handler(self, wrapped_handler=None):
787+
def _make_enrich_statuses_handler(self, wrapped_handler=None):
787788
async def handler(metadata):
788789
def enrich(metadata_):
789790
if not isinstance(metadata_, dict):
@@ -794,6 +795,7 @@ def enrich(metadata_):
794795
for status in statuses:
795796
if not isinstance(status, dict):
796797
continue
798+
status["description"] = status.get("status_description")
797799
diag_record = status.setdefault("diagnostic_record", {})
798800
if not isinstance(diag_record, dict):
799801
log.info("[#%04X] _: <CONNECTION> Server supplied an "
@@ -810,14 +812,44 @@ def enrich(metadata_):
810812

811813
def discard(self, n=-1, qid=-1, dehydration_hooks=None,
812814
hydration_hooks=None, **handlers):
813-
handlers["on_success"] = self._make_enrich_diagnostic_record_handler(
815+
handlers["on_success"] = self._make_enrich_statuses_handler(
814816
wrapped_handler=handlers.get("on_success")
815817
)
816818
super().discard(n, qid, dehydration_hooks, hydration_hooks, **handlers)
817819

818820
def pull(self, n=-1, qid=-1, dehydration_hooks=None, hydration_hooks=None,
819821
**handlers):
820-
handlers["on_success"] = self._make_enrich_diagnostic_record_handler(
822+
handlers["on_success"] = self._make_enrich_statuses_handler(
821823
wrapped_handler=handlers.get("on_success")
822824
)
823825
super().pull(n, qid, dehydration_hooks, hydration_hooks, **handlers)
826+
827+
828+
class AsyncBolt5x6(AsyncBolt5x5):
829+
830+
PROTOCOL_VERSION = Version(5, 6)
831+
832+
def _make_enrich_statuses_handler(self, wrapped_handler=None):
833+
async def handler(metadata):
834+
def enrich(metadata_):
835+
if not isinstance(metadata_, dict):
836+
return
837+
statuses = metadata_.get("statuses")
838+
if not isinstance(statuses, list):
839+
return
840+
for status in statuses:
841+
if not isinstance(status, dict):
842+
continue
843+
diag_record = status.setdefault("diagnostic_record", {})
844+
if not isinstance(diag_record, dict):
845+
log.info("[#%04X] _: <CONNECTION> Server supplied an "
846+
"invalid diagnostic record (%r).",
847+
self.local_port, diag_record)
848+
continue
849+
for key, value in self.DEFAULT_DIAGNOSTIC_RECORD:
850+
diag_record.setdefault(key, value)
851+
852+
enrich(metadata)
853+
await AsyncUtil.callback(wrapped_handler, metadata)
854+
855+
return handler

src/neo4j/_sync/io/_bolt.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ def protocol_handlers(cls, protocol_version=None):
284284
Bolt5x3,
285285
Bolt5x4,
286286
Bolt5x5,
287+
Bolt5x6,
287288
)
288289

289290
handlers = {
@@ -299,6 +300,7 @@ def protocol_handlers(cls, protocol_version=None):
299300
Bolt5x3.PROTOCOL_VERSION: Bolt5x3,
300301
Bolt5x4.PROTOCOL_VERSION: Bolt5x4,
301302
Bolt5x5.PROTOCOL_VERSION: Bolt5x5,
303+
Bolt5x6.PROTOCOL_VERSION: Bolt5x6,
302304
}
303305

304306
if protocol_version is None:
@@ -413,7 +415,10 @@ def open(
413415

414416
# Carry out Bolt subclass imports locally to avoid circular dependency
415417
# issues.
416-
if protocol_version == (5, 5):
418+
if protocol_version == (5, 6):
419+
from ._bolt5 import Bolt5x6
420+
bolt_cls = Bolt5x6
421+
elif protocol_version == (5, 5):
417422
from ._bolt5 import Bolt5x5
418423
bolt_cls = Bolt5x5
419424
elif protocol_version == (5, 4):

src/neo4j/_sync/io/_bolt5.py

+35-3
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,7 @@ def telemetry(self, api: TelemetryAPI, dehydration_hooks=None,
685685
Response(self, "telemetry", hydration_hooks, **handlers),
686686
dehydration_hooks=dehydration_hooks)
687687

688+
688689
class Bolt5x5(Bolt5x4):
689690

690691
PROTOCOL_VERSION = Version(5, 5)
@@ -783,7 +784,7 @@ def begin(self, mode=None, bookmarks=None, metadata=None, timeout=None,
783784
("CURRENT_SCHEMA", "/"),
784785
)
785786

786-
def _make_enrich_diagnostic_record_handler(self, wrapped_handler=None):
787+
def _make_enrich_statuses_handler(self, wrapped_handler=None):
787788
def handler(metadata):
788789
def enrich(metadata_):
789790
if not isinstance(metadata_, dict):
@@ -794,6 +795,7 @@ def enrich(metadata_):
794795
for status in statuses:
795796
if not isinstance(status, dict):
796797
continue
798+
status["description"] = status.get("status_description")
797799
diag_record = status.setdefault("diagnostic_record", {})
798800
if not isinstance(diag_record, dict):
799801
log.info("[#%04X] _: <CONNECTION> Server supplied an "
@@ -810,14 +812,44 @@ def enrich(metadata_):
810812

811813
def discard(self, n=-1, qid=-1, dehydration_hooks=None,
812814
hydration_hooks=None, **handlers):
813-
handlers["on_success"] = self._make_enrich_diagnostic_record_handler(
815+
handlers["on_success"] = self._make_enrich_statuses_handler(
814816
wrapped_handler=handlers.get("on_success")
815817
)
816818
super().discard(n, qid, dehydration_hooks, hydration_hooks, **handlers)
817819

818820
def pull(self, n=-1, qid=-1, dehydration_hooks=None, hydration_hooks=None,
819821
**handlers):
820-
handlers["on_success"] = self._make_enrich_diagnostic_record_handler(
822+
handlers["on_success"] = self._make_enrich_statuses_handler(
821823
wrapped_handler=handlers.get("on_success")
822824
)
823825
super().pull(n, qid, dehydration_hooks, hydration_hooks, **handlers)
826+
827+
828+
class Bolt5x6(Bolt5x5):
829+
830+
PROTOCOL_VERSION = Version(5, 6)
831+
832+
def _make_enrich_statuses_handler(self, wrapped_handler=None):
833+
def handler(metadata):
834+
def enrich(metadata_):
835+
if not isinstance(metadata_, dict):
836+
return
837+
statuses = metadata_.get("statuses")
838+
if not isinstance(statuses, list):
839+
return
840+
for status in statuses:
841+
if not isinstance(status, dict):
842+
continue
843+
diag_record = status.setdefault("diagnostic_record", {})
844+
if not isinstance(diag_record, dict):
845+
log.info("[#%04X] _: <CONNECTION> Server supplied an "
846+
"invalid diagnostic record (%r).",
847+
self.local_port, diag_record)
848+
continue
849+
for key, value in self.DEFAULT_DIAGNOSTIC_RECORD:
850+
diag_record.setdefault(key, value)
851+
852+
enrich(metadata)
853+
Util.callback(wrapped_handler, metadata)
854+
855+
return handler

src/neo4j/_work/summary.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def _set_notifications(self):
163163
for notification_key, status_key in (
164164
("title", "title"),
165165
("code", "neo4j_code"),
166-
("description", "status_description"),
166+
("description", "description"),
167167
):
168168
value = status.get(status_key)
169169
if not isinstance(value, str) or not value:

testkitbackend/test_config.json

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"Feature:Bolt:5.3": true,
5858
"Feature:Bolt:5.4": true,
5959
"Feature:Bolt:5.5": true,
60+
"Feature:Bolt:5.6": true,
6061
"Feature:Bolt:Patch:UTC": true,
6162
"Feature:Impersonation": true,
6263
"Feature:TLS:1.1": "Driver blocks TLS 1.1 for security reasons.",

tests/unit/async_/io/test_class_bolt.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def test_class_method_protocol_handlers():
3737
expected_handlers = {
3838
(3, 0),
3939
(4, 1), (4, 2), (4, 3), (4, 4),
40-
(5, 0), (5, 1), (5, 2), (5, 3), (5, 4), (5, 5),
40+
(5, 0), (5, 1), (5, 2), (5, 3), (5, 4), (5, 5), (5, 6),
4141
}
4242

4343
protocol_handlers = AsyncBolt.protocol_handlers()
@@ -65,7 +65,8 @@ def test_class_method_protocol_handlers():
6565
((5, 3), 1),
6666
((5, 4), 1),
6767
((5, 5), 1),
68-
((5, 6), 0),
68+
((5, 6), 1),
69+
((5, 7), 0),
6970
((6, 0), 0),
7071
]
7172
)
@@ -85,7 +86,7 @@ def test_class_method_protocol_handlers_with_invalid_protocol_version():
8586
# [bolt-version-bump] search tag when changing bolt version support
8687
def test_class_method_get_handshake():
8788
handshake = AsyncBolt.get_handshake()
88-
assert (b"\x00\x05\x05\x05\x00\x02\x04\x04\x00\x00\x01\x04\x00\x00\x00\x03"
89+
assert (b"\x00\x06\x06\x05\x00\x02\x04\x04\x00\x00\x01\x04\x00\x00\x00\x03"
8990
== handshake)
9091

9192

@@ -134,6 +135,7 @@ async def test_cancel_hello_in_open(mocker, none_auth):
134135
((5, 3), "neo4j._async.io._bolt5.AsyncBolt5x3"),
135136
((5, 4), "neo4j._async.io._bolt5.AsyncBolt5x4"),
136137
((5, 5), "neo4j._async.io._bolt5.AsyncBolt5x5"),
138+
((5, 6), "neo4j._async.io._bolt5.AsyncBolt5x6"),
137139
),
138140
)
139141
@mark_async_test
@@ -166,14 +168,14 @@ async def test_version_negotiation(
166168
(2, 0),
167169
(4, 0),
168170
(3, 1),
169-
(5, 6),
171+
(5, 7),
170172
(6, 0),
171173
))
172174
@mark_async_test
173175
async def test_failing_version_negotiation(mocker, bolt_version, none_auth):
174176
supported_protocols = (
175177
"('3.0', '4.1', '4.2', '4.3', '4.4', "
176-
"'5.0', '5.1', '5.2', '5.3', '5.4', '5.5')"
178+
"'5.0', '5.1', '5.2', '5.3', '5.4', '5.5', '5.6')"
177179
)
178180

179181
address = ("localhost", 7687)

tests/unit/async_/io/test_class_bolt5x5.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,7 @@ async def test_tracks_last_database(fake_socket_pair, actions):
615615
)
616616
@pytest.mark.parametrize("method", ("pull", "discard"))
617617
@mark_async_test
618-
async def test_enriches_diagnostic_record(
618+
async def test_enriches_statuses(
619619
sent_diag_records,
620620
method,
621621
fake_socket_pair,
@@ -628,7 +628,9 @@ async def test_enriches_diagnostic_record(
628628

629629
sent_metadata = {
630630
"statuses": [
631-
{"diagnostic_record": r} if r is not ... else {}
631+
{"status_description": "the status description", "diagnostic_record": r}
632+
if r is not ...
633+
else { "status_description": "the status description" }
632634
for r in sent_diag_records
633635
]
634636
}
@@ -654,7 +656,9 @@ def extend_diag_record(r):
654656
expected_diag_records = [extend_diag_record(r) for r in sent_diag_records]
655657
expected_metadata = {
656658
"statuses": [
657-
{"diagnostic_record": r} if r is not ... else {}
659+
{"status_description": "the status description", "description": "the status description", "diagnostic_record": r}
660+
if r is not ...
661+
else { "status_description": "the status description", "description": "the status description" }
658662
for r in expected_diag_records
659663
]
660664
}

0 commit comments

Comments
 (0)