TuringDB Python SDK
The TuringDB Python SDK provides an easy interface for connecting to your local TuringDB server, running queries, and managing graphs programmatically.
Install the PythonSDK:
Using uv package manager (you will need to create a project first):
uv add turingdbor using pip:
pip install turingdbThe SDK targets TuringDB v3: pin turingdb>=3.0 in your project (older projects may pin 1.x, which uses the previous engine and a much smaller Cypher subset). The wheel supports Python 3.10 to 3.14 on Linux (x86_64, aarch64) and macOS 15 or later (arm64). It also contains the turingdb command-line tool, the turing-parquet import tool, and the in-process engine.
Getting Started
from turingdb import TuringDB
# Create TuringDB client
# set host parameter to the URL (as string) on which TuringDB is running,
# default "http://localhost:6666"
client = TuringDB(host="http://localhost:6666")
# List available graphs
print(client.list_available_graphs())Client backends
TuringDB(...) is a facade over three interchangeable backends, chosen with the type argument (or the TURINGDB_TYPE environment variable, default "json"):
type | Encoding / transport | Constructor arguments |
|---|---|---|
"json" (default) | JSON over HTTP against a running server | host="http://localhost:6666", optional token |
"native" | TuringDB’s binary protocol against a running server. Faster for large results | host="localhost", port=6666, optional token |
"embedded" | In-process engine, no server or socket | data_dir="/path/to/.turing" |
# Default HTTP/JSON client
client = TuringDB(host="http://localhost:6666")
# Native binary protocol
client = TuringDB(type="native", host="localhost", port=6666)
# Embedded, in-process: no server needed
client = TuringDB(type="embedded", data_dir="/home/me/.turing")Use the default json backend unless you have a reason not to. The other two are optimisations for specific situations:
nativewhen you control the server and need more throughput on large results.embeddedwhen you want a self-contained engine with no server to start, stop or connect to.
The native binary protocol
The binary protocol is a server-side mode, selected once when the server starts with the environment variable USE_TURING_PROTO=1:
USE_TURING_PROTO=1 turingdb start -demon -p 6666client = TuringDB(type="native", host="localhost", port=6666)A server speaks either JSON or the binary protocol on a given port:
| Server started | type="json" client | type="native" client |
|---|---|---|
| normally (JSON, the default) | works | returns Proto header dataLen does not match chunk payload size |
with USE_TURING_PROTO=1 | returns Invalid response from the server | works |
A server in binary mode serves the binary protocol only, so curl, the JSON client and the browser visualizer (-ui) connect to a server running in the default mode. To use both, run two servers on different ports.
hostaccepts"localhost"together withport=, or a full URL such as"http://localhost:6666". Use one of these two forms.- The query API is identical, and results come back as the same DataFrames. Embeddings are returned as numpy
float32arrays instead of Python lists. - To list the graphs on disk, use the JSON client. On the native backend,
list_loaded_graphs()lists the loaded graphs. token,reconnect()and the S3 helpers work as on the JSON backend.
Authentication
A server started with -auth-on requires a bearer token. Pass it with token= or set the TURINGDB_AUTH_TOKEN environment variable, which the SDK reads automatically. This works on both the json and native backends:
TURINGDB_AUTH_TOKEN=s3cret turingdb start -demon -auth-onclient = TuringDB(host="http://localhost:6666", token="s3cret")A missing or incorrect token returns HTTP 401, which the JSON client raises as an httpx.HTTPStatusError. The embedded backend runs in-process and needs no authentication, so it does not take a token.
The embedded backend
import os
client = TuringDB(type="embedded", data_dir=os.path.expanduser("~/.turing"))data_diris the TuringDB root directory (the one that holdsgraphs/,data/andvector/). Omit it to use~/.turing.- Use an absolute path, for example with
os.path.expanduser("~/..."), since a leading~is not expanded. - Writes still persist to disk when you submit a change, so your work survives the process ending.
- The embedded backend is designed for a single process. For the browser visualizer,
list_available_graphs(), concurrent access from several processes, the S3 helpers andtoken=, use a server.
list_available_graphs() is only supported on the default JSON backend. The S3 helpers (s3_connect, transfer) work on the JSON and native backends.
Core Methods
list_available_graphs() → list[str]
Returns the graphs persisted on disk in the TuringDB data directory (JSON backend only).
client.list_available_graphs()list_loaded_graphs() → list[str]
Returns the currently loaded graphs in memory.
client.list_loaded_graphs()create_graph(graph_name: str)
Creates a new empty graph with the specified name.
client.create_graph("mygraph")Internally executes
CREATE GRAPH "graph_name"
load_graph(graph_name: str, raise_if_loaded: bool = True)
Loads a previously created graph into memory.
client.load_graph("mygraph")If raise_if_loaded=False, it will silently continue if the graph is already loaded.
query(query: str) → pandas.DataFrame
Runs a Cypher query on the current graph.
df = client.query("MATCH (n:Person) RETURN n.name, n.age")Returns results as a pandas.DataFrame, with automatic typing and parsing.
query_raw(query: str) → dict
Like query, but returns the raw server response as a dict instead of a DataFrame. Useful for inspecting result metadata or column dtypes directly.
get_graph() → str / set_graph(graph_name: str)
Get or set the active graph for subsequent queries.
is_graph_loaded() → bool
Returns whether the current graph is loaded in memory.
new_change() → int
Creates a new isolated change (equivalent to CHANGE NEW), makes it the active change, and returns its integer ID. The client is already on the new change, so a separate checkout(change=...) is not needed.
change = client.new_change()new_change() raises if the client is already on a change or on a past commit. Call client.checkout() first.
Submit a change by running client.query("CHANGE SUBMIT"), then call client.checkout() to return to main. The client points at the submitted change until you do, so call checkout() before the next query.
Version Control Helpers
TuringDB supports snapshot isolation and versioned commits.
Use the following helpers to navigate graph history or work in isolated changes:
checkout(change: int | str = "main", commit: str = "HEAD")
Switches the working context to a specific change or commit.
client.checkout(change=2, commit="abc123")
client.checkout("main") # Reset to defaultchange: change ID or"main"commit: commit hash or"HEAD"
set_commit(commit: str)
Manually set the commit hash to use.
client.set_commit("haax42")set_change(change: int | str)
Manually set the change ID to use (accepts int or hex string).
client.set_change(3)set_graph(graph_name: str)
Change the current graph context.
client.set_graph("another_graph")You can also read the current context through the current_graph, current_commit (default "HEAD"), and current_change (default "main") properties.
Connection & Timing Helpers
reconnect()
Resets the connection. A no-op for the HTTP and embedded backends. Reopens the socket for the native backend.
try_reach(timeout: int = 5) / warmup(timeout: int = 5)
Probe connectivity (try_reach) or warm the connection with a lightweight query (warmup).
get_query_exec_time() → float | None / get_total_exec_time() → float | None
Return the server-side execution time and the full round-trip time (in milliseconds) of the last query.
S3 / Data Transfer
These helpers move graph data between local paths, S3, and the TuringDB server. They are available on the JSON and native backends, but not on the embedded backend.
s3_connect(bucket_name, access_key=None, secret_key=None, region=None, use_scratch=True)
Connect an S3 client for transfers. Credentials are optional — if omitted, the machine’s configured AWS credentials are used.
client.s3_connect("my-bucket", region="eu-west-1")transfer(src, dst)
Transfer data between a local path, an s3://… URI, or a turingdb://… path. The direction and mechanism are inferred from the URI schemes.
client.transfer("s3://graphs/data.jsonl", "turingdb://data.jsonl") # S3 → server
client.transfer("./local.gml", "turingdb://local.gml") # local → server (via scratch)The equivalent engine commands (issued directly via query) are S3 CONNECT "<key>" "<secret>" "<region>", S3 PULL "<s3-url>" "<dst>", and S3 PUSH "<src>" "<s3-url>".
Response Format
All queries return a pandas.DataFrame, typed according to the schema:
| Cypher Type | pandas dtype |
|---|---|
String | string |
Int64 | Int64 |
UInt64 | UInt64 |
Double | float64 |
Bool | boolean |
DateTime | datetime64[us, UTC] |
Duration | timedelta64[us] |
List, Map | object (Python list, dict) |
Embedding | object (list over HTTP, numpy float32 array on the native and embedded backends) |
Nodes and edges (RETURN n) come back as their internal integer IDs (UInt64), so project the properties you need. A named path comes back as a list of {'type': 'node' | 'edge', 'id': ...} dictionaries. See Functions and data types for the full table.
Over the JSON backend, Doubles are serialised with 6 decimal places. Use the native or embedded backend if you need full precision.
Error Handling
Query errors raise TuringDBException. The message starts with the error code (PARSE_ERROR, ANALYZE_ERROR, PLAN_ERROR, EXEC_ERROR, CHANGE_NOT_FOUND, COMMIT_NOT_FOUND, …) and points at the offending part of the query.
from turingdb import TuringDB, TuringDBException
try:
client.query("MATCH (n RETURN n")
except TuringDBException as e:
print("Query failed:", e)On the JSON backend, transport failures (the server is down, or HTTP 401 for a bad or missing token) surface as httpx exceptions instead.
Example Workflow
from turingdb import TuringDB
# Create TuringDB client
# set host parameter to the URL (as string) on which TuringDB is running,
# default "http://localhost:6666"
client = TuringDB(host="http://localhost:6666")
# Create a new graph called mygraph
client.create_graph("mygraph")
# Set working graph
client.set_graph("mygraph")
# Create a new change on the graph
change = client.new_change()
# Checkout into the change
client.checkout(change=change)
# Multiple nodes and edges can be created at the same time
# using the following syntax
client.query("""CREATE (apoe_gene:Gene {name: 'APOE', chromosome: '19', location: '19q13.32'}),
(apoe_protein:Protein {name: 'Apolipoprotein E', function: 'Lipid_transport', tissue: 'Brain_and_liver'}),
(alzheimers:Disease {name: 'Alzheimers_Disease', type: 'Neurodegenerative'}),
(cholesterol:Molecule {name: 'Cholesterol', type: 'Lipid'}),
(apoe_gene)-[:ENCODES]->(apoe_protein),
(apoe_protein)-[:BINDS]->(cholesterol),
(apoe_protein)-[:ASSOCIATED_WITH]->(alzheimers)
""")
# Commit the change
client.query("COMMIT")
client.query("CHANGE SUBMIT")
# Checkout into main
client.checkout()
# Query graph
df = client.query("MATCH (n:Gene {name: 'APOE'}) RETURN n.name, n.chromosome")
print(df)Notes
- The JSON backend (
type="json") useshttpx; thenativeandembeddedbackends use a compiled extension - Only the graph name, change ID, and commit hash are sent with HTTP requests
- The SDK supports the full TuringDB Cypher dialect, including matching by internal node ID (
WHERE n = 1234) and versioned queries against past commits
Need help writing queries? → See the TuringDB Query Cheatsheet
You can also find the TuringDB PythonSDK in Github

