Document Models¶
SurrealEngine provides a powerful Document class that serves as the base for all your data models. Document models map to SurrealDB tables and provide field validation, type conversion, and database operations.
Defining Documents¶
Basic Document Definition¶
from surrealengine import Document, StringField, IntField, DateTimeField
class User(Document):
name = StringField(required=True, max_length=100)
email = StringField(required=True)
age = IntField(min_value=0, max_value=150)
created_at = DateTimeField(auto_now_add=True)
class Meta:
collection = "users"
schemafull = True
Meta Options¶
Configure document behavior with the Meta class:
class User(Document):
name = StringField(required=True)
class Meta:
collection = "users" # Table name
schemafull = True # Enforce schema
drop = False # Drop table on migration
permissions = "FULL" # Table permissions
Creating and Saving Documents¶
Async Operations¶
# Create and save
user = User(name="Alice", email="alice@example.com", age=30)
await user.save()
print(f"Created user: {user.id}")
# Create with custom ID
user = User(id="user:alice", name="Alice", email="alice@example.com")
await user.save()
# Save updates
user.age = 31
await user.save()
Sync Operations (v0.4.1+)¶
All async operations now have synchronous equivalents:
# Create and save synchronously
user = User(name="Bob", email="bob@example.com", age=25)
user.save_sync()
print(f"Created user: {user.id}")
# Save updates synchronously
user.age = 26
user.save_sync()
Updating Documents¶
Partial Updates (v0.4.1+)¶
Update specific fields without loading the entire document:
# Async partial update
user = await User.objects.get(id="user:alice")
await user.update(age=32, email="newemail@example.com")
# Sync partial update
user = User.objects.get_sync(id="user:bob")
user.update_sync(age=27)
The update() and update_sync() methods:
Only modify specified fields
Don’t overwrite other fields
Automatically refresh the instance with latest data
More efficient than loading full document and saving
Full Document Save¶
# Load document
user = await User.objects.get(id="user:alice")
# Modify multiple fields
user.name = "Alice Smith"
user.age = 33
user.email = "alice.smith@example.com"
# Save all changes
await user.save()
Refreshing Documents¶
Reload document data from the database:
Async Refresh¶
user = await User.objects.get(id="user:alice")
# ... some time passes, data might have changed ...
await user.refresh()
print(f"Current age: {user.age}")
Sync Refresh (v0.4.1+)¶
user = User.objects.get_sync(id="user:alice")
# ... some time passes ...
user.refresh_sync()
print(f"Current age: {user.age}")
Deleting Documents¶
Async Deletion¶
user = await User.objects.get(id="user:alice")
await user.delete()
Sync Deletion (v0.4.1+)¶
user = User.objects.get_sync(id="user:bob")
user.delete_sync()
Document Validation¶
Documents are automatically validated before saving:
from surrealengine.exceptions import ValidationError
try:
# This will fail - name is required
user = User(email="test@example.com")
await user.save()
except ValidationError as e:
print(f"Validation error: {e}")
try:
# This will fail - age exceeds max_value
user = User(name="Test", age=200)
await user.save()
except ValidationError as e:
print(f"Validation error: {e}")
Default Values¶
Set default values for fields:
from datetime import datetime
class Post(Document):
title = StringField(required=True)
content = StringField()
status = StringField(default="draft")
created_at = DateTimeField(default=datetime.now)
views = IntField(default=0)
class Meta:
collection = "posts"
# Default values are applied automatically
post = Post(title="My Post", content="Hello world")
await post.save()
print(post.status) # "draft"
print(post.views) # 0
Auto-Generated Fields¶
Some fields can auto-populate:
class Article(Document):
title = StringField(required=True)
created_at = DateTimeField(auto_now_add=True) # Set on creation
updated_at = DateTimeField(auto_now=True) # Update on each save
class Meta:
collection = "articles"
article = Article(title="New Article")
await article.save()
print(article.created_at) # Automatically set
article.title = "Updated Article"
await article.save()
print(article.updated_at) # Automatically updated
Relation Documents¶
For graph edge relationships:
from surrealengine import RelationDocument, ReferenceField
class Follows(RelationDocument):
since = DateTimeField(auto_now_add=True)
class Meta:
collection = "follows"
in_doc = "User"
out_doc = "User"
# Create relationship
follows = Follows(
in_doc="user:alice",
out_doc="user:bob",
)
await follows.save()
# Update relationship (v0.4.1+)
await follows.update(since=datetime.now())
follows.update_sync(since=datetime.now())
Document Lifecycle¶
Document operations follow this lifecycle:
Instantiation: Create document instance
Validation: Fields are validated
Pre-save: Signals fired (if enabled)
Save/Update: Data persisted to database
Post-save: Signals fired (if enabled)
Refresh: Reload from database if needed
Delete: Remove from database
# 1. Create
user = User(name="Charlie", email="charlie@example.com")
# 2. Validation happens here
await user.save() # 3-5: Pre-save, save, post-save
# 6. Refresh if needed
await user.refresh()
# 7. Delete
await user.delete()
API Parity¶
Complete sync/async parity (v0.4.1+):
Operation |
Async |
Sync |
|---|---|---|
Save document |
|
|
Update fields |
|
|
Refresh from DB |
|
|
Delete document |
|
|
Get by ID |
|
|
Filter query |
|
|
Best Practices¶
Use update() for partial updates:
# Good - only updates specified fields await user.update(age=30) # Less efficient - loads and saves entire document user = await User.objects.get(id=user_id) user.age = 30 await user.save()
Choose sync or async consistently:
# Good - all async user = await User.objects.get(id=user_id) await user.update(age=30) await user.refresh() # Good - all sync user = User.objects.get_sync(id=user_id) user.update_sync(age=30) user.refresh_sync() # Avoid mixing - may cause issues user = await User.objects.get(id=user_id) user.update_sync(age=30) # Mixed sync/async
Validate before saving:
user = User(name="Test") try: await user.save() except ValidationError as e: print(f"Fix these errors: {e}")
Use auto fields for timestamps:
class Document: created_at = DateTimeField(auto_now_add=True) updated_at = DateTimeField(auto_now=True)
Set defaults in field definition:
status = StringField(default="draft") # Better # Instead of setting in __init__
Migration Guide¶
If upgrading from versions before v0.4.1, you can now use sync methods:
# OLD - only async available
user = await User.objects.get(id=user_id)
user.age = 30
await user.save()
# NEW - sync methods available (v0.4.1+)
user = User.objects.get_sync(id=user_id)
user.update_sync(age=30)
# NEW - more efficient partial updates (v0.4.1+)
await user.update(age=30) # Only updates age field