The solution to this problem has to do with the objects returned by the various commands in py2neo v 4.1.0. I've tried to generalize the problem/solution to be helpful beyond my particular use-case.
Running the Cypher command in neo4j browser returns a RECORD
object and all the nodes and edges (and in the Browser it seems all edges among all the nodes found even if you didn't ask for those edges). The browser will show you all the items in that record without you having to do anything special (though you can limit what the browser returns using LIMIT
for the number and WHERE
for filtering labels and properties).
Py2neo has a variety of options for returning objects from Cypher queries, none of which are well documented and none of which have useful examples or sufficient explanations of differences. But after lots of trying and failing I managed to figure out a few things and get it to work. I'm going to share what I learned so that hopefully somebody else trying to use this package doesn't lose hours due to poor documentation.
Consider the following method you might use to retrieve a node from your database.
import py2neo as pn
graph = pn.Graph("bolt://localhost:####/", user="neo4j", password="pwd")
theCypherQuery= '''MATCH (n:Label1) WHERE n.label1_name=$para1
OPTIONAL MATCH (n:Label1)<-[:REL1]-(n2:Label2) WHERE n2.label2_name = $para2
OPTIONAL MATCH (n:Label1)<-[:REL1]-(n3:Label3) WHERE n3.label3_name = $para2
RETURN n2, n3'''
def getNode(thisCypherQuery, parameter1, parameter2):
cypherResult = graph.evaluate(thisCypherQuery, parameters = {'para1':parameter1, 'para2':parameter2})
return cypherResult
someNode = getNode(theCypherQuery,firstParameter,secondParameter)
If theCypherQuery
always returns exactly one node, then graph.evaluate
will work because it actually returns the first object in the record generated by the query.
However, if you have a more complicated query and/or database that potentially returns multiple items (even if all but one item is None
) then you need to use graph.run
instead of graph.evaluate
. But graph.run
returns a record object that is not something you can deal with in Python easily, so there are options:
graph.run(theCypherQuery).data()
returns the result as a list of one dictionary reporting all the nodes returned in the record.
graph.run(theCypherQuery).table()
returns that dictionary as a table of keys and values, which seems mostly useful for printing to the console.
graph.run(theCypherQuery).evaluate()
is equivalent to graph.evaluate(theCypherQuery)
above and returns the first object.
In my real case I wanted to match a name across nodes with different kinds of labels that are children of another node with a particular label. My Cypher query always returned the correct node, but there were five None
objects returned as well (for the other node labels) that were simply ignored in the Browser but were breaking my Python code. Maybe I could change my Cypher query to only return that one node regardless of its label type, but regardless I figured it's a good idea to learn how to deal with these record objects.
Here is an example of manipulating the returned record object in Python to extract nodes and eliminate the None
responses.
def getNode(thisCypherQuery, parameter1, parameter2):
## The returned node is None by default
thisNode = None
## Retrieve the record object from the query, substituting in the parameter values.
## The .data() part returns a list containing a single dictionary.
## So I extract the dictionary by simply pulling item [0].
thisRecord = graph.run(theCypherQuery, parameters = {'para1':parameter1, 'para2':parameter2}).data()[0]
## Now I create a list of non-None values from the dictionary using "list comprehension".
theseNodes = [val for key,val in thisRecord .items() if val != None]
## Perhaps nothing was found, but if at least one item was found...
if len(theseNodes) > 0:
## Then return the first found object (which in my case is the unique matching node)
## Note that this is also necessary to convert the list into a node object.
thisNode = theseNodes[0]
return thisNode
Nothing here is especially strange of difficult Python if you are already proficient, but without proper documentation it may be difficult to understand the data structures and what is necessary to retrieve and manipulate them. In this case, the retrieved node objects are compatible with py2neo commands such as the following to create a link between two found nodes based on their names (from another data source).
firstNode = getNode(theCypherQuery,'UnitedStates','Georgia')
secondNode = getNode(theCypherQuery,'UnitedStates','Jacksonville')
graph.merge(pn.Relationship(firstNode,'BORDERING',secondNode))
Note that I haven't tried to return and manipulate a relationship object yet, but hopefully it won't be too different from getting and using the node objects. And hopefully you can modify this code to retrieve nodes and relationships to fit your needs.
graph.run()
doesn't include parameters and hence doesn't explain how to properly include them. It also doesn't save the result to a variable. Also, I don't have a problem getting some nodes from a Cypher query in py2neo, but this py2neo cypher query doesn't return the same thing that Neo4j does, and I don't know why or how to fix it. – Aaron BramsonNone
. I've edited my question again with more info based on what I've tried so far. – Aaron Bramson