Skip to content

Pydantic ORM — overview

cypher_validator ships a Pydantic-based graph ORM that lets you declare your schema as Python classes and then build, validate, and execute Cypher queries against Neo4j type-safely. The ORM layer is pure Python — it sits on top of the Rust validator and generator, not under them.

The design goals:

  1. Familiar — looks like SQLAlchemy / Django ORM if you squint.
  2. Schema-driven — your declared models are the schema, and they feed straight into the Rust CypherValidator.
  3. Parameterised by default — every value path emits $param placeholders.
  4. Agent-friendly — every query exposes (cypher, params) so an LLM can inspect or modify it before execution.

How the pieces fit together

                        Pydantic NodeModel / RelationshipModel
                ┌───────────────────────────────────────────┐
                │              GraphSchema                  │
                │  (collects models, computes shape)        │
                └───────────────────────────────────────────┘
                       │                          │
            to_dict()  │                          │  to_cypher_schema()
                       ▼                          ▼
              ┌─────────────────┐         ┌─────────────────┐
              │ AgentTools spec │         │  Rust  Schema   │
              │ (OpenAI / Anth) │         │ (HashSet props) │
              └─────────────────┘         └─────────────────┘
                                          ┌─────────────────┐
                                          │ CypherValidator │  ← validates every
                                          │   (Rust core)   │     generated query
                                          └─────────────────┘
   ┌──────────────────────────────┬────────────────┴──────────────────────────────┐
   ▼                              ▼                                               ▼
┌──────────────┐         ┌─────────────────┐                          ┌────────────────────┐
│   Query      │         │   Repository    │                          │   GraphSession     │
│ (builder)    │         │ (CRUD per model)│                          │ (execute, hydrate) │
└──────────────┘         └─────────────────┘                          └────────────────────┘
   │  build()                                                                     │
   ▼                                                                              ▼
(cypher, params)  →  db.execute  →  records  →  Model.from_records(...)  →  Pydantic instances

Component cheat-sheet

Component Purpose
NodeModel Base class for node types. Subclass and add fields.
RelationshipModel Base class for relationships. Set __source__, __target__, __rel_type__.
Query Fluent Cypher builder — .match().where().return_().build().
Cond Single condition Cond(left, op, right). Inlines scalar literals.
CondGroup Group of conditions joined by AND / OR.
RawExpr Escape hatch — inject raw Cypher text.
GraphSchema Bridge from Pydantic models to the Rust Schema.
AgentTools OpenAI / Anthropic function-call tool specs for an LLM agent.
ExtendedAgentTools Adds search_nodes, find_neighbors, find_path, etc.
QueryPlan Multi-step plan with dependencies; topologically sorted into waves.
QueryStep One step in a QueryPlan.
QueryResult Structured wrapper around execution output.
Traversal Pre-built patterns: neighbors, shortest_path, degree, path_exists, …
BulkOps UNWIND-based bulk create / merge / delete.
SchemaDDL Auto-generate CREATE CONSTRAINT / CREATE INDEX.
GraphSession Sync session over a Neo4jDatabase. Hydrates results into models.
AsyncGraphSession Async sibling — async with, await session.query(...).
PropExpr Typed property reference: p.name == "Alice"Cond.
NodeRef Typed variable reference for nodes.
RelRef Typed variable reference for relationships.
QueryHistory LRU history for agent conversation context.
SchemaDiff Compare two schemas; emit migration DDL.
CypherFn / fn Type-safe wrappers for built-in functions (count, avg, coalesce, …).
PathBuilder Multi-hop path construction: PathBuilder(Person).rel(ActedIn).to(Movie).
Repository Typed CRUD over a single model.
schema_to_pipeline_kwargs Hand off a GraphSchema to LLMNLToCypher.

A complete example

from cypher_validator import (
    NodeModel, RelationshipModel,
    Query, Cond, GraphSchema,
    CypherValidator, GraphSession, Neo4jDatabase,
    Repository,
)

# 1) Declare schema as Python classes
class Person(NodeModel):
    __label__ = "Person"
    name: str
    age: int = 0

class Movie(NodeModel):
    __label__ = "Movie"
    title: str
    year: int

class ActedIn(RelationshipModel):
    __source__ = Person
    __target__ = Movie
    __rel_type__ = "ACTED_IN"
    roles: list[str] = []

# 2) Bridge to the Rust validator
schema = GraphSchema.from_models([Person, Movie, ActedIn])
validator = CypherValidator(schema.to_cypher_schema())

# 3) Build a query
q = (Query()
     .match(Person, "p")
     .where(Cond("p.age", ">=", 18))    # → p.age >= 18   (literal inlined)
     .return_("p.name AS name", "p.age AS age"))
cypher, params = q.build()
result = q.validate(schema)             # validates against the Rust validator
assert result.is_valid

# 4) Execute via a GraphSession
db = Neo4jDatabase("bolt://localhost:7687", "neo4j", "password")
with GraphSession(db, schema) as session:
    rows = session.execute(cypher, params)

# 5) Or use a per-model Repository for CRUD
repo = Repository(Person, db)
alice = repo.find_one(name="Alice")
all_people = repo.find_all(limit=100)

Integration with the LLM pipeline

schema_to_pipeline_kwargs(graph_schema) converts a GraphSchema into the kwargs LLMNLToCypher expects:

from cypher_validator import LLMNLToCypher, schema_to_pipeline_kwargs

pipe = LLMNLToCypher.from_openai(
    model="gpt-4o",
    api_key="...",
    **schema_to_pipeline_kwargs(graph_schema),
)
cypher = pipe("List actors over 60 who appeared in films from the 90s.", mode="match")

This is the recommended way to share a single source-of-truth schema between your Pydantic models, the validator, and the LLM pipeline.

Where to go from here