Getting Started
This guide will help you get started with Observant, a reactive state management library for Python.
Installation
Install Observant using pip:
Basic Concepts
Before diving into code examples, let's understand the core concepts of Observant.
Observable
An Observable is a wrapper around a value that notifies listeners when the value changes. It's the simplest building block in Observant.
from observant import Observable
# Create an observable with an initial value
name = Observable[str]("Alice")
# Register a callback to be notified when the value changes
name.on_change(lambda value: print(f"Name changed to: {value}"))
# Change the value
name.set("Bob") # Prints: "Name changed to: Bob"
# Get the current value
current_name = name.get() # Returns: "Bob"
ObservableList and ObservableDict
Observant also provides observable collections that notify listeners when items are added, removed, or updated.
from observant import ObservableList, ObservableDict
# Observable list
tasks = ObservableList[str](["Task 1"])
tasks.on_change(lambda change: print(f"Tasks changed: {change}"))
tasks.append("Task 2") # Notifies listeners
# Observable dictionary
settings = ObservableDict[str, str]({"theme": "dark"})
settings.on_change(lambda change: print(f"Settings changed: {change}"))
settings["language"] = "en" # Notifies listeners
ObservableProxy
The ObservableProxy is the most powerful component in Observant. It wraps an object (typically a dataclass) and provides observable access to its fields.
from dataclasses import dataclass
from observant import ObservableProxy
@dataclass
class User:
name: str
age: int
email: str
# Create a user and wrap it with a proxy
user = User(name="Alice", age=30, email="alice@example.com")
proxy = ObservableProxy(user)
# Get observables for individual fields
name_obs = proxy.observable(str, "name")
age_obs = proxy.observable(int, "age")
# Register change listeners
name_obs.on_change(lambda value: print(f"Name changed to: {value}"))
age_obs.on_change(lambda value: print(f"Age changed to: {value}"))
# Update fields
name_obs.set("Alicia") # Prints: "Name changed to: Alicia"
age_obs.set(31) # Prints: "Age changed to: 31"
# Save changes back to the original object
proxy.save_to(user)
print(user.name) # Prints: "Alicia"
print(user.age) # Prints: 31
Minimal Example
Here's a complete example showing how to use Observant with a simple form:
from dataclasses import dataclass
from observant import ObservableProxy
@dataclass
class LoginForm:
username: str
password: str
remember_me: bool
# Create a form and proxy
form = LoginForm(username="", password="", remember_me=False)
proxy = ObservableProxy(form)
# Add validation
proxy.add_validator("username", lambda v: "Username required" if not v else None)
proxy.add_validator("password", lambda v: "Password too short" if len(v) < 8 else None)
# Track changes
proxy.observable(str, "username").on_change(lambda v: print(f"Username: {v}"))
proxy.observable(str, "password").on_change(lambda v: print(f"Password: {'*' * len(v)}"))
proxy.observable(bool, "remember_me").on_change(lambda v: print(f"Remember me: {v}"))
# Update fields
proxy.observable(str, "username").set("alice")
proxy.observable(str, "password").set("securepassword")
proxy.observable(bool, "remember_me").set(True)
# Check validation
if proxy.is_valid():
print("Form is valid!")
proxy.save_to(form)
else:
print("Validation errors:", proxy.validation_errors())
Anatomy of a Proxy
The ObservableProxy is the central component of Observant. Here's what it provides:
-
Field Observables: Access individual fields as observables
-
Collection Observables: Access lists and dictionaries as observable collections
-
Validation: Add validators to fields and check validation state
-
Computed Properties: Define properties that depend on other fields
-
Undo/Redo: Track changes and undo/redo them
-
Dirty Tracking: Track unsaved changes
-
Saving and Loading: Save changes back to the model or load from a dictionary
-
Nested Paths: Access deeply nested properties with dot notation
Next Steps
Now that you understand the basics, you can explore more advanced features:
- Change Tracking: Learn more about observables and change notifications
- Validation: Add validation to your models
- Computed Properties: Create properties that depend on other fields
- Undo and Redo: Implement undo/redo functionality
- Dirty Tracking: Track unsaved changes
- Sync vs Non-Sync: Understand immediate vs. deferred updates
- Saving and Loading: Save changes and load data
- Nested Paths: Access deeply nested properties with optional chaining