def save(self, subj, node=None): """ Save an object to a database node. :param subj: the object to save :param node: the database node to save to (if omitted, will re-save to same node as previous save) """ if node is not None: subj.__node__ = node # naively copy properties from object to node props = {} for key, value in subj.__dict__.items(): if not key.startswith("_"): props[key] = value if hasattr(subj, "__node__"): subj.__node__.set_properties(props) query = CypherQuery(self.graph, "START a=node({A}) " "MATCH (a)-[r]->(b) " "DELETE r") query.execute(A=subj.__node__._id) else: subj.__node__, = self.graph.create(props) # write rels if hasattr(subj, "__rel__"): batch = WriteBatch(self.graph) for rel_type, rels in subj.__rel__.items(): for rel_props, endpoint in rels: end_node = self._get_node(endpoint) if end_node not in self.graph: raise ValueError(end_node) batch.create((subj.__node__, rel_type, end_node, rel_props)) batch.run() return subj
def _execute_load_batch(self, graph, unique): # build batch request batch = LegacyWriteBatch(graph) # 1. indexed nodes index_entries = list(self.index_entries.values()) for entry in index_entries: batch.get_or_create_in_index(Node, entry.index_name, entry.key, entry.value, entry.node.properties) # 2. related nodes if self._rels: query, params, related_names = self._get_relationship_query(unique) batch.append(CypherJob(query, params)) else: related_names = [] # 3. odd nodes odd_names = list(self._odd_nodes.keys()) for name in odd_names: batch.create(self._nodes[name].properties) # submit batch unless empty (in which case bail out and return nothing) if batch: responses = batch.submit() else: return {} # parse response and build return value return_nodes = {} # 1. unique_nodes for i, entry in enumerate(index_entries): return_nodes[entry.node.name] = responses.pop(0) # 2. related nodes if self._rels: r = responses.pop(0) if r: for i, value in enumerate(r): return_nodes[related_names[i]] = value # 3. odd nodes for i, name in enumerate(odd_names): return_nodes[name] = responses.pop(0) return return_nodes
class TestRelationshipCreation(object): @pytest.fixture(autouse=True) def setup(self, legacy_graph): self.batch = LegacyWriteBatch(legacy_graph) def test_can_create_relationship_with_new_nodes(self): self.batch.create({"name": "Alice"}) self.batch.create({"name": "Bob"}) self.batch.create((0, "KNOWS", 1)) alice, bob, knows = self.batch.submit() assert isinstance(knows, Relationship) assert knows.start_node == alice assert knows.type == "KNOWS" assert knows.end_node == bob assert knows.get_properties() == {} self.recycling = [knows, alice, bob] def test_can_create_relationship_with_new_indexed_nodes(self, legacy_graph): try: legacy_graph.delete_index(Node, "people") except LookupError: pass try: legacy_graph.delete_index(Relationship, "friendships") except LookupError: pass self.batch.get_or_create_in_index(Node, "people", "name", "Alice", {"name": "Alice"}) self.batch.get_or_create_in_index(Node, "people", "name", "Bob", {"name": "Bob"}) self.batch.get_or_create_in_index(Relationship, "friendships", "names", "alice_bob", (0, "KNOWS", 1)) #self.batch.create((0, "KNOWS", 1)) alice, bob, knows = self.batch.submit() assert isinstance(knows, Relationship) assert knows.start_node == alice assert knows.type == "KNOWS" assert knows.end_node == bob assert knows.get_properties() == {} self.recycling = [knows, alice, bob] def test_can_create_relationship_with_existing_nodes(self): self.batch.create({"name": "Alice"}) self.batch.create({"name": "Bob"}) alice, bob = self.batch.submit() self.batch.clear() self.batch.create((alice, "KNOWS", bob)) knows, = self.batch.submit() assert isinstance(knows, Relationship) assert knows.start_node == alice assert knows.type == "KNOWS" assert knows.end_node == bob assert knows.get_properties() == {} self.recycling = [knows, alice, bob] def test_can_create_relationship_with_existing_start_node(self): self.batch.create({"name": "Alice"}) alice, = self.batch.submit() self.batch.clear() self.batch.create({"name": "Bob"}) self.batch.create((alice, "KNOWS", 0)) bob, knows = self.batch.submit() assert isinstance(knows, Relationship) assert knows.start_node == alice assert knows.type == "KNOWS" assert knows.end_node == bob assert knows.get_properties() == {} self.recycling = [knows, alice, bob] def test_can_create_relationship_with_existing_end_node(self): self.batch.create({"name": "Bob"}) bob, = self.batch.submit() self.batch.clear() self.batch.create({"name": "Alice"}) self.batch.create((0, "KNOWS", bob)) alice, knows = self.batch.submit() assert isinstance(knows, Relationship) assert knows.start_node == alice assert knows.type == "KNOWS" assert knows.end_node == bob assert knows.get_properties() == {} self.recycling = [knows, alice, bob] def test_can_create_multiple_relationships(self): self.batch.create({"name": "Alice"}) self.batch.create({"name": "Bob"}) self.batch.create({"name": "Carol"}) self.batch.create((0, "KNOWS", 1)) self.batch.create((1, "KNOWS", 2)) self.batch.create((2, "KNOWS", 0)) alice, bob, carol, ab, bc, ca = self.batch.submit() for relationship in [ab, bc, ca]: assert isinstance(relationship, Relationship) assert relationship.type == "KNOWS" def test_can_create_overlapping_relationships(self): self.batch.create({"name": "Alice"}) self.batch.create({"name": "Bob"}) self.batch.create((0, "KNOWS", 1)) self.batch.create((0, "KNOWS", 1)) alice, bob, knows1, knows2 = self.batch.submit() assert isinstance(knows1, Relationship) assert knows1.start_node == alice assert knows1.type == "KNOWS" assert knows1.end_node == bob assert knows1.get_properties() == {} assert isinstance(knows2, Relationship) assert knows2.start_node == alice assert knows2.type == "KNOWS" assert knows2.end_node == bob assert knows2.get_properties() == {} self.recycling = [knows1, knows2, alice, bob] def test_can_create_relationship_with_properties(self): self.batch.create({"name": "Alice"}) self.batch.create({"name": "Bob"}) self.batch.create((0, "KNOWS", 1, {"since": 2000})) alice, bob, knows = self.batch.submit() assert isinstance(knows, Relationship) assert knows.start_node == alice assert knows.type == "KNOWS" assert knows.end_node == bob assert knows["since"] == 2000 self.recycling = [knows, alice, bob] def test_create_function(self): self.batch.create(node(name="Alice")) self.batch.create(node(name="Bob")) self.batch.create(rel(0, "KNOWS", 1)) alice, bob, ab = self.batch.submit() assert isinstance(alice, Node) assert alice["name"] == "Alice" assert isinstance(bob, Node) assert bob["name"] == "Bob" assert isinstance(ab, Relationship) assert ab.start_node == alice assert ab.type == "KNOWS" assert ab.end_node == bob self.recycling = [ab, alice, bob]
class TestIndexedNodeCreation(object): @pytest.fixture(autouse=True) def setup(self, legacy_graph): try: legacy_graph.delete_index(Node, "People") except LookupError: pass self.people = legacy_graph.get_or_create_index(Node, "People") self.batch = LegacyWriteBatch(legacy_graph) self.graph = legacy_graph def test_can_create_single_indexed_node(self): properties = {"name": "Alice Smith"} # need to execute a pair of commands as "create in index" not available self.batch.create(properties) self.batch.add_to_index(Node, self.people, "surname", "Smith", 0) alice, index_entry = self.batch.submit() assert isinstance(alice, Node) assert alice.get_properties() == properties self.graph.delete(alice) def test_can_create_two_similarly_indexed_nodes(self): # create Alice alice_props = {"name": "Alice Smith"} # need to execute a pair of commands as "create in index" not available self.batch.create(alice_props) self.batch.add_to_index(Node, self.people, "surname", "Smith", 0) alice, alice_index_entry = self.batch.submit() assert isinstance(alice, Node) assert alice.get_properties() == alice_props self.batch.clear() # create Bob bob_props = {"name": "Bob Smith"} # need to execute a pair of commands as "create in index" not available self.batch.create(bob_props) self.batch.add_to_index(Node, self.people, "surname", "Smith", 0) bob, bob_index_entry = self.batch.submit() assert isinstance(bob, Node) assert bob.get_properties() == bob_props # check entries smiths = self.people.get("surname", "Smith") assert len(smiths) == 2 assert alice in smiths assert bob in smiths # done self.graph.delete(alice, bob) def test_can_get_or_create_uniquely_indexed_node(self): # create Alice alice_props = {"name": "Alice Smith"} self.batch.get_or_create_in_index(Node, self.people, "surname", "Smith", alice_props) alice, = self.batch.submit() assert isinstance(alice, Node) assert alice.get_properties() == alice_props self.batch.clear() # create Bob bob_props = {"name": "Bob Smith"} self.batch.get_or_create_in_index(Node, self.people, "surname", "Smith", bob_props) bob, = self.batch.submit() assert isinstance(bob, Node) assert bob.get_properties() != bob_props assert bob.get_properties() == alice_props assert bob == alice # check entries smiths = self.people.get("surname", "Smith") assert len(smiths) == 1 assert alice in smiths # done self.graph.delete(alice, bob)
class TestRelationshipCreation(object): @pytest.fixture(autouse=True) def setup(self, graph): self.batch = LegacyWriteBatch(graph) def test_can_create_relationship_with_new_nodes(self): self.batch.create({"name": "Alice"}) self.batch.create({"name": "Bob"}) self.batch.create((0, "KNOWS", 1)) alice, bob, knows = self.batch.submit() assert isinstance(knows, Relationship) assert knows.start_node == alice assert knows.type == "KNOWS" assert knows.end_node == bob assert knows.properties == {} self.recycling = [knows, alice, bob] def test_can_create_relationship_with_new_indexed_nodes(self, graph): try: graph.legacy.delete_index(Node, "people") except LookupError: pass try: graph.legacy.delete_index(Relationship, "friendships") except LookupError: pass self.batch.get_or_create_in_index(Node, "people", "name", "Alice", {"name": "Alice"}) self.batch.get_or_create_in_index(Node, "people", "name", "Bob", {"name": "Bob"}) self.batch.get_or_create_in_index(Relationship, "friendships", "names", "alice_bob", (0, "KNOWS", 1)) #self.batch.create((0, "KNOWS", 1)) alice, bob, knows = self.batch.submit() assert isinstance(knows, Relationship) assert knows.start_node == alice assert knows.type == "KNOWS" assert knows.end_node == bob assert knows.properties == {} self.recycling = [knows, alice, bob] def test_can_create_relationship_with_existing_nodes(self): self.batch.create({"name": "Alice"}) self.batch.create({"name": "Bob"}) alice, bob = self.batch.submit() self.batch.clear() self.batch.create((alice, "KNOWS", bob)) knows, = self.batch.submit() assert isinstance(knows, Relationship) assert knows.start_node == alice assert knows.type == "KNOWS" assert knows.end_node == bob assert knows.properties == {} self.recycling = [knows, alice, bob] def test_can_create_relationship_with_existing_start_node(self): self.batch.create({"name": "Alice"}) alice, = self.batch.submit() self.batch.clear() self.batch.create({"name": "Bob"}) self.batch.create((alice, "KNOWS", 0)) bob, knows = self.batch.submit() assert isinstance(knows, Relationship) assert knows.start_node == alice assert knows.type == "KNOWS" assert knows.end_node == bob assert knows.properties == {} self.recycling = [knows, alice, bob] def test_can_create_relationship_with_existing_end_node(self): self.batch.create({"name": "Bob"}) bob, = self.batch.submit() self.batch.clear() self.batch.create({"name": "Alice"}) self.batch.create((0, "KNOWS", bob)) alice, knows = self.batch.submit() assert isinstance(knows, Relationship) assert knows.start_node == alice assert knows.type == "KNOWS" assert knows.end_node == bob assert knows.properties == {} self.recycling = [knows, alice, bob] def test_can_create_multiple_relationships(self): self.batch.create({"name": "Alice"}) self.batch.create({"name": "Bob"}) self.batch.create({"name": "Carol"}) self.batch.create((0, "KNOWS", 1)) self.batch.create((1, "KNOWS", 2)) self.batch.create((2, "KNOWS", 0)) alice, bob, carol, ab, bc, ca = self.batch.submit() for relationship in [ab, bc, ca]: assert isinstance(relationship, Relationship) assert relationship.type == "KNOWS" def test_can_create_overlapping_relationships(self): self.batch.create({"name": "Alice"}) self.batch.create({"name": "Bob"}) self.batch.create((0, "KNOWS", 1)) self.batch.create((0, "KNOWS", 1)) alice, bob, knows1, knows2 = self.batch.submit() assert isinstance(knows1, Relationship) assert knows1.start_node == alice assert knows1.type == "KNOWS" assert knows1.end_node == bob assert knows1.properties == {} assert isinstance(knows2, Relationship) assert knows2.start_node == alice assert knows2.type == "KNOWS" assert knows2.end_node == bob assert knows2.properties == {} self.recycling = [knows1, knows2, alice, bob] def test_can_create_relationship_with_properties(self): self.batch.create({"name": "Alice"}) self.batch.create({"name": "Bob"}) self.batch.create((0, "KNOWS", 1, {"since": 2000})) alice, bob, knows = self.batch.submit() assert isinstance(knows, Relationship) assert knows.start_node == alice assert knows.type == "KNOWS" assert knows.end_node == bob assert knows["since"] == 2000 self.recycling = [knows, alice, bob] def test_create_function(self): self.batch.create(node(name="Alice")) self.batch.create(node(name="Bob")) self.batch.create(rel(0, "KNOWS", 1)) alice, bob, ab = self.batch.submit() assert isinstance(alice, Node) assert alice["name"] == "Alice" assert isinstance(bob, Node) assert bob["name"] == "Bob" assert isinstance(ab, Relationship) assert ab.start_node == alice assert ab.type == "KNOWS" assert ab.end_node == bob self.recycling = [ab, alice, bob]
class TestIndexedNodeCreation(object): @pytest.fixture(autouse=True) def setup(self, graph): try: graph.legacy.delete_index(Node, "People") except LookupError: pass self.people = graph.legacy.get_or_create_index(Node, "People") self.batch = LegacyWriteBatch(graph) self.graph = graph def test_can_create_single_indexed_node(self): properties = {"name": "Alice Smith"} # need to execute a pair of commands as "create in index" not available self.batch.create(properties) self.batch.add_to_index(Node, self.people, "surname", "Smith", 0) alice, index_entry = self.batch.submit() assert isinstance(alice, Node) assert alice.properties == properties self.graph.delete(alice) def test_can_create_two_similarly_indexed_nodes(self): # create Alice alice_props = {"name": "Alice Smith"} # need to execute a pair of commands as "create in index" not available self.batch.create(alice_props) self.batch.add_to_index(Node, self.people, "surname", "Smith", 0) alice, alice_index_entry = self.batch.submit() assert isinstance(alice, Node) assert alice.properties == alice_props self.batch.clear() # create Bob bob_props = {"name": "Bob Smith"} # need to execute a pair of commands as "create in index" not available self.batch.create(bob_props) self.batch.add_to_index(Node, self.people, "surname", "Smith", 0) bob, bob_index_entry = self.batch.submit() assert isinstance(bob, Node) assert bob.properties == bob_props # check entries smiths = self.people.get("surname", "Smith") assert len(smiths) == 2 assert alice in smiths assert bob in smiths # done self.graph.delete(alice, bob) def test_can_get_or_create_uniquely_indexed_node(self): # create Alice alice_props = {"name": "Alice Smith"} self.batch.get_or_create_in_index(Node, self.people, "surname", "Smith", alice_props) alice, = self.batch.submit() assert isinstance(alice, Node) assert alice.properties == alice_props self.batch.clear() # create Bob bob_props = {"name": "Bob Smith"} self.batch.get_or_create_in_index(Node, self.people, "surname", "Smith", bob_props) bob, = self.batch.submit() assert isinstance(bob, Node) assert bob.properties != bob_props assert bob.properties == alice_props assert bob == alice # check entries smiths = self.people.get("surname", "Smith") assert len(smiths) == 1 assert alice in smiths # done self.graph.delete(alice, bob)