11.2. A sample app using traversals and indexes

11.2.1. Domain logic
11.2.2. Creating data and getting it back

For detailed documentation on the concepts use here, see Section 19.3, “Indexes” and Section 19.5, “Traversals”.

This example shows you how to get started building something like a simple invoice tracking application with Neo4j.

We start out by importing Neo4j, and creating some meta data that we will use to organize our actual data with.

from neo4j import GraphDatabase, INCOMING, Evaluation

# Create a database
db = GraphDatabase(folder_to_put_db_in)

# All write operations happen in a transaction
with db.transaction:

    # A node to connect customers to
    customers = db.node()

    # A node to connect invoices to
    invoices = db.node()

    # Connected to the reference node, so
    # that we can always find them.
    db.reference_node.CUSTOMERS(customers)
    db.reference_node.INVOICES(invoices)

    # An index, helps us rapidly look up customers
    customer_idx = db.node.indexes.create('customers')

11.2.1. Domain logic

Then we define some domain logic that we want our application to be able to perform. Our application has two domain objects, Customers and Invoices. Let’s create methods to add new customers and invoices.

def create_customer(name):
    with db.transaction:
        customer = db.node(name=name)
        customer.INSTANCE_OF(customers)

        # Index the customer by name
        customer_idx['name'][name] = customer
    return customer

def create_invoice(customer, amount):
    with db.transaction:
        invoice = db.node(amount=amount)
        invoice.INSTANCE_OF(invoices)

        invoice.RECIPIENT(customer)
    return customer

In the customer case, we create a new node to represent the customer and connect it to the customers node. This helps us find customers later on, as well as determine if a given node is a customer.

We also index the name of the customer, to allow for quickly finding customers by name.

In the invoice case, we do the same, except no indexing. We also connect each new invoice to the customer it was sent to, using a relationship of type SENT_TO.

Next, we want to be able to retrieve customers and invoices that we have added. Because we are indexing customer names, finding them is quite simple.

def get_customer(name):
    return customer_idx['name'][name].single

Lets say we also like to do something like finding all invoices for a given customer that are above some given amount. This could be done by writing a traversal, like this:

def get_invoices_with_amount_over(customer, min_sum):
    def evaluator(path):
        node = path.end
        if node.has_key('amount') and node['amount'] > min_sum:
            return Evaluation.INCLUDE_AND_CONTINUE
        return Evaluation.EXCLUDE_AND_CONTINUE

    return db.traversal()\
             .relationships('RECIPIENT', INCOMING)\
             .evaluator(evaluator)\
             .traverse(customer)\
             .nodes

11.2.2. Creating data and getting it back

Putting it all together, we can create customers and invoices, and use the search methods we wrote to find them.

for name in ['Acme Inc.', 'Example Ltd.']:
   create_customer(name)

# Loop through customers
for relationship in customers.INSTANCE_OF:
   customer = relationship.start
   for i in range(1,12):
       create_invoice(customer, 100 * i)

# Finding large invoices
large_invoices = get_invoices_with_amount_over(get_customer('Acme Inc.'), 500)

# Getting all invoices per customer:
for relationship in get_customer('Acme Inc.').RECIPIENT.incoming:
    invoice = relationship.start