fire-prox
Prototyping focused acceleration layer for Firestore
Installation
pip install fire-prox
Project Overview
Fire-Prox is a schemaless, state-aware proxy library for Google Cloud Firestore designed to accelerate rapid prototyping. Unlike traditional Object-Document Mappers (ODMs), Fire-Prox embraces dynamic, schema-free development by providing an intuitive, Pythonic interface that reduces boilerplate and aligns with object-oriented programming patterns.
Key Philosophy: Fire-Prox is an "anti-ODM" for the prototyping stage, wrapping (not replacing) the official google-cloud-firestore library to provide a higher-level abstraction optimized for developer velocity during rapid iteration.
Features
Fire-Prox provides a comprehensive feature set for rapid Firestore prototyping, wrapping the native client with an intuitive, Pythonic interface.
Core Architecture
State Machine
Fire-Prox manages documents through a four-state lifecycle that enables lazy loading and safe state management:
# DETACHED - New document, not yet in Firestore
user = users.new()
user.name = 'Ada Lovelace'
# ATTACHED - Linked to Firestore path, but data not loaded
user = db.doc('users/ada')
# LOADED - Full data loaded from Firestore
user.fetch()
print(user.name)
# DELETED - Marked as deleted
user.delete()
Check state anytime: user.is_detached(), user.is_loaded(), user.is_deleted().
Dual API (Sync & Async)
Complete synchronous and asynchronous implementations with shared base classes:
# Synchronous API
from fireprox import FireProx
db = FireProx(firestore.Client())
user = db.doc('users/ada')
user.fetch()
user.score += 10
user.save()
# Asynchronous API
from fireprox import AsyncFireProx
async_db = AsyncFireProx(firestore.AsyncClient())
user = async_db.doc('users/ada')
await user.fetch()
user.score += 10
await user.save()
Demo: Phase 1 Sync | Phase 1 Async
Dynamic Schema
Work without predefined schemas or models - pure Python object manipulation:
# No schema definition needed
user = users.new()
user.name = 'Ada'
user.birth_year = 1815
user.tags = ['mathematician', 'programmer']
user.metadata = {'verified': True}
user.save() # All fields saved automatically
Data Operations
Field-Level Dirty Tracking
Fire-Prox tracks exactly which fields changed, enabling efficient partial updates:
user = db.doc('users/ada')
user.fetch()
user.email = 'ada@example.com'
user.bio = 'Pioneer programmer'
print(user.dirty_fields) # {'email', 'bio'}
user.save() # Only 2 fields sent to Firestore, not entire document
Performance: 50-90% bandwidth reduction for typical updates. Lower Firestore costs.
Demo: Phase 2 Features
Atomic Operations
Concurrency-safe operations that execute server-side without read-modify-write conflicts:
# Increment counters safely across multiple clients
post.increment('views', 1)
post.increment('likes', 1)
# Array operations with automatic deduplication
user.array_union('tags', ['python', 'firestore', 'python']) # No duplicates
user.array_remove('tags', ['deprecated'])
# Combine multiple atomic operations
post.increment('comment_count', 1)
post.array_union('commenters', [user_id])
post.save() # All operations atomic
Use Cases: View counters, like buttons, inventory tracking, tag management.
Demo: Phase 2 Features
Subcollections
Organize data hierarchically with nested collections:
# Create subcollection under a document
user = db.doc('users/ada')
posts = user.collection('posts')
post = posts.new()
post.title = 'On the Analytical Engine'
post.year = 1843
post.save() # Saved to users/ada/posts/{auto-id}
# Nested subcollections (unlimited depth)
comment = post.collection('comments').new()
comment.text = 'Brilliant!'
comment.save() # users/ada/posts/{id}/comments/{id}
# Inspect all subcollections beneath the document
user.collections(names_only=True) # ['posts']
db.collections('users/ada') # [FireCollection('users/ada/posts')]
Demo: Phase 2 Features
Collection Cleanup
Remove prototype data quickly without manual scripts:
# Drop every document in a collection (and nested subcollections)
users = db.collection('users')
summary = users.delete_all(batch_size=100, recursive=True)
print(summary) # {'documents': 42, 'collections': 7}
# Target a single subcollection beneath a document
user = db.doc('users/alovelace')
posts = user.delete_subcollection('posts')
print(posts)
# Delete document and all descendants (default behaviour)
user.delete()
# Skip recursion if you only want to remove the parent document
user = db.doc('users/gracehopper')
user.delete(recursive=False)
Demo: Collection Deletion Helpers
Document References
Automatic conversion between FireObjects and DocumentReferences with lazy loading:
# Assign FireObjects directly - auto-converts to DocumentReference
post.author = user # user is a FireObject
post.reviewers = [user1, user2, user3] # Works in lists
post.contributors = {'lead': user1, 'editor': user2} # And dicts
post.save()
# Read back - references auto-hydrate to FireObjects
post = db.doc('posts/article1')
post.fetch()
# Lazy loading on first access
print(post.author.name) # Automatically fetches author document
for reviewer in post.reviewers:
print(reviewer.name) # Each loads on-demand
Features: Object identity (same reference = same object), nested support, type safety.
Demo: Document References
Batch Operations
Atomic multi-document writes for efficient bulk operations:
batch = db.batch()
# Accumulate operations (up to 500)
for user_id in user_ids:
user = db.doc(f'users/{user_id}')
user.fetch()
user.status = 'active'
user.credits = 50
user.save(batch=batch) # Queued, not executed yet
# Commit all atomically in single request
batch.commit() # All succeed or all fail
Performance: 100x faster for 100 documents (1 round trip vs 100).
Demo: Batch Operations
Querying & Analytics
Query Builder
Chainable, intuitive query interface returning hydrated FireObjects:
# Simple filtering
active_users = users.where('active', '==', True).get()
# Complex queries
top_scorers = (users
.where('country', '==', 'England')
.where('birth_year', '>', 1800)
.order_by('score', direction='DESCENDING')
.limit(10)
.get())
for user in top_scorers:
print(f"{user.name}: {user.score}") # Fully loaded FireObjects
Features: All Firestore operators, multiple orderings, immutable query pattern.
Demo: Phase 2.5 Query Builder
Pagination
Cursor-based navigation for efficient large dataset traversal:
# Get first page
page1 = users.order_by('created_at').limit(20).get()
# Get next page using cursor
last_created = page1[-1].created_at
page2 = users.order_by('created_at').start_after({'created_at': last_created}).limit(20).get()
# Previous page
first_created = page2[0].created_at
page1_again = users.order_by('created_at').end_before({'created_at': first_created}).limit(20).get()
Methods: start_at(), start_after(), end_at(), end_before().
Demo: Pagination Patterns
Projections
Field-level query optimization for massive bandwidth savings:
# Select only needed fields
names_only = users.select('name').get()
# Returns: [{'name': 'Ada'}, {'name': 'Grace'}, ...]
# Combine with filtering and sorting
high_earners = (users
.where('salary', '>', 100000)
.select('name', 'salary', 'department')
.order_by('salary', direction='DESCENDING')
.limit(10)
.get())
# Returns vanilla dicts, not FireObjects (by design)
for emp in high_earners:
print(f"{emp['name']}: ${emp['salary']:,}")
Performance: 50-95% bandwidth reduction for large documents.
Demo: Field Projections
Aggregations
Server-side analytics without document transfer - dramatically faster and cheaper:
from fire_prox import Count, Sum, Avg
# Simple aggregations
total_users = users.count()
total_revenue = orders.sum('amount')
avg_rating = products.avg('rating')
# With filtering
active_users = users.where('active', '==', True).count()
dept_payroll = employees.where('dept', '==', 'Engineering').sum('salary')
# Multiple aggregations in one query
stats = employees.aggregate(
total=Count(),
payroll=Sum('salary'),
avg_salary=Avg('salary'),
avg_age=Avg('age')
)
print(f"Team: {stats['total']}, Payroll: ${stats['payroll']:,}, Avg: ${stats['avg_salary']:,}")
Performance: 2-10x faster than client-side calculations. Only aggregated results transferred, not documents.
Demo: Aggregations
Advanced Features
Transactions
ACID transactions with decorator pattern and automatic retry:
from google.cloud import firestore
transaction = db.transaction()
@firestore.transactional
def transfer_money(transaction, from_id, to_id, amount):
from_user = db.doc(f'users/{from_id}')
to_user = db.doc(f'users/{to_id}')
# Transactional reads
from_user.fetch(transaction=transaction)
to_user.fetch(transaction=transaction)
# Modify and save
from_user.balance -= amount
to_user.balance += amount
from_user.save(transaction=transaction)
to_user.save(transaction=transaction)
# Automatically retries on conflicts
transfer_money(transaction, 'alice', 'bob', 100)
Features: Read-modify-write atomicity, automatic retry, works with atomic operations.
Demo: Transactions
Real-time Listeners
Live updates via on_snapshot() for reactive applications:
# Sync callback
def on_user_change(docs, changes, read_time):
for doc in docs:
print(f"User updated: {doc.name}")
unsubscribe = users.where('active', '==', True).on_snapshot(on_user_change)
# Async handler
async def on_user_change_async(docs, changes, read_time):
for doc in docs:
print(f"User updated: {doc.name}")
await process_update(doc)
unsubscribe = await users.on_snapshot(on_user_change_async)
# Stop listening
unsubscribe()
Features: Document and collection listeners, sync and async handlers, change types (added/modified/removed).
Demo: Real-time Listeners
Vector Embeddings
Semantic search integration with Firestore's vector capabilities:
from fire_prox import FireVector
# Store embeddings
doc.embedding = FireVector([0.1, 0.2, 0.3, ...]) # Your embedding vector
doc.save()
# Vector search (using native Firestore vector search)
# See demo notebook for full examples with actual embedding models
Use Cases: Semantic search, recommendation systems, similarity matching.
Demo: Vector Embeddings
Native Query Integration
Seamlessly mix Fire-Prox with native Firestore API when needed:
from google.cloud.firestore_v1.base_query import Or, FieldFilter
# Complex query with native API
native_query = client.collection('users').where(
filter=Or([
FieldFilter('country', '==', 'England'),
FieldFilter('country', '==', 'USA')
])
)
# Hydrate results to FireObjects
users = [FireObject.from_snapshot(snap) for snap in native_query.stream()]
# Now use Fire-Prox features on these objects
for user in users:
user.last_accessed = firestore.SERVER_TIMESTAMP
user.save() # Partial updates, dirty tracking, etc.
Philosophy: Fire-Prox wraps, not replaces. Use native API when needed, Fire-Prox when convenient.
Additional Features
Timestamp Handling: Automatic timezone support and SERVER_TIMESTAMP integration. Demo
Error Messages: Clear, actionable error messages with state information for debugging.
Type Safety: Comprehensive type hints for IDE support and static analysis.
Test Harness: Built-in testing utilities for Firebase emulator integration.
Use Cases
Firestore serves as a powerful primitive for distributed applications. Fire-Prox makes Firestore convenient for research workflows and rapid prototyping. Example applications where Fire-Prox excels:
- Multi-agent systems like Alpha Evolve, where large swarms of AI agents optimize specific problems while coordinating loosely through a shared database of successful ideas
- Long-running agent workflows (such as Claude Code) that implement complex plans in a step-by-step fashion
In both scenarios, AI agents need persistent storage, but you also need observability: the ability to monitor what's happening in real-time at both macro and micro scales, drill down into specific execution threads, and analyze results post-hoc in aggregate and granular detail to optimize the system.
Firestore provides an ideal framework for this, offering both Python and JavaScript clients. On the client side, you can build web applications that leverage Firestore's built-in authentication without requiring separate authentication infrastructure. Fire-Prox fills the missing piece: making it easy to quickly interrogate your Firestore database, create ad-hoc analysis tools, convert data to DataFrames, and build the shims and harnesses needed to develop and test complex distributed applications.
In addition to the agentic applications above, fire-prox might be also useful for the following:
- Implementing a distributed queue large portion of the semantics available with AWS SQS queues.
- An Airflow-like distributed task graph executor.
Target Audience
If you understand Firestore and are excited by the myriad of cool applications you could build with it, but wish that interacting with it in Python was a little bit easier, then Fire-Prox is might be a good library for you.
On the other hand, if you already have an application and you need some storage for your objects, and you think that Firestore might be a good solution, then this may not be the right fit. Firestore is pretty close to letting you think of it as a distributed dictionary or list, but you will likely fight with it a little bit because it comes with certain constraints. That's not a big deal. It's worth the effort, but I'm not sure it's worth compounding that fight by using a proxy layer, which will just make the errors more opaque. Also, if you're about to build something, but you have a very clear plan about what you're going to build and it's just a matter of delivery, then there are other Python libraries that are proper object-document mappers that work well with schemas that you have already worked out in advance.
Understanding Firestore is sort of critical. This library provides convenience but doesn't try to simplify or relieve the user from understanding the semantics and details of Firestore. With that said, if you're not that familiar with Firestore, you can look through the demos starting with phase 1, phase 2, and phase 3, and then proceed to the topics. You'll sort of see how the library was built up step-by-step, and that's a pretty good way to not only understand the library in a digestible format, but also get some exposure to various Firestore topics. Unfortunately, the Firestore Python SDK's documentation isn't very strong. It's much easier to learn about Firestore by reading about the documentation in JavaScript. One issue with the JavaScript is has features and semantics that are important on client-side devices that might have limited connectivity. This provides it with a certain amount of magic. For instance, in the browser, you typically don't await an operation in Firestore that ends up effectively blocking. Whereas if you use the "sync" version, the control flow continues in a way that is actualy async, however the local state reflects the impact of your operation but can be rolled back if something happens when it's finally persisted to the database. This magic doesn't exist in the Python and other server-oriented SDKs. But that's a bit of a digression. Good luck!
Why AI?
fire-prox has been almost entirely written by AI agents. The development process involves dictating prompts and having AI agents handle implementation, testing, and documentation.
The workflow typically begins with creating an architecture document. For this project, I consulted multiple leading AI systems—Gemini, GPT, and Claude—to evaluate different architectural approaches before selecting the most promising design. From there, AI agents implement features incrementally, following the architectural blueprint.
This approach has proven remarkably effective. AI-assisted development enables consistent quality throughout the project by eliminating the natural fatigue that comes with extensive coding sessions. Rather than managing implementation details, I focus on architecture, design decisions, and quality oversight. The result is comprehensive test coverage, detailed documentation, and cleaner code than would typically emerge from a rushed implementation.
More broadly, this project serves as an exploration of AI-assisted software development. The tools have matured to where they can handle complex, multi-phase projects with proper scaffolding—good test infrastructure, clear architecture documents, and iterative validation. The development velocity is substantial, but more importantly, the consistency of output quality remains high across all phases. For prototyping and research tools where iteration speed matters, AI-assisted development offers a compelling approach worth exploring.