diff --git a/neo4j/graph/__init__.py b/neo4j/graph/__init__.py index bc1e8a52..cc0d97ed 100644 --- a/neo4j/graph/__init__.py +++ b/neo4j/graph/__init__.py @@ -31,6 +31,8 @@ from collections.abc import Mapping +from ..meta import deprecated + class Graph: """ Local, self-contained graph object that acts as a container for @@ -71,35 +73,61 @@ class Hydrator: def __init__(self, graph): self.graph = graph - def hydrate_node(self, n_id, n_labels=None, properties=None): + def hydrate_node(self, id_, labels=None, + properties=None, element_id=None): assert isinstance(self.graph, Graph) + # backwards compatibility with Neo4j < 5.0 + if element_id is None: + element_id = str(id_) + try: - inst = self.graph._nodes[n_id] + inst = self.graph._nodes[element_id] except KeyError: - inst = self.graph._nodes[n_id] = Node(self.graph, n_id, n_labels, properties) + inst = self.graph._nodes[element_id] = Node( + self.graph, element_id, id_, labels, properties + ) else: # If we have already hydrated this node as the endpoint of # a relationship, it won't have any labels or properties. # Therefore, we need to add the ones we have here. - if n_labels: - inst._labels = inst._labels.union(n_labels) # frozen_set + if labels: + inst._labels = inst._labels.union(labels) # frozen_set if properties: inst._properties.update(properties) return inst - def hydrate_relationship(self, r_id, n0_id, n1_id, r_type, properties=None): - inst = self.hydrate_unbound_relationship(r_id, r_type, properties) - inst._start_node = self.hydrate_node(n0_id) - inst._end_node = self.hydrate_node(n1_id) + def hydrate_relationship(self, id_, n0_id, n1_id, type_, + properties=None, element_id=None, + n0_element_id=None, n1_element_id=None): + # backwards compatibility with Neo4j < 5.0 + if element_id is None: + element_id = str(id_) + if n0_element_id is None: + n0_element_id = str(n0_id) + if n1_element_id is None: + n1_element_id = str(n1_id) + + inst = self.hydrate_unbound_relationship(id_, type_, properties, + element_id) + inst._start_node = self.hydrate_node(n0_id, + element_id=n0_element_id) + inst._end_node = self.hydrate_node(n1_id, element_id=n1_element_id) return inst - def hydrate_unbound_relationship(self, r_id, r_type, properties=None): + def hydrate_unbound_relationship(self, id_, type_, properties=None, + element_id=None): assert isinstance(self.graph, Graph) + # backwards compatibility with Neo4j < 5.0 + if element_id is None: + element_id = str(id_) + try: - inst = self.graph._relationships[r_id] + inst = self.graph._relationships[element_id] except KeyError: - r = self.graph.relationship_type(r_type) - inst = self.graph._relationships[r_id] = r(self.graph, r_id, properties) + r = self.graph.relationship_type(type_) + inst = self.graph._relationships[element_id] = r( + self.graph, element_id, id_, properties + ) return inst def hydrate_path(self, nodes, relationships, sequence): @@ -131,14 +159,19 @@ class Entity(Mapping): functionality. """ - def __init__(self, graph, id, properties): + def __init__(self, graph, element_id, id_, properties): self._graph = graph - self._id = id - self._properties = dict((k, v) for k, v in (properties or {}).items() if v is not None) + self._element_id = element_id + self._id = id_ + self._properties = { + k: v for k, v in (properties or {}).items() if v is not None + } def __eq__(self, other): try: - return type(self) == type(other) and self.graph == other.graph and self.id == other.id + return (type(self) == type(other) + and self.graph == other.graph + and self.element_id == other.element_id) except AttributeError: return False @@ -146,7 +179,7 @@ def __ne__(self, other): return not self.__eq__(other) def __hash__(self): - return hash(self.id) + return hash(self._element_id) def __len__(self): return len(self._properties) @@ -167,11 +200,30 @@ def graph(self): return self._graph @property + @deprecated("`id` is deprecated, use `element_id` instead") def id(self): - """ The identity of this entity in its container :class:`.Graph`. + """The legacy identity of this entity in its container :class:`.Graph`. + + Depending on the version of the server this entity was retrieved from, + this may be empty (None). + + .. deprecated:: 5.0 + Use :attr:`.element_id` instead. + + :rtype: int """ return self._id + @property + def element_id(self): + """The identity of this entity in its container :class:`.Graph`. + + .. added:: 5.0 + + :rtype: str + """ + return self._element_id + def get(self, name, default=None): """ Get a property value by name, optionally with a default. """ @@ -214,12 +266,14 @@ class Node(Entity): """ Self-contained graph node. """ - def __init__(self, graph, n_id, n_labels=None, properties=None): - Entity.__init__(self, graph, n_id, properties) + def __init__(self, graph, element_id, id_, n_labels=None, + properties=None): + Entity.__init__(self, graph, element_id, id_, properties) self._labels = frozenset(n_labels or ()) def __repr__(self): - return "" % (self._id, self._labels, self._properties) + return (f"") @property def labels(self): @@ -232,14 +286,15 @@ class Relationship(Entity): """ Self-contained graph relationship. """ - def __init__(self, graph, r_id, properties): - Entity.__init__(self, graph, r_id, properties) + def __init__(self, graph, element_id, id_, properties): + Entity.__init__(self, graph, element_id, id_, properties) self._start_node = None self._end_node = None def __repr__(self): - return "" % ( - self._id, self._start_node, self._end_node, self.type, self._properties) + return (f"") @property def nodes(self): diff --git a/tests/unit/common/test_data.py b/tests/unit/common/test_data.py index 14577efd..7a799a6c 100644 --- a/tests/unit/common/test_data.py +++ b/tests/unit/common/test_data.py @@ -16,6 +16,8 @@ # limitations under the License. +import pytest + from neo4j.data import DataHydrator from neo4j.packstream import Structure @@ -23,18 +25,87 @@ # python -m pytest -s -v tests/unit/test_data.py -def test_can_hydrate_node_structure(): +def test_can_hydrate_v1_node_structure(): hydrant = DataHydrator() struct = Structure(b'N', 123, ["Person"], {"name": "Alice"}) alice, = hydrant.hydrate([struct]) - assert alice.id == 123 + with pytest.warns(DeprecationWarning, match="element_id"): + assert alice.id == 123 + # for backwards compatibility, the driver should compy 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): + hydrant = DataHydrator() + + id_ = 123 if with_id else None + + struct = Structure(b'N', id_, ["Person"], {"name": "Alice"}, "abc") + alice, = hydrant.hydrate([struct]) + + with pytest.warns(DeprecationWarning, match="element_id"): + assert alice.id == id_ + assert alice.element_id == "abc" assert alice.labels == {"Person"} assert set(alice.keys()) == {"name"} assert alice.get("name") == "Alice" +def test_can_hydrate_v1_relationship_structure(): + hydrant = DataHydrator() + + struct = Structure(b'R', 123, 456, 789, "KNOWS", {"since": 1999}) + rel, = hydrant.hydrate([struct]) + + with pytest.warns(DeprecationWarning, match="element_id"): + assert rel.id == 123 + with pytest.warns(DeprecationWarning, match="element_id"): + assert rel.start_node.id == 456 + with pytest.warns(DeprecationWarning, match="element_id"): + assert rel.end_node.id == 789 + # for backwards compatibility, the driver should compy the element_id + assert rel.element_id == "123" + assert rel.start_node.element_id == "456" + assert rel.end_node.element_id == "789" + assert rel.type == "KNOWS" + assert set(rel.keys()) == {"since"} + assert rel.get("since") == 1999 + + +@pytest.mark.parametrize("with_ids", (True, False)) +def test_can_hydrate_v2_relationship_structure(with_ids): + 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}, + "abc", "def", "ghi") + + rel, = hydrant.hydrate([struct]) + + with pytest.warns(DeprecationWarning, match="element_id"): + assert rel.id == id_ + with pytest.warns(DeprecationWarning, match="element_id"): + assert rel.start_node.id == start_id + with pytest.warns(DeprecationWarning, match="element_id"): + assert rel.end_node.id == end_id + # for backwards compatibility, the driver should compy the element_id + assert rel.element_id == "abc" + assert rel.start_node.element_id == "def" + assert rel.end_node.element_id == "ghi" + assert rel.type == "KNOWS" + assert set(rel.keys()) == {"since"} + assert rel.get("since") == 1999 + + def test_hydrating_unknown_structure_returns_same(): hydrant = DataHydrator() diff --git a/tests/unit/common/test_record.py b/tests/unit/common/test_record.py index 85e0bc1f..f976d78b 100644 --- a/tests/unit/common/test_record.py +++ b/tests/unit/common/test_record.py @@ -273,7 +273,7 @@ def test_record_repr(len_): {"x": {"one": 1, "two": 2}} ), ( - zip(["a"], [Node("graph", 42, "Person", {"name": "Alice"})]), + zip(["a"], [Node("graph", "42", 42, "Person", {"name": "Alice"})]), (), {"a": {"name": "Alice"}} ), diff --git a/tests/unit/common/test_types.py b/tests/unit/common/test_types.py index 979c4080..25206760 100644 --- a/tests/unit/common/test_types.py +++ b/tests/unit/common/test_types.py @@ -34,12 +34,26 @@ # Node -def test_can_create_node(): +@pytest.mark.parametrize(("id_", "element_id"), ( + (123, "123"), + (123, None), + (None, "foobar"), +)) +def test_can_create_node(id_, element_id): g = Graph() gh = Graph.Hydrator(g) - alice = gh.hydrate_node(123, {"Person"}, {"name": "Alice", "age": 33}) + + fields = [id_, {"Person"}, {"name": "Alice", "age": 33}] + if element_id is not None: + fields.append(element_id) + alice = gh.hydrate_node(*fields) assert isinstance(alice, Node) - assert alice.id == 123 + with pytest.warns(DeprecationWarning, match="element_id"): + assert alice.id == id_ + if element_id is not None: + assert alice.element_id == element_id + else: + assert alice.element_id == str(id_) assert alice.labels == {"Person"} assert set(alice.keys()) == {"name", "age"} assert set(alice.values()) == {"Alice", 33} @@ -75,54 +89,81 @@ def test_node_with_null_properties(): assert "bad" not in stuff -@pytest.mark.parametrize(("g1", "id1", "props1", "g2", "id2", "props2"), ( +@pytest.mark.parametrize(("g1", "id1", "eid1", "props1", + "g2", "id2", "eid2", "props2"), ( (*n1, *n2) for n1, n2 in product( ( - (g, id_, props) + (g, id_, element_id, props) for g in (0, 1) - for id_ in (1, 1234) + for id_, element_id in ( + (1, "1"), + (1234, "1234"), + (None, "1234"), + (None, "foobar"), + ) for props in (None, {}, {"a": 1}) ), repeat=2 ) )) -def test_node_equality(g1, id1, props1, g2, id2, props2): +def test_node_equality(g1, id1, eid1, props1, g2, id2, eid2, props2): graphs = (Graph(), Graph()) - node_1 = Node(graphs[g1], id1, props1) - node_2 = Node(graphs[g2], id2, props2) - if g1 == g2 and id1 == id2: + node_1 = Node(graphs[g1], eid1, id1, props1) + node_2 = Node(graphs[g2], eid2, id2, props2) + if g1 == g2 and eid1 == eid2: assert node_1 == node_2 else: assert node_1 != node_2 assert node_1 != "this is not a node" -def test_node_hashing(): +@pytest.mark.parametrize("legacy_id", (True, False)) +def test_node_hashing(legacy_id): g = Graph() - node_1 = Node(g, 1234) - node_2 = Node(g, 1234) - node_3 = Node(g, 5678) + node_1 = Node(g, "1234" + ("abc" if not legacy_id else ""), + 1234 if legacy_id else None) + node_2 = Node(g, "1234" + ("abc" if not legacy_id else ""), + 1234 if legacy_id else None) + node_3 = Node(g, "5678" + ("abc" if not legacy_id else ""), + 5678 if legacy_id else None) assert hash(node_1) == hash(node_2) assert hash(node_1) != hash(node_3) -def test_node_repr(): +def test_node_v1_repr(): g = Graph() gh = Graph.Hydrator(g) alice = gh.hydrate_node(1, {"Person"}, {"name": "Alice"}) - assert repr(alice) == "" + assert repr(alice) == ( + "" + ) + + +@pytest.mark.parametrize("legacy_id", (True, False)) +def test_node_v2_repr(legacy_id): + g = Graph() + gh = Graph.Hydrator(g) + id_ = 1234 if legacy_id else None + element_id = str(id_) if legacy_id else "foobar" + alice = gh.hydrate_node(id_, {"Person"}, {"name": "Alice"}, element_id) + assert repr(alice) == ( + f"" + ) # Relationship -def test_can_create_relationship(): +def test_can_create_relationship_v1(): g = Graph() 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", {"since": 1999}) + alice_knows_bob = gh.hydrate_relationship(1, 1, 2, "KNOWS", + {"since": 1999}) assert isinstance(alice_knows_bob, Relationship) assert alice_knows_bob.start_node == alice assert alice_knows_bob.type == "KNOWS" @@ -134,28 +175,133 @@ def test_can_create_relationship(): assert alice_knows_bob.get("since") == 1999 -def test_relationship_repr(): +@pytest.mark.parametrize("legacy_id", (True, False)) +def test_can_create_relationship_v2(legacy_id): g = Graph() gh = Graph.Hydrator(g) - alice = gh.hydrate_node(1, {"Person"}, {"name": "Alice"}) - bob = gh.hydrate_node(2, {"Person"}, {"name": "Bob"}) - alice_knows_bob = gh.hydrate_relationship(1, alice.id, bob.id, "KNOWS", {"since": 1999}) - assert repr(alice_knows_bob) == ", ) type='KNOWS' properties={'since': 1999}>" + alice = gh.hydrate_node( + 1 if legacy_id else None, {"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" 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, + "KNOWS", {"since": 1999}, + "1" if legacy_id else "alice_knows_bob", + "1" if legacy_id else "alice", "2" if legacy_id else "bob" + ) + assert isinstance(alice_knows_bob, Relationship) + assert alice_knows_bob.start_node == alice + assert alice_knows_bob.type == "KNOWS" + assert alice_knows_bob.end_node == bob + assert dict(alice_knows_bob) == {"since": 1999} + assert set(alice_knows_bob.keys()) == {"since"} + assert set(alice_knows_bob.values()) == {1999} + assert set(alice_knows_bob.items()) == {("since", 1999)} + assert alice_knows_bob.get("since") == 1999 -# Path +def test_relationship_v1_repr(): + g = Graph() + gh = Graph.Hydrator(g) + _alice = gh.hydrate_node(1, {"Person"}, {"name": "Alice"}) + _bob = gh.hydrate_node(2, {"Person"}, {"name": "Bob"}) + alice_knows_bob = gh.hydrate_relationship(3, 1, 2, "KNOWS", + {"since": 1999}) + assert repr(alice_knows_bob) == ( + ", ) " + "type='KNOWS' properties={'since': 1999}>" + ) -def test_can_create_path(): +@pytest.mark.parametrize("legacy_id", (True, False)) +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" if legacy_id else "alice" + ) + bob = gh.hydrate_node( + 2 if legacy_id else None, {"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, + "KNOWS", {"since": 1999}, + "1" if legacy_id else "alice_knows_bob", + "1" if legacy_id else "alice", "2" if legacy_id else "bob" + ) + expected_eid = "1" if legacy_id else "alice_knows_bob" + expected_eid_alice = "1" if legacy_id else "alice" + expected_eid_bob = "2" if legacy_id else "bob" + assert repr(alice_knows_bob) == ( + f", " + f") " + "type='KNOWS' properties={'since': 1999}>" + ) + + +# Path + +def test_can_create_path_v1(): g = Graph() gh = Graph.Hydrator(g) alice = gh.hydrate_node(1, {"Person"}, {"name": "Alice", "age": 33}) bob = gh.hydrate_node(2, {"Person"}, {"name": "Bob", "age": 44}) carol = gh.hydrate_node(3, {"Person"}, {"name": "Carol", "age": 55}) - alice_knows_bob = gh.hydrate_relationship(1, alice.id, bob.id, "KNOWS", + alice_knows_bob = gh.hydrate_relationship(1, 1, 2, "KNOWS", {"since": 1999}) - carol_dislikes_bob = gh.hydrate_relationship(2, carol.id, bob.id, - "DISLIKES", {}) + carol_dislikes_bob = gh.hydrate_relationship(2, 3, 2, "DISLIKES", {}) + path = Path(alice, alice_knows_bob, carol_dislikes_bob) + assert isinstance(path, Path) + assert path.start_node is alice + assert path.end_node is carol + assert path.nodes == (alice, bob, carol) + assert path.relationships == (alice_knows_bob, carol_dislikes_bob) + assert list(path) == [alice_knows_bob, carol_dislikes_bob] + + +@pytest.mark.parametrize("legacy_id", (True, False)) +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" if legacy_id else "alice" + ) + bob = gh.hydrate_node( + 2 if legacy_id else None, {"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" 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, + "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, + "DISLIKES", {}, "2" if legacy_id else "carol_dislikes_bob", + "3" if legacy_id else "carol", "2" if legacy_id else "bob" + ) path = Path(alice, alice_knows_bob, carol_dislikes_bob) assert isinstance(path, Path) assert path.start_node is alice @@ -169,61 +315,181 @@ def test_can_create_path(): def test_can_hydrate_path(cyclic): g = Graph() 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 = gh.hydrate_node(1, {"Person"}, {"name": "Alice", "age": 33}, "1") + bob = gh.hydrate_node(2, {"Person"}, {"name": "Bob", "age": 44}, "2") if cyclic: + carol_id = 1 + carol_eid = "1" carol = alice else: - carol = gh.hydrate_node(3, {"Person"}, {"name": "Carol", "age": 55}) - r = [gh.hydrate_unbound_relationship(1, "KNOWS", {"since": 1999}), - gh.hydrate_unbound_relationship(2, "DISLIKES", {})] + carol_id = 3 + carol_eid = "3" + carol = gh.hydrate_node(carol_id, {"Person"}, + {"name": "Carol", "age": 55}, carol_eid) + r = [gh.hydrate_unbound_relationship(1, "KNOWS", {"since": 1999}, "1"), + gh.hydrate_unbound_relationship(2, "DISLIKES", {}), "2"] path = gh.hydrate_path([alice, bob, carol], r, [1, 1, -2, 2]) assert path.start_node is alice assert path.end_node is carol assert path.nodes == (alice, bob, carol) - expected_alice_knows_bob = gh.hydrate_relationship(1, alice.id, bob.id, - "KNOWS", {"since": 1999}) - expected_carol_dislikes_bob = gh.hydrate_relationship(2, carol.id, bob.id, - "DISLIKES", {}) + expected_alice_knows_bob = gh.hydrate_relationship( + 1, 1, 2, "KNOWS", {"since": 1999}, "1", "1", "2" + ) + expected_carol_dislikes_bob = gh.hydrate_relationship( + 2, carol_id, 2, "DISLIKES", {}, "2", carol_eid, "2" + ) assert path.relationships == (expected_alice_knows_bob, expected_carol_dislikes_bob) - assert list(path) == [expected_alice_knows_bob, expected_carol_dislikes_bob] + assert list(path) == [expected_alice_knows_bob, + expected_carol_dislikes_bob] -def test_path_equality(): +def test_path_v1_equality(): g = Graph() gh = Graph.Hydrator(g) alice = gh.hydrate_node(1, {"Person"}, {"name": "Alice", "age": 33}) - bob = gh.hydrate_node(2, {"Person"}, {"name": "Bob", "age": 44}) - carol = gh.hydrate_node(3, {"Person"}, {"name": "Carol", "age": 55}) - alice_knows_bob = gh.hydrate_relationship(1, alice.id, bob.id, "KNOWS", {"since": 1999}) - carol_dislikes_bob = gh.hydrate_relationship(2, carol.id, bob.id, "DISLIKES", {}) + _bob = gh.hydrate_node(2, {"Person"}, {"name": "Bob", "age": 44}) + _carol = gh.hydrate_node(3, {"Person"}, {"name": "Carol", "age": 55}) + alice_knows_bob = gh.hydrate_relationship(1, 1, 2, "KNOWS", + {"since": 1999}) + carol_dislikes_bob = gh.hydrate_relationship(2, 3, 2, "DISLIKES", {}) path_1 = Path(alice, alice_knows_bob, carol_dislikes_bob) path_2 = Path(alice, alice_knows_bob, carol_dislikes_bob) assert path_1 == path_2 assert path_1 != "this is not a path" -def test_path_hashing(): +@pytest.mark.parametrize("legacy_id", (True, False)) +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" if legacy_id else "alice" + ) + _bob = gh.hydrate_node( + 2 if legacy_id else None, {"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" 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, + "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, + "DISLIKES", {}, "2" if legacy_id else "carol_dislikes_bob", + "3" if legacy_id else "carol", "2" if legacy_id else "bob" + ) + path_1 = Path(alice, alice_knows_bob, carol_dislikes_bob) + path_2 = Path(alice, alice_knows_bob, carol_dislikes_bob) + assert path_1 == path_2 + assert path_1 != "this is not a path" + + +def test_path_v1_hashing(): g = Graph() gh = Graph.Hydrator(g) alice = gh.hydrate_node(1, {"Person"}, {"name": "Alice", "age": 33}) - bob = gh.hydrate_node(2, {"Person"}, {"name": "Bob", "age": 44}) - carol = gh.hydrate_node(3, {"Person"}, {"name": "Carol", "age": 55}) - alice_knows_bob = gh.hydrate_relationship(1, alice.id, bob.id, "KNOWS", {"since": 1999}) - carol_dislikes_bob = gh.hydrate_relationship(2, carol.id, bob.id, "DISLIKES", {}) + _bob = gh.hydrate_node(2, {"Person"}, {"name": "Bob", "age": 44}) + _carol = gh.hydrate_node(3, {"Person"}, {"name": "Carol", "age": 55}) + alice_knows_bob = gh.hydrate_relationship(1, 1, 2, "KNOWS", + {"since": 1999}) + carol_dislikes_bob = gh.hydrate_relationship(2, 3, 2, "DISLIKES", {}) path_1 = Path(alice, alice_knows_bob, carol_dislikes_bob) path_2 = Path(alice, alice_knows_bob, carol_dislikes_bob) assert hash(path_1) == hash(path_2) -def test_path_repr(): +@pytest.mark.parametrize("legacy_id", (True, False)) +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" if legacy_id else "alice" + ) + _bob = gh.hydrate_node( + 2 if legacy_id else None, {"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" 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, + "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, + "DISLIKES", {}, "2" if legacy_id else "carol_dislikes_bob", + "3" if legacy_id else "carol", "2" if legacy_id else "bob" + ) + path_1 = Path(alice, alice_knows_bob, carol_dislikes_bob) + path_2 = Path(alice, alice_knows_bob, carol_dislikes_bob) + assert hash(path_1) == hash(path_2) + + +def test_path_v1_repr(): 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, alice.id, bob.id, "KNOWS", {"since": 1999}) - carol_dislikes_bob = gh.hydrate_relationship(2, carol.id, bob.id, "DISLIKES", {}) + _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", {}) + path = Path(alice, alice_knows_bob, carol_dislikes_bob) + assert repr(path) == ( + " end= size=2>" + ) + + +@pytest.mark.parametrize("legacy_id", (True, False)) +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" if legacy_id else "alice" + + ) + bob = gh.hydrate_node( + 2 if legacy_id else None, {"Person"}, {"name": "Bob"}, + "2" if legacy_id else "bob" + + ) + carol = gh.hydrate_node( + 3 if legacy_id else None, {"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" 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" if legacy_id else "carol_dislikes_bob", + carol.element_id, bob.element_id + ) path = Path(alice, alice_knows_bob, carol_dislikes_bob) - assert repr(path) == " end= size=2>" + assert repr(path) == ( + f" " + f"end= size=2>" + )