Sync vs Non-Sync
Observant provides two modes of operation for ObservableProxy: sync mode and non-sync mode. This page explains the differences between these modes and when to use each.
Overview
When you create an ObservableProxy, you can specify whether changes to the proxy should be immediately synchronized with the underlying model:
sync=True: Changes to the proxy are immediately applied to the underlying modelsync=False: Changes to the proxy are only applied to the underlying model when you callsave_to()
from dataclasses import dataclass
from observant import ObservableProxy
@dataclass
class User:
name: str
age: int
# Create a user
user = User(name="Alice", age=30)
# Create a proxy with sync=True
proxy_sync = ObservableProxy(user, sync=True)
# Create a proxy with sync=False
proxy_non_sync = ObservableProxy(user, sync=False)
sync=True vs sync=False
sync=True
When sync=True, any changes you make to the proxy are immediately applied to the underlying model:
# Create a user and proxy with sync=True
user = User(name="Alice", age=30)
proxy = ObservableProxy(user, sync=True)
# Change a field
proxy.observable(str, "name").set("Bob")
# The change is immediately applied to the user object
print(user.name) # "Bob"
This mode is useful when:
- You want to keep the model and proxy in sync at all times
- You're working with a model that needs to reflect changes immediately
- You don't need to validate or review changes before applying them
sync=False
When sync=False, changes to the proxy are not automatically applied to the underlying model. You need to explicitly call save_to() to apply the changes:
# Create a user and proxy with sync=False
user = User(name="Alice", age=30)
proxy = ObservableProxy(user, sync=False)
# Change a field
proxy.observable(str, "name").set("Bob")
# The change is not applied to the user object yet
print(user.name) # "Alice"
# Apply the changes
proxy.save_to(user)
# Now the change is applied
print(user.name) # "Bob"
This mode is useful when:
- You want to validate changes before applying them
- You need to support undo/redo functionality
- You want to track dirty state
- You need to review or confirm changes before saving
When to Use Each
Use sync=True When
- You need immediate synchronization between the proxy and the model
- You're working with a model that needs to reflect changes in real-time
- You don't need undo/redo functionality
- You don't need to track dirty state
Use sync=False When
- You need to validate changes before applying them
- You want to support undo/redo functionality
- You want to track dirty state
- You need to review or confirm changes before saving
- You want to optimize performance by batching updates
save_to() and load_dict()
When using sync=False, you need to explicitly apply changes to the model using save_to():
# Create a user and proxy with sync=False
user = User(name="Alice", age=30)
proxy = ObservableProxy(user, sync=False)
# Make changes
proxy.observable(str, "name").set("Bob")
proxy.observable(int, "age").set(31)
# Validate changes
if proxy.is_valid():
# Apply changes
proxy.save_to(user)
else:
# Show validation errors
print("Validation errors:", proxy.validation_errors())
You can also load data into the proxy from a dictionary using load_dict():
# Load data from a dictionary
proxy.load_dict({
"name": "Charlie",
"age": 32
})
# The changes are not applied to the user object yet
print(user.name) # "Bob" (or "Alice" if save_to() wasn't called)
# Apply the changes
proxy.save_to(user)
# Now the changes are applied
print(user.name) # "Charlie"
update() vs load_dict()
Observant provides two methods for updating multiple fields at once: update() and load_dict(). The main difference is that update() only updates the fields that are provided, while load_dict() updates all fields in the dictionary.
update()
The update() method updates only the fields that are provided in the dictionary:
# Create a user and proxy
user = User(name="Alice", age=30)
proxy = ObservableProxy(user, sync=False)
# Update specific fields
proxy.update({
"name": "Bob"
})
# Only the specified fields are updated
print(proxy.observable(str, "name").get()) # "Bob"
print(proxy.observable(int, "age").get()) # 30 (unchanged)
load_dict()
The load_dict() method updates all fields in the dictionary, and can optionally reset fields that are not in the dictionary:
# Create a user and proxy
user = User(name="Alice", age=30)
proxy = ObservableProxy(user, sync=False)
# Load a dictionary
proxy.load_dict({
"name": "Bob"
}, reset_missing=False)
# Only the specified fields are updated
print(proxy.observable(str, "name").get()) # "Bob"
print(proxy.observable(int, "age").get()) # 30 (unchanged)
# Load a dictionary with reset_missing=True
proxy.load_dict({
"name": "Charlie"
}, reset_missing=True)
# Fields not in the dictionary are reset to their default values
print(proxy.observable(str, "name").get()) # "Charlie"
print(proxy.observable(int, "age").get()) # 0 (reset to default)
Performance Considerations
The choice between sync=True and sync=False can affect performance:
sync=Truemay be slower if you're making many changes, since each change triggers an update to the modelsync=Falsemay be faster for bulk updates, since you can batch changes and apply them all at once
# sync=True: Each change triggers an update
proxy_sync = ObservableProxy(user, sync=True)
for i in range(1000):
proxy_sync.observable(int, "age").set(i) # 1000 updates to the model
# sync=False: Changes are batched
proxy_non_sync = ObservableProxy(user, sync=False)
for i in range(1000):
proxy_non_sync.observable(int, "age").set(i) # No updates to the model yet
proxy_non_sync.save_to(user) # 1 update to the model
Sync and Undo
The sync and undo options can be used together, but this can lead to unexpected behavior. When sync=True, changes are immediately applied to the underlying model, which means that undo operations will not affect the model until you call save_to.
user = User(name="Alice", age=30)
proxy = ObservableProxy(user, sync=True, undo=True)
# Make a change
proxy.observable(str, "name").set("Bob")
print(user.name) # "Bob" (sync=True applies changes immediately)
# Undo the change
proxy.undo("name")
print(proxy.observable(str, "name").get()) # "Alice"
print(user.name) # Still "Bob" until save_to is called
# Save changes back to the model
proxy.save_to(user)
print(user.name) # Now "Alice"
For this reason, it's generally recommended to use sync=False when using undo functionality.
Next Steps
Now that you understand the difference between sync and non-sync modes in Observant, you might want to explore:
- Saving and Loading: Learn more about saving changes and loading data
- Dirty Tracking: Track unsaved changes
- Undo and Redo: Implement undo/redo functionality