neo4j-contrib / py2neo

EOL! Py2neo is a comprehensive Neo4j driver library and toolkit for Python.

Home Page:https://py2neo.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

func merge Parameter does not correspond

lifetruth-liu opened this issue · comments

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...

  1. Extend Transaction.merge to permit multiple keys
  2. 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