Learn more about the Pypher library that allows you to express Cypher queries in pure PythonCypher is a pretty cool language. It allows you to easily manipulate and query your graph in a familiar – but at the same time – unique way. If you’re familiar with SQL, mixing in Cypher’s ASCII node and relationship characters becomes second nature, allowing you to be very productive early on.

A query language is the main interface for the data stored in a database. In most cases, that language is completely different than the programming language interacting with the actual database. This results in query building through either string concatenation or with a few well-structured query-builder objects (which themselves resolve to concatenated strings).

In my research, the majority of Python Neo4j packages either offered no query builder or a query builder that is a part of a project with a broader scope.

Being a person who dislikes writing queries by string contention, I figured that Neo4j should have a simple and lightweight query builder. That is how Pypher was born.

What Is Pypher?


Pypher is a suite of lightweight Python objects that allow the user to express Cypher queries in pure Python.

Its main goals are to cover all of the Cypher use-cases through an interface that isn’t too far from Cypher and to be easily expandable for future updates to the query language.

What Does Pypher Look Like?

from pypher import Pypher

p = Pypher()
p.Match.node('a').relationship('r').node('b').RETURN('a', 'b', 'r')

str(p) # MAtCH ('a')-['r']-('b') RETURN a, b, r

Pypher is set up to look and feel just like the Cypher that you’re familiar with. It has all of the keywords and functions that you need to create the Cypher queries that power your applications.

All of the examples found in this article can be run in an interactive Python Notebook located here.

Why Use Pypher?

    • No need for convoluted and messy string concatenation. Use the Pypher object to build out your Cypher queries without having to worry about missing or nesting quotes.
    • Easily create partial Cypher queries and apply them in various situations. These Partial objects can be combined, nested, extended and reused.
    • Automatic parameter binding. You do not have to worry about binding parameters as Pypher will take care of that for you. You can even manually control the bound parameter naming if you see fit.
    • Pypher makes your Cypher queries a tad bit safer by reducing the chances of Cypher injection (this is still quite possible with the usage of the Raw or FuncRaw objects, so be careful).
Why Not Use Pypher?

    • Strings are a Python primitive and could use a lot less memory in long-running processes. Not much, but it is a fair point.
    • Python objects are susceptible to manipulation outside of the current execution scope if you aren’t too careful with passing them around (if this is an issue with your Pypher, maybe you should re-evaluate your code structure).
    • You must learn both Cypher and Pypher and have an understanding of where they intersect and diverge. Luckily for you, Pypher’s interface is small and very easy to digest.
Pypher makes my Cypher code easier to wrangle and manage in the long run. It allows me to conditionally build queries and relieves the hassle of worrying about string concatenation or parameter passing.

If you’re using Cypher with Python, give Pypher a try. You’ll love it.

Examples


Let’s take a look at how Pypher works with some common Cypher queries.

Cypher:

MATCH (u:User)
RETURN u

Pypher:

from pypher import Pypher, __

p = Pypher()
p.MATCH.node('u', labels='User').RETURN.u

str(p) # MATCH (u:`User`) RETURN u

Cypher:

OPTIONAL MATCH (user:User)-[:FRIENDS_WITH]-(friend:User)
WHERE user.Id = 1234
RETURN user, count(friend) AS number_of_friends

Pypher:

p.OPTIONAL.MATCH.node('user', 'User').rel(labels='FRIENDS_WITH').node('friend', 'User')
# continue later
p.WHERE.user.__id__ == 1234
p.RETURN(__.user, __.count('friend').alias('number_of_friends'))

str(p) # OPTIONAL MATCH (user:`User`)-[FRIENDS_WITH]-(friend:`User`) 
WHERE user.`id` = $NEO_964c1_0 RETURN user, count($NEO_964c1_1) 
AS $NEO_964c1_2
print(dict(p.bound_params)) # {'NEO_964c1_0': 1234, 'NEO_964c1_1': 'friend',
'NEO_964c1_2': 'number_of_friends'}

