Search code examples
pythonneo4jpy2neo

py2neo: Graph.find_one with multiple key/values


I have some trouble with the py2neo find and find_one (http://py2neo.org/2.0/essentials.html)

What I want in Cypher is:

MATCH (p:Person) WHERE p.name='Alice' AND p.age=22 RETURN p

Say, where there are more than one key/value set (eg. if there are more than one 'Alice' in the graph).

My problem is that I don't know what to give graph.find_one, a working code is:

graph.find_one('Person', 'name', 'Alice')

What I would like is something like (This is not working!):

graph.find_one('Person', {'name': 'Alice', 'age': 22}) 

A possible (bad) solution would be to make a graph.find, and then loop through the results properties and look for the age, but I don't like that solution.

Bonus: Would it be possible with graph.find to do something like age > 25?


EDIT: New "solution"

find_person = "MATCH (p:Person) WHERE p.name = {N} AND p.age = {A} RETURN p"

>>> tx = graph.cypher.begin()
>>> tx.append(find_person, {'N': 'Alice', 'A': 22})
>>> res = tx.process()
>>> print(res[0][0][0])
(n423:Person {age:22,name:"Lisa"})

What I don't like about this is I miss the Note-object, (And I don't fully understand the RecordListList, and how to navigate it nicley)


Solution

  • Based on @elyase answer and the original py2neo.Graph.find, I've made this code. Please feel free to comment and improve.. :-)

    def find_dict(graph, label, key_value=None, limit=None):
        """ Iterate through a set of labelled nodes, optionally filtering
        by property key/value dictionary
        """
        if not label:
            raise ValueError("Empty label")
        from py2neo.cypher.lang import cypher_escape
        if key_value is None:
            statement = "MATCH (n:%s) RETURN n,labels(n)" % cypher_escape(label)
        else:
            # quote string values
            d = {k: "'{}'".format(v) if isinstance(v, str) else v
                 for k, v in key_value.items()}
    
            cond = ""
            for prop, value in d.items():
                if not isinstance(value, tuple):
                    value = ('=', value)
    
                if cond == "":
                    cond += "n.{prop}{value[0]}{value[1]}".format(
                        prop=prop,
                        value=value,
                    )
                else:
                    cond += " AND n.{prop}{value[0]}{value[1]}".format(
                        prop=prop,
                        value=value,
                    )
    
            statement = "MATCH (n:%s ) WHERE %s RETURN n,labels(n)" % (
                cypher_escape(label), cond)
        if limit:
            statement += " LIMIT %s" % limit
        response = graph.cypher.post(statement)
        for record in response.content["data"]:
            dehydrated = record[0]
            dehydrated.setdefault("metadata", {})["labels"] = record[1]
            yield graph.hydrate(dehydrated)
        response.close()
    
    
    def find_dict_one(graph, label, key_value=None):
        """ Find a single node by label and optional property. This method is
        intended to be used with a unique constraint and does not fail if more
        than one matching node is found.
        """
        for node in find_dict(graph, label, key_value, limit=1):
            return node
    

    use of find_dict_one:

    >>> a = find_dict_one(graph, 'Person', {'name': 'Lisa', 'age': 23})
    >>>     print(a)
    (n1:Person {age:23,name:"Lisa"})
    

    Use of find_dict with tuple:

    >>> a = find_dict(graph, 'Person', {'age': ('>', 21)}, 2)    >>> for i in a:
    >>>     print(i)
    (n2:Person {age:22,name:"Bart"})
    (n1:Person {age:23,name:"Lisa"})
    

    Use of find_dict without tuple:

    >>> a = find_dict(graph, 'Person', {'age': 22}, 2)    >>> for i in a:
    >>>     print(i)
    (n2:Person {age:22,name:"Bart"})