Skip to content

Commit 97be40c

Browse files
committed
Adjust graph EntitySetView to meet new element_id spec
It's not guaranteed that `element_id == str(id_)` if the server provides both.
1 parent 4c7726d commit 97be40c

File tree

5 files changed

+104
-33
lines changed

5 files changed

+104
-33
lines changed

neo4j/graph/__init__.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,13 @@ class Graph:
4444

4545
def __init__(self):
4646
self._nodes = {}
47+
self._legacy_nodes = {} # TODO: 6.0 - remove
4748
self._relationships = {}
49+
self._legacy_relationships = {} # TODO: 6.0 - remove
4850
self._relationship_types = {}
49-
self._node_set_view = EntitySetView(self._nodes)
50-
self._relationship_set_view = EntitySetView(self._relationships)
51+
self._node_set_view = EntitySetView(self._nodes, self._legacy_nodes)
52+
self._relationship_set_view = EntitySetView(self._relationships,
53+
self._legacy_relationships)
5154

5255
@property
5356
def nodes(self):
@@ -86,9 +89,9 @@ def hydrate_node(self, id_, labels=None,
8689
try:
8790
inst = self.graph._nodes[element_id]
8891
except KeyError:
89-
inst = self.graph._nodes[element_id] = Node(
90-
self.graph, element_id, id_, labels, properties
91-
)
92+
inst = Node(self.graph, element_id, id_, labels, properties)
93+
self.graph._nodes[element_id] = inst
94+
self.graph._legacy_nodes[id_] = inst
9295
else:
9396
# If we have already hydrated this node as the endpoint of
9497
# a relationship, it won't have any labels or properties.
@@ -128,9 +131,11 @@ def hydrate_unbound_relationship(self, id_, type_, properties=None,
128131
inst = self.graph._relationships[element_id]
129132
except KeyError:
130133
r = self.graph.relationship_type(type_)
131-
inst = self.graph._relationships[element_id] = r(
134+
inst = r(
132135
self.graph, element_id, id_, properties
133136
)
137+
self.graph._relationships[element_id] = inst
138+
self.graph._legacy_relationships[id_] = inst
134139
return inst
135140

136141
def hydrate_path(self, nodes, relationships, sequence):
@@ -260,8 +265,9 @@ class EntitySetView(Mapping):
260265
""" View of a set of :class:`.Entity` instances within a :class:`.Graph`.
261266
"""
262267

263-
def __init__(self, entity_dict):
268+
def __init__(self, entity_dict, legacy_entity_dict):
264269
self._entity_dict = entity_dict
270+
self._legacy_entity_dict = legacy_entity_dict # TODO: 6.0 - remove
265271

266272
def __getitem__(self, e_id):
267273
# TODO: 6.0 - remove this compatibility shim
@@ -270,14 +276,7 @@ def __getitem__(self, e_id):
270276
"Accessing entities by an integer id is deprecated, "
271277
"use the new style element_id (str) instead"
272278
)
273-
if isinstance(e_id, float) and int(e_id) == e_id:
274-
# Non-int floats would always fail for legacy IDs
275-
e_id = int(e_id)
276-
elif isinstance(e_id, complex) and int(e_id.real) == e_id:
277-
# complex numbers with imaginary parts or non-integer real
278-
# parts would always fail for legacy IDs
279-
e_id = int(e_id.real)
280-
e_id = str(e_id)
279+
return self._legacy_entity_dict[e_id]
281280
return self._entity_dict[e_id]
282281

283282
def __len__(self):

tests/unit/common/test_api.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,8 @@ def test_serverinfo_initialization():
377377
assert server_info.address is address
378378
assert server_info.protocol_version is version
379379
assert server_info.agent is None
380-
assert server_info.connection_id is None
380+
with pytest.warns(DeprecationWarning):
381+
assert server_info.connection_id is None
381382

382383

383384
@pytest.mark.parametrize(

tests/unit/common/test_data.py

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def test_can_hydrate_v1_node_structure():
3333

3434
with pytest.warns(DeprecationWarning, match="element_id"):
3535
assert alice.id == 123
36-
# for backwards compatibility, the driver should compy the element_id
36+
# for backwards compatibility, the driver should compute the element_id
3737
assert alice.element_id == "123"
3838
assert alice.labels == {"Person"}
3939
assert set(alice.keys()) == {"name"}
@@ -43,13 +43,11 @@ def test_can_hydrate_v1_node_structure():
4343
def test_can_hydrate_v2_node_structure():
4444
hydrant = DataHydrator()
4545

46-
id_ = 123
47-
48-
struct = Structure(b'N', id_, ["Person"], {"name": "Alice"}, "abc")
46+
struct = Structure(b'N', 123, ["Person"], {"name": "Alice"}, "abc")
4947
alice, = hydrant.hydrate([struct])
5048

5149
with pytest.warns(DeprecationWarning, match="element_id"):
52-
assert alice.id == id_
50+
assert alice.id == 123
5351
assert alice.element_id == "abc"
5452
assert alice.labels == {"Person"}
5553
assert set(alice.keys()) == {"name"}
@@ -80,20 +78,17 @@ def test_can_hydrate_v1_relationship_structure():
8078
def test_can_hydrate_v2_relationship_structure():
8179
hydrant = DataHydrator()
8280

83-
id_ = 123
84-
start_id = 456
85-
end_id = 789
86-
struct = Structure(b'R', id_, start_id, end_id, "KNOWS", {"since": 1999},
81+
struct = Structure(b'R', 123, 456, 789, "KNOWS", {"since": 1999},
8782
"abc", "def", "ghi")
8883

8984
rel, = hydrant.hydrate([struct])
9085

9186
with pytest.warns(DeprecationWarning, match="element_id"):
92-
assert rel.id == id_
87+
assert rel.id == 123
9388
with pytest.warns(DeprecationWarning, match="element_id"):
94-
assert rel.start_node.id == start_id
89+
assert rel.start_node.id == 456
9590
with pytest.warns(DeprecationWarning, match="element_id"):
96-
assert rel.end_node.id == end_id
91+
assert rel.end_node.id == 789
9792
# for backwards compatibility, the driver should compy the element_id
9893
assert rel.element_id == "abc"
9994
assert rel.start_node.element_id == "def"
@@ -122,7 +117,8 @@ def test_can_hydrate_in_list():
122117

123118
alice, = alice_in_list
124119

125-
assert alice.id == 123
120+
with pytest.warns(DeprecationWarning, match="element_id"):
121+
assert alice.id == 123
126122
assert alice.labels == {"Person"}
127123
assert set(alice.keys()) == {"name"}
128124
assert alice.get("name") == "Alice"
@@ -138,7 +134,8 @@ def test_can_hydrate_in_dict():
138134

139135
alice = alice_in_dict["foo"]
140136

141-
assert alice.id == 123
137+
with pytest.warns(DeprecationWarning, match="element_id"):
138+
assert alice.id == 123
142139
assert alice.labels == {"Person"}
143140
assert set(alice.keys()) == {"name"}
144141
assert alice.get("name") == "Alice"

tests/unit/common/test_record.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ def test_data_relationship():
287287
gh = Graph.Hydrator(g)
288288
alice = gh.hydrate_node(1, {"Person"}, {"name": "Alice", "age": 33})
289289
bob = gh.hydrate_node(2, {"Person"}, {"name": "Bob", "age": 44})
290-
alice_knows_bob = gh.hydrate_relationship(1, alice.id, bob.id, "KNOWS",
290+
alice_knows_bob = gh.hydrate_relationship(1, 1, 2, "KNOWS",
291291
{"since": 1999})
292292
record = Record(zip(["a", "b", "r"], [alice, bob, alice_knows_bob]))
293293
assert record.data() == {

tests/unit/common/test_types.py

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -477,12 +477,12 @@ def test_path_v2_repr(legacy_id):
477477

478478
)
479479
alice_knows_bob = gh.hydrate_relationship(
480-
1, alice.id, bob.id, "KNOWS", {"since": 1999},
480+
1, 1, 2, "KNOWS", {"since": 1999},
481481
"1" if legacy_id else "alice_knows_bob",
482482
alice.element_id, bob.element_id
483483
)
484484
carol_dislikes_bob = gh.hydrate_relationship(
485-
2, carol.id, bob.id, "DISLIKES", {},
485+
2, 3, 2, "DISLIKES", {},
486486
"2" if legacy_id else "carol_dislikes_bob",
487487
carol.element_id, bob.element_id
488488
)
@@ -493,3 +493,77 @@ def test_path_v2_repr(legacy_id):
493493
f"end=<Node element_id={carol.element_id!r} "
494494
"labels=frozenset({'Person'}) properties={'name': 'Carol'}> size=2>"
495495
)
496+
497+
498+
def test_graph_views_v1():
499+
g = Graph()
500+
gh = Graph.Hydrator(g)
501+
alice = gh.hydrate_node(1, {"Person"}, {"name": "Alice"})
502+
bob = gh.hydrate_node(2, {"Person"}, {"name": "Bob"})
503+
carol = gh.hydrate_node(3, {"Person"}, {"name": "Carol"})
504+
alice_knows_bob = gh.hydrate_relationship(1, 1, 2, "KNOWS",
505+
{"since": 1999})
506+
carol_dislikes_bob = gh.hydrate_relationship(2, 3, 2, "DISLIKES", {})
507+
508+
assert len(g.nodes) == 3
509+
for id_, node in ((1, alice), (2, bob), (3, carol)):
510+
with pytest.warns(DeprecationWarning, match=r"element_id \(str\)"):
511+
assert g.nodes[id_] == node
512+
assert g.nodes[str(id_)] == node
513+
514+
assert len(g.relationships) == 2
515+
for id_, rel in ((1, alice_knows_bob), (2, carol_dislikes_bob)):
516+
with pytest.warns(DeprecationWarning, match=r"element_id \(str\)"):
517+
assert g.relationships[id_] == rel
518+
assert g.relationships[str(id_)] == rel
519+
520+
521+
@pytest.mark.parametrize("legacy_id", (True, False))
522+
def test_graph_views_v2_repr(legacy_id):
523+
g = Graph()
524+
gh = Graph.Hydrator(g)
525+
526+
alice_element_id = "1" if legacy_id else "alice"
527+
bob_element_id = "2" if legacy_id else "bob"
528+
carol_element_id = "3" if legacy_id else "carol"
529+
530+
alice = gh.hydrate_node(1, {"Person"}, {"name": "Alice"}, alice_element_id)
531+
bob = gh.hydrate_node(2, {"Person"}, {"name": "Bob"}, bob_element_id)
532+
carol = gh.hydrate_node(3, {"Person"}, {"name": "Carol"}, carol_element_id)
533+
534+
alice_knows_bob_element_id = "1" if legacy_id else "alice_knows_bob"
535+
carol_dislikes_bob_element_id = "2" if legacy_id else "carol_dislikes_bob"
536+
537+
alice_knows_bob = gh.hydrate_relationship(
538+
1, 1, 2, "KNOWS", {"since": 1999}, alice_knows_bob_element_id,
539+
alice_element_id, bob_element_id
540+
)
541+
carol_dislikes_bob = gh.hydrate_relationship(
542+
2, 3, 2, "DISLIKES", {}, carol_dislikes_bob_element_id,
543+
carol_element_id, bob_element_id
544+
)
545+
546+
assert len(g.nodes) == 3
547+
for id_, element_id, node in (
548+
(1, alice_element_id, alice),
549+
(2, bob_element_id, bob),
550+
(3, carol_element_id, carol)
551+
):
552+
with pytest.warns(DeprecationWarning, match=r"element_id \(str\)"):
553+
assert g.nodes[id_] == node
554+
assert g.nodes[element_id] == node
555+
if not legacy_id:
556+
with pytest.raises(KeyError):
557+
g.nodes[str(id_)]
558+
559+
assert len(g.relationships) == 2
560+
for id_, element_id, rel in (
561+
(1, alice_knows_bob_element_id, alice_knows_bob),
562+
(2, carol_dislikes_bob_element_id, carol_dislikes_bob)
563+
):
564+
with pytest.warns(DeprecationWarning, match=r"element_id \(str\)"):
565+
assert g.relationships[id_] == rel
566+
assert g.relationships[element_id] == rel
567+
if not legacy_id:
568+
with pytest.raises(KeyError):
569+
g.relationships[str(id_)]

0 commit comments

Comments
 (0)