
neo4j-driver-python-skill
Neo4j Python Driver v6 — driver lifecycle, executequery, managed and explicit transactions, async (AsyncGraphDatabase), result handling, data type mapping, error handling, UNWIND batching, connection pool tuning, and causal consistency. Use when writing Python code that connects to Neo4j via GraphDatabase.driver, executequery, executeread, executewrite, AsyncGraphDatabase, neo4j.Result, or RoutingControl. Package name is neo4j (not neo4j-driver) since v6. Python =3.10 required. Does NOT handle Cypher query authoring — use neo4j-cypher-skill. Does NOT cover driver upgrades or breaking changes — use neo4j-migration-skill. Does NOT cover GraphRAG pipelines (neo4j-graphrag package) — use neo4j-graphrag-skill.
- Installs
- 984
- Last 24h
- +8
- Trending rank
- #9519
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/neo4j-contrib/neo4j-skills --skill neo4j-driver-python-skill- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads neo4j-driver-python-skill automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
When to Use
- Writing Python code that connects to Neo4j
- Setting up driver, sessions, transactions, or async patterns
- Debugging result handling, serialization, or UNWIND batching
- Reviewing Neo4j driver usage in Python code
When NOT to Use
- Writing/optimizing Cypher →
neo4j-cypher-skill - Driver version upgrades →
neo4j-migration-skill - GraphRAG pipelines (
neo4j-graphragpackage) →neo4j-graphrag-skill
Installation
pip install neo4j # package name is `neo4j`, NOT `neo4j-driver` (deprecated since v6)
pip install neo4j-rust-ext # optional: 3–10× faster serialization, same API
Python >=3.10 required for v6.x. Python 3.14 supported [6.1+]. Pandas 3 and PyArrow 23/24 supported [6.2+]. PyArrow 25 and Bolt 6.1 uuid.UUID values supported [6.3+]; driver-created SSL contexts honour SSLKEYLOGFILE [6.3+]. Python 3.15 supported [6.4+]. Pin neo4j>=6.4.1 — 6.4.0 can spin forever on pool acquisition and double-encodes bearer tokens over HTTP Query API.
Neo4j 2026.08+ UUID properties require neo4j>=6.3 to round-trip as uuid.UUID. Storing UUID needs block store format (Enterprise); Community (aligned format) fails: storing properties of type UUID is not supported in aligned store format — store str(uuid).
Environment Variables
Load connection config from environment — never hardcode credentials.
import os
from dotenv import load_dotenv # pip install python-dotenv
load_dotenv(".env") # reads NEO4J_URI / NEO4J_USERNAME / NEO4J_PASSWORD / NEO4J_DATABASE
URI = os.getenv("NEO4J_URI", "neo4j://localhost:7687")
USER = os.getenv("NEO4J_USERNAME", "neo4j")
PASSWORD = os.getenv("NEO4J_PASSWORD", "")
DATABASE = os.getenv("NEO4J_DATABASE", "neo4j")
.env file format:
NEO4J_URI=neo4j+s://xxx.databases.neo4j.io
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=secret
NEO4J_DATABASE=neo4j
Add .env to .gitignore. Without python-dotenv, use export in shell or os.getenv directly.
Driver Lifecycle
Create one Driver per application. Thread-safe, expensive to create. Never create per-request.
from neo4j import GraphDatabase
URI = "neo4j+s://xxx.databases.neo4j.io" # Aura
AUTH = ("neo4j", "password")
# Context manager — preferred for scripts
with GraphDatabase.driver(URI, auth=AUTH) as driver:
driver.verify_connectivity()
# ... work ...
# Long-lived singleton (service / web app)
driver = GraphDatabase.driver(URI, auth=AUTH)
driver.verify_connectivity()
# on shutdown:
driver.close()
URI schemes:
| Scheme | Use |
|---|---|
neo4j+s:// | TLS + cluster routing — Aura default |
neo4j:// | Unencrypted + cluster routing |
bolt+s:// | TLS, single instance |
bolt:// | Unencrypted, single instance |
https:// / http:// (e.g. https://host:7474/) | HTTP Query API instead of Bolt [6.4+, preview] — pip install "neo4j[http]" |
Auth options: ("user", "pass") tuple, basic_auth(), bearer_auth("jwt"), kerberos_auth("b64").
HTTP Query API [6.4+, preview — no semver guarantee]: pass database_= / database= on every call (no home-database resolution); auth basic (no realm) or bearer only. Ignored/unsupported: tx metadata + timeouts, fetch_size (no backpressure), notification filters; VECTOR, UUID, UnsupportedType cannot be exchanged. Needs Bolt for any of these → use neo4j+s:// / bolt+s://.
Choosing the Right API
| API | Use when | Auto-retry | Streaming |
|---|---|---|---|
driver.execute_query() | Most queries — simple, safe default | ✅ | ❌ eager |
session.execute_read/write() | Large results / multiple queries in one tx | ✅ | ✅ |
session.run() | LOAD CSV, CALL {} IN TRANSACTIONS, scripts | ⚠️ one-shot [6.2+] | ✅ |
AsyncGraphDatabase | asyncio applications | ✅ | ✅ |
session.run() retry [6.2+]: single immediate retry on DBMS-marked idempotent errors only (currently admission control). Disable with disable_auto_commit_retries=True at driver or session level.
execute_query — Default API
from neo4j import GraphDatabase, RoutingControl
# Tuple unpacking — most common
records, summary, keys = driver.execute_query(
"MATCH (p:Person {name: $name})-[:KNOWS]->(f) RETURN f.name AS name",
name="Alice",
routing_=RoutingControl.READ, # route reads to replicas
database_="neo4j", # always specify — saves a round-trip
)
for record in records:
print(record["name"])
print(summary.result_available_after, "ms")
# Write — check counters
summary = driver.execute_query(
"CREATE (p:Person {name: $name, age: $age})",
name="Bob", age=30,
database_="neo4j",
).summary
print(summary.counters.nodes_created)
Trailing-underscore convention — config kwargs end with _ (database_, routing_, auth_, result_transformer_, bookmark_manager_). No query parameter name may end with _; pass those via parameters_={"key_": val}.
Never f-string or format Cypher. Always $param — prevents injection and enables plan caching.
result_transformer_ — reshape before return:
import neo4j
df = driver.execute_query("MATCH (p:Person) RETURN p.name, p.age", database_="neo4j",
result_transformer_=neo4j.Result.to_df)
record = driver.execute_query("MATCH (p:Person {name:$n}) RETURN p", n="Alice", database_="neo4j",
result_transformer_=neo4j.Result.single) # None if 0 rows; first record + warning if 2+
Result.single() defaults to strict=False: 0 rows → None; 2+ rows → first record + warning (no exception). Only single(strict=True) raises ResultNotSingleError (0 or 2+). Use strict=True when exactly one row required, e.g. result_transformer_=lambda r: r.single(strict=True); else check for None.
Managed Transactions (execute_read / execute_write)
Use for large results or multiple queries in one transaction.
with driver.session(database="neo4j") as session:
def get_people(tx):
result = tx.run("MATCH (p:Person) WHERE p.name STARTS WITH $pfx RETURN p.name AS name",
pfx="Al")
return [r["name"] for r in result] # consume INSIDE callback — Result invalid after tx closes
names = session.execute_read(get_people)
def create_person(tx):
tx.run("CREATE (p:Person {name: $name})", name="Carol")
session.execute_write(create_person)
Result lifetime — Result is a lazy cursor backed by the open transaction. Returning it unconsumed raises ResultConsumedError. Always collect to list inside the callback.
Callback may retry on transient failures — keep callbacks idempotent; move side effects (HTTP calls, emails) outside the callback.
Timeout/metadata via @unit_of_work (named functions only — cannot decorate lambdas):
from neo4j import unit_of_work
@unit_of_work(timeout=5.0, metadata={"app": "svc", "user": user_id})
def get_people(tx):
return [r["name"] for r in tx.run("MATCH (p:Person) RETURN p.name AS name")]
session.execute_read(get_people)
Implicit Transactions (session.run)
Use only for LOAD CSV, CALL {} IN TRANSACTIONS, or quick scripts. session.run() does a single immediate retry on idempotent (DBMS-marked) errors only [6.2+]; other errors do not retry.
with driver.session(database="neo4j") as session:
result = session.run("CREATE (p:Person {name: $name})", name="Alice")
summary = result.consume() # call consume() to guarantee commit before proceeding
print(summary.counters.nodes_created)
# Opt out of one-shot retry [6.2+] — driver- or session-level
driver = GraphDatabase.driver(URI, auth=AUTH, disable_auto_commit_retries=True)
with driver.session(database="neo4j", disable_auto_commit_retries=True) as session:
session.run("...")
Async API
Mirror of sync API — replace GraphDatabase with AsyncGraphDatabase, await every call.
from neo4j import AsyncGraphDatabase
import asyncio
# Singleton — same rule as sync: never create per-request
driver = AsyncGraphDatabase.driver(URI, auth=AUTH)
async def main():
records, _, _ = await driver.execute_query(
"MATCH (p:Person) RETURN p.name AS name",
database_="neo4j", routing_=RoutingControl.READ,
)
print([r["name"] for r in records])
await driver.close()
asyncio.run(main())
FastAPI lifespan pattern:
from contextlib import asynccontextmanager
from fastapi import FastAPI
_driver = None
@asynccontextmanager
async def lifespan(app: FastAPI):
global _driver
_driver = AsyncGraphDatabase.driver(URI, auth=AUTH)
await _driver.verify_connectivity()
yield
await _driver.close()
app = FastAPI(lifespan=lifespan)
Parallel queries with asyncio.gather:
results = await asyncio.gather(
driver.execute_query("MATCH (a:Artist) RETURN a.name AS name", database_="neo4j"),
driver.execute_query("MATCH (v:Venue) RETURN v.name AS name", database_="neo4j"),
)
Never use sync GraphDatabase in asyncio — blocks the event loop.
Full async patterns → references/async.md
Error Handling
from neo4j.exceptions import (
Neo4jError, ServiceUnavailable, TransientError,
AuthError, ConstraintError,
)
try:
driver.execute_query("...", database_="neo4j")
except AuthError:
... # bad credentials
except ServiceUnavailable:
... # no servers reachable
except ConstraintError as e:
# unique/existence constraint violation — catch BEFORE Neo4jError (it's a subclass)
print(e.code, e.message)
except TransientError as e:
# raised only after retries exhausted (execute_query retries automatically)
print(e.code)
except Neo4jError as e:
print(e.code, e.message, e.gql_status)
Catch ConstraintError before Neo4jError — it is a subclass and will be swallowed otherwise.
Result Access & Null Safety
record = records[0]
record["name"] # by key — KeyError if absent
record[0] # by index
record.get("name") # None for absent key OR graph null
record.get("name", "Unknown")
d = record.data() # dict — Node → dict of properties, Relationship → tuple, Path → list; temporal values stay driver objects
record.data(): Node → dict of properties, Relationship → (start_props, type, end_props) tuple (own properties dropped), Path → list. json.dumps accepts these but loses labels, element IDs, relationship properties. neo4j.time.Date/Time/DateTime stay driver objects → json.dumps raises TypeError. Project needed scalars in Cypher (toString() temporals); don't return whole entities.
# ❌ raises TypeError on json.dumps (temporal value)
records, _, _ = driver.execute_query("MATCH (p:Person) RETURN p.name AS name, p.created_at AS created_at", database_="neo4j")
json.dumps(records[0].data())
# ✅ project scalars
records, _, _ = driver.execute_query(
"MATCH (p:Person) RETURN p.name AS name, p.age AS age, toString(p.created_at) AS created_at", database_="neo4j")
json.dumps(records[0].data()) # safe
Node/Relationship/temporal access:
node = record["p"] # neo4j.graph.Node
node.element_id # stable within this transaction only
node.labels # frozenset({'Person'})
dict(node) # all properties as plain dict
rel = record["r"] # neo4j.graph.Relationship
rel.type # 'KNOWS'
dt = record["created_at"] # neo4j.time.DateTime
dt.to_native() # datetime.datetime (loses sub-µs precision)
Full type mapping table → references/data-types.md
Batch Writes with UNWIND
Pass list[dict] — only shape the driver serializes correctly for UNWIND.
people = [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]
driver.execute_query(
"UNWIND $rows AS row MERGE (p:Person {name: row.name}) SET p.age = row.age",
rows=people,
database_="neo4j",
)
Custom objects and dataclasses must be converted to dict before passing as parameters.
Performance
- Always set
database_/database=— omitting triggers a home-database round-trip per call. execute_readroutes to replicas automatically; userouting_=RoutingControl.READwithexecute_query.- Batch writes: one
execute_writecallback for the whole list > one tx per item. - Large results: stream lazily inside
execute_readcallback;execute_queryis always eager.
Connection pool tuning:
driver = GraphDatabase.driver(URI, auth=AUTH,
max_connection_pool_size=50, # default 100
connection_acquisition_timeout=30, # seconds to wait for free connection
max_connection_lifetime=3600, # seconds; recycles stale connections
connection_timeout=15,
keep_alive=True,
)
Session exhaustion: each open session holds a connection. Always use with driver.session(...) as session.
Full performance patterns → references/performance.md
Common Errors
| Mistake | Fix |
|---|---|
f-string / .format() Cypher params | Use $param placeholders always |
Param name ending with _ | Pass via parameters_={"key_": val} |
Omitting database_ | Always set — saves a round-trip every call |
Returning Result from tx callback | Consume to list inside callback |
Side effects in execute_read/write callback | Move outside — callback may retry |
| Passing dataclass/Pydantic as param | Convert to dict first |
UNWIND with list of objects | list[dict] only |
record.get() for absent-key detection | "key" in record.keys() for absent; .get() returns None for both absent and graph null |
No .consume() after session.run() | Commit timing undefined; call .consume() |
| Sync driver inside asyncio | Use AsyncGraphDatabase — sync blocks event loop |
| Async driver created per request | Singleton — create once at startup |
| Leaked sessions | with driver.session(...) as session always |
json.dumps(record.data()) with temporal values | TypeError — toString() in Cypher or convert. Whole nodes/relationships serialize but lose labels, IDs, relationship properties — project scalars |
result["name"] on EagerResult | Index result.records[0]["name"] or unpack records, _, _ = ... |
Assuming Result.single() raises on 0 or 2+ rows | Default strict=False: None (0 rows) or first record + warning (2+). single(strict=True) raises |
@unit_of_work on lambda | Use named function |
Neo4jError caught before ConstraintError | Catch ConstraintError first — it's a subclass |
neo4j-driver package name | Package is neo4j since v6; neo4j-driver deprecated |
References
Load on demand:
- references/async.md — full async patterns: managed transactions, result methods, concurrency
- references/data-types.md — complete Python↔Cypher type mapping, temporal conversion, graph object API, spatial types (CartesianPoint/WGS84Point)
- references/performance.md — connection pool, lazy streaming, threading vs asyncio, bookmarks/causal consistency
- references/transactions.md — explicit transactions, rollback, commit uncertainty,
unit_of_workdetails
Docs:
Checklist
- Package installed as
neo4j(notneo4j-driver) - One Driver instance created at startup; shared everywhere
-
verify_connectivity()called at startup -
database_/database=set on every call -
$paramplaceholders used — no f-strings or.format() - Result consumed inside tx callback (not returned raw)
- Sessions used as context managers (
with driver.session(...) as session) -
ConstraintErrorcaught beforeNeo4jError -
AsyncGraphDatabaseused in asyncio code (not sync driver) - Async driver created once at app startup (not per request)
- Side effects outside
execute_read/writecallbacks - UNWIND batches use
list[dict]
FAQ
What does the neo4j-driver-python-skill skill do?
Neo4j Python Driver v6 — driver lifecycle, executequery, managed and explicit transactions, async (AsyncGraphDatabase), result handling, data type mapping, error handling, UNWIND batching, connection pool tuning, and causal consistency. Use when writing Python code that connects to Neo4j via GraphDatabase.driver, executequery, executeread, executewrite, AsyncGraphDatabase, neo4j.Result, or RoutingControl. Package name is neo4j (not neo4j-driver) since v6. Python =3.10 required. Does NOT handle Cypher query authoring — use neo4j-cypher-skill. Does NOT cover driver upgrades or breaking changes — use neo4j-migration-skill. Does NOT cover GraphRAG pipelines (neo4j-graphrag package) — use neo4j-graphrag-skill.
How do I install neo4j-driver-python-skill?
Run npx skills add https://github.com/neo4j-contrib/neo4j-skills --skill neo4j-driver-python-skill in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/neo4j-driver-python-skill/ (personal) or .claude/skills/neo4j-driver-python-skill/ (project).
Which AI agents can use neo4j-driver-python-skill?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is neo4j-driver-python-skill safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains neo4j-driver-python-skill?
It is published by neo4j-contrib in neo4j-contrib/neo4j-skills. It has 984 recorded installs.
More from neo4j-contrib/neo4j-skills
neo4j-cypher-skill
neo4j-contrib/neo4j-skills
Generates, optimizes, and validates Cypher 25 queries for Neo4j 2025.x and 2026.x. Use when writing new Cypher queries, optimizing slow queries, graph pattern matching, vector or fulltext search, subqueries, or batch writes. Covers MATCH, MERGE, CREATE, WITH, RETURN, CALL, UNWIND, FOREACH, LOAD CSV, SEARCH, expressions, functions, indexes, and subqueries. Does NOT handle driver migration or API changes — use neo4j-migration-skill. Does NOT cover DB administration or server ops — use neo4j-cli-tools-skill.
neo4j-modeling-skill
neo4j-contrib/neo4j-skills
Design, review, and refactor Neo4j graph data models. Use when choosing node labels vs relationship types vs properties, migrating relational/document schemas to graph, detecting anti-patterns (generic labels, supernodes, missing constraints), designing intermediate nodes for n-ary relationships, enforcing schema with constraints and indexes, or assessing an existing model against graph modeling best practices. Does NOT handle Cypher query authoring — use neo4j-cypher-skill. Does NOT handle Spring Data Neo4j entity mapping — use neo4j-spring-data-skill. Does NOT handle GraphQL type definitions — use neo4j-graphql-skill. Does NOT handle data import — use neo4j-import-skill.
neo4j-graphrag-skill
neo4j-contrib/neo4j-skills
Build GraphRAG retrieval pipelines on Neo4j using the neo4j-graphrag Python package (v1.22.0+). Covers retriever selection (VectorRetriever, HybridRetriever, VectorCypherRetriever, HybridCypherRetriever, Text2CypherRetriever, ToolsRetriever), external vector DB retrievers (Weaviate, Pinecone, Qdrant), retrievalquery Cypher fragments, queryparams, filters, GraphRAG pipeline wiring (GraphRAG + LLM + prompt), all LLM providers (OpenAI, Anthropic, Gemini/VertexAI, Bedrock, Cohere, Mistral, Ollama), embedder setup, index creation, token usage tracking, Cypher 25 SEARCH clause, and LangChain/LlamaIndex integration. Does NOT handle KG construction — use neo4j-document-import-skill. Does NOT handle plain vector search — use neo4j-vector-index-skill. Does NOT handle GDS analytics — use neo4j-gds-skill. Does NOT handle agent memory — use neo4j-agent-memory-skill.
neo4j-document-import-skill
neo4j-contrib/neo4j-skills
Ingests unstructured and semi-structured documents into Neo4j as a knowledge graph. Use when chunking PDFs, HTML, plain text, or Markdown; extracting entities and relationships from text with an LLM (SimpleKGPipeline, neo4j-graphrag); loading JSON via apoc.load.json; building Document→Chunk→Entity graph structures; or connecting LangChain/LlamaIndex document loaders to Neo4j. Covers neo4j-graphrag SimpleKGPipeline, LLM Graph Builder web UI, entity resolution, chunking strategies, and graph schema design for RAG pipelines. Does NOT handle structured CSV/relational import — use neo4j-import-skill. Does NOT handle GraphRAG retrieval after ingestion — use neo4j-graphrag-skill. Does NOT handle vector index creation — use neo4j-vector-search-skill.
neo4j-query-tuning-skill
neo4j-contrib/neo4j-skills
Diagnoses and fixes slow Neo4j Cypher queries by reading execution plans, identifying bad operators (AllNodesScan, CartesianProduct, Eager, NodeByLabelScan), and prescribing fixes (indexes, hints, query rewrites, runtime selection). Use when a query is slow, when EXPLAIN or PROFILE output needs interpretation, when dbHits or pageCacheHitRatio are poor, when cardinality estimation diverges from actuals, or when deciding between slotted/pipelined/parallel runtimes. Covers USING INDEX / USING SCAN / USING JOIN hints, db.stats.retrieve, SHOW QUERIES, SHOW TRANSACTIONS, TERMINATE TRANSACTION. Does NOT write new Cypher from scratch — use neo4j-cypher-skill. Does NOT cover GDS algorithm tuning — use neo4j-gds-skill. Does NOT cover index/constraint creation syntax details — use neo4j-cypher-skill references/indexes.md.
neo4j-vector-index-skill
neo4j-contrib/neo4j-skills
Create and manage Neo4j vector indexes, run vector similarity search (ANN/kNN), store embeddings on nodes or relationships, use SEARCH clause (Neo4j 2026.01+, preferred) or db.index.vector.queryNodes() procedure (deprecated 2026.04, still works on 2025.x), configure HNSW and quantization options, pick similarity function and embedding provider dimensions, and batch-update embeddings. Use when tasks involve CREATE VECTOR INDEX, vector.dimensions, cosine/euclidean search, embedding ingestion pipelines, semantic or structural nearest-neighbor lookup, or hybrid search (vector + fulltext, multiple vector sources, or graph-derived scores). Does NOT handle GraphRAG retrievalquery graph traversal — use neo4j-graphrag-skill. Does NOT handle fulltext-only/keyword-only search — use neo4j-cypher-skill. Does NOT compute GDS graph embeddings (FastRP, Node2Vec) — use neo4j-gds-skill.
Popular code quality & workflow skills
grill-me
mattpocock/skills
A relentless interview to sharpen a plan or design.
improve-codebase-architecture
mattpocock/skills
Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick.
handoff
mattpocock/skills
Compact the current conversation into a handoff document for another agent to pick up.
domain-modeling
mattpocock/skills
Build and sharpen a project's domain model. Use when discussing codebase terminology, writing or editing a GLOSSARY.md, or recording or editing an ADR.
codebase-design
mattpocock/skills
Shared vocabulary for designing deep modules. Use when the user wants to design or improve a module's interface, find deepening opportunities, decide where a seam goes, make code more testable or AI-navigable, or when another skill needs the deep-module vocabulary.
diagnosing-bugs
mattpocock/skills
Diagnosis loop for hard bugs and performance regressions. Use when the user says "diagnose"/"debug this", or reports something broken/throwing/failing/slow.