def test_can_create_relationship(self): a = Node("Person", name="Alice") b = Node("Person", name="Bob") r = Relationship(a, "KNOWS", b, since=1999) self.graph.create(r) assert remote(a) assert remote(b) assert remote(r) assert r.start_node() == a assert r.end_node() == b
def test_walkable_repr(self): a = Node("Person", name="Alice") b = Node("Person", name="Bob") c = Node("Person", name="Carol") d = Node("Person", name="Dave") ab = Relationship(a, "LOVES", b) cb = Relationship(c, "HATES", b) cd = Relationship(c, "KNOWS", d) t = Walkable([a, ab, b, cb, c, cd, d]) r = repr(t) expected = "(alice)-[:LOVES]->(bob)<-[:HATES]-(carol)-[:KNOWS]->(dave)" assert r == expected
def hydrate_(data, inst=None): if isinstance(data, dict): if "self" in data: if "type" in data: return Relationship.hydrate(data["self"], inst=inst, **data) else: return Node.hydrate(data["self"], inst=inst, **data) elif "nodes" in data and "relationships" in data: if "directions" not in data: directions = [] relationships = graph.evaluate( "MATCH ()-[r]->() WHERE id(r) IN {x} RETURN collect(r)", x=[int(uri.rpartition("/")[-1]) for uri in data["relationships"]]) node_uris = data["nodes"] for i, relationship in enumerate(relationships): if remote(relationship.start_node()).uri == node_uris[i]: directions.append("->") else: directions.append("<-") data["directions"] = directions return Path.hydrate(data) else: # from warnings import warn # warn("Map literals returned over the Neo4j REST interface are ambiguous " # "and may be hydrated as graph objects") return data elif is_collection(data): return type(data)(map(hydrate_, data)) else: return data
def test_can_write_with_wrapper_function(self): alice, bob, carol, dave = Node(name="Alice"), Node(name="Bob"), \ Node(name="Carol"), Node(name="Dave") path = Path(alice, "LOVES", bob, Relationship(carol, "HATES", bob), carol, "KNOWS", dave) written = cypher_repr(path) assert written == "(alice)-[:LOVES]->(bob)<-[:HATES]-(carol)-[:KNOWS]->(dave)"
def test_can_write_relationship_with_name(self): r = Relationship(Node(name="Fred"), "LIVES WITH", Node(name="Wilma")) string = StringIO() writer = CypherWriter(string) writer.write_relationship(r, name="fred_wilma") written = string.getvalue() assert written == '(fred)-[fred_wilma:`LIVES WITH`]->(wilma)'
def test_can_push_path(self): alice = Node(name="Alice") bob = Node(name="Bob") carol = Node(name="Carol") dave = Node(name="Dave") path = Path(alice, "LOVES", bob, Relationship(carol, "HATES", bob), carol, "KNOWS", dave) self.graph.create(path) statement = ("MATCH ()-[ab]->() WHERE id(ab)={ab} " "MATCH ()-[bc]->() WHERE id(bc)={bc} " "MATCH ()-[cd]->() WHERE id(cd)={cd} " "RETURN ab.amount, bc.amount, cd.since") parameters = { "ab": remote(path[0])._id, "bc": remote(path[1])._id, "cd": remote(path[2])._id } path[0]["amount"] = "lots" path[1]["amount"] = "some" path[2]["since"] = 1999 ab_amount, bc_amount, cd_since = self.graph.run(statement, parameters).next() assert ab_amount is None assert bc_amount is None assert cd_since is None self.graph.push(path) ab_amount, bc_amount, cd_since = self.graph.run(statement, parameters).next() assert ab_amount == "lots" assert bc_amount == "some" assert cd_since == 1999
def test_can_pull_path(self): alice = Node(name="Alice") bob = Node(name="Bob") carol = Node(name="Carol") dave = Node(name="Dave") path = Path(alice, "LOVES", bob, Relationship(carol, "HATES", bob), carol, "KNOWS", dave) self.graph.create(path) assert path[0]["amount"] is None assert path[1]["amount"] is None assert path[2]["since"] is None statement = ( "MATCH ()-[ab]->() WHERE id(ab)={ab} " "MATCH ()-[bc]->() WHERE id(bc)={bc} " "MATCH ()-[cd]->() WHERE id(cd)={cd} " "SET ab.amount = 'lots', bc.amount = 'some', cd.since = 1999") id_0 = remote(path[0])._id id_1 = remote(path[1])._id id_2 = remote(path[2])._id parameters = {"ab": id_0, "bc": id_1, "cd": id_2} self.graph.run(statement, parameters) self.graph.pull(path) assert path[0]["amount"] == "lots" assert path[1]["amount"] == "some" assert path[2]["since"] == 1999
def test_can_delete_nodes_and_relationship_nodes_first(self): alice = Node("Person", name="Alice") bob = Node("Person", name="Bob") ab = Relationship(alice, "KNOWS", bob) self.graph.create(alice | bob | ab) assert self.graph.exists(alice | bob | ab) self.graph.delete(alice | bob | ab) assert not self.graph.exists(alice | bob | ab)
def test_can_delete_path(self): alice, bob, carol, dave = Node(), Node(), Node(), Node() path = Path(alice, "LOVES", bob, Relationship(carol, "HATES", bob), carol, "KNOWS", dave) self.graph.create(path) assert self.graph.exists(path) self.graph.delete(path) assert not self.graph.exists(path)
def test_can_write_relationship_with_properties(self): r = Relationship(Node(name="Fred"), ("LIVES WITH", { "place": "Bedrock" }), Node(name="Wilma")) string = StringIO() writer = CypherWriter(string) writer.write(r) written = string.getvalue() assert written == '(fred)-[:`LIVES WITH` {place:"Bedrock"}]->(wilma)'
def test_can_write_simple_path(self): alice, bob, carol, dave = Node(name="Alice"), Node(name="Bob"), \ Node(name="Carol"), Node(name="Dave") path = Path(alice, "LOVES", bob, Relationship(carol, "HATES", bob), carol, "KNOWS", dave) string = StringIO() writer = CypherWriter(string) writer.write(path) written = string.getvalue() assert written == "(alice)-[:LOVES]->(bob)<-[:HATES]-(carol)-[:KNOWS]->(dave)"
def test_can_write_simple_relationship(self): a = Node() b = Node() r = Relationship(a, "KNOWS", b) a.__name__ = "a" b.__name__ = "b" string = StringIO() writer = CypherWriter(string) writer.write(r) written = string.getvalue() assert written == "(a)-[:KNOWS]->(b)"
def test_subgraph_repr(self): a = Node("Person", name="Alice") b = Node("Person", name="Bob") ab = Relationship(a, "TO", b) ba = Relationship(b, "FROM", a) s = ab | ba assert isinstance(s, Subgraph) r = repr(s) assert r.startswith("({") assert r.endswith("})") nodes, _, relationships = r[2:-2].partition("}, {") items = [item.strip() for item in nodes.split(",")] assert len(items) == 2 for i, item in enumerate(items): assert re.match( r'\(_?[0-9A-Za-z]+:Person \{name:"(Alice|Bob)"\}\)', item) items = [item.strip() for item in relationships.split(",")] assert len(items) == 2 for _ in items: assert re.match(r'\(.*\)-\[:(TO|FROM)\]->\(.*\)', repr(ab))
def test_can_add_single_relationship(self): alice = Node(name="Alice Smith") bob = Node(name="Bob Smith") ab = Relationship(alice, "KNOWS", bob) self.graph.create(alice | bob | ab) self.batch.add_to_index(Relationship, self.friendships, "friends", "alice_&_bob", ab) self.batch.run() # check entries rels = self.friendships.get("friends", "alice_&_bob") assert len(rels) == 1 assert ab in rels # done self.recycling = [ab, alice, bob]
def test_can_push_relationship(self): a = Node() b = Node() ab = Relationship(a, "KNOWS", b) self.graph.create(ab) value = self.graph.evaluate( "MATCH ()-[ab:KNOWS]->() WHERE id(ab)={i} " "RETURN ab.since", i=remote(ab)._id) assert value is None ab["since"] = 1999 self.graph.push(ab) value = self.graph.evaluate( "MATCH ()-[ab:KNOWS]->() WHERE id(ab)={i} " "RETURN ab.since", i=remote(ab)._id) assert value == 1999
def test_param_reused_twice_after_with_statement(self): a, b, ab = self.alice_and_bob c = Node(name="Carol", age=88) bc = Relationship(b, "KNOWS", c) self.graph.create(c | bc) query = ("MATCH (a) WHERE id(a)={A} " "MATCH (a)-[:KNOWS]->(b) " "WHERE a.age > {min_age} " "WITH a " "MATCH (a)-[:KNOWS]->(b) " "WHERE b.age > {min_age} " "WITH b " "MATCH (b)-[:KNOWS]->(c) " "WHERE c.age > {min_age} " "RETURN c") params = {"A": remote(a)._id, "min_age": 50} record = self.graph.run(query, params).next() assert record["c"] == c
class RelationshipLoopTestCase(TestCase): loop = Relationship(alice, "LIKES", alice) def test_nodes(self): nodes = self.loop.nodes() assert isinstance(nodes, tuple) assert nodes == (alice, alice) def test_relationships(self): relationships = self.loop.relationships() assert isinstance(relationships, tuple) assert relationships == (self.loop, ) def test_order(self): assert order(self.loop) == 1 def test_size(self): assert size(self.loop) == 1
def test_relationship_property_pull_scenarios(self): property_sets = [{}, { "name": "Alice" }, { "name": "Alice", "age": 33 }, { "name": "Bob" }] for old_props in property_sets: for new_props in property_sets: a = Node() b = Node() relationship = Relationship(a, "TO", b, **old_props) self.graph.create(relationship) relationship_id = remote(relationship)._id assert dict(relationship) == old_props self.graph.run("MATCH ()-[r]->() WHERE id(r)={x} SET r={y}", x=relationship_id, y=new_props) self.graph.pull(relationship) assert dict(relationship) == new_props, \ "Failed to pull new properties %r over old properties %r" % \ (new_props, old_props)
def test_construction_from_no_arguments(self): with self.assertRaises(TypeError): _ = Relationship()
def setUp(self): a = Node(name="Alice", age=66) b = Node(name="Bob", age=77) ab = Relationship(a, "KNOWS", b) self.graph.create(ab) self.alice_and_bob = (a, b, ab)
def test_can_create_nodes_and_relationship(self): self.graph.delete_all() a = Node() b = Node() c = Node() ab = Relationship(a, "TO", b) bc = Relationship(b, "TO", c) ca = Relationship(c, "TO", a) self.graph.create(ab | bc | ca) assert remote(a) assert remote(b) assert remote(c) assert remote(ab) assert ab.start_node() == a assert ab.end_node() == b assert remote(bc) assert bc.start_node() == b assert bc.end_node() == c assert remote(ca) assert ca.start_node() == c assert ca.end_node() == a assert order(self.graph) == 3 assert size(self.graph) == 3
# Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from unittest import TestCase from py2neo.types import PropertyDict, Subgraph, Walkable, Node, Relationship, Path, walk, order, size alice = Node("Person", "Employee", name="Alice", age=33) bob = Node("Person") carol = Node("Person") dave = Node("Person") alice_knows_bob = Relationship(alice, "KNOWS", bob, since=1999) alice_likes_carol = Relationship(alice, "LIKES", carol) carol_dislikes_bob = Relationship(carol, "DISLIKES", bob) carol_married_to_dave = Relationship(carol, "MARRIED_TO", dave) dave_works_for_dave = Relationship(dave, "WORKS_FOR", dave) class PropertyCoercionTestCase(TestCase): def test_boolean(self): props = PropertyDict({"value": True}) assert props == {"value": True} def test_integer_in_range(self): props = PropertyDict({"value": 1}) assert props == {"value": 1}
def test_can_concatenate_node_and_reversed_relationship(self): bob_knows_alice = Relationship(bob, "KNOWS", alice) result = alice + bob_knows_alice assert result == Walkable([alice, bob_knows_alice, bob])
def test_inequality(self): other_rel = Relationship(alice, "KNOWS", bob, since=1999) assert alice != other_rel
def test_construction_from_more_arguments(self): with self.assertRaises(TypeError): Relationship(alice, "KNOWS", bob, carol)
def test_construction_from_three_arguments(self): rel = Relationship(alice, "KNOWS", bob) assert rel.start_node() is alice assert rel.end_node() is bob assert rel.type() == "KNOWS"
def test_construction_from_node_and_type_arguments(self): rel = Relationship(alice, "LIKES") assert rel.start_node() is alice assert rel.end_node() is alice assert rel.type() == "LIKES"
def test_relationship_repr(self): a = Node("Person", name="Alice") b = Node("Person", name="Bob") ab = Relationship(a, "KNOWS", b, since=1999) assert repr(ab) == '(alice)-[:KNOWS {since:1999}]->(bob)'
def test_construction_from_one_argument(self): rel = Relationship(alice) assert rel.start_node() is alice assert rel.end_node() is alice assert rel.type() == "TO"
def hydrate_(obj, inst=None): # TODO: hydrate directly instead of via HTTP hydration if isinstance(obj, Structure): signature, args = obj if signature == b"N": uri = "%snode/%s" % (graph_uri, args[0]) return Node.hydrate(uri, inst=inst, metadata={"labels": list(args[1])}, data=hydrate_(args[2])) elif signature == b"R": uri = "%srelationship/%s" % (graph_uri, args[0]) return Relationship.hydrate( uri, inst=inst, start="%snode/%s" % (graph_uri, args[1]), end="%snode/%s" % (graph_uri, args[2]), type=args[3], data=hydrate_(args[4])) elif signature == b"P": nodes = [hydrate_(node) for node in args[0]] u_rels = [ UnboundRelationship.hydrate(*map(hydrate_, r)) for _, r in args[1] ] sequence = args[2] last_node = nodes[0] steps = [last_node] for i, rel_index in enumerate(sequence[::2]): next_node = nodes[sequence[2 * i + 1]] last_node_uri = "%snode/%s" % (graph_uri, remote(last_node)._id) next_node_uri = "%snode/%s" % (graph_uri, remote(next_node)._id) if rel_index > 0: u_rel = u_rels[rel_index - 1] uri = "%srelationship/%s" % (graph_uri, u_rel.id) rel = Relationship.hydrate(uri, start=last_node_uri, end=next_node_uri, type=u_rel.type, data=u_rel.properties) else: u_rel = u_rels[-rel_index - 1] uri = "%srelationship/%s" % (graph_uri, u_rel.id) rel = Relationship.hydrate(uri, start=next_node_uri, end=last_node_uri, type=u_rel.type, data=u_rel.properties) steps.append(rel) steps.append(next_node) last_node = next_node return Path(*steps) else: # If we don't recognise the structure type, just return it as-is # TODO: add warning for unsupported structure types return obj elif isinstance(obj, list): return list(map(hydrate_, obj)) elif isinstance(obj, dict): return {key: hydrate_(value) for key, value in obj.items()} else: return obj