Neo4j Cypher compiler for building domain-specific APIs
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
cypher
.gitignore
LICENSE
README.md
setup.py

README.md

Cypher

The goal of the API is to serve as a foundation for building simpler domain-specific APIs. If nothing else, it prevents needing to use string formatting or concatenation for building a Cypher statement programmatically.

Examples

Node

from cypher import *

Node({'id': 1}, identifier='n', labels=['Foo', 'Bar'])

Result

(n:Foo:Bar {id: 1})

Match Path

rel1 = Rel(Node(), Node({'name': 'Bob'}), 'FRIENDS')
rel2 = Rel(rel1.end, Node({'name': 'Sue'}), '*1..3', reverse=True)
path = Path([rel1, rel2], identifier='p')

Query([
    Match(path),
    Return(Identifier(path.identifier)),
])

Result

MATCH p = ()-[:FRIENDS]->({name: 'Bob'})<-[*1..3]-({name: 'Sue'})
RETURN p

Merge Relationship

charlie = Node({'name': 'Charlie Sheen'}, identifier='charlie', labels=['Person'])
oliver = Node({'name': 'Oliver Stone'}, identifier='oliver', labels=['Person'])

knows = Rel(charlie, oliver, 'KNOWS', identifier='knows')

Query([
    Merge(knows),
    Return([
        Identifer(charlie.identifier),
        Identifer(oliver.identifier),
        Identifer(knows.identifier),
    ]),
])

Result

MERGE (charlie:Person {name: 'Charlie Sheen'})-[r:KNOWS]->(oliver:Person {name: 'Oliver Stone'})
RETURN DISTINCT  c, o, r

Conditional Match

gene = Node({'name': 'Gene Hackman'}, identifier='gene')
movie = Node(identifier='movie')
n = Node(identifier='n')

acted_in = Rel(gene, movie, 'ACTED_IN')
other_in = Rel(movie, n, 'ACTED_IN', reverse=True)
path = Path([acted_in, other_in])

directed = Rel(n, Node(), 'DIRECTED')

match = Match(path)
where = Where(directed)
ret = Return(Property('name', identifier='n'), distinct=True)

Query([match, where, ret])

Result

MATCH (gene {name: 'Gene Hackman'})-[:ACTED_IN]->(movie)<-[:ACTED_IN]-(n)
WHERE (n)-[:DIRECTED]->()
RETURN DISTINCT n.name

Status

  • Alpha in API design
  • Incomplete set of token classes
    • Undecided with how granular the API needs to be
  • No protection against wrong types
  • Contributions to the overall approach, design, or code are welcome!

Supported Syntax

API docs coming soon!

  • Value
  • Identifer
  • Function
  • Map
  • Collection
  • Node
  • Rel
  • Path
  • Property
  • PropertyList
  • Predicate
  • PredicateList
  • Start
  • Where
  • Match
  • OptionalMatch
  • Create
  • CreateUnique
  • Delete
  • Return
  • ReturnDistinct
  • Skip
  • Limit
  • OrderBy
  • With
  • Merge
  • OnCreate
  • OnMatch
  • Set
  • Union
  • UnionAll
  • Query
  • StartNode
  • StartRel

Docs

Value

Converts a Python value into a Cypher equivalent string. If a value is passed that is already a Token instance, it will pass through. Lists, tuples, and dicts are wrapped in their respective token classes (Collection and Map).

>>> Value(True)
TRUE
>>> Value(False)
FALSE
>>> Value(None)
NULL
>>> Value({'a': 1})
{a: 1}
>>> Value([1, 'a'])
[1, 'a']
>>> Value(b'foo')
'foo'
>>> Value(u'foo')
'foo'
>>> Value('foo')
'foo'

Identifer

Takes a string and outputs a valid Cypher identifier by wrapping it in backticks if necessary.

>>> Identifier('a b'))
`a b`

# Auto-extraction of object identifiers
>>> node = Node(identifier='joe')
>>> Identifier(node)
joe

# A map property can be represented
>>> Identifier('name', identifier='joe')
n.name

# Alias to the identifier
>>> Identifier('n', alias='joe')
n AS joe

Function

Takes a function name and one or more arguments.

>>> Function(ROUND, 1.54)
round(1.54)

# Alias to the identifier
>>> Function(ROUND, 1.54, alias='rounded')
round(1.54) AS rounded

Node

Node pattern.

# Bare
>>> Node()
()

# Properties
>>> Node({'a': 1})
({a: 1})

# Identifier
>>> Node({'a': 1}, identifier='n')
(n {a: 1})

# Labels
>>> Node({'a': 1}, identifier='n', labels=['Person'])
(n:Person {a: 1})

Rel

Relationship pattern.

# Bare
>>> Rel()
()-->()

# Reverse
>>> Rel(reverse=True)
()<--()

# Not directed
>>> Rel(directed=False)
()--()

# Identifiers
>>> Rel('a', 'TO', 'b')
(a)-[:TO]->(b)

# Properties
>>> Rel(type='TO', props={'foo': 1})
()-[:TO {foo: 1}]->()

# Variable length relationship
>>> Rel(type='*1..3')
()-[*1..3]->()

# Multiple types
>>> Rel(type=['TO', 'FROM'])
()-[:TO|FROM]-()

# Nodes
>>> a = Node({'label': 'A'}, identifier='a')
>>> b = Node({'label': 'B'}, identifier='b')
>>> Rel(a, 'TO', b)
(a {label: 'A'})-[:TO]->(b {label: 'B'})

Path

Path pattern.

>>> Path()

StartNode/StartRel

Lookup for node or relationship in the START statement.

>>> StartNode(10, identifier='n')
n=node(10)

# Multiple ints
>>> StartNode([10, 20], identifier='n')
n=node(10,20)

# Index; pass key and value rather than int
>>> StartNode('name', 'Joe', identifier='joe', index='names')
joe=node:names(name='Joe')