py2neo
py2neo copied to clipboard
func merge Parameter does not correspond
in database.py
class Graph(object):
def merge(self, subgraph, label=None, *property_keys):
...
self.update(lambda tx: tx.merge(subgraph, label, *property_keys))
class Transaction(object):
def merge(self, subgraph, primary_label=None, primary_key=None):
pass
Note to self: there are two possible ways to fix this...
- Extend
Transaction.merge
to permit multiple keys - Limit
Graph.merge
to only permit a single key
The choice will be dependent on the underlying functionality that is available. If Neo4j supports multiple keys in all contexts here, then option 1 is fine. If not, option 2 will have to suffice.
monkey patch:
from py2neo import Graph, Node, Relationship, Transaction, Subgraph
from py2neo.cypher.queries import (
unwind_merge_nodes_query,
unwind_merge_relationships_query,
)
from py2neo.cypher import cypher_join
class UniquenessError(Exception):
""" Raised when a condition assumed to be unique is determined
non-unique.
"""
def __db_merge__(self, tx, primary_label=None, *primary_key):
""" Merge data into a remote :class:`.Graph` from this
:class:`.Subgraph`.
:param tx:
:param primary_label:
:param primary_key:
"""
graph = tx.graph
# Convert nodes into a dictionary of
# {(p_label, p_key, frozenset(labels)): [Node, Node, ...]}
node_dict = {}
for node in self.nodes:
if not self._is_bound(node, graph):
# Determine primary label
if node.__primarylabel__ is not None:
p_label = node.__primarylabel__
elif node.__model__ is not None:
p_label = node.__model__.__primarylabel__ or primary_label
else:
p_label = primary_label
# Determine primary key
if node.__primarykey__ is not None:
p_key = node.__primarykey__
elif node.__model__ is not None:
p_key = node.__model__.__primarykey__ or primary_key
else:
p_key = primary_key
# Add node to the node dictionary
key = (p_label, frozenset(node.labels), *p_key)
node_dict.setdefault(key, []).append(node)
# Convert relationships into a dictionary of
# {rel_type: [Rel, Rel, ...]}
rel_dict = {}
for relationship in self.relationships:
if not self._is_bound(relationship, graph):
key = type(relationship).__name__
rel_dict.setdefault(key, []).append(relationship)
for (pl, labels, *pk), nodes in node_dict.items():
if pl is None or pk is None:
raise ValueError("Primary label and primary key are required for MERGE operation")
pq = unwind_merge_nodes_query(map(dict, nodes), (pl, *pk), labels)
pq = cypher_join(pq, "RETURN id(_)")
identities = [record[0] for record in tx.run(*pq)]
if len(identities) > len(nodes):
raise UniquenessError("Found %d matching nodes for primary label %r and primary "
"key %r with labels %r but merging requires no more than "
"one" % (len(identities), pl, pk, set(labels)))
for i, identity in enumerate(identities):
node = nodes[i]
node.graph = graph
node.identity = identity
node._remote_labels = labels
for r_type, relationships in rel_dict.items():
data = map(lambda r: [r.start_node.identity, dict(r), r.end_node.identity],
relationships)
pq = unwind_merge_relationships_query(data, r_type)
pq = cypher_join(pq, "RETURN id(_)")
for i, record in enumerate(tx.run(*pq)):
relationship = relationships[i]
relationship.graph = graph
relationship.identity = record[0]
def merge(self, subgraph, primary_label=None, *primary_key):
try:
merge = subgraph.__db_merge__
except AttributeError:
raise TypeError("No method defined to merge object %r" % subgraph)
else:
merge(self, primary_label, *primary_key)
Subgraph.__db_merge__ = __db_merge__
Transaction.merge = merge