diff --git a/neo4j/graph/__init__.py b/neo4j/graph/__init__.py index c1721512..a614a2d2 100644 --- a/neo4j/graph/__init__.py +++ b/neo4j/graph/__init__.py @@ -44,10 +44,13 @@ class Graph: def __init__(self): self._nodes = {} + self._legacy_nodes = {} # TODO: 6.0 - remove self._relationships = {} + self._legacy_relationships = {} # TODO: 6.0 - remove self._relationship_types = {} - self._node_set_view = EntitySetView(self._nodes) - self._relationship_set_view = EntitySetView(self._relationships) + self._node_set_view = EntitySetView(self._nodes, self._legacy_nodes) + self._relationship_set_view = EntitySetView(self._relationships, + self._legacy_relationships) @property def nodes(self): @@ -86,9 +89,9 @@ def hydrate_node(self, id_, labels=None, try: inst = self.graph._nodes[element_id] except KeyError: - inst = self.graph._nodes[element_id] = Node( - self.graph, element_id, id_, labels, properties - ) + inst = Node(self.graph, element_id, id_, labels, properties) + self.graph._nodes[element_id] = inst + self.graph._legacy_nodes[id_] = inst else: # If we have already hydrated this node as the endpoint of # a relationship, it won't have any labels or properties. @@ -128,9 +131,11 @@ def hydrate_unbound_relationship(self, id_, type_, properties=None, inst = self.graph._relationships[element_id] except KeyError: r = self.graph.relationship_type(type_) - inst = self.graph._relationships[element_id] = r( + inst = r( self.graph, element_id, id_, properties ) + self.graph._relationships[element_id] = inst + self.graph._legacy_relationships[id_] = inst return inst def hydrate_path(self, nodes, relationships, sequence): @@ -260,8 +265,9 @@ class EntitySetView(Mapping): """ View of a set of :class:`.Entity` instances within a :class:`.Graph`. """ - def __init__(self, entity_dict): + def __init__(self, entity_dict, legacy_entity_dict): self._entity_dict = entity_dict + self._legacy_entity_dict = legacy_entity_dict # TODO: 6.0 - remove def __getitem__(self, e_id): # TODO: 6.0 - remove this compatibility shim @@ -270,14 +276,7 @@ def __getitem__(self, e_id): "Accessing entities by an integer id is deprecated, " "use the new style element_id (str) instead" ) - if isinstance(e_id, float) and int(e_id) == e_id: - # Non-int floats would always fail for legacy IDs - e_id = int(e_id) - elif isinstance(e_id, complex) and int(e_id.real) == e_id: - # complex numbers with imaginary parts or non-integer real - # parts would always fail for legacy IDs - e_id = int(e_id.real) - e_id = str(e_id) + return self._legacy_entity_dict[e_id] return self._entity_dict[e_id] def __len__(self): diff --git a/testkitbackend/test_config.json b/testkitbackend/test_config.json index bdf420f5..6751cfb8 100644 --- a/testkitbackend/test_config.json +++ b/testkitbackend/test_config.json @@ -53,8 +53,6 @@ "Optimization:PullPipelining": true, "Optimization:ResultListFetchAll": "The idiomatic way to cast to list is indistinguishable from iterating over the result.", - "Detail:NullOnMissingId": true, - "ConfHint:connection.recv_timeout_seconds": true, "Backend:RTFetch": true, diff --git a/tests/unit/common/test_api.py b/tests/unit/common/test_api.py index f0920d95..2cf519b5 100644 --- a/tests/unit/common/test_api.py +++ b/tests/unit/common/test_api.py @@ -377,7 +377,8 @@ def test_serverinfo_initialization(): assert server_info.address is address assert server_info.protocol_version is version assert server_info.agent is None - assert server_info.connection_id is None + with pytest.warns(DeprecationWarning): + assert server_info.connection_id is None @pytest.mark.parametrize( diff --git a/tests/unit/common/test_data.py b/tests/unit/common/test_data.py index 59641aef..d24c2920 100644 --- a/tests/unit/common/test_data.py +++ b/tests/unit/common/test_data.py @@ -33,24 +33,21 @@ def test_can_hydrate_v1_node_structure(): with pytest.warns(DeprecationWarning, match="element_id"): assert alice.id == 123 - # for backwards compatibility, the driver should compy the element_id + # for backwards compatibility, the driver should compute the element_id assert alice.element_id == "123" assert alice.labels == {"Person"} assert set(alice.keys()) == {"name"} assert alice.get("name") == "Alice" -@pytest.mark.parametrize("with_id", (True, False)) -def test_can_hydrate_v2_node_structure(with_id): +def test_can_hydrate_v2_node_structure(): hydrant = DataHydrator() - id_ = 123 if with_id else None - - struct = Structure(b'N', id_, ["Person"], {"name": "Alice"}, "abc") + struct = Structure(b'N', 123, ["Person"], {"name": "Alice"}, "abc") alice, = hydrant.hydrate([struct]) with pytest.warns(DeprecationWarning, match="element_id"): - assert alice.id == id_ + assert alice.id == 123 assert alice.element_id == "abc" assert alice.labels == {"Person"} assert set(alice.keys()) == {"name"} @@ -78,25 +75,20 @@ def test_can_hydrate_v1_relationship_structure(): assert rel.get("since") == 1999 -@pytest.mark.parametrize("with_ids", (True, False)) -def test_can_hydrate_v2_relationship_structure(with_ids): +def test_can_hydrate_v2_relationship_structure(): hydrant = DataHydrator() - id_ = 123 if with_ids else None - start_id = 456 if with_ids else None - end_id = 789 if with_ids else None - - struct = Structure(b'R', id_, start_id, end_id, "KNOWS", {"since": 1999}, + struct = Structure(b'R', 123, 456, 789, "KNOWS", {"since": 1999}, "abc", "def", "ghi") rel, = hydrant.hydrate([struct]) with pytest.warns(DeprecationWarning, match="element_id"): - assert rel.id == id_ + assert rel.id == 123 with pytest.warns(DeprecationWarning, match="element_id"): - assert rel.start_node.id == start_id + assert rel.start_node.id == 456 with pytest.warns(DeprecationWarning, match="element_id"): - assert rel.end_node.id == end_id + assert rel.end_node.id == 789 # for backwards compatibility, the driver should compy the element_id assert rel.element_id == "abc" assert rel.start_node.element_id == "def" @@ -125,7 +117,8 @@ def test_can_hydrate_in_list(): alice, = alice_in_list - assert alice.id == 123 + with pytest.warns(DeprecationWarning, match="element_id"): + assert alice.id == 123 assert alice.labels == {"Person"} assert set(alice.keys()) == {"name"} assert alice.get("name") == "Alice" @@ -141,7 +134,8 @@ def test_can_hydrate_in_dict(): alice = alice_in_dict["foo"] - assert alice.id == 123 + with pytest.warns(DeprecationWarning, match="element_id"): + assert alice.id == 123 assert alice.labels == {"Person"} assert set(alice.keys()) == {"name"} assert alice.get("name") == "Alice" diff --git a/tests/unit/common/test_record.py b/tests/unit/common/test_record.py index 8999af83..8ad15559 100644 --- a/tests/unit/common/test_record.py +++ b/tests/unit/common/test_record.py @@ -287,7 +287,7 @@ def test_data_relationship(): gh = Graph.Hydrator(g) alice = gh.hydrate_node(1, {"Person"}, {"name": "Alice", "age": 33}) bob = gh.hydrate_node(2, {"Person"}, {"name": "Bob", "age": 44}) - alice_knows_bob = gh.hydrate_relationship(1, alice.id, bob.id, "KNOWS", + alice_knows_bob = gh.hydrate_relationship(1, 1, 2, "KNOWS", {"since": 1999}) record = Record(zip(["a", "b", "r"], [alice, bob, alice_knows_bob])) assert record.data() == { diff --git a/tests/unit/common/test_types.py b/tests/unit/common/test_types.py index 8657288c..4b97529b 100644 --- a/tests/unit/common/test_types.py +++ b/tests/unit/common/test_types.py @@ -122,11 +122,11 @@ def test_node_equality(g1, id1, eid1, props1, g2, id2, eid2, props2): def test_node_hashing(legacy_id): g = Graph() node_1 = Node(g, "1234" + ("abc" if not legacy_id else ""), - 1234 if legacy_id else None) + 1234) node_2 = Node(g, "1234" + ("abc" if not legacy_id else ""), - 1234 if legacy_id else None) + 1234) node_3 = Node(g, "5678" + ("abc" if not legacy_id else ""), - 5678 if legacy_id else None) + 5678) assert hash(node_1) == hash(node_2) assert hash(node_1) != hash(node_3) @@ -145,7 +145,7 @@ def test_node_v1_repr(): def test_node_v2_repr(legacy_id): g = Graph() gh = Graph.Hydrator(g) - id_ = 1234 if legacy_id else None + id_ = 1234 element_id = str(id_) if legacy_id else "foobar" alice = gh.hydrate_node(id_, {"Person"}, {"name": "Alice"}, element_id) assert repr(alice) == ( @@ -180,16 +180,16 @@ def test_can_create_relationship_v2(legacy_id): g = Graph() gh = Graph.Hydrator(g) alice = gh.hydrate_node( - 1 if legacy_id else None, {"Person"}, {"name": "Alice", "age": 33}, + 1, {"Person"}, {"name": "Alice", "age": 33}, "1" if legacy_id else "alice" ) bob = gh.hydrate_node( - 2 if legacy_id else None, {"Person"}, {"name": "Bob", "age": 44}, + 2, {"Person"}, {"name": "Bob", "age": 44}, "2" if legacy_id else "bob" ) alice_knows_bob = gh.hydrate_relationship( - 1 if legacy_id else None, - 1 if legacy_id else None, 2 if legacy_id else None, + 1, + 1, 2, "KNOWS", {"since": 1999}, "1" if legacy_id else "alice_knows_bob", "1" if legacy_id else "alice", "2" if legacy_id else "bob" @@ -226,16 +226,16 @@ def test_relationship_v2_repr(legacy_id): g = Graph() gh = Graph.Hydrator(g) alice = gh.hydrate_node( - 1 if legacy_id else None, {"Person"}, {"name": "Alice"}, + 1, {"Person"}, {"name": "Alice"}, "1" if legacy_id else "alice" ) bob = gh.hydrate_node( - 2 if legacy_id else None, {"Person"}, {"name": "Bob"}, + 2, {"Person"}, {"name": "Bob"}, "2" if legacy_id else "bob" ) alice_knows_bob = gh.hydrate_relationship( - 1 if legacy_id else None, - 1 if legacy_id else None, 2 if legacy_id else None, + 1, + 1, 2, "KNOWS", {"since": 1999}, "1" if legacy_id else "alice_knows_bob", "1" if legacy_id else "alice", "2" if legacy_id else "bob" @@ -278,27 +278,27 @@ def test_can_create_path_v2(legacy_id): g = Graph() gh = Graph.Hydrator(g) alice = gh.hydrate_node( - 1 if legacy_id else None, {"Person"}, {"name": "Alice", "age": 33}, + 1, {"Person"}, {"name": "Alice", "age": 33}, "1" if legacy_id else "alice" ) bob = gh.hydrate_node( - 2 if legacy_id else None, {"Person"}, {"name": "Bob", "age": 44}, + 2, {"Person"}, {"name": "Bob", "age": 44}, "2" if legacy_id else "bob" ) carol = gh.hydrate_node( - 3 if legacy_id else None, {"Person"}, {"name": "Carol", "age": 55}, + 3, {"Person"}, {"name": "Carol", "age": 55}, "3" if legacy_id else "carol" ) alice_knows_bob = gh.hydrate_relationship( - 1 if legacy_id else None, - 1 if legacy_id else None, 2 if legacy_id else None, + 1, + 1, 2, "KNOWS", {"since": 1999}, "1" if legacy_id else "alice_knows_bob", "1" if legacy_id else "alice", "2" if legacy_id else "bob" ) carol_dislikes_bob = gh.hydrate_relationship( - 2 if legacy_id else None, - 3 if legacy_id else None, 2 if legacy_id else None, + 2, + 3, 2, "DISLIKES", {}, "2" if legacy_id else "carol_dislikes_bob", "3" if legacy_id else "carol", "2" if legacy_id else "bob" ) @@ -364,26 +364,26 @@ def test_path_v2_equality(legacy_id): g = Graph() gh = Graph.Hydrator(g) alice = gh.hydrate_node( - 1 if legacy_id else None, {"Person"}, {"name": "Alice", "age": 33}, + 1, {"Person"}, {"name": "Alice", "age": 33}, "1" if legacy_id else "alice" ) _bob = gh.hydrate_node( - 2 if legacy_id else None, {"Person"}, {"name": "Bob", "age": 44}, + 2, {"Person"}, {"name": "Bob", "age": 44}, "2" if legacy_id else "bob" ) _carol = gh.hydrate_node( - 3 if legacy_id else None, {"Person"}, {"name": "Carol", "age": 55}, + 3, {"Person"}, {"name": "Carol", "age": 55}, "3" if legacy_id else "carol" ) alice_knows_bob = gh.hydrate_relationship( - 1 if legacy_id else None, - 1 if legacy_id else None, 2 if legacy_id else None, + 1, + 1, 2, "KNOWS", {"since": 1999}, "1" if legacy_id else "alice_knows_bob", "1" if legacy_id else "alice", "2" if legacy_id else "bob" ) carol_dislikes_bob = gh.hydrate_relationship( - 2 if legacy_id else None, - 3 if legacy_id else None, 2 if legacy_id else None, + 2, + 3, 2, "DISLIKES", {}, "2" if legacy_id else "carol_dislikes_bob", "3" if legacy_id else "carol", "2" if legacy_id else "bob" ) @@ -412,26 +412,26 @@ def test_path_v2_hashing(legacy_id): g = Graph() gh = Graph.Hydrator(g) alice = gh.hydrate_node( - 1 if legacy_id else None, {"Person"}, {"name": "Alice", "age": 33}, + 1, {"Person"}, {"name": "Alice", "age": 33}, "1" if legacy_id else "alice" ) _bob = gh.hydrate_node( - 2 if legacy_id else None, {"Person"}, {"name": "Bob", "age": 44}, + 2, {"Person"}, {"name": "Bob", "age": 44}, "2" if legacy_id else "bob" ) _carol = gh.hydrate_node( - 3 if legacy_id else None, {"Person"}, {"name": "Carol", "age": 55}, + 3, {"Person"}, {"name": "Carol", "age": 55}, "3" if legacy_id else "carol" ) alice_knows_bob = gh.hydrate_relationship( - 1 if legacy_id else None, - 1 if legacy_id else None, 2 if legacy_id else None, + 1, + 1, 2, "KNOWS", {"since": 1999}, "1" if legacy_id else "alice_knows_bob", "1" if legacy_id else "alice", "2" if legacy_id else "bob" ) carol_dislikes_bob = gh.hydrate_relationship( - 2 if legacy_id else None, - 3 if legacy_id else None, 2 if legacy_id else None, + 2, + 3, 2, "DISLIKES", {}, "2" if legacy_id else "carol_dislikes_bob", "3" if legacy_id else "carol", "2" if legacy_id else "bob" ) @@ -462,27 +462,27 @@ def test_path_v2_repr(legacy_id): g = Graph() gh = Graph.Hydrator(g) alice = gh.hydrate_node( - 1 if legacy_id else None, {"Person"}, {"name": "Alice"}, + 1, {"Person"}, {"name": "Alice"}, "1" if legacy_id else "alice" ) bob = gh.hydrate_node( - 2 if legacy_id else None, {"Person"}, {"name": "Bob"}, + 2, {"Person"}, {"name": "Bob"}, "2" if legacy_id else "bob" ) carol = gh.hydrate_node( - 3 if legacy_id else None, {"Person"}, {"name": "Carol"}, + 3, {"Person"}, {"name": "Carol"}, "3" if legacy_id else "carol" ) alice_knows_bob = gh.hydrate_relationship( - 1 if legacy_id else None, alice.id, bob.id, "KNOWS", {"since": 1999}, + 1, 1, 2, "KNOWS", {"since": 1999}, "1" if legacy_id else "alice_knows_bob", alice.element_id, bob.element_id ) carol_dislikes_bob = gh.hydrate_relationship( - 2 if legacy_id else None, carol.id, bob.id, "DISLIKES", {}, + 2, 3, 2, "DISLIKES", {}, "2" if legacy_id else "carol_dislikes_bob", carol.element_id, bob.element_id ) @@ -493,3 +493,77 @@ def test_path_v2_repr(legacy_id): f"end= size=2>" ) + + +def test_graph_views_v1(): + g = Graph() + gh = Graph.Hydrator(g) + alice = gh.hydrate_node(1, {"Person"}, {"name": "Alice"}) + bob = gh.hydrate_node(2, {"Person"}, {"name": "Bob"}) + carol = gh.hydrate_node(3, {"Person"}, {"name": "Carol"}) + alice_knows_bob = gh.hydrate_relationship(1, 1, 2, "KNOWS", + {"since": 1999}) + carol_dislikes_bob = gh.hydrate_relationship(2, 3, 2, "DISLIKES", {}) + + assert len(g.nodes) == 3 + for id_, node in ((1, alice), (2, bob), (3, carol)): + with pytest.warns(DeprecationWarning, match=r"element_id \(str\)"): + assert g.nodes[id_] == node + assert g.nodes[str(id_)] == node + + assert len(g.relationships) == 2 + for id_, rel in ((1, alice_knows_bob), (2, carol_dislikes_bob)): + with pytest.warns(DeprecationWarning, match=r"element_id \(str\)"): + assert g.relationships[id_] == rel + assert g.relationships[str(id_)] == rel + + +@pytest.mark.parametrize("legacy_id", (True, False)) +def test_graph_views_v2_repr(legacy_id): + g = Graph() + gh = Graph.Hydrator(g) + + alice_element_id = "1" if legacy_id else "alice" + bob_element_id = "2" if legacy_id else "bob" + carol_element_id = "3" if legacy_id else "carol" + + alice = gh.hydrate_node(1, {"Person"}, {"name": "Alice"}, alice_element_id) + bob = gh.hydrate_node(2, {"Person"}, {"name": "Bob"}, bob_element_id) + carol = gh.hydrate_node(3, {"Person"}, {"name": "Carol"}, carol_element_id) + + alice_knows_bob_element_id = "1" if legacy_id else "alice_knows_bob" + carol_dislikes_bob_element_id = "2" if legacy_id else "carol_dislikes_bob" + + alice_knows_bob = gh.hydrate_relationship( + 1, 1, 2, "KNOWS", {"since": 1999}, alice_knows_bob_element_id, + alice_element_id, bob_element_id + ) + carol_dislikes_bob = gh.hydrate_relationship( + 2, 3, 2, "DISLIKES", {}, carol_dislikes_bob_element_id, + carol_element_id, bob_element_id + ) + + assert len(g.nodes) == 3 + for id_, element_id, node in ( + (1, alice_element_id, alice), + (2, bob_element_id, bob), + (3, carol_element_id, carol) + ): + with pytest.warns(DeprecationWarning, match=r"element_id \(str\)"): + assert g.nodes[id_] == node + assert g.nodes[element_id] == node + if not legacy_id: + with pytest.raises(KeyError): + g.nodes[str(id_)] + + assert len(g.relationships) == 2 + for id_, element_id, rel in ( + (1, alice_knows_bob_element_id, alice_knows_bob), + (2, carol_dislikes_bob_element_id, carol_dislikes_bob) + ): + with pytest.warns(DeprecationWarning, match=r"element_id \(str\)"): + assert g.relationships[id_] == rel + assert g.relationships[element_id] == rel + if not legacy_id: + with pytest.raises(KeyError): + g.relationships[str(id_)]