A code-first database toolkit for SurrealDB. Define schemas, generate migrations, build queries, and perform typed CRUD -- all from Python.
- Code-First Migrations - Schema changes defined in code with automatic migration generation
- Type-Safe Query Builder - Composable queries with Pydantic model integration
- SurrealDB v3 Ready - Emits v3-correct SurrealQL (datetime casts,
count() GROUP ALL,type::record, buffered transactions, idempotent DDL) - Query UX Helpers - First-class wrappers for
time::now,math::*,string::*,count_if,type_record, and typed aggregations -- no raw SurrealQL required - Vector Search - HNSW and MTREE index support with 8 distance metrics and EFC/M tuning
- Graph Traversal - Native SurrealDB graph features with edge relationships
- Query Caching - Memory and Redis-backed caching with
@cache_querydecorator - Live Queries - Real-time change notifications and streaming
- Schema Visualization - Mermaid, GraphViz, and ASCII diagrams
- CLI Tools - Migrations, schema inspection, multi-environment orchestration, validation, and database management
- Async-First - Built with async/await, connection pooling, and retry logic
pip install oneiriq-surql
# or with uv
uv add oneiriq-surqlfrom surql.schema.fields import string_field, int_field, datetime_field
from surql.schema.table import table_schema, unique_index, TableMode
user_schema = table_schema(
'user',
mode=TableMode.SCHEMAFULL,
fields=[
string_field('name'),
string_field('email', assertion='string::is::email($value)'),
int_field('age', assertion='$value >= 0 AND $value <= 150'),
datetime_field('created_at', default='time::now()', readonly=True),
],
indexes=[unique_index('email_idx', ['email'])],
)surql migrate create "Add user table"
surql migrate up
surql migrate statusfrom surql import (
Query,
aggregate_records,
count_if,
math_mean_fn,
math_sum_fn,
time_now_fn,
type_record,
)
# Fluent UPDATE with server-side function values
sql = (
Query()
.update('user:alice')
.set(status='active', last_seen=time_now_fn())
.to_surql()
)
# Typed aggregate -- GROUP ALL + count() rendered correctly for v3
rows = await aggregate_records(
table='order',
select={
'total': count_if(),
'revenue': math_sum_fn('amount'),
'avg_ticket': math_mean_fn('amount'),
},
where="status = 'paid'",
group_all=True,
)
# Record-ID construction without string concatenation
ref = type_record('user', 'alice').to_surql()
# -> type::record('user', 'alice')# Sequential deploy to staging then production
surql orchestrate deploy -e staging,production
# Rolling deploy in batches of 2
surql orchestrate deploy -e prod1,prod2,prod3,prod4 \
--strategy rolling --batch-size 2Full documentation at oneiriq.github.io/surql-py:
- SurrealDB v3 patterns -- the forms surql emits for v3 compatibility
- Query UX helpers -- typed wrappers for common SurrealQL calls
- Upgrade notes -- 1.3.1 -> 1.4.0 -> 1.5.0 -> 1.5.1
- Python 3.12+
- SurrealDB 1.0+ (integration CI runs against SurrealDB v3.0.5)
Apache License 2.0 - see LICENSE.
Looking for SurrealDB tooling in TypeScript? Check out surql -- a type-safe query builder and client for SurrealDB available on JSR and NPM.
- Documentation: oneiriq.github.io/surql-py
- Issues: GitHub Issues
- Changelog: CHANGES