Pydantic-native graph data governance: one declared contract for your property graph, continuously checked against your data, your queries, and your live database.
Orthograph is a library — not a platform, not an ORM — that gives a property graph the thing it usually lacks: a single declared contract the application can read, and an enforcement loop around it. You declare node types, relationship types, properties, and cardinalities once in Python or YAML; Orthograph then validates data against that contract, governs a typed Cypher query catalogue, and detects drift between the contract and a live database. It sits above the database, driver, and any ORM, and never owns a connection — the caller passes one in when Orthograph needs it.
It is vendor-agnostic and works with Neo4j, Memgraph, NetworkX, and raw Cypher.
Full documentation: https://orthograph.readthedocs.io
What it does
Property graphs are schema-flexible by design. That flexibility is an asset during exploration and a liability in production: properties get loosely typed, cardinalities are assumed but never checked, queries are raw strings that keep running after a label is renamed (returning wrong or empty results with no error), and the live database drifts away from the model nobody wrote down. The database’s own constraints enforce only a subset, below the application, and are not the same as the application’s intended contract.
Orthograph closes that gap with four distinct capabilities:
- 1. Define the contract
Declare node types, relationship types, properties, and cardinality constraints once — in Python or YAML — and use that declaration as the single source of truth across every validation and query path.
- 2. Validate data
Validate in-memory graph data against the contract before it reaches the database. Produces a structured ValidationResult with typed, differentiable error codes.
- 3. Govern queries
Register named Cypher queries in a typed QueryCatalogue. Each query declares its parameter and output models; the catalogue validates parameter↔template alignment at registration and checks each query — without executing it — for Cypher language correctness and for domain match against the contract (labels, relationship types, properties, endpoints).
- 4. Detect drift
Detect divergence across the three-layer stack. validate_catalogue() compares a whole query set against the contract; compare inspects a live database into a profile and compares that profile against the contract; validate_catalogue_against_profile() checks the query set against both at once — so schema evolution never silently desynchronises your queries and your database from your declared truth.
Query governance and drift detection are separate concerns: governance keeps individual queries honest against the contract at the moment you register them; drift detection answers whether whole sets — the query catalogue, the live database — have diverged from the contract over time.
Extensions
The core library (contract definition, data validation, and Cypher query authoring/validation) depends only on Pydantic, PyYAML, and the graphglot Cypher parser. Database-specific functionality ships as optional extras:
Extra |
What it adds |
|---|---|
neo4j |
inspect_neo4j — inspect a live Neo4j database into a GraphProfile (APOC / SCHEMA / Cypher strategies, auto-detected). |
memgraph |
inspect_memgraph — same interface, using Memgraph’s schema procedures over the Bolt driver. |
networkx |
inspect_networkx — in-process inspection of a nx.MultiDiGraph. |
cypher |
Backward-compat alias. graphglot is now a core dependency, so query authoring and validation are always available; this extra adds no new packages. |
gqlalchemy |
GQLAlchemy OGM integration: codegen of Node / Relationship classes and validated fluent queries. |
Installation
Create a dedicated Python environment first (Python 3.11+):
python -m venv .venv && source .venv/bin/activate
Install the core library:
pip install orthograph
Install with a specific extra:
pip install "orthograph[neo4j]"
pip install "orthograph[memgraph]"
pip install "orthograph[networkx]"
pip install "orthograph[gqlalchemy]"
Install everything:
pip install "orthograph[all]"
For development (all extras + test/lint/docs tooling):
git clone <repo-url>
cd orthograph
pip install -e ".[dev]"
Quick start
Define the contract:
from typing import Optional
from orthograph.definition import (
GraphDefinition,
NodeModel,
RelationshipModel,
validate_data,
)
class Person(NodeModel):
__label__ = "Person"
__uid_field__ = "name"
name: str
born: Optional[int] = None
class Movie(NodeModel):
__label__ = "Movie"
__uid_field__ = "title"
title: str
year: int
class ActedIn(RelationshipModel):
__label__ = "ACTED_IN"
__source_label__ = "Person"
__target_label__ = "Movie"
role: str
definition = GraphDefinition(
name="Filmography",
node_types=[Person, Movie],
relationship_types=[ActedIn],
)
Validate in-memory data against the contract before writing to the database:
nodes = [
{"__label__": "Person", "name": "Alice", "born": 1985},
{"__label__": "Movie", "title": "Inception", "year": 2010},
]
relationships = [
{"__label__": "ACTED_IN", "__source_uid__": "Alice",
"__target_uid__": "Inception", "role": "Lead"},
]
result = validate_data(definition, nodes, relationships)
print(result.is_valid) # True / False
for issue in result.issues:
print(issue.code, issue.message) # structured, typed error codes
Govern a typed Cypher query — declared parameters, validated against the contract without executing it:
from pydantic import BaseModel
from orthograph.queries import new_catalogue, simple_query, validate_catalogue
class FindPersonParams(BaseModel):
name: str
catalogue = new_catalogue()
catalogue.register_cypher_query(
simple_query(
name="find_person_by_name",
cypher_template="MATCH (p:Person {name: $name}) RETURN p",
params=FindPersonParams,
)
)
# Drift detection: is the whole query set still consistent with the contract?
drift = validate_catalogue(catalogue, definition)
print(drift.is_valid)
Detect drift against a live database (requires the neo4j extra):
from neo4j import GraphDatabase
from orthograph.compare import profile_to_definition
from orthograph.profile import inspect_neo4j
driver = GraphDatabase.driver("bolt://localhost:7687", auth=("neo4j", "password"))
profile = inspect_neo4j(driver)
result = profile_to_definition(profile, definition)
print(result.is_valid)
for issue in result.issues:
print(issue.code, issue.message)
Contributing
Setup, the full test matrix (unit, in-process integration, live Neo4j/Memgraph flags, credential handling, and running the reference notebooks) are documented in CONTRIBUTING.md.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file orthograph-0.1.0.tar.gz.
File metadata
- Download URL: orthograph-0.1.0.tar.gz
- Upload date:
- Size: 173.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.10.19
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ad8301e91672f8db402ba7c02599e8309e8a9d2e34a08a7bd7346a92c7dd0553
|
|
| MD5 |
d49fe928f3df72473485455ada07c382
|
|
| BLAKE2b-256 |
d24f84413b547bb80419c2943923beea1fa22629d74a2cc9c9ceebd917bc8bc9
|
File details
Details for the file orthograph-0.1.0-py3-none-any.whl.
File metadata
- Download URL: orthograph-0.1.0-py3-none-any.whl
- Upload date:
- Size: 194.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.10.19
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4e5cdb29bf824a81cd51c946bf7f1a6e316a184fd84d734bbe7d6ceefb9e6415
|
|
| MD5 |
b753dd009b59c087aa36ab384fc9b583
|
|
| BLAKE2b-256 |
ea8964f7fcd46fb348b365a93ca1f295352be238f589dec4fe8c04334c5c2234
|