cancel
Showing results for 
Search instead for 
Did you mean: 

Head's Up! These forums are read-only. All users and content have migrated. Please join us at community.neo4j.com.

Loops nodes on a path using python

I want to iterate two paths of nodes and make comparison on properties. Then update the nodes in the database accordingly.

I connect to neo4j using the python driver and create a read transaction to get the paths. My function returns the nodes, but I don't understand how I can iterate it.

Just as an example, here is a graph with two node types "Box" and "Replace".

Path 1 is (:Box {number:14})-[:NEXT]->(:Box{number:15})-[:NEXT]->(:Box{number:16})

Path 2 is (:Replace{number:14})-[:NEW]->(:Replace{number:15})

I want a loop on Path 1 starting at the last node (number16), going to the first one (number 14). And on each iteration I want to compare the property "fruit" with the last node of path 2.

If the property is the same I want to do some changes (such as delete node or update node etc) which will update the nodes and the relatioships. But I can't seem to find the right way to do this.

If you could point me to a resource which explains loops, I would really appreciate it. Thanks!!

4 REPLIES 4

Hello and thanks for reaching out.

The part of the docs you are looking for is here: API Documentation — Neo4j Python Driver 4.4
With path.nodes you can access all nodes of a path. To iterate them in revers you can do for node in reversed(path.nodes):.

Here is a code snipped that does what you ask for. If there is something unclear please ask.

import neo4j


def query(tx, query_, **params):
    result = tx.run(query_, params)
    return list(result)


def main():
    auth = ('neo4j', 'pass')
    with neo4j.GraphDatabase.driver('bolt://localhost:7687', auth=auth) as d:
        with d.session() as s:
            # make sure db is clean (delete all nodes and relationships)
            s.write_transaction(query, "MATCH (n) DETACH DELETE n")
            # create the data
            s.write_transaction(
                query,
                "MERGE (:Box {Fruit: 'Apple', Number: 13})-[:NEXT]->"
                "(:Box {Fruit: 'Orange', Number: 14})-[:NEXT]->"
                "(:Box {Fruit: 'Apple', Number: 15})-[:NEXT]->"
                "(:Box {Fruit: 'Orange', Number: 16})-[:NEXT]->"
                "(:Box {Fruit: 'Orange', Number: 17})"
            )
            s.write_transaction(
                query,
                "MATCH "
                "   (s:Box {Fruit: 'Apple', Number: 13}),"
                "   (e:Box {Fruit: 'Orange', Number: 17}) "
                "CREATE (s)-[:NEW]->"
                "(:Replace {Fruit: 'Orange', Number: 14})-[:NEW]->"
                "(:Replace {Fruit: 'Apple', Number: 15})-[:NEW]->"
                "(e)"
            )

        # fetching the two described paths
        with d.session() as s:
            path1 = s.read_transaction(
                query,
                "MATCH path = "
                "(:Box {Number: 13})-[:NEXT*]->(:Box {Number: 16}) "
                "RETURN path"
            )
            assert len(path1) == 1 or print(path1)
            path1 = path1[0]["path"]

            path2 = s.read_transaction(
                query,
                "MATCH path = "
                "(:Replace {Number: 14})-[:NEW]->(:Replace {Number: 15}) "
                "RETURN path"
            )
            assert len(path2) == 1 or print(path2)
            path2 = path2[0]["path"]

        print(path1, path2)

        last_node_path2 = path2.nodes[-1]

        # traversing the nodes of path1 in reverse order
        for node in reversed(path1.nodes):
            if node["Fruit"] == last_node_path2["Fruit"]:
                print("Cool, let's do things here!", node, last_node_path2)


if __name__ == "__main__":
    main()

Note: since reading and the action (print("Cool, let's do things here!"...)) are not withing one transaction, the change is not atomic. So you might want to consider moving all queries into a single transaction.

Thanks, this is very helpful. It to work well if I have one path. But if my MATCH path query returns more than one path or if I try to reterive a subset of a path using apoc.path.slice I get the following error:

Traceback (most recent call last):
File "C:\Python39\lib\site-packages\neo4j\data.py", line 139, in index
return self.__keys.index(key)
ValueError: tuple.index(x): x not in tuple

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "F:\test\example.py", line 60, in
main()
File "F:\test\example.py", line 33, in main
path1 = path1[0]["path"]
File "C:\Python39\lib\site-packages\neo4j\data.py", line 97, in __ getitem__
index = self.index(key)
File "C:\Python39\lib\site-packages\neo4j\data.py", line 141, in index
raise KeyError(key)
KeyError: 'path'

My guess is that in the case of slice, the subset of the path is not returned as a path but something else...and in the case of more than one paths being returned by the query,
path1 = path1[0]["path"] can't handle more than one path.

What would you suggest for this please?

Also, if the path return only one node, I get an index out of range error

Traceback (most recent call last):
File "F:\test\exampe.py", line 72, in
main()
File "F:\test\exampe.py", line 58, in main
path2 = path2[0]["path"]
IndexError: list index out of range

I think there is a fundamental misconception and I'm guilty of choosing misleading variable names, sorry.

Let me try to clarify. But before I start, I highly recommend running this script with a debugger and exploring the API that way, or alternatively, running it in an interactive environment, e.g., a Python shell or a Jupyter notebook. This way, you can see what keys and things exist inside path1 and path2 when running different queries.

Anyway here goes:

  • tx.run(query_, params) returns a Result object (see docs).
  • I then call list(result) which will turn this result into a list of Record objects (see docs).
  • The exact keys the Record contains, depends on the query. So when you alter the query, you potentially alter what keys there are. If you don't want to rely on the names of the keys, you can also index the records with integers. So instead of path1[0]["path"], you could do path1[0][0].

Regarding your last message Loops nodes on a path using python - #4 by sanna.aizad This error does not stem from the line you quoted me on, but from this line path2 = path2[0]["path"], which fails for the reason explained above.