Py2neo 2.0, Unleashed!

Written by Nigel Small, Senior Engineer at Neo Technology

Py2neo 2.0, Unleashed!

py2neo.800x800It’s been over three years since I first began work on py2neo. If memory serves, the first version was written for Neo4j 1.3 and consisted of barely more than a few lines of hastily-thrown-together Python code. Neo4j has obviously evolved considerably since then and now boasts labels, a schemaand Cypher transactions. Actually, it also boasts Cypher as a core component – back in the day it was merely a plugin!

And so, inevitably, py2neo has to move on as well. I’d previously tacked on some of the new Neo4j 2.x series features but in doing so had introduced a bit more technical debt than I really wanted. A proper rewrite was needed. And so today (after quite a few months) I’m releasing py2neo 2.0. This brings with it a heavily refactored core, a cleaner API, better performance and a few new idioms. So what’s actually changed? Well, some old py2neo 1.6 code will run in 2.0 but not all of it – most code will require a tweak. But there are many benefits from upgrading to the new library so I’ll highlight some of the big changes below…


The py2neo.neo4j namespace has been deprecated completely. That is: while it still exists, most references to it can simply be replaced by just py2neo. And GraphDatabaseService is now just known as Graph. So the code that was previously written as…

from py2neo.neo4j import GraphDatabaseService
graph = GraphDatabaseService()
…can now be written as…
from py2neo import Graph
graph = Graph()
That’s a bit cleaner – I hope you’ll agree 🙂

Another namespace change is the introduction of py2neo.legacy. This contains legacy nodes (those without labels for pre-2.0 server versions), legacy batch classes (batches are now more specific) and legacy indexes (for clarity on indexes, look here). You might need these bits and bobs if you have some existing code to port over but they can be ignored for anything new.


The Node and Relationship constructors have now become a lot more useful but have changed their signatures completely; they are therefore notbackward compatible. This shouldn’t affect many apps as it would have been rare to construct a node directly by URI, but the equivalent of…
node = Node("https://localhost:7474/db/data/node/1")
…is now…
node = Node()
This then leaves the constructor free to be used for passing in labels and properties. So, the Node class can now more comfortably serve as either a concrete or an abstract entity by having a bound state.

The Relationship class has undergone a similar transformation and bothNode and Relationship now expose a cast classmethod that replaces thenode and rel functions in 1.6.


Labels are now (at last!) a first class feature of py2neo. Due to certain limitations in the REST interface, making this so wasn’t straightforward until certain parts of the library’s core had been rewritten. Now though, a node can be created with labels instead of having to add labels in a separate step:
alice = Node("Person", name="Alice", email="")
bob = Node("Person", name="Bob", email="")
graph.create(alice, bob)
Some other handy label-related functions have been introduced too:

Uniqueness constraints…
graph.schema.create_uniqueness_constraint("Person", "email")
Merging nodes by label…
graph.merge_one("Person", "email", "")
Finding nodes by label…
for person in graph.find("Person"):

Push & Pull

One of the biggest changes in py2neo 2.0 is how data is synchorised between client and server. Previously, changes made to node properties (for instance) were sent instantly to the server. It therefore followed that changing several properties triggered several HTTP requests and this (obviously) didn’t scale well.

So, in py2neo 2.0, data is only synchronised on an explicit pull or push. This gives the application developer far more control over the HTTP requests that occur. This conversation can now even be monitored with the handy watch function.


The story wouldn’t of course be complete without mentioning Cypher. Py2neo 2.0 integrates Cypher in a much cleaner and more intuitive way and it all hinges on the new Graph.cypher attribute.

It’s easy to execute a single statement…
from py2neo import Graph
graph = Graph()
results = graph.cypher.execute("MATCH (n:Person) RETURN n")
…or just as easy to use a transaction
from py2neo import Graph

graph = Graph()
statement = "MERGE (n:Person {name:{N}}) RETURN n"

tx = graph.cypher.begin()

def add_names(*names):
    for name in names:
        tx.append(statement, {"N": name})

add_names("Homer", "Marge", "Bart", "Lisa", "Maggie")
add_names("Peter", "Lois", "Chris", "Meg", "Stewie")


And the REST

And that’s it. Well… except for the Rel, Rev and Path classes, new serverand store modules, a handful of batch changes and a bunch of extensions and command line tools. The full set of documentation can be found here, so have a browse, download the package and get in touch if you need any pointers.

I guess it’s now time for me to start planning the next three years!


Want to learn more about graph databases? Click below to get your free copy of O’Reilly’s Graph Databases ebook and discover how to use graph technologies for your application today.

Download My Ebook