FireProx Phase 1 Demo - Synchronous API¶
This notebook demonstrates all Phase 1 features of the FireProx synchronous API.
Prerequisites: Firestore emulator must be running on port 8080.
1. Setup and Initialization¶
from fire_prox import FireProx
from fire_prox.testing import demo_client
# Initialize native Firestore client (assumes emulator is running)
client = demo_client()
db = FireProx(client)
print("FireProx initialized successfully!")
FireProx initialized successfully!
2. Creating a New Document (DETACHED State)¶
# Get a collection reference
users = db.collection('users')
# Create a new document (not yet in Firestore)
user = users.new()
print(f"State: {user.state}")
print(f"Is detached: {user.is_detached()}")
print(f"Is dirty: {user.is_dirty()}")
State: DETACHED Is detached: True Is dirty: True
3. Setting Attributes on a DETACHED Document¶
# Set attributes using dot notation
user.name = 'Ada Lovelace'
user.year = 1815
user.occupation = 'Mathematician'
print(f"Name: {user.to_dict()['name']}")
print(f"Data: {user.to_dict()}")
print(f"Still detached: {user.is_detached()}")
Name: Ada Lovelace
Data: {'name': 'Ada Lovelace', 'year': 1815, 'occupation': 'Mathematician'}
Still detached: True
user.name
'Ada Lovelace'
4. Saving with Custom ID (DETACHED → LOADED)¶
# Save with a custom document ID
user.save(doc_id='alovelace')
print(f"State after save: {user.state}")
print(f"Is loaded: {user.is_loaded()}")
print(f"Is dirty: {user.is_dirty()}")
print(f"Document ID: {user.id}")
print(f"Document path: {user.path}")
State after save: LOADED Is loaded: True Is dirty: False Document ID: alovelace Document path: users/alovelace
5. Getting a Document by Path (ATTACHED State)¶
# Get a document reference (doesn't fetch data yet)
user2 = db.doc('users/alovelace')
print(f"State: {user2.state}")
print(f"Is attached: {user2.is_attached()}")
print(f"Document ID: {user2.id}")
print(f"Document path: {user2.path}")
print("Data not fetched yet!")
State: ATTACHED Is attached: True Document ID: alovelace Document path: users/alovelace Data not fetched yet!
6. Lazy Loading (ATTACHED → LOADED)¶
# Accessing an attribute automatically fetches data (lazy loading)
name = user2.name
print(f"Name: {name}")
print(f"State after access: {user2.state}")
print(f"Is loaded: {user2.is_loaded()}")
print(f"Full data: {user2.to_dict()}")
Name: Ada Lovelace
State after access: LOADED
Is loaded: True
Full data: {'occupation': 'Mathematician', 'year': 1815, 'name': 'Ada Lovelace'}
7. Explicit Fetch (Alternative to Lazy Loading)¶
# Create another reference
user3 = db.doc('users/alovelace')
print(f"Before fetch - State: {user3.state}")
# Explicitly fetch data
user3.fetch()
print(f"After fetch - State: {user3.state}")
print(f"Data: {user3.to_dict()}")
Before fetch - State: ATTACHED
After fetch - State: LOADED
Data: {'occupation': 'Mathematician', 'year': 1815, 'name': 'Ada Lovelace'}
8. Modifying a LOADED Document¶
# Modify attributes
user3.year = 1816
user3.contributions = ['Analytical Engine', 'First Algorithm']
print(f"Is dirty: {user3.is_dirty()}")
print(f"Modified year: {user3.year}")
print(f"Contributions: {user3.contributions}")
Is dirty: True Modified year: 1816 Contributions: ['Analytical Engine', 'First Algorithm']
9. Saving Updates¶
# Save the modifications
user3.save()
print(f"Is dirty after save: {user3.is_dirty()}")
print(f"State: {user3.state}")
print("Changes saved to Firestore!")
Is dirty after save: False State: LOADED Changes saved to Firestore!
10. Refreshing Data with force=True¶
# Fetch fresh data from Firestore (force refresh)
user3.fetch(force=True)
print(f"Refreshed data: {user3.to_dict()}")
print(f"Year after refresh: {user3.year}")
print(f"Contributions: {user3.contributions}")
Refreshed data: {'occupation': 'Mathematician', 'contributions': ['Analytical Engine', 'First Algorithm'], 'year': 1816, 'name': 'Ada Lovelace'}
Year after refresh: 1816
Contributions: ['Analytical Engine', 'First Algorithm']
11. Deleting Attributes¶
# Delete an attribute
del user3.contributions
print(f"Is dirty: {user3.is_dirty()}")
print(f"Data after delete: {user3.to_dict()}")
print("Attribute 'contributions' removed locally")
Is dirty: True
Data after delete: {'occupation': 'Mathematician', 'year': 1816, 'name': 'Ada Lovelace'}
Attribute 'contributions' removed locally
# Save to persist the deletion
user3.save()
user3.fetch(force=True)
print(f"Data after save: {user3.to_dict()}")
print("'contributions' field removed from Firestore")
Data after save: {'occupation': 'Mathematician', 'year': 1816, 'name': 'Ada Lovelace'}
'contributions' field removed from Firestore
12. Creating Document with Auto-Generated ID¶
# Create a new document without specifying ID
user4 = users.new()
user4.name = 'Grace Hopper'
user4.year = 1906
# Save without doc_id - Firestore generates ID
user4.save()
print(f"Auto-generated ID: {user4.id}")
print(f"Path: {user4.path}")
print(f"Data: {user4.to_dict()}")
Auto-generated ID: kONl81HwCSUxLvZg7Aft
Path: users/kONl81HwCSUxLvZg7Aft
Data: {'name': 'Grace Hopper', 'year': 1906}
13. Collection Properties¶
# Inspect collection properties
print(f"Collection ID: {users.id}")
print(f"Collection path: {users.path}")
print(f"String repr: {str(users)}")
print(f"Repr: {repr(users)}")
Collection ID: users Collection path: users String repr: FireCollection(users) Repr: <FireCollection path='users'>
14. Deleting a Document (LOADED → DELETED)¶
# Delete a document from Firestore
user4.delete()
print(f"State after delete: {user4.state}")
print(f"Is deleted: {user4.is_deleted()}")
print(f"ID still accessible: {user4.id}")
print(f"Path still accessible: {user4.path}")
State after delete: DELETED Is deleted: True ID still accessible: kONl81HwCSUxLvZg7Aft Path still accessible: users/kONl81HwCSUxLvZg7Aft
15. Error Handling - Invalid Operations on DELETED¶
# Attempting operations on DELETED document raises errors
try:
user4.save()
except RuntimeError as e:
print(f"Save error: {e}")
try:
user4.fetch()
except RuntimeError as e:
print(f"Fetch error: {e}")
Save error: Cannot save() on a DELETED FireObject Fetch error: Cannot fetch() on a DELETED FireObject
16. Hydration from Native Firestore Snapshot¶
# Use native Firestore API to get a snapshot
from fire_prox import FireObject
doc_ref = client.collection('users').document('alovelace')
snapshot = doc_ref.get()
# Hydrate to FireObject
user5 = FireObject.from_snapshot(snapshot)
print(f"State: {user5.state}")
print(f"Is loaded: {user5.is_loaded()}")
print(f"Data: {user5.to_dict()}")
print("Hydrated from native snapshot!")
State: LOADED
Is loaded: True
Data: {'occupation': 'Mathematician', 'year': 1816, 'name': 'Ada Lovelace'}
Hydrated from native snapshot!
17. Working with Nested Data¶
# Create document with nested data structures
user6 = users.new()
user6.name = 'Alan Turing'
user6.address = {
'city': 'London',
'country': 'UK'
}
user6.achievements = ['Turing Machine', 'Enigma', 'Turing Test']
user6.save(doc_id='aturing')
print(f"Nested data saved: {user6.to_dict()}")
Nested data saved: {'name': 'Alan Turing', 'address': {'city': 'London', 'country': 'UK'}, 'achievements': ['Turing Machine', 'Enigma', 'Turing Test']}
18. Accessing Nested Data¶
# Access nested fields
user7 = db.doc('users/aturing')
user7.fetch()
print(f"City: {user7.address['city']}")
print(f"First achievement: {user7.achievements[0]}")
print(f"All achievements: {user7.achievements}")
City: London First achievement: Turing Machine All achievements: ['Turing Machine', 'Enigma', 'Turing Test']
Summary¶
This demo covered all Phase 1 features:
✅ State Machine: DETACHED → ATTACHED → LOADED → DELETED
✅ Dynamic Attributes: Set/get/delete using dot notation
✅ Lazy Loading: Automatic fetch on attribute access (sync)
✅ Explicit Fetch: Manual data loading with fetch()
✅ Save Operations: Create with custom/auto ID, update existing
✅ Delete Operations: Remove documents from Firestore
✅ State Inspection: state, is_loaded(), is_dirty(), etc.
✅ Collection Interface: new(), doc(), properties
✅ Hydration: from_snapshot() for native query results
✅ Nested Data: Dictionaries and lists as attributes
✅ Error Handling: Clear messages for invalid operations
Next: See Phase 2 features (subcollections, queries, partial updates)