Lightweight Django-style ORM for SurrealDB using the official Python SDK. Async support with Pydantic validation.
Project description
Surreal ORM Lite
Surreal ORM Lite is a lightweight, Django-style ORM for SurrealDB that uses the official SurrealDB Python SDK. It provides a simple and intuitive interface for database operations with full async support and Pydantic validation.
Why This Project?
This ORM is designed to:
- Use the official SurrealDB SDK (
surrealdb>=1.0.8) for maximum compatibility - Stay lightweight with minimal dependencies
- Keep up-to-date with SurrealDB and SDK releases
- Provide Django-style query syntax that developers love
Requirements
| Dependency | Version |
|---|---|
| Python | 3.11+ |
| SurrealDB | >=2.6.x, <3.0 |
| Official SDK | surrealdb>=1.0.8 |
| Pydantic | >=2.12.5 |
Note: The official SurrealDB Python SDK (
surrealdb>=1.0.8) only supports SurrealDB 2.X. For SurrealDB 3.X support, use the full SurrealDB-ORM (v0.30.0+).
Installation
pip install surreal-orm-lite
Or with uv:
uv add surreal-orm-lite
Quick Start
1. Configure the Connection
from surreal_orm_lite import SurrealDBConnectionManager
SurrealDBConnectionManager.set_connection(
url="http://localhost:8000",
user="root",
password="root",
namespace="my_namespace",
database="my_database",
)
2. Define a Model
from surreal_orm_lite import BaseSurrealModel
from pydantic import Field
class User(BaseSurrealModel):
id: str | None = None
name: str = Field(..., max_length=100)
email: str
age: int = Field(..., ge=0)
3. CRUD Operations
# Create
user = User(name="Alice", email="alice@example.com", age=30)
await user.save()
# Read
user = await User.objects().get("alice_id")
users = await User.objects().filter(age__gte=18).exec()
# Update
user.age = 31
await user.update()
# Or partial update
await user.merge(age=31)
# Delete
await user.delete()
4. QuerySet Methods
# Filter with Django-style lookups
users = await User.objects().filter(
age__gte=18,
name__startswith="A"
).exec()
# Ordering (with -field shorthand for DESC)
users = await User.objects().order_by("name").exec()
users = await User.objects().order_by("-age", "name").exec()
# Pagination
users = await User.objects().limit(10).offset(20).exec()
# Select specific fields
results = await User.objects().select("name", "email").exec()
# Get first result
user = await User.objects().filter(name="Alice").first()
# Get all records
all_users = await User.objects().all()
# Custom query
results = await User.objects().query(
"SELECT * FROM User WHERE age > $min_age",
{"min_age": 21}
)
Features
| Feature | Status |
|---|---|
| Async/await support | ✅ |
| Pydantic validation | ✅ |
| CRUD operations | ✅ |
| QuerySet with filters | ✅ |
| Django-style lookups | ✅ |
| Custom primary keys | ✅ |
| HTTP connections | ✅ |
| WebSocket connections | ✅ |
| Aggregations | ✅ |
| GROUP BY | ✅ |
| Model Signals | ✅ |
| Raw SurrealQL queries | ✅ |
| Q Objects (OR/AND/NOT) | ✅ |
| Parameterized filters | ✅ |
| Bulk operations | ✅ |
-field ordering |
✅ |
| Relations & Graph | ✅ |
| FETCH clause | ✅ |
Supported Filter Lookups
exact(default)gt,gte,lt,ltein,not_incontains,not_containscontainsall,containsanystartswith,endswithlike,ilikematch,regexisnull
5. Q Objects (Complex Queries)
from surreal_orm_lite import Q
# OR queries
users = await User.objects().filter(Q(name="Alice") | Q(name="Bob")).exec()
# NOT queries
active = await User.objects().filter(~Q(status="banned")).exec()
# Complex combinations
results = await User.objects().filter(
Q(age__gte=18) & (Q(role="admin") | Q(role="mod"))
).exec()
# Mix Q objects with keyword filters
results = await User.objects().filter(
Q(role="admin") | Q(role="mod"),
age__gte=25
).exec()
6. Bulk Operations
# Bulk create
users = [User(name="Alice", age=30), User(name="Bob", age=25)]
created = await User.objects().bulk_create(users)
# Bulk update (returns count of updated records)
count = await User.objects().filter(status="pending").bulk_update(status="active")
# Bulk delete (returns count of deleted records)
count = await User.objects().filter(status="inactive").bulk_delete()
7. Relations & Graph
# Create a relation
await user.relate("follows", other_user)
# With data on the edge
await user.relate("purchased", product, data={"quantity": 2, "price": 29.99})
# Get related records (outgoing)
following = await user.get_related("follows", direction="out", model_class=User)
# Get related records (incoming)
followers = await user.get_related("follows", direction="in", model_class=User)
# Remove a specific relation
await user.remove_relation("follows", other_user)
# Remove all outgoing relations of a type
await user.remove_all_relations("follows", direction="out")
# Graph traversal
friends_of_friends = await user.traverse("->follows->User->follows->User")
8. FETCH Clause
# Resolve record links inline (prevents N+1 queries)
posts = await Post.objects().fetch("author", "tags").exec()
# Generates: SELECT * FROM Post FETCH author, tags;
9. Aggregations
from surreal_orm_lite import Count, Sum, Avg, Min, Max
# Simple aggregations
count = await User.objects().count()
total = await Order.objects().sum("amount")
avg_age = await User.objects().avg("age")
max_price = await Product.objects().max("price")
min_price = await Product.objects().min("price")
# Check existence
has_admins = await User.objects().filter(role="admin").exists()
# GROUP BY with annotations
results = await User.objects().values("status").annotate(count=Count()).exec()
# [{"status": "active", "count": 42}, {"status": "inactive", "count": 8}]
# Raw SurrealQL queries
results = await User.raw_query(
"SELECT * FROM User WHERE age > $min_age",
variables={"min_age": 18}
)
10. Model Signals
from surreal_orm_lite import pre_save, post_save, pre_delete, post_delete
@post_save.connect(User)
async def on_user_saved(sender, instance, created, **kwargs):
"""Called after every User save."""
if created:
await send_welcome_email(instance.email)
await invalidate_cache(f"user:{instance.id}")
@pre_delete.connect(User)
async def on_user_deleting(sender, instance, **kwargs):
"""Called before User deletion."""
await archive_user_data(instance.id)
Available signals:
| Signal | When | Extra kwargs |
|---|---|---|
pre_save |
Before save() |
|
post_save |
After save() |
created |
pre_update |
Before update()/merge() |
update_fields |
post_update |
After update()/merge() |
update_fields |
pre_delete |
Before delete() |
|
post_delete |
After delete() |
|
around_save |
Wraps save() |
|
around_update |
Wraps update()/merge() |
update_fields |
around_delete |
Wraps delete() |
Around signals use async generators to wrap operations:
from surreal_orm_lite import around_save
@around_save.connect(User)
async def time_user_save(sender, instance, **kwargs):
import time
start = time.time()
yield # save() executes here
duration = time.time() - start
print(f"Save took {duration:.3f}s")
Configuration Options
Custom Primary Key
from surreal_orm_lite import BaseSurrealModel, SurrealConfigDict
class Product(BaseSurrealModel):
model_config = SurrealConfigDict(primary_key="sku")
sku: str
name: str
price: float
Context Manager
async with SurrealDBConnectionManager():
users = await User.objects().all()
# Connection automatically closed
Compatibility
This ORM is tested and compatible with SurrealDB 2.X only (SDK limitation). For SurrealDB 3.X, use SurrealDB-ORM v0.30.0+.
| SurrealDB Version | SDK Version | Status |
|---|---|---|
| 2.6.3 | 1.0.8 | ✅ Tested |
| 2.6.x | 1.0.8 | ✅ Compatible |
| 2.5.x | 1.0.8 | ✅ Compatible |
| 3.x | — | ❌ Not supported |
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m "Add amazing feature") - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Roadmap
| Version | Theme | Status |
|---|---|---|
| v0.2.x | Core ORM (CRUD, QuerySet) | ✅ Released |
| v0.3.0 | Aggregations & Utilities | ✅ Released |
| v0.4.0 | Model Signals | ✅ Released |
| v0.5.0 | Bulk Operations & Q Objects | ✅ Released |
| v0.6.0 | Relations & Graph | ✅ Released |
| v0.7.0 | Transactions ORM | 📋 Planned |
| v0.8.0 | SurrealFunc & Computed Fields | 📋 Planned |
| v0.9.0 | Field Aliases & DX | 📋 Planned |
| v1.0.0 | Production Ready | 📋 Planned |
See docs/ROADMAP.md for full details.
SurrealDB-ORM-lite vs SurrealDB-ORM
This project prioritizes stability and compatibility with the official SurrealDB Python SDK. The full SurrealDB-ORM uses a custom SDK for advanced features.
| Feature | ORM-lite (official SDK) | ORM (custom SDK) |
|---|---|---|
| CRUD & QuerySet | ✅ | ✅ |
| Aggregations & GROUP BY | ✅ | ✅ |
| Model Signals | ✅ | ✅ |
| Bulk Operations | ✅ | ✅ |
| Q Objects (OR/AND/NOT) | ✅ | ✅ |
| Parameterized Filters | ✅ | ✅ |
| Relations & Graph | ✅ | ✅ |
| FETCH clause | ✅ | ✅ |
| Transactions (tx=) | v0.7.0 | ✅ |
| SurrealFunc & Computed | v0.8.0 | ✅ |
| Field Aliases | v0.9.0 | ✅ |
| Retry, Logging, Metrics | v0.10.0 | ✅ |
| Live Models / CDC | ❌ | ✅ |
| Vector / Full-Text Search | ❌ | ✅ |
| Hybrid Search (RRF) | ❌ | ✅ |
| Migrations & CLI | ❌ | ✅ |
| JWT Authentication | ❌ | ✅ |
| Schema Introspection | ❌ | ✅ |
| Connection Pool | ❌ | ✅ |
| CBOR Protocol | ❌ | ✅ |
| Subqueries & Query Cache | ❌ | ✅ |
| Geospatial Fields | ❌ | ✅ |
| DEFINE EVENT | ❌ | ✅ |
| Test Fixtures & Factories | ❌ | ✅ |
| Atomic Array Operations | ❌ | ✅ |
Choose ORM-lite if you want the official SDK, minimal dependencies, and core ORM features.
Choose ORM if you need live queries, migrations, authentication, vector search, or advanced features.
- SurrealDB-ORM GitHub: github.com/EulogySnowfall/SurrealDB-ORM
- SurrealDB-ORM PyPI: surrealdb-orm
License
MIT License - see LICENSE for details.
Author
Yannick Croteau GitHub: @EulogySnowfall
Related Projects
- SurrealDB - The database
- surrealdb.py - Official Python SDK
- SurrealDB-ORM - Full-featured ORM with custom SDK
Project details
Release history Release notifications | RSS feed
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 surreal_orm_lite-0.6.8.tar.gz.
File metadata
- Download URL: surreal_orm_lite-0.6.8.tar.gz
- Upload date:
- Size: 28.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
917821879b75f8bb2e68bcee6272ecfac2460373ab5531601397fe85e0709680
|
|
| MD5 |
d3128cfc19b4d48ba2d40bc3ce81b605
|
|
| BLAKE2b-256 |
42aed4d9b411d3b13c09dccf16c4ebaef863db6bc18401a58618ff2f1e245531
|
Provenance
The following attestation bundles were made for surreal_orm_lite-0.6.8.tar.gz:
Publisher:
publish.yml on EulogySnowfall/SurrealDB-ORM-lite
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
surreal_orm_lite-0.6.8.tar.gz -
Subject digest:
917821879b75f8bb2e68bcee6272ecfac2460373ab5531601397fe85e0709680 - Sigstore transparency entry: 1209306738
- Sigstore integration time:
-
Permalink:
EulogySnowfall/SurrealDB-ORM-lite@7e1e947224bf5c32823a631989cba7be7af8775a -
Branch / Tag:
refs/tags/v0.6.8 - Owner: https://github.com/EulogySnowfall
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7e1e947224bf5c32823a631989cba7be7af8775a -
Trigger Event:
push
-
Statement type:
File details
Details for the file surreal_orm_lite-0.6.8-py3-none-any.whl.
File metadata
- Download URL: surreal_orm_lite-0.6.8-py3-none-any.whl
- Upload date:
- Size: 28.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
62d2f27c018400a77b8fe567d9f78978abab6c38de9e7006ed85572f6c3e56c5
|
|
| MD5 |
611ad108c1c93761f29992b824ab4d49
|
|
| BLAKE2b-256 |
726e02c69f50f55eed5a6d1cee32e8625334a4b30655a3759d1178be3900e9c2
|
Provenance
The following attestation bundles were made for surreal_orm_lite-0.6.8-py3-none-any.whl:
Publisher:
publish.yml on EulogySnowfall/SurrealDB-ORM-lite
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
surreal_orm_lite-0.6.8-py3-none-any.whl -
Subject digest:
62d2f27c018400a77b8fe567d9f78978abab6c38de9e7006ed85572f6c3e56c5 - Sigstore transparency entry: 1209306769
- Sigstore integration time:
-
Permalink:
EulogySnowfall/SurrealDB-ORM-lite@7e1e947224bf5c32823a631989cba7be7af8775a -
Branch / Tag:
refs/tags/v0.6.8 - Owner: https://github.com/EulogySnowfall
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7e1e947224bf5c32823a631989cba7be7af8775a -
Trigger Event:
push
-
Statement type: