Getting to Know NeoAlchemy

NeoAlchemy features both a low-level and a high-level API for working with Neo4J. The low-level API aims to be expressive, offering the user flexibility and control. The high-level API is built on top of the low-level API, and trades control for automation.

You don’t have to choose between APIs! The low-level and high-level APIs can be used in conjunction with one another as well as with manual Cypher querying with graph.query.

The Low-Level API

NeoAlchemy’s low-level API is called The QueryBuilder API. It is similar in feel and purpose to the SqlAlchemy Expression Language. In SqlAlchemy, defining the schema for a table and writing its metadata to the database looks like this:

from sqlalchemy import Table, Column, Integer, String, MetaData

metadata = MetaData()

users = Table('users', metadata,
    Column('id', Integer, primary_key=True),
    Column('name', String),
    Column('fullname', String)
)

# Emit schema-generating DDL
metadata.create_all(engine)

This defines a simple users table with three columns. A call to metadata.create_all() is required to emit schema-generating DDL, laying out the empty table.

The same thing in NeoAlchemy looks like this:

import uuid

from neoalchemy import Node, Property, Graph
from neoalchemy.validators import UUID

graph = Graph()

user = Node('User',
    uuid=Property(unique=True, type=UUID, default=uuid.uuid4),
    name=Property(indexed=True),
    full_name=Property(required=True)
)

# Emit schema-generating DDL
graph.schema.create(user)

This creates a simple Node with three properties similar to the above table. Each property represents an available constraint in Neo4J. The proper indexes and constraints are created when graph.schema.create() is called.

Note

The required property represents a Property Existence constraint which is only supported in the Neo4J Enterprise Edition.

Can’t wait to learn more? Dive into The QueryBuilder API.

The High-Level API

NeoAlchemy’s high-level API is Schema OGM. Python classes are used to map metadata to the database transparently. It is compared below to SqlAlchemy’s Declarative ORM:

from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    name = Column(String)
    fullname = Column(String)

# Emit schema-generating DDL
Base.metadata.create_all(engine)

The same thing in NeoAlchemy looks like this:

import uuid

from neoalchemy import OGMBase, Property, Graph
from neoalchemy.validators import UUID

class User(OGMBase):
    graph = Graph()

    uuid = Property(unique=True, type=UUID, default=uuid.uuid4)
    name = Property(indexed=True)
    fullname = Property(required=True)

# Cypher schema generation emitted automatically
# No user action required

Notice that unlike SqlAlchemy, we have far less to import and we do not need to manually trigger metadata creation. We also don’t have to explicitly specify a label for our underlying Node. NeoAlchemy uses the name of the class if none is specified.

Note

Since every class is connected to a graph explicitly via its .graph property, users running multiple instances of Neo4J should have no trouble distinguishing which classes map to which graphs, even if multiple classes touching different graphs are grouped in the same file.

Wanna learn more? Skip straight to the Schema OGM.