Use this accompanying interactive Python Notebook to play around with Pypher and get comfortable with the syntax.

So How Does Pypher Work?


Pypher is a tiny Python object that manages a linked list with a fluent interface.

Each method, attribute call, comparison or assignment taken against the Pypher object adds a link to the linked list. Each link is a Pypher instance allowing for composition of very complex chains without having to worry about the plumbing and how to fit things together.

Certain objects will automatically bind the arguments passed in replacing them with either a randomly generated or user-defined variable. When the Pypher object is turned into a Cypher string by calling the __str__ method on it, the Pypher instance will build the final dictionary of bound_params (every nested instance will automatically share the same Params object with the main Pypher object).

Pypher also offers partials in the form of Partial objects. These objects are useful for creating complex, but reusable, chunks of Cypher. Check out the Case object for a cool example on how to build a Partial with a custom interface.

Things to Watch Out for


As you can see in the examples above, Pypher doesn’t map one-to-one with Cypher, and you must learn some special syntax in order to produce the desired Cypher query. Here is a short list of things to consider when writing Pypher:

Watch Out for Assignments

When doing assignment or comparison operations, you must use a new Pypher instance on the other side of the operation. Pypher works by building a simple linked list. Every operation taken against the Pypher instance will add more to the list and you do not want to add the list to itself.

Luckily this problem is pretty easy to rectify. When doing something that will break out of the fluent interface it is recommended that you use the Pypher factory instance __ or create a new Pypher instance yourself, or even import and use one of the many Pypher objects from the package.

p = Pypher()

p.MATCH.node('p', labels='Person')
p.SET(__.p.prop('name') == 'Mark)
p.RETURN.p

#or

p.mark.property('age') <= __.you.property('age')

If you are doing a function call followed by an assignment operator, you must get back to the Pypher instance using the single underscore member

p.property('age')._ += 44

Watch Out for Python Keywords

Python keywords that are either Pypher Statement or Func objects are in all caps. So when you need an AS in the resulting Cypher, you simply write it as all caps in Pypher.

p.RETURN.person.AS.p

Watch Out for Bound Parameters

If you do not manually bind params, Pypher will create the param name with a randomly generated string. This is good because it binds the parameters; however, it also doesn't allow the Cypher caching engine in the Neo4j server to property cache your query as a template.

The solution is to create an instance of the Param object with the name that you want to be used in the resulting Cypher query.

name = Param('my_param', 'Mark')

p.MATCH.node('n').WHERE(__.n.__name__ == name).RETURN.n

str(p) # MATCH (n) WHERE n.`name` = $my_param RETURN n
print(dict(p.bound_params)) # {'my_param': 'Mark'}

Watch Out for Property Access

When accessing node or relationship properties, you must either use the .property function or add a double underscore to the front and back of the property name node.__name__.

Documentation & How to Contribute


Pypher is a living project, and my goal is to keep it current with the evolution of the Cypher language. So if you come across any bugs or missing features or have suggestions for improvements, you can add a ticket to the GitHub repo.

If you need any help with how to set things up or advanced Pypher use cases, you can always jump into the Neo4j users Slack and ping me @emehrkay.

Have fun. Use Pypher to build some cool things and drop me a link when you do.


Take your Neo4j skills up a notch:
Take our online training class, Neo4j in Production, and learn how to scale the #1 graph platform to unprecedented levels.


Take the Class

 

Keywords:  


About the Author

Mark Henderson, Neo4j Community Member

Mark Henderson Image

Mark Henderson is a software engineer from Baltimore, Maryland. He is a 20-year software veteran who focuses on web technologies and has a budding interest in graph databases and video game creation. One of Mark’s main goals is to provide robust tools for people to build cool things. Hit Mark up on Twitter @emehrkay


Leave a Reply

Your email address will not be published. Required fields are marked *

Subscribe

Upcoming Event

 


Have a Graph Question?

Stack Overflow
Slack
Contact Us

Share your Graph Story?

Email us: content@neo4j.com