API
FireProx: A schemaless, state-aware proxy library for Google Cloud Firestore.
FireProx provides a simplified, Pythonic interface for working with Firestore during rapid prototyping. It wraps the official google-cloud-firestore client with an intuitive object-oriented API that minimizes boilerplate and aligns with Python's programming paradigms.
Main Components: Synchronous API: FireProx: Main entry point for sync operations FireObject: State-aware proxy for Firestore documents FireCollection: Interface for working with collections
Asynchronous API:
AsyncFireProx: Main entry point for async operations
AsyncFireObject: Async state-aware proxy for documents
AsyncFireCollection: Async interface for collections
Shared:
State: Enum representing FireObject lifecycle states
Example Usage (Synchronous): from google.cloud import firestore from fire_prox import FireProx
# Initialize
native_client = firestore.Client(project='my-project')
db = FireProx(native_client)
# Create a document
users = db.collection('users')
user = users.new()
user.name = 'Ada Lovelace'
user.year = 1815
user.save()
# Read a document (lazy loading)
user = db.doc('users/alovelace')
print(user.name) # Automatically fetches data
# Update a document
user.year = 1816
user.save()
# Delete a document
user.delete()
Example Usage (Asynchronous): from google.cloud import firestore from fire_prox import AsyncFireProx
# Initialize
native_client = firestore.AsyncClient(project='my-project')
db = AsyncFireProx(native_client)
# Create a document
users = db.collection('users')
user = users.new()
user.name = 'Ada Lovelace'
user.year = 1815
await user.save()
# Read a document (explicit fetch required)
user = db.doc('users/alovelace')
await user.fetch()
print(user.name)
# Update a document
user.year = 1816
await user.save()
# Delete a document
await user.delete()
AsyncFireCollection
Bases: BaseFireCollection
A wrapper around Firestore AsyncCollectionReference for document management.
AsyncFireCollection provides a simplified interface for creating new documents and querying collections asynchronously.
Usage Examples: # Get a collection users = db.collection('users')
# Create a new document in DETACHED state
new_user = users.new()
new_user.name = 'Ada Lovelace'
new_user.year = 1815
await new_user.save()
# Create with explicit ID
user = users.new()
user.name = 'Charles Babbage'
await user.save(doc_id='cbabbage')
# Phase 2: Query the collection
query = users.where('year', '>', 1800).limit(10)
async for user in query.get():
print(user.name)
Source code in src/fire_prox/async_fire_collection.py
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 | |
parent
property
Get the parent document if this is a subcollection.
Phase 2 feature.
Returns: AsyncFireObject representing the parent document if this is a subcollection, None if this is a root-level collection.
aggregate(**aggregations)
async
Execute multiple aggregations in a single query.
Phase 4 Part 5 feature. Performs multiple aggregation operations (count, sum, avg) in one efficient query.
Args: **aggregations: Named aggregation operations using Count(), Sum(), or Avg().
Returns: Dictionary mapping aggregation names to their results.
Example: from fire_prox import Count, Sum, Avg
stats = await users.aggregate(
total=Count(),
total_score=Sum('score'),
avg_age=Avg('age')
)
# Returns: {'total': 42, 'total_score': 5000, 'avg_age': 28.5}
Source code in src/fire_prox/async_fire_collection.py
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 | |
avg(field)
async
Average a numeric field across all documents.
Phase 4 Part 5 feature. Calculates the average of a numeric field without fetching document data.
Args: field: The field name to average.
Returns: The average of the field values (float).
Example: avg_rating = await products.avg('rating')
Source code in src/fire_prox/async_fire_collection.py
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 | |
count()
async
Count documents in the collection.
Phase 4 Part 5 feature. Returns the total count of documents without fetching their data.
Returns: The number of documents in the collection.
Example: total = await users.count() print(f"Total users: {total}")
Source code in src/fire_prox/async_fire_collection.py
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 | |
delete_all(*, batch_size=50, recursive=True, dry_run=False)
async
Delete every document in this collection asynchronously.
Firestore does not expose a server-side "drop collection" operation. This helper batches document deletes and, when recursive is True (default), also clears any nested subcollections before removing the parent document.
Args: batch_size: Maximum number of deletes per commit. recursive: Whether to delete nested subcollections. dry_run: Count affected documents without executing writes.
Returns: Dictionary with counts for deleted documents and subcollections visited during recursion.
Raises: ValueError: If batch_size is not positive.
Source code in src/fire_prox/async_fire_collection.py
395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 | |
doc(doc_id)
Get a reference to a specific document in this collection.
Source code in src/fire_prox/async_fire_collection.py
82 83 84 | |
find_nearest(vector_field, query_vector, distance_measure, limit, distance_result_field=None)
Find the nearest neighbors based on vector similarity.
Performs a vector similarity search to find documents with embeddings nearest to the query vector. Requires a single-field vector index on the vector_field.
Args: vector_field: Name of the field containing vector embeddings. query_vector: Vector to compare against (google.cloud.firestore_v1.vector.Vector). distance_measure: Distance calculation method (DistanceMeasure.EUCLIDEAN, DistanceMeasure.COSINE, or DistanceMeasure.DOT_PRODUCT). limit: Maximum number of nearest neighbors to return (max 1000). distance_result_field: Optional field name to store the calculated distance in the query results.
Returns: An AsyncFireQuery instance for method chaining and execution.
Example: from google.cloud.firestore_v1.base_vector_query import DistanceMeasure from google.cloud.firestore_v1.vector import Vector
collection = db.collection("documents")
query = collection.find_nearest(
vector_field="embedding",
query_vector=Vector([0.1, 0.2, 0.3]),
distance_measure=DistanceMeasure.EUCLIDEAN,
limit=5
)
async for doc in query.stream():
print(f"{doc.title}: {doc.embedding}")
Note: - Requires a vector index on the vector_field - Maximum limit is 1000 documents - Can be combined with where() for pre-filtering (requires composite index) - Does not work with Firestore emulator (production only)
Source code in src/fire_prox/async_fire_collection.py
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 | |
get_all()
async
Retrieve all documents in the collection.
Phase 2.5 feature. Returns an async iterator of all documents.
Yields: AsyncFireObject instances in LOADED state for each document.
Example: async for user in users.get_all(): print(f"{user.name}: {user.year}")
Source code in src/fire_prox/async_fire_collection.py
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 | |
limit(count)
Create a query with a result limit.
Phase 2.5 feature.
Args: count: Maximum number of results to return.
Returns: An AsyncFireQuery instance for method chaining.
Source code in src/fire_prox/async_fire_collection.py
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | |
new()
Create a new AsyncFireObject in DETACHED state.
Source code in src/fire_prox/async_fire_collection.py
78 79 80 | |
order_by(field, direction='ASCENDING')
Create a query with ordering.
Phase 2.5 feature.
Args: field: The field path to order by. direction: 'ASCENDING' or 'DESCENDING'.
Returns: An AsyncFireQuery instance for method chaining.
Source code in src/fire_prox/async_fire_collection.py
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | |
select(*field_paths)
Create a query with field projection.
Phase 4 Part 3 feature. Selects specific fields to return in query results. Returns vanilla dictionaries instead of AsyncFireObject instances.
Args: *field_paths: One or more field paths to select.
Returns: An AsyncFireQuery instance with projection applied.
Example: # Select specific fields results = await users.select('name', 'email').get() # Returns: [{'name': 'Alice', 'email': 'alice@example.com'}, ...]
Source code in src/fire_prox/async_fire_collection.py
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | |
sum(field)
async
Sum a numeric field across all documents.
Phase 4 Part 5 feature. Calculates the sum of a numeric field without fetching document data.
Args: field: The field name to sum.
Returns: The sum of the field values (int or float).
Example: total_revenue = await orders.sum('amount')
Source code in src/fire_prox/async_fire_collection.py
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 | |
where(field, op, value)
Create a query with a filter condition.
Phase 2.5 feature. Builds a lightweight query for common filtering needs.
Args: field: The field path to filter on. op: Comparison operator. value: The value to compare against.
Returns: An AsyncFireQuery instance for method chaining.
Example: query = users.where('birth_year', '>', 1800) .where('country', '==', 'UK') .limit(10) async for user in query.stream(): print(user.name)
Source code in src/fire_prox/async_fire_collection.py
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | |
AsyncFireObject
Bases: BaseFireObject
Asynchronous schemaless, state-aware proxy for a Firestore document.
AsyncFireObject provides an object-oriented interface to Firestore documents using the async/await pattern for all I/O operations.
Lazy Loading: AsyncFireObject supports lazy loading via automatic fetch on attribute access. When accessing an attribute on an ATTACHED object, it will automatically fetch data from Firestore (using a synchronous thread to run the async fetch). This happens once per object - subsequent accesses are instant dict lookups.
Usage Examples: # Create a new document (DETACHED state) user = collection.new() user.name = 'Ada Lovelace' user.year = 1815 await user.save() # Transitions to LOADED
# Load existing document with lazy loading (automatic fetch)
user = db.doc('users/alovelace') # ATTACHED state
print(user.name) # Automatically fetches data, transitions to LOADED
# Or explicitly fetch if preferred
user = db.doc('users/alovelace')
await user.fetch() # Explicit async fetch
print(user.name)
# Update and save
user.year = 1816
await user.save()
# Delete
await user.delete()
Source code in src/fire_prox/async_fire_object.py
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 | |
__getattr__(name)
Handle attribute access for document fields with lazy loading.
This method implements lazy loading: if the object is in ATTACHED state, accessing any data attribute will automatically trigger a synchronous fetch to load the data from Firestore using a companion sync client.
This fetch happens once per object - after the first attribute access, the object transitions to LOADED state and subsequent accesses are instant dict lookups.
Args: name: The attribute name being accessed.
Returns: The value of the field from the internal _data cache.
Raises: AttributeError: If the attribute doesn't exist in _data after fetching (if necessary). NotFound: If document doesn't exist in Firestore (during lazy load).
State Transitions: ATTACHED -> LOADED: Automatically fetches data on first access.
Example: user = db.doc('users/alovelace') # ATTACHED name = user.name # Triggers sync fetch, transitions to LOADED year = user.year # No fetch needed, already LOADED
Source code in src/fire_prox/async_fire_object.py
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | |
collections(names_only=False)
async
List subcollections beneath this document asynchronously.
Args: names_only: When True, return collection IDs instead of wrappers.
Returns: List of subcollection names or AsyncFireCollection wrappers.
Source code in src/fire_prox/async_fire_object.py
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 | |
delete(batch=None, *, recursive=True, batch_size=50)
async
Delete the document from Firestore asynchronously.
Args: batch: Optional batch object for batched deletes. If provided, the delete will be accumulated in the batch (committed later). recursive: When True (default), delete all subcollections first. batch_size: Batch size to use for recursive subcollection cleanup.
Raises: ValueError: If called on DETACHED object. RuntimeError: If called on DELETED object. ValueError: If recursive deletion is requested while using a batch.
State Transitions: ATTACHED -> DELETED LOADED -> DELETED
Example: user = db.doc('users/alovelace') await user.delete()
# Batch delete
batch = db.batch()
user1.delete(batch=batch, recursive=False)
user2.delete(batch=batch, recursive=False)
await batch.commit() # Commit all operations
Source code in src/fire_prox/async_fire_object.py
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 | |
delete_subcollection(name, *, batch_size=50, recursive=True, dry_run=False)
async
Delete a subcollection beneath this document asynchronously.
Args: name: Subcollection name relative to this document. batch_size: Maximum number of deletes per commit. recursive: Whether to delete nested subcollections. dry_run: Count affected documents without executing writes.
Returns: Dictionary with counts for deleted documents and subcollections.
Source code in src/fire_prox/async_fire_object.py
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 | |
fetch(force=False, transaction=None)
async
Fetch document data from Firestore asynchronously.
Args: force: If True, fetch data even if already LOADED. transaction: Optional transaction object for transactional reads.
Returns: Self, to allow method chaining.
Raises: ValueError: If called on DETACHED object. RuntimeError: If called on DELETED object. NotFound: If document doesn't exist.
State Transitions: ATTACHED -> LOADED LOADED -> LOADED (if force=True)
Example: # Normal fetch user = db.doc('users/alovelace') # ATTACHED await user.fetch() # Now LOADED
# Transactional fetch
transaction = db.transaction()
@firestore.async_transactional
async def read_user(transaction):
await user.fetch(transaction=transaction)
return user.credits
credits = await read_user(transaction)
Source code in src/fire_prox/async_fire_object.py
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 | |
from_snapshot(snapshot, parent_collection=None, sync_client=None)
classmethod
Create an AsyncFireObject from a DocumentSnapshot.
Args: snapshot: DocumentSnapshot from native async API. parent_collection: Optional parent collection reference. sync_client: Optional sync Firestore client for async lazy loading.
Returns: AsyncFireObject in LOADED state.
Raises: ValueError: If snapshot doesn't exist.
Example: async for doc in query.stream(): user = AsyncFireObject.from_snapshot(doc)
Source code in src/fire_prox/async_fire_object.py
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 | |
save(doc_id=None, transaction=None, batch=None)
async
Save the object's data to Firestore asynchronously.
Args: doc_id: Optional custom document ID for DETACHED objects. transaction: Optional transaction object for transactional writes. batch: Optional batch object for batched writes. If provided, the write will be accumulated in the batch (committed later).
Returns: Self, to allow method chaining.
Raises: RuntimeError: If called on DELETED object. ValueError: If DETACHED without parent_collection, or if trying to create a new document within a transaction or batch.
State Transitions: DETACHED -> LOADED (creates new document) LOADED -> LOADED (updates if dirty)
Example: # Normal save user = collection.new() user.name = 'Ada' await user.save(doc_id='alovelace')
# Transactional save
transaction = db.transaction()
@firestore.async_transactional
async def update_user(transaction):
await user.fetch(transaction=transaction)
user.credits += 10
await user.save(transaction=transaction)
await update_user(transaction)
# Batch save
batch = db.batch()
user1.save(batch=batch)
user2.save(batch=batch)
await batch.commit() # Commit all operations
Source code in src/fire_prox/async_fire_object.py
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 | |
AsyncFireProx
Bases: BaseFireProx
Main entry point for the async FireProx library.
AsyncFireProx wraps the native google-cloud-firestore AsyncClient and provides a simplified, Pythonic interface for working with Firestore asynchronously.
Usage Examples: # Initialize with a pre-configured native async client from google.cloud import firestore from fire_prox import AsyncFireProx
native_client = firestore.AsyncClient(project='my-project')
db = AsyncFireProx(native_client)
# Access a document (ATTACHED state)
user = db.doc('users/alovelace')
await user.fetch()
print(user.name)
# Create a new document
users = db.collection('users')
new_user = users.new()
new_user.name = 'Charles Babbage'
new_user.year = 1791
await new_user.save()
# Update a document
user = db.doc('users/alovelace')
await user.fetch()
user.year = 1816
await user.save()
# Delete a document
await user.delete()
Source code in src/fire_prox/async_fireprox.py
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | |
__init__(client)
Initialize AsyncFireProx with a native async Firestore client.
Args: client: A configured google.cloud.firestore.AsyncClient instance. Authentication and project configuration should be handled before creating this instance.
Raises: TypeError: If client is not a google.cloud.firestore.AsyncClient.
Example: from google.cloud import firestore from fire_prox import AsyncFireProx
# Option 1: Default credentials
native_client = firestore.AsyncClient()
# Option 2: Explicit project
native_client = firestore.AsyncClient(project='my-project-id')
# Initialize AsyncFireProx
db = AsyncFireProx(native_client)
Source code in src/fire_prox/async_fireprox.py
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | |
collection(path)
Get a reference to a collection by its path.
Creates an AsyncFireCollection wrapper around the native AsyncCollectionReference.
Args: path: The collection path, e.g., 'users' or 'users/uid/posts'. Must have an odd number of segments.
Returns: An AsyncFireCollection instance.
Raises: ValueError: If path has an even number of segments.
Example: # Root-level collection users = db.collection('users') new_user = users.new() new_user.name = 'Ada' await new_user.save()
# Subcollection
posts = db.collection('users/alovelace/posts')
new_post = posts.new()
new_post.title = 'Analysis Engine'
await new_post.save()
Source code in src/fire_prox/async_fireprox.py
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | |
collections(path, *, names_only=False)
async
List subcollections beneath the specified document path asynchronously.
Args: path: Document path whose subcollections should be listed. names_only: Return collection IDs instead of AsyncFireCollection wrappers.
Returns: List of subcollection names or AsyncFireCollection wrappers.
Source code in src/fire_prox/async_fireprox.py
181 182 183 184 185 186 187 188 189 190 191 192 193 | |
doc(path)
Get a reference to a document by its full path.
Creates an AsyncFireObject in ATTACHED state. No data is fetched from Firestore until fetch() is called or an attribute is accessed (lazy loading).
Args: path: The full document path, e.g., 'users/alovelace' or 'users/uid/posts/post123'. Must be a valid Firestore document path with an even number of segments.
Returns: An AsyncFireObject instance in ATTACHED state.
Raises: ValueError: If path has an odd number of segments.
Example: # Root-level document with lazy loading user = db.doc('users/alovelace') print(user.name) # Triggers automatic fetch
# Or explicit fetch
user = db.doc('users/alovelace')
await user.fetch()
print(user.name)
# Nested document (subcollection)
post = db.doc('users/alovelace/posts/post123')
await post.fetch()
Source code in src/fire_prox/async_fireprox.py
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | |
document(path)
Alias for doc(). Get a reference to a document by its full path.
Args: path: The full document path.
Returns: An AsyncFireObject instance in ATTACHED state.
Source code in src/fire_prox/async_fireprox.py
133 134 135 136 137 138 139 140 141 142 143 | |
AsyncFireQuery
A chainable query builder for Firestore collections (asynchronous).
AsyncFireQuery wraps the native google-cloud-firestore AsyncQuery object and provides a simplified, chainable interface for building and executing async queries. It follows an immutable pattern - each method returns a new AsyncFireQuery instance with the modified query.
This is the asynchronous implementation. For sync queries, use FireQuery.
Usage Examples: # Basic filtering query = users.where('birth_year', '>', 1800) results = await query.get() for user in results: print(user.name)
# Chaining multiple conditions
query = (users
.where('birth_year', '>', 1800)
.where('country', '==', 'England')
.order_by('birth_year')
.limit(10))
async for user in query.stream():
print(f"{user.name} - {user.birth_year}")
# Async iteration
async for user in users.where('active', '==', True).stream():
print(user.name)
Design Note: For complex queries beyond the scope of this builder (e.g., OR queries, advanced filtering), use the native AsyncQuery API directly and hydrate results with AsyncFireObject.from_snapshot():
native_query = client.collection('users').where(...)
results = [AsyncFireObject.from_snapshot(snap) async for snap in native_query.stream()]
Source code in src/fire_prox/async_fire_query.py
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 | |
__init__(native_query, parent_collection=None, projection=None)
Initialize an AsyncFireQuery.
Args: native_query: The underlying native AsyncQuery object from google-cloud-firestore. parent_collection: Optional reference to parent AsyncFireCollection. projection: Optional tuple of field paths to project (select specific fields).
Source code in src/fire_prox/async_fire_query.py
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | |
__repr__()
Return string representation of the query.
Source code in src/fire_prox/async_fire_query.py
903 904 905 | |
__str__()
Return human-readable string representation.
Source code in src/fire_prox/async_fire_query.py
907 908 909 | |
aggregate(**aggregations)
async
Perform multiple aggregations in a single query.
Executes an aggregation query with multiple aggregation operations (count, sum, average) without fetching the actual documents. This is more efficient than running multiple separate aggregation queries.
Args: **aggregations: Named aggregations using Count(), Sum(field), or Avg(field) from fire_prox.aggregation module.
Returns: Dictionary mapping aggregation names to their results.
Raises: ValueError: If no aggregations are provided or if invalid aggregation types are used.
Example: from fire_prox.aggregation import Count, Sum, Avg
# Multiple aggregations in one query
stats = await employees.aggregate(
total_count=Count(),
total_salary=Sum('salary'),
avg_salary=Avg('salary'),
avg_age=Avg('age')
)
# Returns: {
# 'total_count': 150,
# 'total_salary': 15000000,
# 'avg_salary': 100000.0,
# 'avg_age': 35.2
# }
# With filters
eng_stats = await (employees
.where('department', '==', 'Engineering')
.aggregate(
count=Count(),
total_salary=Sum('salary')
))
# Returns: {'count': 50, 'total_salary': 5000000}
# Financial dashboard
financials = await (transactions
.where('date', '>=', start_date)
.aggregate(
total_transactions=Count(),
total_revenue=Sum('amount'),
avg_transaction=Avg('amount')
))
Note: - Much more efficient than multiple separate aggregation queries - All aggregations execute in a single round-trip to Firestore - Null values are ignored in sum and average calculations
Source code in src/fire_prox/async_fire_query.py
577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 | |
avg(field)
async
Average a numeric field across all matching documents.
Executes an aggregation query to calculate the arithmetic mean of a specific field without fetching the actual documents. The field must contain numeric values (int or float).
Args: field: Name of the numeric field to average.
Returns: Average of the field values across all matching documents. Returns 0.0 if no documents match or if all values are null.
Raises: ValueError: If field is None or empty.
Example: # Average age of all users avg_age = await users.avg('age') # Returns: 32.5
# Average with filters
avg_salary = await (employees
.where('department', '==', 'Engineering')
.avg('salary'))
# Returns: 125000.0
# Average rating for active products
avg_rating = await (products
.where('active', '==', True)
.avg('rating'))
# Returns: 4.2
Note: - Null values are ignored in the average calculation - Non-numeric values will cause an error - This is more efficient than fetching all documents
Source code in src/fire_prox/async_fire_query.py
522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 | |
count()
async
Count documents matching the query.
Executes an aggregation query to count the number of documents that match the current query filters without fetching the actual documents. This is more efficient than fetching all documents and counting them.
Returns: Integer count of matching documents. Returns 0 if no documents match.
Example: # Count all users total_users = await users.count() # Returns: 150
# Count with filters
active_users = await users.where('active', '==', True).count()
# Returns: 42
# Count with complex query
count = await (users
.where('age', '>', 25)
.where('country', '==', 'USA')
.count())
# Returns: 37
Note: This uses Firestore's native aggregation API, which is more efficient than fetching documents. However, it still counts as one document read per 1000 documents in the collection.
Source code in src/fire_prox/async_fire_query.py
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 | |
end_at(*document_fields_or_snapshot)
End query results at a cursor position (inclusive).
Creates a new AsyncFireQuery that ends at the specified cursor. The cursor document is included in the results.
Args: *document_fields_or_snapshot: Either: - A dictionary of field values: {'field': value} - A DocumentSnapshot - Direct field values matching order_by clause order
Returns: A new AsyncFireQuery instance with the end cursor applied.
Example: # Get all users up to and including age 50 query = users.order_by('age').end_at({'age': 50})
# Using a specific document as endpoint
target_doc_ref = users.doc('user123')._doc_ref
target_snapshot = await target_doc_ref.get()
query = users.order_by('age').end_at(target_snapshot)
Source code in src/fire_prox/async_fire_query.py
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 | |
end_before(*document_fields_or_snapshot)
End query results before a cursor position (exclusive).
Creates a new AsyncFireQuery that ends before the specified cursor. The cursor document itself is excluded from results.
Args: *document_fields_or_snapshot: Either: - A dictionary of field values: {'field': value} - A DocumentSnapshot - Direct field values matching order_by clause order
Returns: A new AsyncFireQuery instance with the end-before cursor applied.
Example: # Get all users before age 50 (exclude 50) query = users.order_by('age').end_before({'age': 50})
# Using a specific document as exclusive endpoint
target_doc_ref = users.doc('user123')._doc_ref
target_snapshot = await target_doc_ref.get()
query = users.order_by('age').end_before(target_snapshot)
Source code in src/fire_prox/async_fire_query.py
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 | |
find_nearest(vector_field, query_vector, distance_measure, limit, distance_result_field=None)
Find the nearest neighbors based on vector similarity.
Performs a vector similarity search on top of the current query filters. This allows you to combine pre-filtering with vector search (requires a composite index).
Args: vector_field: Name of the field containing vector embeddings. query_vector: Vector to compare against (google.cloud.firestore_v1.vector.Vector). distance_measure: Distance calculation method (DistanceMeasure.EUCLIDEAN, DistanceMeasure.COSINE, or DistanceMeasure.DOT_PRODUCT). limit: Maximum number of nearest neighbors to return (max 1000). distance_result_field: Optional field name to store the calculated distance in the query results.
Returns: A new AsyncFireQuery instance with the vector search applied.
Example: from google.cloud.firestore_v1.base_vector_query import DistanceMeasure from google.cloud.firestore_v1.vector import Vector
# Find nearest neighbors with pre-filtering
query = (collection
.where('category', '==', 'tech')
.find_nearest(
vector_field="embedding",
query_vector=Vector([0.1, 0.2, 0.3]),
distance_measure=DistanceMeasure.COSINE,
limit=5
))
async for doc in query.stream():
print(f"{doc.title}: {doc.category}")
Note: - Requires a composite index when combining with where() clauses - Maximum limit is 1000 documents - Does not work with Firestore emulator (production only)
Source code in src/fire_prox/async_fire_query.py
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 | |
get()
async
Execute the query and return results as a list.
Fetches all matching documents asynchronously and hydrates them into AsyncFireObject instances in LOADED state. If a projection is active (via .select()), returns vanilla dictionaries instead of AsyncFireObject instances.
Returns: - If no projection: List of AsyncFireObject instances for all documents matching the query. - If projection active: List of dictionaries containing only the selected fields. DocumentReferences are converted to AsyncFireObjects. - Empty list if no documents match.
Example: # Get all results as AsyncFireObjects users = await query.get() for user in users: print(f"{user.name}: {user.birth_year}")
# Get projected results as dictionaries
users = await query.select('name', 'email').get()
for user_dict in users:
print(f"{user_dict['name']}: {user_dict['email']}")
# Check if results exist
results = await query.get()
if results:
print(f"Found {len(results)} users")
else:
print("No users found")
Source code in src/fire_prox/async_fire_query.py
747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 | |
limit(count)
Limit the number of results returned.
Creates a new AsyncFireQuery that will return at most count results.
Args: count: Maximum number of documents to return. Must be positive.
Returns: A new AsyncFireQuery instance with the limit applied.
Raises: ValueError: If count is not positive.
Example: # Get top 10 results query = users.order_by('score', direction='DESCENDING').limit(10)
# Get first 5 matching documents
query = users.where('active', '==', True).limit(5)
Source code in src/fire_prox/async_fire_query.py
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | |
on_snapshot(callback)
Listen for real-time updates to this query.
This method sets up a real-time listener that fires the callback whenever any document matching the query changes. The listener runs on a separate thread managed by the Firestore SDK.
Important: This is a sync-only feature. Even for AsyncFireQuery, the listener uses a synchronous query (via the parent collection's _sync_client) to run on a background thread. This is the standard Firestore pattern for real-time listeners in Python.
Args: callback: Callback function invoked on query changes. Signature: callback(query_snapshot, changes, read_time) - query_snapshot: List of DocumentSnapshot objects matching the query - changes: List of DocumentChange objects (ADDED, MODIFIED, REMOVED) - read_time: Timestamp of the snapshot
Returns:
Watch object with an .unsubscribe() method to stop listening.
Example: import threading
callback_done = threading.Event()
def on_change(query_snapshot, changes, read_time):
for change in changes:
if change.type.name == 'ADDED':
print(f"New: {change.document.id}")
elif change.type.name == 'MODIFIED':
print(f"Modified: {change.document.id}")
elif change.type.name == 'REMOVED':
print(f"Removed: {change.document.id}")
callback_done.set()
# Listen to active users only (async query)
active_users = users.where('status', '==', 'active')
watch = active_users.on_snapshot(on_change)
# Wait for initial snapshot
callback_done.wait()
# Later: stop listening
watch.unsubscribe()
Note: The callback runs on a separate thread. Use threading primitives (Event, Lock, Queue) for synchronization with your main thread.
Source code in src/fire_prox/async_fire_query.py
847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 | |
order_by(field, direction='ASCENDING')
Add an ordering clause to the query.
Creates a new AsyncFireQuery with ordering by the specified field.
Args: field: The field path to order by. direction: Sort direction. Either 'ASCENDING' or 'DESCENDING'. Default is 'ASCENDING'.
Returns: A new AsyncFireQuery instance with the ordering applied.
Example: # Ascending order query = users.order_by('birth_year')
# Descending order
query = users.order_by('birth_year', direction='DESCENDING')
# Multiple orderings (chained)
query = (users
.order_by('country')
.order_by('birth_year', direction='DESCENDING'))
Source code in src/fire_prox/async_fire_query.py
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | |
select(*field_paths)
Select specific fields to return (projection).
Creates a new AsyncFireQuery that only returns the specified fields in the query results. When using projections, query results will be returned as vanilla dictionaries instead of AsyncFireObject instances. Any DocumentReferences in the returned dictionaries will be automatically converted to AsyncFireObject instances in ATTACHED state.
Args: *field_paths: One or more field paths to select. Field paths can include nested fields using dot notation (e.g., 'address.city').
Returns: A new AsyncFireQuery instance with the projection applied.
Raises: ValueError: If no field paths are provided.
Example: # Select a single field query = users.select('name') results = await query.get() # Returns: [{'name': 'Alice'}, {'name': 'Bob'}, ...]
# Select multiple fields
query = users.select('name', 'email', 'birth_year')
results = await query.get()
# Returns: [{'name': 'Alice', 'email': 'alice@example.com', 'birth_year': 1990}, ...]
# Select with filtering and ordering
query = (users
.where('birth_year', '>', 1990)
.select('name', 'birth_year')
.order_by('birth_year')
.limit(10))
# DocumentReferences are auto-converted to AsyncFireObjects
query = posts.select('title', 'author') # author is a DocumentReference
results = await query.get()
# results[0]['author'] is an AsyncFireObject, not a DocumentReference
await results[0]['author'].fetch()
print(results[0]['author'].name)
Note: - Projection queries return dictionaries, not AsyncFireObject instances - Only the selected fields will be present in the returned dictionaries - DocumentReferences are automatically hydrated to AsyncFireObject instances - Projected results are more bandwidth-efficient for large documents
Source code in src/fire_prox/async_fire_query.py
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 | |
start_after(*document_fields_or_snapshot)
Start query results after a cursor position (exclusive).
Creates a new AsyncFireQuery that starts after the specified cursor. The cursor document itself is excluded from results. This is typically used for pagination to avoid duplicating the last document from the previous page.
Args: *document_fields_or_snapshot: Either: - A dictionary of field values: {'field': value} - A DocumentSnapshot from a previous query - Direct field values matching order_by clause order
Returns: A new AsyncFireQuery instance with the start-after cursor applied.
Example: # Pagination: exclude the last document from previous page page1 = await users.order_by('age').limit(10).get() last_age = page1[-1].age page2 = await users.order_by('age').start_after({'age': last_age}).limit(10).get()
# Using a document snapshot (common pattern)
last_doc_ref = page1[-1]._doc_ref
last_snapshot = await last_doc_ref.get()
page2 = await users.order_by('age').start_after(last_snapshot).limit(10).get()
Source code in src/fire_prox/async_fire_query.py
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 | |
start_at(*document_fields_or_snapshot)
Start query results at a cursor position (inclusive).
Creates a new AsyncFireQuery that starts at the specified cursor. The cursor can be a document snapshot or a dictionary of field values matching the order_by fields.
Args: *document_fields_or_snapshot: Either: - A dictionary of field values: {'field': value} - A DocumentSnapshot from a previous query - Direct field values matching order_by clause order
Returns: A new AsyncFireQuery instance with the start cursor applied.
Example: # Using field values (requires matching order_by) query = users.order_by('age').start_at({'age': 25})
# Pagination: get first page, then start at last document
page1 = await users.order_by('age').limit(10).get()
last_age = page1[-1].age
page2 = await users.order_by('age').start_at({'age': last_age}).limit(10).get()
# Using a document snapshot
last_doc_ref = page1[-1]._doc_ref
last_snapshot = await last_doc_ref.get()
page2 = await users.order_by('age').start_at(last_snapshot).limit(10).get()
Source code in src/fire_prox/async_fire_query.py
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | |
stream()
async
Execute the query and stream results as an async iterator.
Returns an async generator that yields AsyncFireObject instances one at a time. This is more memory-efficient than .get() for large result sets as it doesn't load all results into memory at once. If a projection is active (via .select()), yields vanilla dictionaries instead.
Yields: - If no projection: AsyncFireObject instances in LOADED state for each matching document. - If projection active: Dictionaries containing only the selected fields. DocumentReferences are converted to AsyncFireObjects.
Example: # Stream results one at a time as AsyncFireObjects async for user in query.stream(): print(f"{user.name}: {user.birth_year}") # Process each user without loading all users into memory
# Stream projected results as dictionaries
async for user_dict in query.select('name', 'email').stream():
print(f"{user_dict['name']}: {user_dict['email']}")
# Works with any query
async for post in (posts
.where('published', '==', True)
.order_by('date', direction='DESCENDING')
.stream()):
print(post.title)
Source code in src/fire_prox/async_fire_query.py
799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 | |
sum(field)
async
Sum a numeric field across all matching documents.
Executes an aggregation query to sum the values of a specific field without fetching the actual documents. The field must contain numeric values (int or float).
Args: field: Name of the numeric field to sum.
Returns: Sum of the field values across all matching documents. Returns 0 if no documents match or if all values are null.
Raises: ValueError: If field is None or empty.
Example: # Sum all salaries total_salary = await employees.sum('salary') # Returns: 5000000
# Sum with filters
engineering_salary = await (employees
.where('department', '==', 'Engineering')
.sum('salary'))
# Returns: 2500000
# Sum revenue from active products
total_revenue = await (products
.where('active', '==', True)
.sum('revenue'))
# Returns: 1250000.50
Note: - Null values are ignored in the sum - Non-numeric values will cause an error - This is more efficient than fetching all documents
Source code in src/fire_prox/async_fire_query.py
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 | |
where(field, op, value)
Add a filter condition to the query.
Creates a new AsyncFireQuery with an additional filter condition. Uses the immutable pattern - returns a new instance rather than modifying the current query.
Args: field: The field path to filter on (e.g., 'name', 'address.city'). op: Comparison operator. Supported operators: '==' (equal), '!=' (not equal), '<' (less than), '<=' (less than or equal), '>' (greater than), '>=' (greater than or equal), 'in' (value in list), 'not-in' (value not in list), 'array-contains' (array contains value), 'array-contains-any' (array contains any of the values). value: The value to compare against.
Returns: A new AsyncFireQuery instance with the added filter.
Example: # Single condition query = users.where('birth_year', '>', 1800)
# Multiple conditions (chained)
query = (users
.where('birth_year', '>', 1800)
.where('country', '==', 'England'))
Source code in src/fire_prox/async_fire_query.py
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | |
Avg
Bases: AggregationType
Average aggregation - averages a numeric field across documents.
Requires a field name. The field must contain numeric values (int or float). Returns the arithmetic mean of all non-null values.
Example: # Average age avg_age = users.avg('age')
# Average via aggregate()
result = users.aggregate(avg_rating=Avg('rating'))
# Returns: {'avg_rating': 4.2}
Args: field: Name of the numeric field to average.
Raises: ValueError: If field is not provided.
Source code in src/fire_prox/aggregation.py
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | |
__init__(field)
Initialize Avg aggregation.
Args: field: Name of the numeric field to average.
Raises: ValueError: If field is None or empty.
Source code in src/fire_prox/aggregation.py
122 123 124 125 126 127 128 129 130 131 132 133 134 | |
Count
Bases: AggregationType
Count aggregation - counts matching documents.
Does not require a field name since it counts documents, not field values.
Example: # Count all active users count = users.where('active', '==', True).count()
# Count via aggregate()
result = users.aggregate(total_users=Count())
# Returns: {'total_users': 42}
Source code in src/fire_prox/aggregation.py
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | |
__init__()
Initialize Count aggregation (no field needed).
Source code in src/fire_prox/aggregation.py
59 60 61 | |
FireCollection
Bases: BaseFireCollection
A wrapper around Firestore CollectionReference for document management (synchronous).
FireCollection provides a simplified interface for creating new documents and querying collections. It serves as a factory for FireObject instances and (in Phase 2) will provide a lightweight query builder.
This is the synchronous implementation.
Usage Examples: # Get a collection users = db.collection('users')
# Create a new document in DETACHED state
new_user = users.new()
new_user.name = 'Ada Lovelace'
new_user.year = 1815
new_user.save()
# Create with explicit ID
user = users.new()
user.name = 'Charles Babbage'
user.save(doc_id='cbabbage')
# Phase 2: Query the collection
query = users.where('year', '>', 1800).limit(10)
for user in query.get():
print(user.name)
Source code in src/fire_prox/fire_collection.py
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 | |
parent
property
Get the parent document if this is a subcollection.
Returns: FireObject representing the parent document if this is a subcollection, None if this is a root-level collection.
Note: Phase 2 feature. Returns None in Phase 1 as subcollections are not yet implemented.
Example: posts = db.doc('users/alovelace').collection('posts') parent = posts.parent print(parent.path) # 'users/alovelace'
aggregate(**aggregations)
Execute multiple aggregations in a single query.
Phase 4 Part 5 feature. Performs multiple aggregation operations (count, sum, avg) in one efficient query.
Args: **aggregations: Named aggregation operations using Count(), Sum(), or Avg().
Returns: Dictionary mapping aggregation names to their results.
Example: from fire_prox import Count, Sum, Avg
stats = users.aggregate(
total=Count(),
total_score=Sum('score'),
avg_age=Avg('age')
)
# Returns: {'total': 42, 'total_score': 5000, 'avg_age': 28.5}
Source code in src/fire_prox/fire_collection.py
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 | |
avg(field)
Average a numeric field across all documents.
Phase 4 Part 5 feature. Calculates the average of a numeric field without fetching document data.
Args: field: The field name to average.
Returns: The average of the field values (float).
Example: avg_rating = products.avg('rating')
Source code in src/fire_prox/fire_collection.py
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 | |
count()
Count documents in the collection.
Phase 4 Part 5 feature. Returns the total count of documents without fetching their data.
Returns: The number of documents in the collection.
Example: total = users.count() print(f"Total users: {total}")
Source code in src/fire_prox/fire_collection.py
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 | |
delete_all(*, batch_size=50, recursive=True, dry_run=False)
Delete every document in this collection.
Firestore offers no atomic "drop collection" operation. This helper iterates through each document and issues batched deletes. When recursive is True (default) it will also clear any nested subcollections before deleting their parent document.
Args: batch_size: Maximum number of deletes to commit at once. recursive: Whether to delete nested subcollections. dry_run: Count what would be removed without executing writes.
Returns: Dictionary with counts for deleted documents and subcollections visited during recursion.
Raises: ValueError: If batch_size is not positive.
Source code in src/fire_prox/fire_collection.py
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 | |
doc(doc_id)
Get a reference to a specific document in this collection.
Source code in src/fire_prox/fire_collection.py
73 74 75 | |
find_nearest(vector_field, query_vector, distance_measure, limit, distance_result_field=None)
Find the nearest neighbors based on vector similarity.
Performs a vector similarity search to find documents with embeddings nearest to the query vector. Requires a single-field vector index on the vector_field.
Args: vector_field: Name of the field containing vector embeddings. query_vector: Vector to compare against (google.cloud.firestore_v1.vector.Vector). distance_measure: Distance calculation method (DistanceMeasure.EUCLIDEAN, DistanceMeasure.COSINE, or DistanceMeasure.DOT_PRODUCT). limit: Maximum number of nearest neighbors to return (max 1000). distance_result_field: Optional field name to store the calculated distance in the query results.
Returns: A FireQuery instance for method chaining and execution.
Example: from google.cloud.firestore_v1.base_vector_query import DistanceMeasure from google.cloud.firestore_v1.vector import Vector
collection = db.collection("documents")
query = collection.find_nearest(
vector_field="embedding",
query_vector=Vector([0.1, 0.2, 0.3]),
distance_measure=DistanceMeasure.EUCLIDEAN,
limit=5
)
for doc in query.get():
print(f"{doc.title}: {doc.embedding}")
Note: - Requires a vector index on the vector_field - Maximum limit is 1000 documents - Can be combined with where() for pre-filtering (requires composite index) - Does not work with Firestore emulator (production only)
Source code in src/fire_prox/fire_collection.py
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 | |
get_all()
Retrieve all documents in the collection.
Phase 2.5 feature. Returns an iterator of all documents.
Yields: FireObject instances in LOADED state for each document.
Example: for user in users.get_all(): print(f"{user.name}: {user.year}")
Source code in src/fire_prox/fire_collection.py
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 | |
limit(count)
Create a query with a result limit.
Phase 2.5 feature. Limits the number of results returned.
Args: count: Maximum number of results to return.
Returns: A FireQuery instance for method chaining.
Source code in src/fire_prox/fire_collection.py
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | |
new()
Create a new FireObject in DETACHED state.
Source code in src/fire_prox/fire_collection.py
69 70 71 | |
order_by(field, direction='ASCENDING')
Create a query with ordering.
Phase 2.5 feature. Orders results by a field.
Args: field: The field path to order by. direction: 'ASCENDING' or 'DESCENDING'. Default is 'ASCENDING'.
Returns: A FireQuery instance for method chaining.
Source code in src/fire_prox/fire_collection.py
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | |
select(*field_paths)
Create a query with field projection.
Phase 4 Part 3 feature. Selects specific fields to return in query results. Returns vanilla dictionaries instead of FireObject instances.
Args: *field_paths: One or more field paths to select.
Returns: A FireQuery instance with projection applied.
Example: # Select specific fields results = users.select('name', 'email').get() # Returns: [{'name': 'Alice', 'email': 'alice@example.com'}, ...]
Source code in src/fire_prox/fire_collection.py
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | |
sum(field)
Sum a numeric field across all documents.
Phase 4 Part 5 feature. Calculates the sum of a numeric field without fetching document data.
Args: field: The field name to sum.
Returns: The sum of the field values (int or float).
Example: total_revenue = orders.sum('amount')
Source code in src/fire_prox/fire_collection.py
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 | |
where(field, op, value)
Create a query with a filter condition.
Phase 2.5 feature. Builds a lightweight query for common filtering needs. For complex queries, users should use the native API and hydrate results with FireObject.from_snapshot().
Args: field: The field path to filter on (e.g., 'name', 'address.city'). op: Comparison operator: '==', '!=', '<', '<=', '>', '>=', 'in', 'not-in', 'array-contains', 'array-contains-any'. value: The value to compare against.
Returns: A FireQuery instance for method chaining.
Example: query = users.where('birth_year', '>', 1800) .where('country', '==', 'UK') .limit(10) for user in query.get(): print(user.name)
Source code in src/fire_prox/fire_collection.py
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | |
FireObject
Bases: BaseFireObject
A schemaless, state-aware proxy for a Firestore document (synchronous).
FireObject provides an object-oriented interface to Firestore documents, allowing attribute-style access to document fields and automatic state management throughout the document's lifecycle.
The object maintains an internal state machine (DETACHED -> ATTACHED -> LOADED -> DELETED) and tracks modifications to enable efficient partial updates.
This is the synchronous implementation that supports lazy loading via automatic fetch on attribute access.
Usage Examples: # Create a new document (DETACHED state) user = collection.new() user.name = 'Ada Lovelace' user.year = 1815 user.save() # Transitions to LOADED
# Load existing document (ATTACHED -> LOADED on access)
user = db.doc('users/alovelace') # ATTACHED state
print(user.name) # Triggers fetch, transitions to LOADED
# Update and save
user.year = 1816 # Marks as dirty
user.save() # Performs update
# Delete
user.delete() # Transitions to DELETED
Source code in src/fire_prox/fire_object.py
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 | |
__getattr__(name)
Handle attribute access for document fields with lazy loading.
This method implements lazy loading: if the object is in ATTACHED state, accessing any data attribute will automatically trigger a fetch() to load the data from Firestore.
Args: name: The attribute name being accessed.
Returns: The value of the field from the internal _data cache.
Raises: AttributeError: If the attribute doesn't exist in _data after fetching (if necessary).
State Transitions: ATTACHED -> LOADED: Automatically fetches data on first access.
Example: user = db.doc('users/alovelace') # ATTACHED name = user.name # Triggers fetch, transitions to LOADED year = user.year # No fetch needed, already LOADED
Source code in src/fire_prox/fire_object.py
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | |
collections(names_only=False)
List subcollections beneath this document.
Args: names_only: When True, return collection IDs instead of wrappers.
Returns: List of subcollection names or FireCollection wrappers.
Source code in src/fire_prox/fire_object.py
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 | |
delete(batch=None, *, recursive=True, batch_size=50)
Delete the document from Firestore (synchronous).
Removes the document from Firestore and transitions the object to DELETED state. After deletion, the object retains its ID and path for reference but cannot be modified or saved.
Args: batch: Optional batch object for batched deletes. If provided, the delete will be accumulated in the batch (committed later). recursive: When True (default), delete all subcollections first. batch_size: Batch size to use for recursive subcollection cleanup.
Raises: ValueError: If called on a DETACHED object (no document to delete). RuntimeError: If called on an already-DELETED object. ValueError: If recursive deletion is requested while using a batch.
State Transitions: ATTACHED -> DELETED: Deletes document (data never loaded) LOADED -> DELETED: Deletes document (data was loaded)
Example: user = db.doc('users/alovelace') user.delete() # Document removed from Firestore print(user.state) # State.DELETED print(user.id) # Still accessible: 'alovelace'
# Batch delete
batch = db.batch()
user1.delete(batch=batch, recursive=False)
user2.delete(batch=batch, recursive=False)
batch.commit() # Commit all operations
Source code in src/fire_prox/fire_object.py
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 | |
delete_subcollection(name, *, batch_size=50, recursive=True, dry_run=False)
Delete a subcollection beneath this document.
Firestore keeps subcollections even after their parent document is deleted. This helper clears a specific subcollection using the same batched logic as FireCollection.delete_all().
Args: name: Subcollection name relative to this document. batch_size: Maximum number of deletes per commit. recursive: Whether to delete nested subcollections. dry_run: Count affected documents without executing writes.
Returns: Dictionary with counts for deleted documents and subcollections.
Source code in src/fire_prox/fire_object.py
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 | |
fetch(force=False, transaction=None)
Fetch document data from Firestore (synchronous).
Retrieves the latest data from Firestore and populates the internal _data cache. This method transitions ATTACHED objects to LOADED state and can refresh data for already-LOADED objects.
Args: force: If True, fetch data even if already LOADED. Useful for refreshing data to get latest changes from Firestore. Default is False. transaction: Optional transaction object for transactional reads. If provided, the read will be part of the transaction.
Returns: Self, to allow method chaining.
Raises: ValueError: If called on a DETACHED object (no DocumentReference). RuntimeError: If called on a DELETED object. NotFound: If document doesn't exist in Firestore.
State Transitions: ATTACHED -> LOADED: First fetch populates data LOADED -> LOADED: Refreshes data if force=True
Example: # Normal fetch user = db.doc('users/alovelace') # ATTACHED user.fetch() # Now LOADED with data
# Transactional fetch
transaction = db.transaction()
@firestore.transactional
def read_user(transaction):
user.fetch(transaction=transaction)
return user.credits
credits = read_user(transaction)
Source code in src/fire_prox/fire_object.py
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | |
from_snapshot(snapshot, parent_collection=None)
classmethod
Create a FireObject from a Firestore DocumentSnapshot.
This factory method is the primary "hydration" mechanism for converting native Firestore query results into FireObject instances. It creates an object in LOADED state with data already populated.
Args: snapshot: A DocumentSnapshot from google-cloud-firestore, typically obtained from query results or document.get(). parent_collection: Optional reference to parent FireCollection.
Returns: A new FireObject instance in LOADED state with data from snapshot.
Raises: ValueError: If snapshot doesn't exist (snapshot.exists is False).
Example: # Hydrate from native query native_query = client.collection('users').where('year', '>', 1800) results = [FireObject.from_snapshot(snap) for snap in native_query.stream()]
# Hydrate from direct get
snap = client.document('users/alovelace').get()
user = FireObject.from_snapshot(snap)
Source code in src/fire_prox/fire_object.py
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 | |
save(doc_id=None, transaction=None, batch=None)
Save the object's data to Firestore (synchronous).
Creates or updates the Firestore document based on the object's current state. For DETACHED objects, creates a new document. For LOADED objects, performs a full overwrite (Phase 1).
Args: doc_id: Optional custom document ID. Only used when saving a DETACHED object. If None, Firestore auto-generates an ID. transaction: Optional transaction object for transactional writes. If provided, the write will be part of the transaction. batch: Optional batch object for batched writes. If provided, the write will be accumulated in the batch (committed later).
Returns: Self, to allow method chaining.
Raises: RuntimeError: If called on a DELETED object. ValueError: If DETACHED object has no parent collection, or if trying to create a new document within a transaction or batch.
State Transitions: DETACHED -> LOADED: Creates new document with doc_id or auto-ID LOADED -> LOADED: Updates document if dirty, no-op if clean
Example: # Create new document user = collection.new() user.name = 'Ada' user.save(doc_id='alovelace') # DETACHED -> LOADED
# Update existing
user.year = 1816
user.save() # Performs update
# Transactional save
transaction = db.transaction()
@firestore.transactional
def update_user(transaction):
user.fetch(transaction=transaction)
user.credits += 10
user.save(transaction=transaction)
update_user(transaction)
# Batch save
batch = db.batch()
user1.save(batch=batch)
user2.save(batch=batch)
batch.commit() # Commit all operations
Source code in src/fire_prox/fire_object.py
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 | |
FireProx
Bases: BaseFireProx
Main entry point for the FireProx library (synchronous).
FireProx wraps the native google-cloud-firestore Client and provides a simplified, Pythonic interface for working with Firestore. It delegates authentication and client configuration to the official library while providing higher-level abstractions for document and collection access.
The design philosophy is "wrap, don't replace" - FireProx leverages the reliability and security of the native client while providing a more intuitive developer experience optimized for rapid prototyping.
This is the synchronous implementation that supports lazy loading.
Usage Examples: # Initialize with a pre-configured native client from google.cloud import firestore from fire_prox import FireProx
native_client = firestore.Client(project='my-project')
db = FireProx(native_client)
# Access a document (ATTACHED state, lazy loading)
user = db.doc('users/alovelace')
print(user.name) # Automatically fetches data
# Create a new document
users = db.collection('users')
new_user = users.new()
new_user.name = 'Charles Babbage'
new_user.year = 1791
new_user.save()
# Update a document
user = db.doc('users/alovelace')
user.year = 1816
user.save()
# Delete a document
user.delete()
Source code in src/fire_prox/fireprox.py
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | |
__init__(client)
Initialize FireProx with a native Firestore client.
Args: client: A configured google.cloud.firestore.Client instance. Authentication and project configuration should be handled before creating this instance.
Raises: TypeError: If client is not a google.cloud.firestore.Client instance.
Example: from google.cloud import firestore from fire_prox import FireProx
# Option 1: Default credentials
native_client = firestore.Client()
# Option 2: Explicit project
native_client = firestore.Client(project='my-project-id')
# Option 3: Service account
native_client = firestore.Client.from_service_account_json(
'path/to/credentials.json'
)
# Initialize FireProx
db = FireProx(native_client)
Source code in src/fire_prox/fireprox.py
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | |
collection(path)
Get a reference to a collection by its path.
Creates a FireCollection wrapper around the native CollectionReference. Used for creating new documents or (in Phase 2) querying.
Args: path: The collection path, e.g., 'users' or 'users/uid/posts'. Can be a root-level collection (odd number of segments) or a subcollection path.
Returns: A FireCollection instance.
Raises: ValueError: If path has an even number of segments (invalid collection path) or contains invalid characters.
Example: # Root-level collection users = db.collection('users') new_user = users.new() new_user.name = 'Ada' new_user.save()
# Subcollection
posts = db.collection('users/alovelace/posts')
new_post = posts.new()
new_post.title = 'Analysis Engine'
new_post.save()
Source code in src/fire_prox/fireprox.py
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | |
collections(path, *, names_only=False)
List subcollections beneath the specified document path.
Args: path: Document path whose subcollections should be listed. names_only: Return collection IDs instead of FireCollection wrappers.
Returns: List of subcollection names or FireCollection wrappers.
Source code in src/fire_prox/fireprox.py
187 188 189 190 191 192 193 194 195 196 197 198 199 | |
doc(path)
Get a reference to a document by its full path.
Creates a FireObject in ATTACHED state. No data is fetched from Firestore until an attribute is accessed (lazy loading).
Args: path: The full document path, e.g., 'users/alovelace' or 'users/uid/posts/post123'. Must be a valid Firestore document path with an even number of segments.
Returns: A FireObject instance in ATTACHED state.
Raises: ValueError: If path has an odd number of segments (invalid document path) or contains invalid characters.
Example: # Root-level document user = db.doc('users/alovelace')
# Nested document (subcollection)
post = db.doc('users/alovelace/posts/post123')
# Lazy loading
print(user.name) # Triggers fetch on first access
Source code in src/fire_prox/fireprox.py
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | |
document(path)
Alias for doc(). Get a reference to a document by its full path.
Provided for API consistency with the native library and user preference. Functionally identical to doc().
Args: path: The full document path.
Returns: A FireObject instance in ATTACHED state.
Source code in src/fire_prox/fireprox.py
134 135 136 137 138 139 140 141 142 143 144 145 146 147 | |
FireQuery
A chainable query builder for Firestore collections (synchronous).
FireQuery wraps the native google-cloud-firestore Query object and provides a simplified, chainable interface for building and executing queries. It follows an immutable pattern - each method returns a new FireQuery instance with the modified query.
This is the synchronous implementation. For async queries, use AsyncFireQuery.
Usage Examples: # Basic filtering query = users.where('birth_year', '>', 1800) for user in query.get(): print(user.name)
# Chaining multiple conditions
query = (users
.where('birth_year', '>', 1800)
.where('country', '==', 'England')
.order_by('birth_year')
.limit(10))
for user in query.get():
print(f"{user.name} - {user.birth_year}")
# Stream results (generator)
for user in users.where('active', '==', True).stream():
print(user.name)
Design Note: For complex queries beyond the scope of this builder (e.g., OR queries, advanced filtering), use the native Query API directly and hydrate results with FireObject.from_snapshot():
native_query = client.collection('users').where(...)
results = [FireObject.from_snapshot(snap) for snap in native_query.stream()]
Source code in src/fire_prox/fire_query.py
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 | |
__init__(native_query, parent_collection=None, projection=None)
Initialize a FireQuery.
Args: native_query: The underlying native Query object from google-cloud-firestore. parent_collection: Optional reference to parent FireCollection. projection: Optional tuple of field paths to project (select specific fields).
Source code in src/fire_prox/fire_query.py
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | |
__repr__()
Return string representation of the query.
Source code in src/fire_prox/fire_query.py
892 893 894 | |
__str__()
Return human-readable string representation.
Source code in src/fire_prox/fire_query.py
896 897 898 | |
aggregate(**aggregations)
Perform multiple aggregations in a single query.
Executes an aggregation query with multiple aggregation operations (count, sum, average) without fetching the actual documents. This is more efficient than running multiple separate aggregation queries.
Args: **aggregations: Named aggregations using Count(), Sum(field), or Avg(field) from fire_prox.aggregation module.
Returns: Dictionary mapping aggregation names to their results.
Raises: ValueError: If no aggregations are provided or if invalid aggregation types are used.
Example: from fire_prox.aggregation import Count, Sum, Avg
# Multiple aggregations in one query
stats = employees.aggregate(
total_count=Count(),
total_salary=Sum('salary'),
avg_salary=Avg('salary'),
avg_age=Avg('age')
)
# Returns: {
# 'total_count': 150,
# 'total_salary': 15000000,
# 'avg_salary': 100000.0,
# 'avg_age': 35.2
# }
# With filters
eng_stats = (employees
.where('department', '==', 'Engineering')
.aggregate(
count=Count(),
total_salary=Sum('salary')
))
# Returns: {'count': 50, 'total_salary': 5000000}
# Financial dashboard
financials = (transactions
.where('date', '>=', start_date)
.aggregate(
total_transactions=Count(),
total_revenue=Sum('amount'),
avg_transaction=Avg('amount')
))
Note: - Much more efficient than multiple separate aggregation queries - All aggregations execute in a single round-trip to Firestore - Null values are ignored in sum and average calculations
Source code in src/fire_prox/fire_query.py
572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 | |
avg(field)
Average a numeric field across all matching documents.
Executes an aggregation query to calculate the arithmetic mean of a specific field without fetching the actual documents. The field must contain numeric values (int or float).
Args: field: Name of the numeric field to average.
Returns: Average of the field values across all matching documents. Returns 0.0 if no documents match or if all values are null.
Raises: ValueError: If field is None or empty.
Example: # Average age of all users avg_age = users.avg('age') # Returns: 32.5
# Average with filters
avg_salary = (employees
.where('department', '==', 'Engineering')
.avg('salary'))
# Returns: 125000.0
# Average rating for active products
avg_rating = (products
.where('active', '==', True)
.avg('rating'))
# Returns: 4.2
Note: - Null values are ignored in the average calculation - Non-numeric values will cause an error - This is more efficient than fetching all documents
Source code in src/fire_prox/fire_query.py
518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 | |
count()
Count documents matching the query.
Executes an aggregation query to count the number of documents that match the current query filters without fetching the actual documents. This is more efficient than fetching all documents and counting them.
Returns: Integer count of matching documents. Returns 0 if no documents match.
Example: # Count all users total_users = users.count() # Returns: 150
# Count with filters
active_users = users.where('active', '==', True).count()
# Returns: 42
# Count with complex query
count = (users
.where('age', '>', 25)
.where('country', '==', 'USA')
.count())
# Returns: 37
Note: This uses Firestore's native aggregation API, which is more efficient than fetching documents. However, it still counts as one document read per 1000 documents in the collection.
Source code in src/fire_prox/fire_query.py
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 | |
end_at(*document_fields_or_snapshot)
End query results at a cursor position (inclusive).
Creates a new FireQuery that ends at the specified cursor. The cursor document is included in the results.
Args: *document_fields_or_snapshot: Either: - A dictionary of field values: {'field': value} - A DocumentSnapshot - Direct field values matching order_by clause order
Returns: A new FireQuery instance with the end cursor applied.
Example: # Get all users up to and including age 50 query = users.order_by('age').end_at({'age': 50})
# Using a specific document as endpoint
target_doc_ref = users.doc('user123')._doc_ref
target_snapshot = target_doc_ref.get()
query = users.order_by('age').end_at(target_snapshot)
Source code in src/fire_prox/fire_query.py
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 | |
end_before(*document_fields_or_snapshot)
End query results before a cursor position (exclusive).
Creates a new FireQuery that ends before the specified cursor. The cursor document itself is excluded from results.
Args: *document_fields_or_snapshot: Either: - A dictionary of field values: {'field': value} - A DocumentSnapshot - Direct field values matching order_by clause order
Returns: A new FireQuery instance with the end-before cursor applied.
Example: # Get all users before age 50 (exclude 50) query = users.order_by('age').end_before({'age': 50})
# Using a specific document as exclusive endpoint
target_doc_ref = users.doc('user123')._doc_ref
target_snapshot = target_doc_ref.get()
query = users.order_by('age').end_before(target_snapshot)
Source code in src/fire_prox/fire_query.py
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 | |
find_nearest(vector_field, query_vector, distance_measure, limit, distance_result_field=None)
Find the nearest neighbors based on vector similarity.
Performs a vector similarity search on top of the current query filters. This allows you to combine pre-filtering with vector search (requires a composite index).
Args: vector_field: Name of the field containing vector embeddings. query_vector: Vector to compare against (google.cloud.firestore_v1.vector.Vector). distance_measure: Distance calculation method (DistanceMeasure.EUCLIDEAN, DistanceMeasure.COSINE, or DistanceMeasure.DOT_PRODUCT). limit: Maximum number of nearest neighbors to return (max 1000). distance_result_field: Optional field name to store the calculated distance in the query results.
Returns: A new FireQuery instance with the vector search applied.
Example: from google.cloud.firestore_v1.base_vector_query import DistanceMeasure from google.cloud.firestore_v1.vector import Vector
# Find nearest neighbors with pre-filtering
query = (collection
.where('category', '==', 'tech')
.find_nearest(
vector_field="embedding",
query_vector=Vector([0.1, 0.2, 0.3]),
distance_measure=DistanceMeasure.COSINE,
limit=5
))
for doc in query.get():
print(f"{doc.title}: {doc.category}")
Note: - Requires a composite index when combining with where() clauses - Maximum limit is 1000 documents - Does not work with Firestore emulator (production only)
Source code in src/fire_prox/fire_query.py
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 | |
get()
Execute the query and return results as a list.
Fetches all matching documents and hydrates them into FireObject instances in LOADED state. If a projection is active (via .select()), returns vanilla dictionaries instead of FireObject instances.
Returns: - If no projection: List of FireObject instances for all documents matching the query. - If projection active: List of dictionaries containing only the selected fields. DocumentReferences are converted to FireObjects. - Empty list if no documents match.
Example: # Get all results as FireObjects users = query.get() for user in users: print(f"{user.name}: {user.birth_year}")
# Get projected results as dictionaries
users = query.select('name', 'email').get()
for user_dict in users:
print(f"{user_dict['name']}: {user_dict['email']}")
# Check if results exist
results = query.get()
if results:
print(f"Found {len(results)} users")
else:
print("No users found")
Source code in src/fire_prox/fire_query.py
741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 | |
limit(count)
Limit the number of results returned.
Creates a new FireQuery that will return at most count results.
Args: count: Maximum number of documents to return. Must be positive.
Returns: A new FireQuery instance with the limit applied.
Raises: ValueError: If count is not positive.
Example: # Get top 10 results query = users.order_by('score', direction='DESCENDING').limit(10)
# Get first 5 matching documents
query = users.where('active', '==', True).limit(5)
Source code in src/fire_prox/fire_query.py
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | |
on_snapshot(callback)
Listen for real-time updates to this query.
This method sets up a real-time listener that fires the callback whenever any document matching the query changes. The listener runs on a separate thread managed by the Firestore SDK.
Important: This is a sync-only feature. The listener uses the underlying synchronous query to run on a background thread. This is the standard Firestore pattern for real-time listeners in Python.
Args: callback: Callback function invoked on query changes. Signature: callback(query_snapshot, changes, read_time) - query_snapshot: List of DocumentSnapshot objects matching the query - changes: List of DocumentChange objects (ADDED, MODIFIED, REMOVED) - read_time: Timestamp of the snapshot
Returns:
Watch object with an .unsubscribe() method to stop listening.
Example: import threading
callback_done = threading.Event()
def on_change(query_snapshot, changes, read_time):
for change in changes:
if change.type.name == 'ADDED':
print(f"New: {change.document.id}")
elif change.type.name == 'MODIFIED':
print(f"Modified: {change.document.id}")
elif change.type.name == 'REMOVED':
print(f"Removed: {change.document.id}")
callback_done.set()
# Listen to active users only
active_users = users.where('status', '==', 'active')
watch = active_users.on_snapshot(on_change)
# Wait for initial snapshot
callback_done.wait()
# Later: stop listening
watch.unsubscribe()
Note: The callback runs on a separate thread. Use threading primitives (Event, Lock, Queue) for synchronization with your main thread.
Source code in src/fire_prox/fire_query.py
838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 | |
order_by(field, direction='ASCENDING')
Add an ordering clause to the query.
Creates a new FireQuery with ordering by the specified field.
Args: field: The field path to order by. direction: Sort direction. Either 'ASCENDING' or 'DESCENDING'. Default is 'ASCENDING'.
Returns: A new FireQuery instance with the ordering applied.
Example: # Ascending order query = users.order_by('birth_year')
# Descending order
query = users.order_by('birth_year', direction='DESCENDING')
# Multiple orderings (chained)
query = (users
.order_by('country')
.order_by('birth_year', direction='DESCENDING'))
Source code in src/fire_prox/fire_query.py
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | |
select(*field_paths)
Select specific fields to return (projection).
Creates a new FireQuery that only returns the specified fields in the query results. When using projections, query results will be returned as vanilla dictionaries instead of FireObject instances. Any DocumentReferences in the returned dictionaries will be automatically converted to FireObject instances in ATTACHED state.
Args: *field_paths: One or more field paths to select. Field paths can include nested fields using dot notation (e.g., 'address.city').
Returns: A new FireQuery instance with the projection applied.
Raises: ValueError: If no field paths are provided.
Example: # Select a single field query = users.select('name') results = query.get() # Returns: [{'name': 'Alice'}, {'name': 'Bob'}, ...]
# Select multiple fields
query = users.select('name', 'email', 'birth_year')
results = query.get()
# Returns: [{'name': 'Alice', 'email': 'alice@example.com', 'birth_year': 1990}, ...]
# Select with filtering and ordering
query = (users
.where('birth_year', '>', 1990)
.select('name', 'birth_year')
.order_by('birth_year')
.limit(10))
# DocumentReferences are auto-converted to FireObjects
query = posts.select('title', 'author') # author is a DocumentReference
results = query.get()
# results[0]['author'] is a FireObject, not a DocumentReference
print(results[0]['author'].name) # Can access fields after fetch()
Note: - Projection queries return dictionaries, not FireObject instances - Only the selected fields will be present in the returned dictionaries - DocumentReferences are automatically hydrated to FireObject instances - Projected results are more bandwidth-efficient for large documents
Source code in src/fire_prox/fire_query.py
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 | |
start_after(*document_fields_or_snapshot)
Start query results after a cursor position (exclusive).
Creates a new FireQuery that starts after the specified cursor. The cursor document itself is excluded from results. This is typically used for pagination to avoid duplicating the last document from the previous page.
Args: *document_fields_or_snapshot: Either: - A dictionary of field values: {'field': value} - A DocumentSnapshot from a previous query - Direct field values matching order_by clause order
Returns: A new FireQuery instance with the start-after cursor applied.
Example: # Pagination: exclude the last document from previous page page1 = users.order_by('age').limit(10).get() last_age = page1[-1].age page2 = users.order_by('age').start_after({'age': last_age}).limit(10).get()
# Using a document snapshot (common pattern)
last_doc_ref = page1[-1]._doc_ref
last_snapshot = last_doc_ref.get()
page2 = users.order_by('age').start_after(last_snapshot).limit(10).get()
Source code in src/fire_prox/fire_query.py
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 | |
start_at(*document_fields_or_snapshot)
Start query results at a cursor position (inclusive).
Creates a new FireQuery that starts at the specified cursor. The cursor can be a document snapshot or a dictionary of field values matching the order_by fields.
Args: *document_fields_or_snapshot: Either: - A dictionary of field values: {'field': value} - A DocumentSnapshot from a previous query - Direct field values matching order_by clause order
Returns: A new FireQuery instance with the start cursor applied.
Example: # Using field values (requires matching order_by) query = users.order_by('age').start_at({'age': 25})
# Pagination: get first page, then start at last document
page1 = users.order_by('age').limit(10).get()
last_age = page1[-1].age
page2 = users.order_by('age').start_at({'age': last_age}).limit(10).get()
# Using a document snapshot
last_doc_ref = page1[-1]._doc_ref
last_snapshot = last_doc_ref.get()
page2 = users.order_by('age').start_at(last_snapshot).limit(10).get()
Source code in src/fire_prox/fire_query.py
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | |
stream()
Execute the query and stream results as an iterator.
Returns a generator that yields FireObject instances one at a time. This is more memory-efficient than .get() for large result sets as it doesn't load all results into memory at once. If a projection is active (via .select()), yields vanilla dictionaries instead.
Yields: - If no projection: FireObject instances in LOADED state for each matching document. - If projection active: Dictionaries containing only the selected fields. DocumentReferences are converted to FireObjects.
Example: # Stream results one at a time as FireObjects for user in query.stream(): print(f"{user.name}: {user.birth_year}") # Process each user without loading all users into memory
# Stream projected results as dictionaries
for user_dict in query.select('name', 'email').stream():
print(f"{user_dict['name']}: {user_dict['email']}")
# Works with any query
for post in (posts
.where('published', '==', True)
.order_by('date', direction='DESCENDING')
.stream()):
print(post.title)
Source code in src/fire_prox/fire_query.py
790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 | |
sum(field)
Sum a numeric field across all matching documents.
Executes an aggregation query to sum the values of a specific field without fetching the actual documents. The field must contain numeric values (int or float).
Args: field: Name of the numeric field to sum.
Returns: Sum of the field values across all matching documents. Returns 0 if no documents match or if all values are null.
Raises: ValueError: If field is None or empty.
Example: # Sum all salaries total_salary = employees.sum('salary') # Returns: 5000000
# Sum with filters
engineering_salary = (employees
.where('department', '==', 'Engineering')
.sum('salary'))
# Returns: 2500000
# Sum revenue from active products
total_revenue = (products
.where('active', '==', True)
.sum('revenue'))
# Returns: 1250000.50
Note: - Null values are ignored in the sum - Non-numeric values will cause an error - This is more efficient than fetching all documents
Source code in src/fire_prox/fire_query.py
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 | |
where(field, op, value)
Add a filter condition to the query.
Creates a new FireQuery with an additional filter condition. Uses the immutable pattern - returns a new instance rather than modifying the current query.
Args: field: The field path to filter on (e.g., 'name', 'address.city'). op: Comparison operator. Supported operators: '==' (equal), '!=' (not equal), '<' (less than), '<=' (less than or equal), '>' (greater than), '>=' (greater than or equal), 'in' (value in list), 'not-in' (value not in list), 'array-contains' (array contains value), 'array-contains-any' (array contains any of the values). value: The value to compare against.
Returns: A new FireQuery instance with the added filter.
Example: # Single condition query = users.where('birth_year', '>', 1800)
# Multiple conditions (chained)
query = (users
.where('birth_year', '>', 1800)
.where('country', '==', 'England'))
Source code in src/fire_prox/fire_query.py
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | |
State
Bases: Enum
Represents the synchronization state of a FireObject with Firestore.
The state machine ensures that FireObject instances correctly manage their lifecycle from creation through deletion, tracking whether data has been loaded from Firestore and whether local modifications need to be saved.
States: DETACHED: Object exists only in Python memory with no Firestore reference. This is the initial state for newly created documents that haven't been saved yet. All data is considered "dirty" as it's new.
ATTACHED: Object is linked to a Firestore document path and has a valid
DocumentReference, but the document's data has not yet been fetched.
This enables lazy loading - the reference exists but no network
request has been made yet.
LOADED: Object is fully synchronized with Firestore. It has a reference
and its data has been fetched from the server into the local cache.
This is the primary operational state for reading and modifying data.
DELETED: Object represents a document that has been deleted from Firestore.
It retains its ID and path for reference but is marked as defunct
to prevent further modifications or save operations.
State Transitions: DETACHED -> LOADED: Via save() with optional doc_id ATTACHED -> LOADED: Via fetch() or implicit fetch on attribute access LOADED -> LOADED: Via save() (if dirty) or fetch() (refresh) LOADED -> DELETED: Via delete()
Source code in src/fire_prox/state.py
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | |
__repr__()
Return a detailed representation of the state.
Source code in src/fire_prox/state.py
54 55 56 | |
__str__()
Return a human-readable string representation of the state.
Source code in src/fire_prox/state.py
50 51 52 | |
Sum
Bases: AggregationType
Sum aggregation - sums a numeric field across documents.
Requires a field name. The field must contain numeric values (int or float).
Example: # Sum all salaries total = employees.sum('salary')
# Sum via aggregate()
result = employees.aggregate(total_revenue=Sum('revenue'))
# Returns: {'total_revenue': 1500000}
Args: field: Name of the numeric field to sum.
Raises: ValueError: If field is not provided.
Source code in src/fire_prox/aggregation.py
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | |
__init__(field)
Initialize Sum aggregation.
Args: field: Name of the numeric field to sum.
Raises: ValueError: If field is None or empty.
Source code in src/fire_prox/aggregation.py
85 86 87 88 89 90 91 92 93 94 95 96 97 | |
aggregation
Aggregation helper classes for Firestore aggregation queries.
Provides Count, Sum, and Avg aggregation types that can be used with FireQuery.aggregate() method for efficient analytics queries without fetching all documents.
Example: from fire_prox.aggregation import Count, Sum, Avg
# Single aggregation
count = users.where('age', '>', 25).count()
# Multiple aggregations
stats = employees.aggregate(
total=Count(),
sum_salary=Sum('salary'),
avg_age=Avg('age')
)
AggregationType
Base class for aggregation types.
Source code in src/fire_prox/aggregation.py
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | |
__init__(field=None)
Initialize aggregation type.
Args: field: Field name to aggregate (None for Count).
Source code in src/fire_prox/aggregation.py
28 29 30 31 32 33 34 35 | |
__repr__()
Return string representation.
Source code in src/fire_prox/aggregation.py
37 38 39 40 41 | |
Avg
Bases: AggregationType
Average aggregation - averages a numeric field across documents.
Requires a field name. The field must contain numeric values (int or float). Returns the arithmetic mean of all non-null values.
Example: # Average age avg_age = users.avg('age')
# Average via aggregate()
result = users.aggregate(avg_rating=Avg('rating'))
# Returns: {'avg_rating': 4.2}
Args: field: Name of the numeric field to average.
Raises: ValueError: If field is not provided.
Source code in src/fire_prox/aggregation.py
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 | |
__init__(field)
Initialize Avg aggregation.
Args: field: Name of the numeric field to average.
Raises: ValueError: If field is None or empty.
Source code in src/fire_prox/aggregation.py
122 123 124 125 126 127 128 129 130 131 132 133 134 | |
Count
Bases: AggregationType
Count aggregation - counts matching documents.
Does not require a field name since it counts documents, not field values.
Example: # Count all active users count = users.where('active', '==', True).count()
# Count via aggregate()
result = users.aggregate(total_users=Count())
# Returns: {'total_users': 42}
Source code in src/fire_prox/aggregation.py
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | |
__init__()
Initialize Count aggregation (no field needed).
Source code in src/fire_prox/aggregation.py
59 60 61 | |
Sum
Bases: AggregationType
Sum aggregation - sums a numeric field across documents.
Requires a field name. The field must contain numeric values (int or float).
Example: # Sum all salaries total = employees.sum('salary')
# Sum via aggregate()
result = employees.aggregate(total_revenue=Sum('revenue'))
# Returns: {'total_revenue': 1500000}
Args: field: Name of the numeric field to sum.
Raises: ValueError: If field is not provided.
Source code in src/fire_prox/aggregation.py
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | |
__init__(field)
Initialize Sum aggregation.
Args: field: Name of the numeric field to sum.
Raises: ValueError: If field is None or empty.
Source code in src/fire_prox/aggregation.py
85 86 87 88 89 90 91 92 93 94 95 96 97 | |
async_fire_collection
AsyncFireCollection: Async version of FireCollection.
This module implements the asynchronous FireCollection class for use with google.cloud.firestore.AsyncClient.
AsyncFireCollection
Bases: BaseFireCollection
A wrapper around Firestore AsyncCollectionReference for document management.
AsyncFireCollection provides a simplified interface for creating new documents and querying collections asynchronously.
Usage Examples: # Get a collection users = db.collection('users')
# Create a new document in DETACHED state
new_user = users.new()
new_user.name = 'Ada Lovelace'
new_user.year = 1815
await new_user.save()
# Create with explicit ID
user = users.new()
user.name = 'Charles Babbage'
await user.save(doc_id='cbabbage')
# Phase 2: Query the collection
query = users.where('year', '>', 1800).limit(10)
async for user in query.get():
print(user.name)
Source code in src/fire_prox/async_fire_collection.py
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 | |
parent
property
Get the parent document if this is a subcollection.
Phase 2 feature.
Returns: AsyncFireObject representing the parent document if this is a subcollection, None if this is a root-level collection.
aggregate(**aggregations)
async
Execute multiple aggregations in a single query.
Phase 4 Part 5 feature. Performs multiple aggregation operations (count, sum, avg) in one efficient query.
Args: **aggregations: Named aggregation operations using Count(), Sum(), or Avg().
Returns: Dictionary mapping aggregation names to their results.
Example: from fire_prox import Count, Sum, Avg
stats = await users.aggregate(
total=Count(),
total_score=Sum('score'),
avg_age=Avg('age')
)
# Returns: {'total': 42, 'total_score': 5000, 'avg_age': 28.5}
Source code in src/fire_prox/async_fire_collection.py
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 | |
avg(field)
async
Average a numeric field across all documents.
Phase 4 Part 5 feature. Calculates the average of a numeric field without fetching document data.
Args: field: The field name to average.
Returns: The average of the field values (float).
Example: avg_rating = await products.avg('rating')
Source code in src/fire_prox/async_fire_collection.py
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 | |
count()
async
Count documents in the collection.
Phase 4 Part 5 feature. Returns the total count of documents without fetching their data.
Returns: The number of documents in the collection.
Example: total = await users.count() print(f"Total users: {total}")
Source code in src/fire_prox/async_fire_collection.py
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 | |
delete_all(*, batch_size=50, recursive=True, dry_run=False)
async
Delete every document in this collection asynchronously.
Firestore does not expose a server-side "drop collection" operation. This helper batches document deletes and, when recursive is True (default), also clears any nested subcollections before removing the parent document.
Args: batch_size: Maximum number of deletes per commit. recursive: Whether to delete nested subcollections. dry_run: Count affected documents without executing writes.
Returns: Dictionary with counts for deleted documents and subcollections visited during recursion.
Raises: ValueError: If batch_size is not positive.
Source code in src/fire_prox/async_fire_collection.py
395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 | |
doc(doc_id)
Get a reference to a specific document in this collection.
Source code in src/fire_prox/async_fire_collection.py
82 83 84 | |
find_nearest(vector_field, query_vector, distance_measure, limit, distance_result_field=None)
Find the nearest neighbors based on vector similarity.
Performs a vector similarity search to find documents with embeddings nearest to the query vector. Requires a single-field vector index on the vector_field.
Args: vector_field: Name of the field containing vector embeddings. query_vector: Vector to compare against (google.cloud.firestore_v1.vector.Vector). distance_measure: Distance calculation method (DistanceMeasure.EUCLIDEAN, DistanceMeasure.COSINE, or DistanceMeasure.DOT_PRODUCT). limit: Maximum number of nearest neighbors to return (max 1000). distance_result_field: Optional field name to store the calculated distance in the query results.
Returns: An AsyncFireQuery instance for method chaining and execution.
Example: from google.cloud.firestore_v1.base_vector_query import DistanceMeasure from google.cloud.firestore_v1.vector import Vector
collection = db.collection("documents")
query = collection.find_nearest(
vector_field="embedding",
query_vector=Vector([0.1, 0.2, 0.3]),
distance_measure=DistanceMeasure.EUCLIDEAN,
limit=5
)
async for doc in query.stream():
print(f"{doc.title}: {doc.embedding}")
Note: - Requires a vector index on the vector_field - Maximum limit is 1000 documents - Can be combined with where() for pre-filtering (requires composite index) - Does not work with Firestore emulator (production only)
Source code in src/fire_prox/async_fire_collection.py
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 | |
get_all()
async
Retrieve all documents in the collection.
Phase 2.5 feature. Returns an async iterator of all documents.
Yields: AsyncFireObject instances in LOADED state for each document.
Example: async for user in users.get_all(): print(f"{user.name}: {user.year}")
Source code in src/fire_prox/async_fire_collection.py
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 | |
limit(count)
Create a query with a result limit.
Phase 2.5 feature.
Args: count: Maximum number of results to return.
Returns: An AsyncFireQuery instance for method chaining.
Source code in src/fire_prox/async_fire_collection.py
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | |
new()
Create a new AsyncFireObject in DETACHED state.
Source code in src/fire_prox/async_fire_collection.py
78 79 80 | |
order_by(field, direction='ASCENDING')
Create a query with ordering.
Phase 2.5 feature.
Args: field: The field path to order by. direction: 'ASCENDING' or 'DESCENDING'.
Returns: An AsyncFireQuery instance for method chaining.
Source code in src/fire_prox/async_fire_collection.py
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | |
select(*field_paths)
Create a query with field projection.
Phase 4 Part 3 feature. Selects specific fields to return in query results. Returns vanilla dictionaries instead of AsyncFireObject instances.
Args: *field_paths: One or more field paths to select.
Returns: An AsyncFireQuery instance with projection applied.
Example: # Select specific fields results = await users.select('name', 'email').get() # Returns: [{'name': 'Alice', 'email': 'alice@example.com'}, ...]
Source code in src/fire_prox/async_fire_collection.py
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | |
sum(field)
async
Sum a numeric field across all documents.
Phase 4 Part 5 feature. Calculates the sum of a numeric field without fetching document data.
Args: field: The field name to sum.
Returns: The sum of the field values (int or float).
Example: total_revenue = await orders.sum('amount')
Source code in src/fire_prox/async_fire_collection.py
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 | |
where(field, op, value)
Create a query with a filter condition.
Phase 2.5 feature. Builds a lightweight query for common filtering needs.
Args: field: The field path to filter on. op: Comparison operator. value: The value to compare against.
Returns: An AsyncFireQuery instance for method chaining.
Example: query = users.where('birth_year', '>', 1800) .where('country', '==', 'UK') .limit(10) async for user in query.stream(): print(user.name)
Source code in src/fire_prox/async_fire_collection.py
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | |
async_fire_object
AsyncFireObject: Async version of FireObject for AsyncClient.
This module implements the asynchronous FireObject class for use with google.cloud.firestore.AsyncClient.
AsyncFireObject
Bases: BaseFireObject
Asynchronous schemaless, state-aware proxy for a Firestore document.
AsyncFireObject provides an object-oriented interface to Firestore documents using the async/await pattern for all I/O operations.
Lazy Loading: AsyncFireObject supports lazy loading via automatic fetch on attribute access. When accessing an attribute on an ATTACHED object, it will automatically fetch data from Firestore (using a synchronous thread to run the async fetch). This happens once per object - subsequent accesses are instant dict lookups.
Usage Examples: # Create a new document (DETACHED state) user = collection.new() user.name = 'Ada Lovelace' user.year = 1815 await user.save() # Transitions to LOADED
# Load existing document with lazy loading (automatic fetch)
user = db.doc('users/alovelace') # ATTACHED state
print(user.name) # Automatically fetches data, transitions to LOADED
# Or explicitly fetch if preferred
user = db.doc('users/alovelace')
await user.fetch() # Explicit async fetch
print(user.name)
# Update and save
user.year = 1816
await user.save()
# Delete
await user.delete()
Source code in src/fire_prox/async_fire_object.py
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 | |
__getattr__(name)
Handle attribute access for document fields with lazy loading.
This method implements lazy loading: if the object is in ATTACHED state, accessing any data attribute will automatically trigger a synchronous fetch to load the data from Firestore using a companion sync client.
This fetch happens once per object - after the first attribute access, the object transitions to LOADED state and subsequent accesses are instant dict lookups.
Args: name: The attribute name being accessed.
Returns: The value of the field from the internal _data cache.
Raises: AttributeError: If the attribute doesn't exist in _data after fetching (if necessary). NotFound: If document doesn't exist in Firestore (during lazy load).
State Transitions: ATTACHED -> LOADED: Automatically fetches data on first access.
Example: user = db.doc('users/alovelace') # ATTACHED name = user.name # Triggers sync fetch, transitions to LOADED year = user.year # No fetch needed, already LOADED
Source code in src/fire_prox/async_fire_object.py
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | |
collections(names_only=False)
async
List subcollections beneath this document asynchronously.
Args: names_only: When True, return collection IDs instead of wrappers.
Returns: List of subcollection names or AsyncFireCollection wrappers.
Source code in src/fire_prox/async_fire_object.py
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 | |
delete(batch=None, *, recursive=True, batch_size=50)
async
Delete the document from Firestore asynchronously.
Args: batch: Optional batch object for batched deletes. If provided, the delete will be accumulated in the batch (committed later). recursive: When True (default), delete all subcollections first. batch_size: Batch size to use for recursive subcollection cleanup.
Raises: ValueError: If called on DETACHED object. RuntimeError: If called on DELETED object. ValueError: If recursive deletion is requested while using a batch.
State Transitions: ATTACHED -> DELETED LOADED -> DELETED
Example: user = db.doc('users/alovelace') await user.delete()
# Batch delete
batch = db.batch()
user1.delete(batch=batch, recursive=False)
user2.delete(batch=batch, recursive=False)
await batch.commit() # Commit all operations
Source code in src/fire_prox/async_fire_object.py
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 | |
delete_subcollection(name, *, batch_size=50, recursive=True, dry_run=False)
async
Delete a subcollection beneath this document asynchronously.
Args: name: Subcollection name relative to this document. batch_size: Maximum number of deletes per commit. recursive: Whether to delete nested subcollections. dry_run: Count affected documents without executing writes.
Returns: Dictionary with counts for deleted documents and subcollections.
Source code in src/fire_prox/async_fire_object.py
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 | |
fetch(force=False, transaction=None)
async
Fetch document data from Firestore asynchronously.
Args: force: If True, fetch data even if already LOADED. transaction: Optional transaction object for transactional reads.
Returns: Self, to allow method chaining.
Raises: ValueError: If called on DETACHED object. RuntimeError: If called on DELETED object. NotFound: If document doesn't exist.
State Transitions: ATTACHED -> LOADED LOADED -> LOADED (if force=True)
Example: # Normal fetch user = db.doc('users/alovelace') # ATTACHED await user.fetch() # Now LOADED
# Transactional fetch
transaction = db.transaction()
@firestore.async_transactional
async def read_user(transaction):
await user.fetch(transaction=transaction)
return user.credits
credits = await read_user(transaction)
Source code in src/fire_prox/async_fire_object.py
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 | |
from_snapshot(snapshot, parent_collection=None, sync_client=None)
classmethod
Create an AsyncFireObject from a DocumentSnapshot.
Args: snapshot: DocumentSnapshot from native async API. parent_collection: Optional parent collection reference. sync_client: Optional sync Firestore client for async lazy loading.
Returns: AsyncFireObject in LOADED state.
Raises: ValueError: If snapshot doesn't exist.
Example: async for doc in query.stream(): user = AsyncFireObject.from_snapshot(doc)
Source code in src/fire_prox/async_fire_object.py
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 | |
save(doc_id=None, transaction=None, batch=None)
async
Save the object's data to Firestore asynchronously.
Args: doc_id: Optional custom document ID for DETACHED objects. transaction: Optional transaction object for transactional writes. batch: Optional batch object for batched writes. If provided, the write will be accumulated in the batch (committed later).
Returns: Self, to allow method chaining.
Raises: RuntimeError: If called on DELETED object. ValueError: If DETACHED without parent_collection, or if trying to create a new document within a transaction or batch.
State Transitions: DETACHED -> LOADED (creates new document) LOADED -> LOADED (updates if dirty)
Example: # Normal save user = collection.new() user.name = 'Ada' await user.save(doc_id='alovelace')
# Transactional save
transaction = db.transaction()
@firestore.async_transactional
async def update_user(transaction):
await user.fetch(transaction=transaction)
user.credits += 10
await user.save(transaction=transaction)
await update_user(transaction)
# Batch save
batch = db.batch()
user1.save(batch=batch)
user2.save(batch=batch)
await batch.commit() # Commit all operations
Source code in src/fire_prox/async_fire_object.py
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 | |
async_fire_query
AsyncFireQuery: Chainable query builder for Firestore (asynchronous).
This module provides the asynchronous AsyncFireQuery class, which wraps native Firestore AsyncQuery objects and provides a chainable interface for building and executing async queries.
AsyncFireQuery
A chainable query builder for Firestore collections (asynchronous).
AsyncFireQuery wraps the native google-cloud-firestore AsyncQuery object and provides a simplified, chainable interface for building and executing async queries. It follows an immutable pattern - each method returns a new AsyncFireQuery instance with the modified query.
This is the asynchronous implementation. For sync queries, use FireQuery.
Usage Examples: # Basic filtering query = users.where('birth_year', '>', 1800) results = await query.get() for user in results: print(user.name)
# Chaining multiple conditions
query = (users
.where('birth_year', '>', 1800)
.where('country', '==', 'England')
.order_by('birth_year')
.limit(10))
async for user in query.stream():
print(f"{user.name} - {user.birth_year}")
# Async iteration
async for user in users.where('active', '==', True).stream():
print(user.name)
Design Note: For complex queries beyond the scope of this builder (e.g., OR queries, advanced filtering), use the native AsyncQuery API directly and hydrate results with AsyncFireObject.from_snapshot():
native_query = client.collection('users').where(...)
results = [AsyncFireObject.from_snapshot(snap) async for snap in native_query.stream()]
Source code in src/fire_prox/async_fire_query.py
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 | |
__init__(native_query, parent_collection=None, projection=None)
Initialize an AsyncFireQuery.
Args: native_query: The underlying native AsyncQuery object from google-cloud-firestore. parent_collection: Optional reference to parent AsyncFireCollection. projection: Optional tuple of field paths to project (select specific fields).
Source code in src/fire_prox/async_fire_query.py
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | |
__repr__()
Return string representation of the query.
Source code in src/fire_prox/async_fire_query.py
903 904 905 | |
__str__()
Return human-readable string representation.
Source code in src/fire_prox/async_fire_query.py
907 908 909 | |
aggregate(**aggregations)
async
Perform multiple aggregations in a single query.
Executes an aggregation query with multiple aggregation operations (count, sum, average) without fetching the actual documents. This is more efficient than running multiple separate aggregation queries.
Args: **aggregations: Named aggregations using Count(), Sum(field), or Avg(field) from fire_prox.aggregation module.
Returns: Dictionary mapping aggregation names to their results.
Raises: ValueError: If no aggregations are provided or if invalid aggregation types are used.
Example: from fire_prox.aggregation import Count, Sum, Avg
# Multiple aggregations in one query
stats = await employees.aggregate(
total_count=Count(),
total_salary=Sum('salary'),
avg_salary=Avg('salary'),
avg_age=Avg('age')
)
# Returns: {
# 'total_count': 150,
# 'total_salary': 15000000,
# 'avg_salary': 100000.0,
# 'avg_age': 35.2
# }
# With filters
eng_stats = await (employees
.where('department', '==', 'Engineering')
.aggregate(
count=Count(),
total_salary=Sum('salary')
))
# Returns: {'count': 50, 'total_salary': 5000000}
# Financial dashboard
financials = await (transactions
.where('date', '>=', start_date)
.aggregate(
total_transactions=Count(),
total_revenue=Sum('amount'),
avg_transaction=Avg('amount')
))
Note: - Much more efficient than multiple separate aggregation queries - All aggregations execute in a single round-trip to Firestore - Null values are ignored in sum and average calculations
Source code in src/fire_prox/async_fire_query.py
577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 | |
avg(field)
async
Average a numeric field across all matching documents.
Executes an aggregation query to calculate the arithmetic mean of a specific field without fetching the actual documents. The field must contain numeric values (int or float).
Args: field: Name of the numeric field to average.
Returns: Average of the field values across all matching documents. Returns 0.0 if no documents match or if all values are null.
Raises: ValueError: If field is None or empty.
Example: # Average age of all users avg_age = await users.avg('age') # Returns: 32.5
# Average with filters
avg_salary = await (employees
.where('department', '==', 'Engineering')
.avg('salary'))
# Returns: 125000.0
# Average rating for active products
avg_rating = await (products
.where('active', '==', True)
.avg('rating'))
# Returns: 4.2
Note: - Null values are ignored in the average calculation - Non-numeric values will cause an error - This is more efficient than fetching all documents
Source code in src/fire_prox/async_fire_query.py
522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 | |
count()
async
Count documents matching the query.
Executes an aggregation query to count the number of documents that match the current query filters without fetching the actual documents. This is more efficient than fetching all documents and counting them.
Returns: Integer count of matching documents. Returns 0 if no documents match.
Example: # Count all users total_users = await users.count() # Returns: 150
# Count with filters
active_users = await users.where('active', '==', True).count()
# Returns: 42
# Count with complex query
count = await (users
.where('age', '>', 25)
.where('country', '==', 'USA')
.count())
# Returns: 37
Note: This uses Firestore's native aggregation API, which is more efficient than fetching documents. However, it still counts as one document read per 1000 documents in the collection.
Source code in src/fire_prox/async_fire_query.py
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 | |
end_at(*document_fields_or_snapshot)
End query results at a cursor position (inclusive).
Creates a new AsyncFireQuery that ends at the specified cursor. The cursor document is included in the results.
Args: *document_fields_or_snapshot: Either: - A dictionary of field values: {'field': value} - A DocumentSnapshot - Direct field values matching order_by clause order
Returns: A new AsyncFireQuery instance with the end cursor applied.
Example: # Get all users up to and including age 50 query = users.order_by('age').end_at({'age': 50})
# Using a specific document as endpoint
target_doc_ref = users.doc('user123')._doc_ref
target_snapshot = await target_doc_ref.get()
query = users.order_by('age').end_at(target_snapshot)
Source code in src/fire_prox/async_fire_query.py
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 | |
end_before(*document_fields_or_snapshot)
End query results before a cursor position (exclusive).
Creates a new AsyncFireQuery that ends before the specified cursor. The cursor document itself is excluded from results.
Args: *document_fields_or_snapshot: Either: - A dictionary of field values: {'field': value} - A DocumentSnapshot - Direct field values matching order_by clause order
Returns: A new AsyncFireQuery instance with the end-before cursor applied.
Example: # Get all users before age 50 (exclude 50) query = users.order_by('age').end_before({'age': 50})
# Using a specific document as exclusive endpoint
target_doc_ref = users.doc('user123')._doc_ref
target_snapshot = await target_doc_ref.get()
query = users.order_by('age').end_before(target_snapshot)
Source code in src/fire_prox/async_fire_query.py
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 | |
find_nearest(vector_field, query_vector, distance_measure, limit, distance_result_field=None)
Find the nearest neighbors based on vector similarity.
Performs a vector similarity search on top of the current query filters. This allows you to combine pre-filtering with vector search (requires a composite index).
Args: vector_field: Name of the field containing vector embeddings. query_vector: Vector to compare against (google.cloud.firestore_v1.vector.Vector). distance_measure: Distance calculation method (DistanceMeasure.EUCLIDEAN, DistanceMeasure.COSINE, or DistanceMeasure.DOT_PRODUCT). limit: Maximum number of nearest neighbors to return (max 1000). distance_result_field: Optional field name to store the calculated distance in the query results.
Returns: A new AsyncFireQuery instance with the vector search applied.
Example: from google.cloud.firestore_v1.base_vector_query import DistanceMeasure from google.cloud.firestore_v1.vector import Vector
# Find nearest neighbors with pre-filtering
query = (collection
.where('category', '==', 'tech')
.find_nearest(
vector_field="embedding",
query_vector=Vector([0.1, 0.2, 0.3]),
distance_measure=DistanceMeasure.COSINE,
limit=5
))
async for doc in query.stream():
print(f"{doc.title}: {doc.category}")
Note: - Requires a composite index when combining with where() clauses - Maximum limit is 1000 documents - Does not work with Firestore emulator (production only)
Source code in src/fire_prox/async_fire_query.py
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 | |
get()
async
Execute the query and return results as a list.
Fetches all matching documents asynchronously and hydrates them into AsyncFireObject instances in LOADED state. If a projection is active (via .select()), returns vanilla dictionaries instead of AsyncFireObject instances.
Returns: - If no projection: List of AsyncFireObject instances for all documents matching the query. - If projection active: List of dictionaries containing only the selected fields. DocumentReferences are converted to AsyncFireObjects. - Empty list if no documents match.
Example: # Get all results as AsyncFireObjects users = await query.get() for user in users: print(f"{user.name}: {user.birth_year}")
# Get projected results as dictionaries
users = await query.select('name', 'email').get()
for user_dict in users:
print(f"{user_dict['name']}: {user_dict['email']}")
# Check if results exist
results = await query.get()
if results:
print(f"Found {len(results)} users")
else:
print("No users found")
Source code in src/fire_prox/async_fire_query.py
747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 | |
limit(count)
Limit the number of results returned.
Creates a new AsyncFireQuery that will return at most count results.
Args: count: Maximum number of documents to return. Must be positive.
Returns: A new AsyncFireQuery instance with the limit applied.
Raises: ValueError: If count is not positive.
Example: # Get top 10 results query = users.order_by('score', direction='DESCENDING').limit(10)
# Get first 5 matching documents
query = users.where('active', '==', True).limit(5)
Source code in src/fire_prox/async_fire_query.py
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | |
on_snapshot(callback)
Listen for real-time updates to this query.
This method sets up a real-time listener that fires the callback whenever any document matching the query changes. The listener runs on a separate thread managed by the Firestore SDK.
Important: This is a sync-only feature. Even for AsyncFireQuery, the listener uses a synchronous query (via the parent collection's _sync_client) to run on a background thread. This is the standard Firestore pattern for real-time listeners in Python.
Args: callback: Callback function invoked on query changes. Signature: callback(query_snapshot, changes, read_time) - query_snapshot: List of DocumentSnapshot objects matching the query - changes: List of DocumentChange objects (ADDED, MODIFIED, REMOVED) - read_time: Timestamp of the snapshot
Returns:
Watch object with an .unsubscribe() method to stop listening.
Example: import threading
callback_done = threading.Event()
def on_change(query_snapshot, changes, read_time):
for change in changes:
if change.type.name == 'ADDED':
print(f"New: {change.document.id}")
elif change.type.name == 'MODIFIED':
print(f"Modified: {change.document.id}")
elif change.type.name == 'REMOVED':
print(f"Removed: {change.document.id}")
callback_done.set()
# Listen to active users only (async query)
active_users = users.where('status', '==', 'active')
watch = active_users.on_snapshot(on_change)
# Wait for initial snapshot
callback_done.wait()
# Later: stop listening
watch.unsubscribe()
Note: The callback runs on a separate thread. Use threading primitives (Event, Lock, Queue) for synchronization with your main thread.
Source code in src/fire_prox/async_fire_query.py
847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 | |
order_by(field, direction='ASCENDING')
Add an ordering clause to the query.
Creates a new AsyncFireQuery with ordering by the specified field.
Args: field: The field path to order by. direction: Sort direction. Either 'ASCENDING' or 'DESCENDING'. Default is 'ASCENDING'.
Returns: A new AsyncFireQuery instance with the ordering applied.
Example: # Ascending order query = users.order_by('birth_year')
# Descending order
query = users.order_by('birth_year', direction='DESCENDING')
# Multiple orderings (chained)
query = (users
.order_by('country')
.order_by('birth_year', direction='DESCENDING'))
Source code in src/fire_prox/async_fire_query.py
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | |
select(*field_paths)
Select specific fields to return (projection).
Creates a new AsyncFireQuery that only returns the specified fields in the query results. When using projections, query results will be returned as vanilla dictionaries instead of AsyncFireObject instances. Any DocumentReferences in the returned dictionaries will be automatically converted to AsyncFireObject instances in ATTACHED state.
Args: *field_paths: One or more field paths to select. Field paths can include nested fields using dot notation (e.g., 'address.city').
Returns: A new AsyncFireQuery instance with the projection applied.
Raises: ValueError: If no field paths are provided.
Example: # Select a single field query = users.select('name') results = await query.get() # Returns: [{'name': 'Alice'}, {'name': 'Bob'}, ...]
# Select multiple fields
query = users.select('name', 'email', 'birth_year')
results = await query.get()
# Returns: [{'name': 'Alice', 'email': 'alice@example.com', 'birth_year': 1990}, ...]
# Select with filtering and ordering
query = (users
.where('birth_year', '>', 1990)
.select('name', 'birth_year')
.order_by('birth_year')
.limit(10))
# DocumentReferences are auto-converted to AsyncFireObjects
query = posts.select('title', 'author') # author is a DocumentReference
results = await query.get()
# results[0]['author'] is an AsyncFireObject, not a DocumentReference
await results[0]['author'].fetch()
print(results[0]['author'].name)
Note: - Projection queries return dictionaries, not AsyncFireObject instances - Only the selected fields will be present in the returned dictionaries - DocumentReferences are automatically hydrated to AsyncFireObject instances - Projected results are more bandwidth-efficient for large documents
Source code in src/fire_prox/async_fire_query.py
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 | |
start_after(*document_fields_or_snapshot)
Start query results after a cursor position (exclusive).
Creates a new AsyncFireQuery that starts after the specified cursor. The cursor document itself is excluded from results. This is typically used for pagination to avoid duplicating the last document from the previous page.
Args: *document_fields_or_snapshot: Either: - A dictionary of field values: {'field': value} - A DocumentSnapshot from a previous query - Direct field values matching order_by clause order
Returns: A new AsyncFireQuery instance with the start-after cursor applied.
Example: # Pagination: exclude the last document from previous page page1 = await users.order_by('age').limit(10).get() last_age = page1[-1].age page2 = await users.order_by('age').start_after({'age': last_age}).limit(10).get()
# Using a document snapshot (common pattern)
last_doc_ref = page1[-1]._doc_ref
last_snapshot = await last_doc_ref.get()
page2 = await users.order_by('age').start_after(last_snapshot).limit(10).get()
Source code in src/fire_prox/async_fire_query.py
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 | |
start_at(*document_fields_or_snapshot)
Start query results at a cursor position (inclusive).
Creates a new AsyncFireQuery that starts at the specified cursor. The cursor can be a document snapshot or a dictionary of field values matching the order_by fields.
Args: *document_fields_or_snapshot: Either: - A dictionary of field values: {'field': value} - A DocumentSnapshot from a previous query - Direct field values matching order_by clause order
Returns: A new AsyncFireQuery instance with the start cursor applied.
Example: # Using field values (requires matching order_by) query = users.order_by('age').start_at({'age': 25})
# Pagination: get first page, then start at last document
page1 = await users.order_by('age').limit(10).get()
last_age = page1[-1].age
page2 = await users.order_by('age').start_at({'age': last_age}).limit(10).get()
# Using a document snapshot
last_doc_ref = page1[-1]._doc_ref
last_snapshot = await last_doc_ref.get()
page2 = await users.order_by('age').start_at(last_snapshot).limit(10).get()
Source code in src/fire_prox/async_fire_query.py
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | |
stream()
async
Execute the query and stream results as an async iterator.
Returns an async generator that yields AsyncFireObject instances one at a time. This is more memory-efficient than .get() for large result sets as it doesn't load all results into memory at once. If a projection is active (via .select()), yields vanilla dictionaries instead.
Yields: - If no projection: AsyncFireObject instances in LOADED state for each matching document. - If projection active: Dictionaries containing only the selected fields. DocumentReferences are converted to AsyncFireObjects.
Example: # Stream results one at a time as AsyncFireObjects async for user in query.stream(): print(f"{user.name}: {user.birth_year}") # Process each user without loading all users into memory
# Stream projected results as dictionaries
async for user_dict in query.select('name', 'email').stream():
print(f"{user_dict['name']}: {user_dict['email']}")
# Works with any query
async for post in (posts
.where('published', '==', True)
.order_by('date', direction='DESCENDING')
.stream()):
print(post.title)
Source code in src/fire_prox/async_fire_query.py
799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 | |
sum(field)
async
Sum a numeric field across all matching documents.
Executes an aggregation query to sum the values of a specific field without fetching the actual documents. The field must contain numeric values (int or float).
Args: field: Name of the numeric field to sum.
Returns: Sum of the field values across all matching documents. Returns 0 if no documents match or if all values are null.
Raises: ValueError: If field is None or empty.
Example: # Sum all salaries total_salary = await employees.sum('salary') # Returns: 5000000
# Sum with filters
engineering_salary = await (employees
.where('department', '==', 'Engineering')
.sum('salary'))
# Returns: 2500000
# Sum revenue from active products
total_revenue = await (products
.where('active', '==', True)
.sum('revenue'))
# Returns: 1250000.50
Note: - Null values are ignored in the sum - Non-numeric values will cause an error - This is more efficient than fetching all documents
Source code in src/fire_prox/async_fire_query.py
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 | |
where(field, op, value)
Add a filter condition to the query.
Creates a new AsyncFireQuery with an additional filter condition. Uses the immutable pattern - returns a new instance rather than modifying the current query.
Args: field: The field path to filter on (e.g., 'name', 'address.city'). op: Comparison operator. Supported operators: '==' (equal), '!=' (not equal), '<' (less than), '<=' (less than or equal), '>' (greater than), '>=' (greater than or equal), 'in' (value in list), 'not-in' (value not in list), 'array-contains' (array contains value), 'array-contains-any' (array contains any of the values). value: The value to compare against.
Returns: A new AsyncFireQuery instance with the added filter.
Example: # Single condition query = users.where('birth_year', '>', 1800)
# Multiple conditions (chained)
query = (users
.where('birth_year', '>', 1800)
.where('country', '==', 'England'))
Source code in src/fire_prox/async_fire_query.py
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | |
async_fireprox
AsyncFireProx: Main entry point for async FireProx usage.
This module provides the AsyncFireProx class, which serves as the primary interface for users to interact with Firestore asynchronously through the FireProx API.
AsyncFireProx
Bases: BaseFireProx
Main entry point for the async FireProx library.
AsyncFireProx wraps the native google-cloud-firestore AsyncClient and provides a simplified, Pythonic interface for working with Firestore asynchronously.
Usage Examples: # Initialize with a pre-configured native async client from google.cloud import firestore from fire_prox import AsyncFireProx
native_client = firestore.AsyncClient(project='my-project')
db = AsyncFireProx(native_client)
# Access a document (ATTACHED state)
user = db.doc('users/alovelace')
await user.fetch()
print(user.name)
# Create a new document
users = db.collection('users')
new_user = users.new()
new_user.name = 'Charles Babbage'
new_user.year = 1791
await new_user.save()
# Update a document
user = db.doc('users/alovelace')
await user.fetch()
user.year = 1816
await user.save()
# Delete a document
await user.delete()
Source code in src/fire_prox/async_fireprox.py
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | |
__init__(client)
Initialize AsyncFireProx with a native async Firestore client.
Args: client: A configured google.cloud.firestore.AsyncClient instance. Authentication and project configuration should be handled before creating this instance.
Raises: TypeError: If client is not a google.cloud.firestore.AsyncClient.
Example: from google.cloud import firestore from fire_prox import AsyncFireProx
# Option 1: Default credentials
native_client = firestore.AsyncClient()
# Option 2: Explicit project
native_client = firestore.AsyncClient(project='my-project-id')
# Initialize AsyncFireProx
db = AsyncFireProx(native_client)
Source code in src/fire_prox/async_fireprox.py
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | |
collection(path)
Get a reference to a collection by its path.
Creates an AsyncFireCollection wrapper around the native AsyncCollectionReference.
Args: path: The collection path, e.g., 'users' or 'users/uid/posts'. Must have an odd number of segments.
Returns: An AsyncFireCollection instance.
Raises: ValueError: If path has an even number of segments.
Example: # Root-level collection users = db.collection('users') new_user = users.new() new_user.name = 'Ada' await new_user.save()
# Subcollection
posts = db.collection('users/alovelace/posts')
new_post = posts.new()
new_post.title = 'Analysis Engine'
await new_post.save()
Source code in src/fire_prox/async_fireprox.py
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | |
collections(path, *, names_only=False)
async
List subcollections beneath the specified document path asynchronously.
Args: path: Document path whose subcollections should be listed. names_only: Return collection IDs instead of AsyncFireCollection wrappers.
Returns: List of subcollection names or AsyncFireCollection wrappers.
Source code in src/fire_prox/async_fireprox.py
181 182 183 184 185 186 187 188 189 190 191 192 193 | |
doc(path)
Get a reference to a document by its full path.
Creates an AsyncFireObject in ATTACHED state. No data is fetched from Firestore until fetch() is called or an attribute is accessed (lazy loading).
Args: path: The full document path, e.g., 'users/alovelace' or 'users/uid/posts/post123'. Must be a valid Firestore document path with an even number of segments.
Returns: An AsyncFireObject instance in ATTACHED state.
Raises: ValueError: If path has an odd number of segments.
Example: # Root-level document with lazy loading user = db.doc('users/alovelace') print(user.name) # Triggers automatic fetch
# Or explicit fetch
user = db.doc('users/alovelace')
await user.fetch()
print(user.name)
# Nested document (subcollection)
post = db.doc('users/alovelace/posts/post123')
await post.fetch()
Source code in src/fire_prox/async_fireprox.py
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | |
document(path)
Alias for doc(). Get a reference to a document by its full path.
Args: path: The full document path.
Returns: An AsyncFireObject instance in ATTACHED state.
Source code in src/fire_prox/async_fireprox.py
133 134 135 136 137 138 139 140 141 142 143 | |
base_fire_collection
BaseFireCollection: Shared logic for sync and async FireCollection implementations.
This module contains the base class that implements all logic that is identical between synchronous and asynchronous FireCollection implementations.
BaseFireCollection
Base class for FireCollection implementations (sync and async).
Contains all shared logic: - Initialization - Properties (id, path) - String representations
Subclasses must implement: - _instantiate_object() - creates FireObject/AsyncFireObject - Query methods (Phase 2)
Source code in src/fire_prox/base_fire_collection.py
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 | |
id
property
Get the collection ID (last segment of collection path).
Returns: The collection ID string.
path
property
Get the full Firestore path of the collection.
Returns: The full path string (e.g., 'users' or 'users/uid/posts').
__init__(collection_ref, client=None, sync_client=None)
Initialize a FireCollection.
Args: collection_ref: The underlying CollectionReference from google-cloud-firestore. client: Optional reference to the parent FireProx client. sync_client: Optional sync Firestore client for lazy loading (async only).
Source code in src/fire_prox/base_fire_collection.py
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | |
__repr__()
Return a detailed string representation for debugging.
Returns: String showing collection path.
Source code in src/fire_prox/base_fire_collection.py
196 197 198 199 200 201 202 203 | |
__str__()
Return a human-readable string representation.
Returns: String showing the collection path.
Source code in src/fire_prox/base_fire_collection.py
205 206 207 208 209 210 211 212 | |
batch()
Create a batch for accumulating multiple write operations.
Convenience method for creating batches directly from a collection reference, eliminating the need to access the root FireProx client.
Returns: A native google.cloud.firestore.WriteBatch or google.cloud.firestore.AsyncWriteBatch instance.
Example: users = db.collection('users') batch = users.batch()
# Accumulate operations
user1 = users.doc('alice')
user1.name = 'Alice'
user1.save(batch=batch)
user2 = users.doc('bob')
user2.name = 'Bob'
user2.save(batch=batch)
# Commit all operations atomically
batch.commit()
Note: See BaseFireProx.batch() for detailed documentation on batch operations.
Source code in src/fire_prox/base_fire_collection.py
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | |
doc(doc_id)
Create a document proxy in ATTACHED state.
Source code in src/fire_prox/base_fire_collection.py
95 96 97 98 99 100 101 102 103 | |
new()
Create a new document proxy in DETACHED state.
Source code in src/fire_prox/base_fire_collection.py
86 87 88 89 90 91 92 93 | |
on_snapshot(callback)
Listen for real-time updates to this collection.
This method sets up a real-time listener that fires the callback whenever any document in the collection changes. The listener runs on a separate thread managed by the Firestore SDK.
Important: This is a sync-only feature. Even for AsyncFireCollection instances, the listener uses the synchronous client (via _sync_client) to run on a background thread. This is the standard Firestore pattern for real-time listeners in Python.
Args: callback: Callback function invoked on collection changes. Signature: callback(col_snapshot, changes, read_time) - col_snapshot: List of DocumentSnapshot objects - changes: List of DocumentChange objects (ADDED, MODIFIED, REMOVED) - read_time: Timestamp of the snapshot
Returns:
Watch object with an .unsubscribe() method to stop listening.
Example: import threading
callback_done = threading.Event()
def on_change(col_snapshot, changes, read_time):
for change in changes:
if change.type.name == 'ADDED':
print(f"New document: {change.document.id}")
elif change.type.name == 'MODIFIED':
print(f"Modified document: {change.document.id}")
elif change.type.name == 'REMOVED':
print(f"Removed document: {change.document.id}")
callback_done.set()
# Start listening to a collection
users = db.collection('users')
watch = users.on_snapshot(on_change)
# Wait for initial snapshot
callback_done.wait()
# Later: stop listening
watch.unsubscribe()
Note: The callback runs on a separate thread. Use threading primitives (Event, Lock, Queue) for synchronization with your main thread.
Source code in src/fire_prox/base_fire_collection.py
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 | |
transaction()
Create a transaction for atomic read-modify-write operations.
Convenience method for creating transactions directly from a collection reference, eliminating the need to access the root FireProx client.
Returns: A native google.cloud.firestore.Transaction or google.cloud.firestore.AsyncTransaction instance.
Example: users = db.collection('users') transaction = users.transaction()
@firestore.transactional
def update_user(transaction, user_id):
user = users.doc(user_id)
user.fetch(transaction=transaction)
user.visits += 1
user.save(transaction=transaction)
update_user(transaction, 'alice')
Source code in src/fire_prox/base_fire_collection.py
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | |
base_fire_object
BaseFireObject: Shared logic for sync and async FireObject implementations.
This module contains the base class that implements all logic that is identical between synchronous and asynchronous FireObject implementations.
BaseFireObject
Base class for FireObject implementations (sync and async).
Contains all shared logic: - State management - State inspection methods - Dirty tracking - Data dictionary management - Property accessors - String representations
Subclasses must implement: - fetch() - with appropriate sync/async signature - save() - with appropriate sync/async signature - delete() - with appropriate sync/async signature - getattr() - may need async support for lazy loading
Source code in src/fire_prox/base_fire_object.py
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 | |
deleted_fields
property
Get the set of deleted field names (Phase 2).
dirty_fields
property
Get the set of modified field names (Phase 2).
id
property
Get document ID, or None if DETACHED.
path
property
Get full document path, or None if DETACHED.
state
property
Get current state of the object.
__delattr__(name)
Remove field from _data and track in deleted fields.
Phase 2: Track deletions for efficient partial updates with DELETE_FIELD.
Source code in src/fire_prox/base_fire_object.py
625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 | |
__init__(doc_ref=None, initial_state=None, parent_collection=None, sync_doc_ref=None, sync_client=None)
Initialize a FireObject.
Args: doc_ref: Optional DocumentReference from native client. initial_state: Initial state (defaults to DETACHED if no doc_ref, ATTACHED if doc_ref provided). parent_collection: Optional reference to parent FireCollection (needed for save() on DETACHED objects). sync_doc_ref: Optional sync DocumentReference (for async lazy loading). sync_client: Optional sync Firestore Client (for async subcollections).
Source code in src/fire_prox/base_fire_object.py
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | |
__repr__()
Return detailed string representation.
Source code in src/fire_prox/base_fire_object.py
662 663 664 665 666 667 | |
__setattr__(name, value)
Store attribute in _data dictionary and track in dirty fields.
Internal attributes (starting with _) are stored directly on object.
Phase 2: Track field-level changes for efficient partial updates. Enforces mutual exclusivity between vanilla and atomic operations.
Source code in src/fire_prox/base_fire_object.py
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 | |
__str__()
Return human-readable string representation.
Source code in src/fire_prox/base_fire_object.py
669 670 671 672 673 | |
array_remove(field, values)
Mark field for ArrayRemove operation and simulate locally.
Phase 2 feature. ArrayRemove removes specified elements from an array field without reading the document first.
The operation is simulated locally, so the array is immediately updated in memory. This eliminates the need to call fetch() after save().
Mutual Exclusivity: A field can be either modified directly (vanilla) OR via atomic operations, but not both. Once array_remove() is called on a field, you cannot modify that field directly until after save().
Args: field: The field name to apply ArrayRemove to. values: List of values to remove from the array.
Raises: RuntimeError: If called on a DELETED object. ValueError: If the field has been modified directly (is dirty).
Example: user = db.doc('users/ada') user.array_remove('tags', ['deprecated']) user.save() # No fetch() needed - local state is already updated!
Source code in src/fire_prox/base_fire_object.py
491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 | |
array_union(field, values)
Mark field for ArrayUnion operation and simulate locally.
Phase 2 feature. ArrayUnion adds elements to an array field without reading the document first. If the array doesn't exist, it creates it. Duplicate values are automatically deduplicated.
The operation is simulated locally, so the array is immediately updated in memory. This eliminates the need to call fetch() after save().
Mutual Exclusivity: A field can be either modified directly (vanilla) OR via atomic operations, but not both. Once array_union() is called on a field, you cannot modify that field directly until after save().
Args: field: The field name to apply ArrayUnion to. values: List of values to add to the array.
Raises: RuntimeError: If called on a DELETED object. ValueError: If the field has been modified directly (is dirty).
Example: user = db.doc('users/ada') user.array_union('tags', ['python', 'firestore']) user.save() # No fetch() needed - local state is already updated!
Source code in src/fire_prox/base_fire_object.py
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 | |
batch()
Create a batch for accumulating multiple write operations.
Convenience method for creating batches directly from a document reference, eliminating the need to access the root FireProx client.
Returns: A native google.cloud.firestore.WriteBatch or google.cloud.firestore.AsyncWriteBatch instance.
Raises: ValueError: If called on a DETACHED object (no document path yet).
Example: user = db.doc('users/alice') batch = user.batch()
# Use the batch for multiple operations
user.credits = 100
user.save(batch=batch)
other_user = db.doc('users/bob')
other_user.delete(batch=batch, recursive=False)
# Commit all operations atomically
batch.commit()
Note: See BaseFireProx.batch() for detailed documentation on batch operations.
Source code in src/fire_prox/base_fire_object.py
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 | |
collection(name)
Get a subcollection reference for this document.
Phase 2 feature. Returns a collection reference for a subcollection under this document, enabling hierarchical data structures.
Args: name: Name of the subcollection.
Returns: FireCollection or AsyncFireCollection instance for the subcollection.
Raises: ValueError: If called on a DETACHED object (no document path yet). RuntimeError: If called on a DELETED object.
Example: user = db.doc('users/alovelace') posts = user.collection('posts') # Gets 'users/alovelace/posts' new_post = posts.new() new_post.title = "On Analytical Engines" new_post.save()
Source code in src/fire_prox/base_fire_object.py
395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 | |
increment(field, value)
Mark field for Increment operation and simulate locally.
Phase 2 feature. Increment atomically increments a numeric field by the given value without reading the document first. If the field doesn't exist, it treats it as 0.
The operation is simulated locally, so the field value is immediately updated in memory. This eliminates the need to call fetch() after save().
Mutual Exclusivity: A field can be either modified directly (vanilla) OR via atomic operations, but not both. Once increment() is called on a field, you cannot modify that field directly until after save().
Args: field: The field name to increment. value: The amount to increment by (can be negative to decrement).
Raises: RuntimeError: If called on a DELETED object. ValueError: If the field has been modified directly (is dirty).
Example: user = db.doc('users/ada') user.increment('view_count', 1) user.increment('score', -5) # Decrement by 5 user.save() # No fetch() needed - local state is already updated!
Source code in src/fire_prox/base_fire_object.py
536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 | |
is_attached()
Check if object has a DocumentReference (ATTACHED or LOADED).
Source code in src/fire_prox/base_fire_object.py
276 277 278 | |
is_deleted()
Check if object is in DELETED state.
Source code in src/fire_prox/base_fire_object.py
284 285 286 | |
is_detached()
Check if object is in DETACHED state.
Source code in src/fire_prox/base_fire_object.py
272 273 274 | |
is_dirty()
Check if object has unsaved changes.
Source code in src/fire_prox/base_fire_object.py
288 289 290 291 292 293 294 | |
is_loaded()
Check if object is in LOADED state.
Source code in src/fire_prox/base_fire_object.py
280 281 282 | |
on_snapshot(callback)
Listen for real-time updates to this document.
This method sets up a real-time listener that fires the callback whenever the document changes in Firestore. The listener runs on a separate thread managed by the Firestore SDK.
Important: This is a sync-only feature. Even for AsyncFireObject instances, the listener uses the synchronous client (via _sync_doc_ref) to run on a background thread. This is the standard Firestore pattern for real-time listeners in Python.
Args: callback: Callback function invoked on document changes. Signature: callback(doc_snapshot, changes, read_time) - doc_snapshot: List of DocumentSnapshot objects - changes: List of DocumentChange objects - read_time: Timestamp of the snapshot
Returns:
Watch object with an .unsubscribe() method to stop listening.
Raises: ValueError: If called on a DETACHED object (no document path). RuntimeError: If called on a DELETED object.
Example: import threading
# Create event for synchronization
callback_done = threading.Event()
def on_change(doc_snapshot, changes, read_time):
for doc in doc_snapshot:
print(f"Document updated: {doc.to_dict()}")
callback_done.set()
# Start listening
user = db.doc('users/alice')
watch = user.on_snapshot(on_change)
# Wait for initial snapshot
callback_done.wait()
# Later: stop listening
watch.unsubscribe()
Note: The callback runs on a separate thread. Use threading primitives (Event, Lock, Queue) for synchronization with your main thread.
Source code in src/fire_prox/base_fire_object.py
757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 | |
to_dict()
Return shallow copy of internal data.
Returns: Dictionary containing all document fields.
Raises: RuntimeError: If object is in ATTACHED state (data not loaded).
Source code in src/fire_prox/base_fire_object.py
647 648 649 650 651 652 653 654 655 656 657 658 659 660 | |
transaction()
Create a transaction for atomic read-modify-write operations.
Convenience method for creating transactions directly from a document reference, eliminating the need to access the root FireProx client.
Returns: A native google.cloud.firestore.Transaction or google.cloud.firestore.AsyncTransaction instance.
Raises: ValueError: If called on a DETACHED object (no document path yet).
Example: user = db.doc('users/alice') transaction = user.transaction()
@firestore.transactional
def update_credits(transaction):
user.fetch(transaction=transaction)
user.credits += 10
user.save(transaction=transaction)
update_credits(transaction)
Source code in src/fire_prox/base_fire_object.py
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 | |
base_fireprox
BaseFireProx: Shared logic for sync and async FireProx implementations.
This module contains the base class that implements all logic that is identical between synchronous and asynchronous FireProx implementations.
BaseFireProx
Base class for FireProx implementations (sync and async).
Contains all shared logic: - Client storage - Path validation - String representations
Subclasses must implement: - doc() - creates FireObject/AsyncFireObject - collection() - creates FireCollection/AsyncFireCollection
Source code in src/fire_prox/base_fireprox.py
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 | |
client
property
Alias for native_client. Get the underlying Firestore Client.
Returns: The google.cloud.firestore.Client or AsyncClient instance.
native_client
property
Get the underlying google-cloud-firestore Client.
Provides an "escape hatch" for users who need to perform operations not yet supported by FireProx or who want to use advanced native features like transactions, batched writes, or complex queries.
Returns: The google.cloud.firestore.Client or AsyncClient instance.
__init__(client)
Initialize FireProx with a native Firestore client.
Args: client: A configured google.cloud.firestore.Client or google.cloud.firestore.AsyncClient instance.
Note: Type checking is handled in subclasses since they know which client type to expect.
Source code in src/fire_prox/base_fireprox.py
27 28 29 30 31 32 33 34 35 36 37 38 39 | |
__repr__()
Return a detailed string representation for debugging.
Returns: String showing the project ID and database.
Source code in src/fire_prox/base_fireprox.py
275 276 277 278 279 280 281 282 283 | |
__str__()
Return a human-readable string representation.
Returns: String showing the project ID.
Source code in src/fire_prox/base_fireprox.py
285 286 287 288 289 290 291 292 293 | |
batch()
Create a batch for accumulating multiple write operations.
Returns the native Firestore WriteBatch object that can be used to accumulate write operations (set, update, delete) and commit them atomically in a single request.
Unlike transactions, batches: - Do NOT support read operations - Do NOT require a decorator - Do NOT automatically retry on conflicts - DO guarantee operation order - ARE more efficient for bulk writes
This method provides a convenient way to create batches without manually accessing the underlying client. The returned batch object is a native Firestore WriteBatch/AsyncWriteBatch.
Returns: A native google.cloud.firestore.WriteBatch or google.cloud.firestore.AsyncWriteBatch instance.
Example (Synchronous): batch = db.batch()
# Accumulate operations
user1 = db.doc('users/alice')
user1.credits = 100
user1.save(batch=batch)
user2 = db.doc('users/bob')
user2.delete(batch=batch, recursive=False)
# Commit all operations atomically
batch.commit()
Example (Asynchronous): batch = db.batch()
# Accumulate operations
user1 = db.doc('users/alice')
user1.credits = 100
await user1.save(batch=batch)
user2 = db.doc('users/bob')
await user2.delete(batch=batch, recursive=False)
# Commit all operations atomically
await batch.commit()
Example (Bulk Operations): batch = db.batch() users = db.collection('users')
# Create multiple documents in one batch
for i in range(100):
user = users.doc(f'user{i}')
user.name = f'User {i}'
user.save(batch=batch)
# All 100 documents created atomically
batch.commit()
Note: - Batches can contain up to 500 operations - All operations execute atomically (all-or-nothing) - Operations execute in the order added - Cannot save DETACHED documents in a batch
Source code in src/fire_prox/base_fireprox.py
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | |
transaction()
Create a transaction for atomic read-modify-write operations.
Returns the native Firestore transaction object that can be used with the @firestore.transactional decorator for synchronous operations or @firestore.async_transactional for asynchronous operations.
This method provides a convenient way to create transactions without manually accessing the underlying client. The returned transaction object is a native Firestore Transaction that should be passed to functions decorated with @firestore.transactional.
Returns: A native google.cloud.firestore.Transaction or google.cloud.firestore.AsyncTransaction instance.
Example (Synchronous): transaction = db.transaction()
@firestore.transactional
def transfer_credits(transaction, from_id, to_id, amount):
from_user = db.doc(f'users/{from_id}')
to_user = db.doc(f'users/{to_id}')
from_user.fetch(transaction=transaction)
to_user.fetch(transaction=transaction)
from_user.credits -= amount
to_user.credits += amount
from_user.save(transaction=transaction)
to_user.save(transaction=transaction)
transfer_credits(transaction, 'alice', 'bob', 100)
Example (Asynchronous): transaction = db.transaction()
@firestore.async_transactional
async def transfer_credits(transaction, from_id, to_id, amount):
from_user = db.doc(f'users/{from_id}')
to_user = db.doc(f'users/{to_id}')
await from_user.fetch(transaction=transaction)
await to_user.fetch(transaction=transaction)
from_user.credits -= amount
to_user.credits += amount
await from_user.save(transaction=transaction)
await to_user.save(transaction=transaction)
await transfer_credits(transaction, 'alice', 'bob', 100)
Source code in src/fire_prox/base_fireprox.py
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | |
fire_collection
FireCollection: Interface for working with Firestore collections (synchronous).
This module provides the synchronous FireCollection class, which represents a Firestore collection and provides methods for creating new documents and querying existing ones.
FireCollection
Bases: BaseFireCollection
A wrapper around Firestore CollectionReference for document management (synchronous).
FireCollection provides a simplified interface for creating new documents and querying collections. It serves as a factory for FireObject instances and (in Phase 2) will provide a lightweight query builder.
This is the synchronous implementation.
Usage Examples: # Get a collection users = db.collection('users')
# Create a new document in DETACHED state
new_user = users.new()
new_user.name = 'Ada Lovelace'
new_user.year = 1815
new_user.save()
# Create with explicit ID
user = users.new()
user.name = 'Charles Babbage'
user.save(doc_id='cbabbage')
# Phase 2: Query the collection
query = users.where('year', '>', 1800).limit(10)
for user in query.get():
print(user.name)
Source code in src/fire_prox/fire_collection.py
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 | |
parent
property
Get the parent document if this is a subcollection.
Returns: FireObject representing the parent document if this is a subcollection, None if this is a root-level collection.
Note: Phase 2 feature. Returns None in Phase 1 as subcollections are not yet implemented.
Example: posts = db.doc('users/alovelace').collection('posts') parent = posts.parent print(parent.path) # 'users/alovelace'
aggregate(**aggregations)
Execute multiple aggregations in a single query.
Phase 4 Part 5 feature. Performs multiple aggregation operations (count, sum, avg) in one efficient query.
Args: **aggregations: Named aggregation operations using Count(), Sum(), or Avg().
Returns: Dictionary mapping aggregation names to their results.
Example: from fire_prox import Count, Sum, Avg
stats = users.aggregate(
total=Count(),
total_score=Sum('score'),
avg_age=Avg('age')
)
# Returns: {'total': 42, 'total_score': 5000, 'avg_age': 28.5}
Source code in src/fire_prox/fire_collection.py
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 | |
avg(field)
Average a numeric field across all documents.
Phase 4 Part 5 feature. Calculates the average of a numeric field without fetching document data.
Args: field: The field name to average.
Returns: The average of the field values (float).
Example: avg_rating = products.avg('rating')
Source code in src/fire_prox/fire_collection.py
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 | |
count()
Count documents in the collection.
Phase 4 Part 5 feature. Returns the total count of documents without fetching their data.
Returns: The number of documents in the collection.
Example: total = users.count() print(f"Total users: {total}")
Source code in src/fire_prox/fire_collection.py
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 | |
delete_all(*, batch_size=50, recursive=True, dry_run=False)
Delete every document in this collection.
Firestore offers no atomic "drop collection" operation. This helper iterates through each document and issues batched deletes. When recursive is True (default) it will also clear any nested subcollections before deleting their parent document.
Args: batch_size: Maximum number of deletes to commit at once. recursive: Whether to delete nested subcollections. dry_run: Count what would be removed without executing writes.
Returns: Dictionary with counts for deleted documents and subcollections visited during recursion.
Raises: ValueError: If batch_size is not positive.
Source code in src/fire_prox/fire_collection.py
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 | |
doc(doc_id)
Get a reference to a specific document in this collection.
Source code in src/fire_prox/fire_collection.py
73 74 75 | |
find_nearest(vector_field, query_vector, distance_measure, limit, distance_result_field=None)
Find the nearest neighbors based on vector similarity.
Performs a vector similarity search to find documents with embeddings nearest to the query vector. Requires a single-field vector index on the vector_field.
Args: vector_field: Name of the field containing vector embeddings. query_vector: Vector to compare against (google.cloud.firestore_v1.vector.Vector). distance_measure: Distance calculation method (DistanceMeasure.EUCLIDEAN, DistanceMeasure.COSINE, or DistanceMeasure.DOT_PRODUCT). limit: Maximum number of nearest neighbors to return (max 1000). distance_result_field: Optional field name to store the calculated distance in the query results.
Returns: A FireQuery instance for method chaining and execution.
Example: from google.cloud.firestore_v1.base_vector_query import DistanceMeasure from google.cloud.firestore_v1.vector import Vector
collection = db.collection("documents")
query = collection.find_nearest(
vector_field="embedding",
query_vector=Vector([0.1, 0.2, 0.3]),
distance_measure=DistanceMeasure.EUCLIDEAN,
limit=5
)
for doc in query.get():
print(f"{doc.title}: {doc.embedding}")
Note: - Requires a vector index on the vector_field - Maximum limit is 1000 documents - Can be combined with where() for pre-filtering (requires composite index) - Does not work with Firestore emulator (production only)
Source code in src/fire_prox/fire_collection.py
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 | |
get_all()
Retrieve all documents in the collection.
Phase 2.5 feature. Returns an iterator of all documents.
Yields: FireObject instances in LOADED state for each document.
Example: for user in users.get_all(): print(f"{user.name}: {user.year}")
Source code in src/fire_prox/fire_collection.py
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 | |
limit(count)
Create a query with a result limit.
Phase 2.5 feature. Limits the number of results returned.
Args: count: Maximum number of results to return.
Returns: A FireQuery instance for method chaining.
Source code in src/fire_prox/fire_collection.py
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | |
new()
Create a new FireObject in DETACHED state.
Source code in src/fire_prox/fire_collection.py
69 70 71 | |
order_by(field, direction='ASCENDING')
Create a query with ordering.
Phase 2.5 feature. Orders results by a field.
Args: field: The field path to order by. direction: 'ASCENDING' or 'DESCENDING'. Default is 'ASCENDING'.
Returns: A FireQuery instance for method chaining.
Source code in src/fire_prox/fire_collection.py
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | |
select(*field_paths)
Create a query with field projection.
Phase 4 Part 3 feature. Selects specific fields to return in query results. Returns vanilla dictionaries instead of FireObject instances.
Args: *field_paths: One or more field paths to select.
Returns: A FireQuery instance with projection applied.
Example: # Select specific fields results = users.select('name', 'email').get() # Returns: [{'name': 'Alice', 'email': 'alice@example.com'}, ...]
Source code in src/fire_prox/fire_collection.py
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | |
sum(field)
Sum a numeric field across all documents.
Phase 4 Part 5 feature. Calculates the sum of a numeric field without fetching document data.
Args: field: The field name to sum.
Returns: The sum of the field values (int or float).
Example: total_revenue = orders.sum('amount')
Source code in src/fire_prox/fire_collection.py
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 | |
where(field, op, value)
Create a query with a filter condition.
Phase 2.5 feature. Builds a lightweight query for common filtering needs. For complex queries, users should use the native API and hydrate results with FireObject.from_snapshot().
Args: field: The field path to filter on (e.g., 'name', 'address.city'). op: Comparison operator: '==', '!=', '<', '<=', '>', '>=', 'in', 'not-in', 'array-contains', 'array-contains-any'. value: The value to compare against.
Returns: A FireQuery instance for method chaining.
Example: query = users.where('birth_year', '>', 1800) .where('country', '==', 'UK') .limit(10) for user in query.get(): print(user.name)
Source code in src/fire_prox/fire_collection.py
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | |
fire_object
FireObject: The core proxy class for Firestore documents (synchronous).
This module implements the synchronous FireObject class, which serves as a schemaless, state-aware proxy for Firestore documents.
FireObject
Bases: BaseFireObject
A schemaless, state-aware proxy for a Firestore document (synchronous).
FireObject provides an object-oriented interface to Firestore documents, allowing attribute-style access to document fields and automatic state management throughout the document's lifecycle.
The object maintains an internal state machine (DETACHED -> ATTACHED -> LOADED -> DELETED) and tracks modifications to enable efficient partial updates.
This is the synchronous implementation that supports lazy loading via automatic fetch on attribute access.
Usage Examples: # Create a new document (DETACHED state) user = collection.new() user.name = 'Ada Lovelace' user.year = 1815 user.save() # Transitions to LOADED
# Load existing document (ATTACHED -> LOADED on access)
user = db.doc('users/alovelace') # ATTACHED state
print(user.name) # Triggers fetch, transitions to LOADED
# Update and save
user.year = 1816 # Marks as dirty
user.save() # Performs update
# Delete
user.delete() # Transitions to DELETED
Source code in src/fire_prox/fire_object.py
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 | |
__getattr__(name)
Handle attribute access for document fields with lazy loading.
This method implements lazy loading: if the object is in ATTACHED state, accessing any data attribute will automatically trigger a fetch() to load the data from Firestore.
Args: name: The attribute name being accessed.
Returns: The value of the field from the internal _data cache.
Raises: AttributeError: If the attribute doesn't exist in _data after fetching (if necessary).
State Transitions: ATTACHED -> LOADED: Automatically fetches data on first access.
Example: user = db.doc('users/alovelace') # ATTACHED name = user.name # Triggers fetch, transitions to LOADED year = user.year # No fetch needed, already LOADED
Source code in src/fire_prox/fire_object.py
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | |
collections(names_only=False)
List subcollections beneath this document.
Args: names_only: When True, return collection IDs instead of wrappers.
Returns: List of subcollection names or FireCollection wrappers.
Source code in src/fire_prox/fire_object.py
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 | |
delete(batch=None, *, recursive=True, batch_size=50)
Delete the document from Firestore (synchronous).
Removes the document from Firestore and transitions the object to DELETED state. After deletion, the object retains its ID and path for reference but cannot be modified or saved.
Args: batch: Optional batch object for batched deletes. If provided, the delete will be accumulated in the batch (committed later). recursive: When True (default), delete all subcollections first. batch_size: Batch size to use for recursive subcollection cleanup.
Raises: ValueError: If called on a DETACHED object (no document to delete). RuntimeError: If called on an already-DELETED object. ValueError: If recursive deletion is requested while using a batch.
State Transitions: ATTACHED -> DELETED: Deletes document (data never loaded) LOADED -> DELETED: Deletes document (data was loaded)
Example: user = db.doc('users/alovelace') user.delete() # Document removed from Firestore print(user.state) # State.DELETED print(user.id) # Still accessible: 'alovelace'
# Batch delete
batch = db.batch()
user1.delete(batch=batch, recursive=False)
user2.delete(batch=batch, recursive=False)
batch.commit() # Commit all operations
Source code in src/fire_prox/fire_object.py
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 | |
delete_subcollection(name, *, batch_size=50, recursive=True, dry_run=False)
Delete a subcollection beneath this document.
Firestore keeps subcollections even after their parent document is deleted. This helper clears a specific subcollection using the same batched logic as FireCollection.delete_all().
Args: name: Subcollection name relative to this document. batch_size: Maximum number of deletes per commit. recursive: Whether to delete nested subcollections. dry_run: Count affected documents without executing writes.
Returns: Dictionary with counts for deleted documents and subcollections.
Source code in src/fire_prox/fire_object.py
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 | |
fetch(force=False, transaction=None)
Fetch document data from Firestore (synchronous).
Retrieves the latest data from Firestore and populates the internal _data cache. This method transitions ATTACHED objects to LOADED state and can refresh data for already-LOADED objects.
Args: force: If True, fetch data even if already LOADED. Useful for refreshing data to get latest changes from Firestore. Default is False. transaction: Optional transaction object for transactional reads. If provided, the read will be part of the transaction.
Returns: Self, to allow method chaining.
Raises: ValueError: If called on a DETACHED object (no DocumentReference). RuntimeError: If called on a DELETED object. NotFound: If document doesn't exist in Firestore.
State Transitions: ATTACHED -> LOADED: First fetch populates data LOADED -> LOADED: Refreshes data if force=True
Example: # Normal fetch user = db.doc('users/alovelace') # ATTACHED user.fetch() # Now LOADED with data
# Transactional fetch
transaction = db.transaction()
@firestore.transactional
def read_user(transaction):
user.fetch(transaction=transaction)
return user.credits
credits = read_user(transaction)
Source code in src/fire_prox/fire_object.py
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | |
from_snapshot(snapshot, parent_collection=None)
classmethod
Create a FireObject from a Firestore DocumentSnapshot.
This factory method is the primary "hydration" mechanism for converting native Firestore query results into FireObject instances. It creates an object in LOADED state with data already populated.
Args: snapshot: A DocumentSnapshot from google-cloud-firestore, typically obtained from query results or document.get(). parent_collection: Optional reference to parent FireCollection.
Returns: A new FireObject instance in LOADED state with data from snapshot.
Raises: ValueError: If snapshot doesn't exist (snapshot.exists is False).
Example: # Hydrate from native query native_query = client.collection('users').where('year', '>', 1800) results = [FireObject.from_snapshot(snap) for snap in native_query.stream()]
# Hydrate from direct get
snap = client.document('users/alovelace').get()
user = FireObject.from_snapshot(snap)
Source code in src/fire_prox/fire_object.py
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 | |
save(doc_id=None, transaction=None, batch=None)
Save the object's data to Firestore (synchronous).
Creates or updates the Firestore document based on the object's current state. For DETACHED objects, creates a new document. For LOADED objects, performs a full overwrite (Phase 1).
Args: doc_id: Optional custom document ID. Only used when saving a DETACHED object. If None, Firestore auto-generates an ID. transaction: Optional transaction object for transactional writes. If provided, the write will be part of the transaction. batch: Optional batch object for batched writes. If provided, the write will be accumulated in the batch (committed later).
Returns: Self, to allow method chaining.
Raises: RuntimeError: If called on a DELETED object. ValueError: If DETACHED object has no parent collection, or if trying to create a new document within a transaction or batch.
State Transitions: DETACHED -> LOADED: Creates new document with doc_id or auto-ID LOADED -> LOADED: Updates document if dirty, no-op if clean
Example: # Create new document user = collection.new() user.name = 'Ada' user.save(doc_id='alovelace') # DETACHED -> LOADED
# Update existing
user.year = 1816
user.save() # Performs update
# Transactional save
transaction = db.transaction()
@firestore.transactional
def update_user(transaction):
user.fetch(transaction=transaction)
user.credits += 10
user.save(transaction=transaction)
update_user(transaction)
# Batch save
batch = db.batch()
user1.save(batch=batch)
user2.save(batch=batch)
batch.commit() # Commit all operations
Source code in src/fire_prox/fire_object.py
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 | |
fire_query
FireQuery: Chainable query builder for Firestore (synchronous).
This module provides the synchronous FireQuery class, which wraps native Firestore Query objects and provides a chainable interface for building and executing queries.
FireQuery
A chainable query builder for Firestore collections (synchronous).
FireQuery wraps the native google-cloud-firestore Query object and provides a simplified, chainable interface for building and executing queries. It follows an immutable pattern - each method returns a new FireQuery instance with the modified query.
This is the synchronous implementation. For async queries, use AsyncFireQuery.
Usage Examples: # Basic filtering query = users.where('birth_year', '>', 1800) for user in query.get(): print(user.name)
# Chaining multiple conditions
query = (users
.where('birth_year', '>', 1800)
.where('country', '==', 'England')
.order_by('birth_year')
.limit(10))
for user in query.get():
print(f"{user.name} - {user.birth_year}")
# Stream results (generator)
for user in users.where('active', '==', True).stream():
print(user.name)
Design Note: For complex queries beyond the scope of this builder (e.g., OR queries, advanced filtering), use the native Query API directly and hydrate results with FireObject.from_snapshot():
native_query = client.collection('users').where(...)
results = [FireObject.from_snapshot(snap) for snap in native_query.stream()]
Source code in src/fire_prox/fire_query.py
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 | |
__init__(native_query, parent_collection=None, projection=None)
Initialize a FireQuery.
Args: native_query: The underlying native Query object from google-cloud-firestore. parent_collection: Optional reference to parent FireCollection. projection: Optional tuple of field paths to project (select specific fields).
Source code in src/fire_prox/fire_query.py
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | |
__repr__()
Return string representation of the query.
Source code in src/fire_prox/fire_query.py
892 893 894 | |
__str__()
Return human-readable string representation.
Source code in src/fire_prox/fire_query.py
896 897 898 | |
aggregate(**aggregations)
Perform multiple aggregations in a single query.
Executes an aggregation query with multiple aggregation operations (count, sum, average) without fetching the actual documents. This is more efficient than running multiple separate aggregation queries.
Args: **aggregations: Named aggregations using Count(), Sum(field), or Avg(field) from fire_prox.aggregation module.
Returns: Dictionary mapping aggregation names to their results.
Raises: ValueError: If no aggregations are provided or if invalid aggregation types are used.
Example: from fire_prox.aggregation import Count, Sum, Avg
# Multiple aggregations in one query
stats = employees.aggregate(
total_count=Count(),
total_salary=Sum('salary'),
avg_salary=Avg('salary'),
avg_age=Avg('age')
)
# Returns: {
# 'total_count': 150,
# 'total_salary': 15000000,
# 'avg_salary': 100000.0,
# 'avg_age': 35.2
# }
# With filters
eng_stats = (employees
.where('department', '==', 'Engineering')
.aggregate(
count=Count(),
total_salary=Sum('salary')
))
# Returns: {'count': 50, 'total_salary': 5000000}
# Financial dashboard
financials = (transactions
.where('date', '>=', start_date)
.aggregate(
total_transactions=Count(),
total_revenue=Sum('amount'),
avg_transaction=Avg('amount')
))
Note: - Much more efficient than multiple separate aggregation queries - All aggregations execute in a single round-trip to Firestore - Null values are ignored in sum and average calculations
Source code in src/fire_prox/fire_query.py
572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 | |
avg(field)
Average a numeric field across all matching documents.
Executes an aggregation query to calculate the arithmetic mean of a specific field without fetching the actual documents. The field must contain numeric values (int or float).
Args: field: Name of the numeric field to average.
Returns: Average of the field values across all matching documents. Returns 0.0 if no documents match or if all values are null.
Raises: ValueError: If field is None or empty.
Example: # Average age of all users avg_age = users.avg('age') # Returns: 32.5
# Average with filters
avg_salary = (employees
.where('department', '==', 'Engineering')
.avg('salary'))
# Returns: 125000.0
# Average rating for active products
avg_rating = (products
.where('active', '==', True)
.avg('rating'))
# Returns: 4.2
Note: - Null values are ignored in the average calculation - Non-numeric values will cause an error - This is more efficient than fetching all documents
Source code in src/fire_prox/fire_query.py
518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 | |
count()
Count documents matching the query.
Executes an aggregation query to count the number of documents that match the current query filters without fetching the actual documents. This is more efficient than fetching all documents and counting them.
Returns: Integer count of matching documents. Returns 0 if no documents match.
Example: # Count all users total_users = users.count() # Returns: 150
# Count with filters
active_users = users.where('active', '==', True).count()
# Returns: 42
# Count with complex query
count = (users
.where('age', '>', 25)
.where('country', '==', 'USA')
.count())
# Returns: 37
Note: This uses Firestore's native aggregation API, which is more efficient than fetching documents. However, it still counts as one document read per 1000 documents in the collection.
Source code in src/fire_prox/fire_query.py
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 | |
end_at(*document_fields_or_snapshot)
End query results at a cursor position (inclusive).
Creates a new FireQuery that ends at the specified cursor. The cursor document is included in the results.
Args: *document_fields_or_snapshot: Either: - A dictionary of field values: {'field': value} - A DocumentSnapshot - Direct field values matching order_by clause order
Returns: A new FireQuery instance with the end cursor applied.
Example: # Get all users up to and including age 50 query = users.order_by('age').end_at({'age': 50})
# Using a specific document as endpoint
target_doc_ref = users.doc('user123')._doc_ref
target_snapshot = target_doc_ref.get()
query = users.order_by('age').end_at(target_snapshot)
Source code in src/fire_prox/fire_query.py
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 | |
end_before(*document_fields_or_snapshot)
End query results before a cursor position (exclusive).
Creates a new FireQuery that ends before the specified cursor. The cursor document itself is excluded from results.
Args: *document_fields_or_snapshot: Either: - A dictionary of field values: {'field': value} - A DocumentSnapshot - Direct field values matching order_by clause order
Returns: A new FireQuery instance with the end-before cursor applied.
Example: # Get all users before age 50 (exclude 50) query = users.order_by('age').end_before({'age': 50})
# Using a specific document as exclusive endpoint
target_doc_ref = users.doc('user123')._doc_ref
target_snapshot = target_doc_ref.get()
query = users.order_by('age').end_before(target_snapshot)
Source code in src/fire_prox/fire_query.py
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 | |
find_nearest(vector_field, query_vector, distance_measure, limit, distance_result_field=None)
Find the nearest neighbors based on vector similarity.
Performs a vector similarity search on top of the current query filters. This allows you to combine pre-filtering with vector search (requires a composite index).
Args: vector_field: Name of the field containing vector embeddings. query_vector: Vector to compare against (google.cloud.firestore_v1.vector.Vector). distance_measure: Distance calculation method (DistanceMeasure.EUCLIDEAN, DistanceMeasure.COSINE, or DistanceMeasure.DOT_PRODUCT). limit: Maximum number of nearest neighbors to return (max 1000). distance_result_field: Optional field name to store the calculated distance in the query results.
Returns: A new FireQuery instance with the vector search applied.
Example: from google.cloud.firestore_v1.base_vector_query import DistanceMeasure from google.cloud.firestore_v1.vector import Vector
# Find nearest neighbors with pre-filtering
query = (collection
.where('category', '==', 'tech')
.find_nearest(
vector_field="embedding",
query_vector=Vector([0.1, 0.2, 0.3]),
distance_measure=DistanceMeasure.COSINE,
limit=5
))
for doc in query.get():
print(f"{doc.title}: {doc.category}")
Note: - Requires a composite index when combining with where() clauses - Maximum limit is 1000 documents - Does not work with Firestore emulator (production only)
Source code in src/fire_prox/fire_query.py
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 | |
get()
Execute the query and return results as a list.
Fetches all matching documents and hydrates them into FireObject instances in LOADED state. If a projection is active (via .select()), returns vanilla dictionaries instead of FireObject instances.
Returns: - If no projection: List of FireObject instances for all documents matching the query. - If projection active: List of dictionaries containing only the selected fields. DocumentReferences are converted to FireObjects. - Empty list if no documents match.
Example: # Get all results as FireObjects users = query.get() for user in users: print(f"{user.name}: {user.birth_year}")
# Get projected results as dictionaries
users = query.select('name', 'email').get()
for user_dict in users:
print(f"{user_dict['name']}: {user_dict['email']}")
# Check if results exist
results = query.get()
if results:
print(f"Found {len(results)} users")
else:
print("No users found")
Source code in src/fire_prox/fire_query.py
741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 | |
limit(count)
Limit the number of results returned.
Creates a new FireQuery that will return at most count results.
Args: count: Maximum number of documents to return. Must be positive.
Returns: A new FireQuery instance with the limit applied.
Raises: ValueError: If count is not positive.
Example: # Get top 10 results query = users.order_by('score', direction='DESCENDING').limit(10)
# Get first 5 matching documents
query = users.where('active', '==', True).limit(5)
Source code in src/fire_prox/fire_query.py
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | |
on_snapshot(callback)
Listen for real-time updates to this query.
This method sets up a real-time listener that fires the callback whenever any document matching the query changes. The listener runs on a separate thread managed by the Firestore SDK.
Important: This is a sync-only feature. The listener uses the underlying synchronous query to run on a background thread. This is the standard Firestore pattern for real-time listeners in Python.
Args: callback: Callback function invoked on query changes. Signature: callback(query_snapshot, changes, read_time) - query_snapshot: List of DocumentSnapshot objects matching the query - changes: List of DocumentChange objects (ADDED, MODIFIED, REMOVED) - read_time: Timestamp of the snapshot
Returns:
Watch object with an .unsubscribe() method to stop listening.
Example: import threading
callback_done = threading.Event()
def on_change(query_snapshot, changes, read_time):
for change in changes:
if change.type.name == 'ADDED':
print(f"New: {change.document.id}")
elif change.type.name == 'MODIFIED':
print(f"Modified: {change.document.id}")
elif change.type.name == 'REMOVED':
print(f"Removed: {change.document.id}")
callback_done.set()
# Listen to active users only
active_users = users.where('status', '==', 'active')
watch = active_users.on_snapshot(on_change)
# Wait for initial snapshot
callback_done.wait()
# Later: stop listening
watch.unsubscribe()
Note: The callback runs on a separate thread. Use threading primitives (Event, Lock, Queue) for synchronization with your main thread.
Source code in src/fire_prox/fire_query.py
838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 | |
order_by(field, direction='ASCENDING')
Add an ordering clause to the query.
Creates a new FireQuery with ordering by the specified field.
Args: field: The field path to order by. direction: Sort direction. Either 'ASCENDING' or 'DESCENDING'. Default is 'ASCENDING'.
Returns: A new FireQuery instance with the ordering applied.
Example: # Ascending order query = users.order_by('birth_year')
# Descending order
query = users.order_by('birth_year', direction='DESCENDING')
# Multiple orderings (chained)
query = (users
.order_by('country')
.order_by('birth_year', direction='DESCENDING'))
Source code in src/fire_prox/fire_query.py
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | |
select(*field_paths)
Select specific fields to return (projection).
Creates a new FireQuery that only returns the specified fields in the query results. When using projections, query results will be returned as vanilla dictionaries instead of FireObject instances. Any DocumentReferences in the returned dictionaries will be automatically converted to FireObject instances in ATTACHED state.
Args: *field_paths: One or more field paths to select. Field paths can include nested fields using dot notation (e.g., 'address.city').
Returns: A new FireQuery instance with the projection applied.
Raises: ValueError: If no field paths are provided.
Example: # Select a single field query = users.select('name') results = query.get() # Returns: [{'name': 'Alice'}, {'name': 'Bob'}, ...]
# Select multiple fields
query = users.select('name', 'email', 'birth_year')
results = query.get()
# Returns: [{'name': 'Alice', 'email': 'alice@example.com', 'birth_year': 1990}, ...]
# Select with filtering and ordering
query = (users
.where('birth_year', '>', 1990)
.select('name', 'birth_year')
.order_by('birth_year')
.limit(10))
# DocumentReferences are auto-converted to FireObjects
query = posts.select('title', 'author') # author is a DocumentReference
results = query.get()
# results[0]['author'] is a FireObject, not a DocumentReference
print(results[0]['author'].name) # Can access fields after fetch()
Note: - Projection queries return dictionaries, not FireObject instances - Only the selected fields will be present in the returned dictionaries - DocumentReferences are automatically hydrated to FireObject instances - Projected results are more bandwidth-efficient for large documents
Source code in src/fire_prox/fire_query.py
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 | |
start_after(*document_fields_or_snapshot)
Start query results after a cursor position (exclusive).
Creates a new FireQuery that starts after the specified cursor. The cursor document itself is excluded from results. This is typically used for pagination to avoid duplicating the last document from the previous page.
Args: *document_fields_or_snapshot: Either: - A dictionary of field values: {'field': value} - A DocumentSnapshot from a previous query - Direct field values matching order_by clause order
Returns: A new FireQuery instance with the start-after cursor applied.
Example: # Pagination: exclude the last document from previous page page1 = users.order_by('age').limit(10).get() last_age = page1[-1].age page2 = users.order_by('age').start_after({'age': last_age}).limit(10).get()
# Using a document snapshot (common pattern)
last_doc_ref = page1[-1]._doc_ref
last_snapshot = last_doc_ref.get()
page2 = users.order_by('age').start_after(last_snapshot).limit(10).get()
Source code in src/fire_prox/fire_query.py
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 | |
start_at(*document_fields_or_snapshot)
Start query results at a cursor position (inclusive).
Creates a new FireQuery that starts at the specified cursor. The cursor can be a document snapshot or a dictionary of field values matching the order_by fields.
Args: *document_fields_or_snapshot: Either: - A dictionary of field values: {'field': value} - A DocumentSnapshot from a previous query - Direct field values matching order_by clause order
Returns: A new FireQuery instance with the start cursor applied.
Example: # Using field values (requires matching order_by) query = users.order_by('age').start_at({'age': 25})
# Pagination: get first page, then start at last document
page1 = users.order_by('age').limit(10).get()
last_age = page1[-1].age
page2 = users.order_by('age').start_at({'age': last_age}).limit(10).get()
# Using a document snapshot
last_doc_ref = page1[-1]._doc_ref
last_snapshot = last_doc_ref.get()
page2 = users.order_by('age').start_at(last_snapshot).limit(10).get()
Source code in src/fire_prox/fire_query.py
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | |
stream()
Execute the query and stream results as an iterator.
Returns a generator that yields FireObject instances one at a time. This is more memory-efficient than .get() for large result sets as it doesn't load all results into memory at once. If a projection is active (via .select()), yields vanilla dictionaries instead.
Yields: - If no projection: FireObject instances in LOADED state for each matching document. - If projection active: Dictionaries containing only the selected fields. DocumentReferences are converted to FireObjects.
Example: # Stream results one at a time as FireObjects for user in query.stream(): print(f"{user.name}: {user.birth_year}") # Process each user without loading all users into memory
# Stream projected results as dictionaries
for user_dict in query.select('name', 'email').stream():
print(f"{user_dict['name']}: {user_dict['email']}")
# Works with any query
for post in (posts
.where('published', '==', True)
.order_by('date', direction='DESCENDING')
.stream()):
print(post.title)
Source code in src/fire_prox/fire_query.py
790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 | |
sum(field)
Sum a numeric field across all matching documents.
Executes an aggregation query to sum the values of a specific field without fetching the actual documents. The field must contain numeric values (int or float).
Args: field: Name of the numeric field to sum.
Returns: Sum of the field values across all matching documents. Returns 0 if no documents match or if all values are null.
Raises: ValueError: If field is None or empty.
Example: # Sum all salaries total_salary = employees.sum('salary') # Returns: 5000000
# Sum with filters
engineering_salary = (employees
.where('department', '==', 'Engineering')
.sum('salary'))
# Returns: 2500000
# Sum revenue from active products
total_revenue = (products
.where('active', '==', True)
.sum('revenue'))
# Returns: 1250000.50
Note: - Null values are ignored in the sum - Non-numeric values will cause an error - This is more efficient than fetching all documents
Source code in src/fire_prox/fire_query.py
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 | |
where(field, op, value)
Add a filter condition to the query.
Creates a new FireQuery with an additional filter condition. Uses the immutable pattern - returns a new instance rather than modifying the current query.
Args: field: The field path to filter on (e.g., 'name', 'address.city'). op: Comparison operator. Supported operators: '==' (equal), '!=' (not equal), '<' (less than), '<=' (less than or equal), '>' (greater than), '>=' (greater than or equal), 'in' (value in list), 'not-in' (value not in list), 'array-contains' (array contains value), 'array-contains-any' (array contains any of the values). value: The value to compare against.
Returns: A new FireQuery instance with the added filter.
Example: # Single condition query = users.where('birth_year', '>', 1800)
# Multiple conditions (chained)
query = (users
.where('birth_year', '>', 1800)
.where('country', '==', 'England'))
Source code in src/fire_prox/fire_query.py
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | |
fireprox
FireProx: Main entry point for the library (synchronous).
This module provides the synchronous FireProx class, which serves as the primary interface for users to interact with Firestore through the simplified FireProx API.
FireProx
Bases: BaseFireProx
Main entry point for the FireProx library (synchronous).
FireProx wraps the native google-cloud-firestore Client and provides a simplified, Pythonic interface for working with Firestore. It delegates authentication and client configuration to the official library while providing higher-level abstractions for document and collection access.
The design philosophy is "wrap, don't replace" - FireProx leverages the reliability and security of the native client while providing a more intuitive developer experience optimized for rapid prototyping.
This is the synchronous implementation that supports lazy loading.
Usage Examples: # Initialize with a pre-configured native client from google.cloud import firestore from fire_prox import FireProx
native_client = firestore.Client(project='my-project')
db = FireProx(native_client)
# Access a document (ATTACHED state, lazy loading)
user = db.doc('users/alovelace')
print(user.name) # Automatically fetches data
# Create a new document
users = db.collection('users')
new_user = users.new()
new_user.name = 'Charles Babbage'
new_user.year = 1791
new_user.save()
# Update a document
user = db.doc('users/alovelace')
user.year = 1816
user.save()
# Delete a document
user.delete()
Source code in src/fire_prox/fireprox.py
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | |
__init__(client)
Initialize FireProx with a native Firestore client.
Args: client: A configured google.cloud.firestore.Client instance. Authentication and project configuration should be handled before creating this instance.
Raises: TypeError: If client is not a google.cloud.firestore.Client instance.
Example: from google.cloud import firestore from fire_prox import FireProx
# Option 1: Default credentials
native_client = firestore.Client()
# Option 2: Explicit project
native_client = firestore.Client(project='my-project-id')
# Option 3: Service account
native_client = firestore.Client.from_service_account_json(
'path/to/credentials.json'
)
# Initialize FireProx
db = FireProx(native_client)
Source code in src/fire_prox/fireprox.py
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | |
collection(path)
Get a reference to a collection by its path.
Creates a FireCollection wrapper around the native CollectionReference. Used for creating new documents or (in Phase 2) querying.
Args: path: The collection path, e.g., 'users' or 'users/uid/posts'. Can be a root-level collection (odd number of segments) or a subcollection path.
Returns: A FireCollection instance.
Raises: ValueError: If path has an even number of segments (invalid collection path) or contains invalid characters.
Example: # Root-level collection users = db.collection('users') new_user = users.new() new_user.name = 'Ada' new_user.save()
# Subcollection
posts = db.collection('users/alovelace/posts')
new_post = posts.new()
new_post.title = 'Analysis Engine'
new_post.save()
Source code in src/fire_prox/fireprox.py
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | |
collections(path, *, names_only=False)
List subcollections beneath the specified document path.
Args: path: Document path whose subcollections should be listed. names_only: Return collection IDs instead of FireCollection wrappers.
Returns: List of subcollection names or FireCollection wrappers.
Source code in src/fire_prox/fireprox.py
187 188 189 190 191 192 193 194 195 196 197 198 199 | |
doc(path)
Get a reference to a document by its full path.
Creates a FireObject in ATTACHED state. No data is fetched from Firestore until an attribute is accessed (lazy loading).
Args: path: The full document path, e.g., 'users/alovelace' or 'users/uid/posts/post123'. Must be a valid Firestore document path with an even number of segments.
Returns: A FireObject instance in ATTACHED state.
Raises: ValueError: If path has an odd number of segments (invalid document path) or contains invalid characters.
Example: # Root-level document user = db.doc('users/alovelace')
# Nested document (subcollection)
post = db.doc('users/alovelace/posts/post123')
# Lazy loading
print(user.name) # Triggers fetch on first access
Source code in src/fire_prox/fireprox.py
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | |
document(path)
Alias for doc(). Get a reference to a document by its full path.
Provided for API consistency with the native library and user preference. Functionally identical to doc().
Args: path: The full document path.
Returns: A FireObject instance in ATTACHED state.
Source code in src/fire_prox/fireprox.py
134 135 136 137 138 139 140 141 142 143 144 145 146 147 | |
state
State management for FireObject instances.
This module defines the state machine that governs the lifecycle of FireObject instances as they transition between different states of synchronization with Firestore.
State
Bases: Enum
Represents the synchronization state of a FireObject with Firestore.
The state machine ensures that FireObject instances correctly manage their lifecycle from creation through deletion, tracking whether data has been loaded from Firestore and whether local modifications need to be saved.
States: DETACHED: Object exists only in Python memory with no Firestore reference. This is the initial state for newly created documents that haven't been saved yet. All data is considered "dirty" as it's new.
ATTACHED: Object is linked to a Firestore document path and has a valid
DocumentReference, but the document's data has not yet been fetched.
This enables lazy loading - the reference exists but no network
request has been made yet.
LOADED: Object is fully synchronized with Firestore. It has a reference
and its data has been fetched from the server into the local cache.
This is the primary operational state for reading and modifying data.
DELETED: Object represents a document that has been deleted from Firestore.
It retains its ID and path for reference but is marked as defunct
to prevent further modifications or save operations.
State Transitions: DETACHED -> LOADED: Via save() with optional doc_id ATTACHED -> LOADED: Via fetch() or implicit fetch on attribute access LOADED -> LOADED: Via save() (if dirty) or fetch() (refresh) LOADED -> DELETED: Via delete()
Source code in src/fire_prox/state.py
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | |
__repr__()
Return a detailed representation of the state.
Source code in src/fire_prox/state.py
54 55 56 | |
__str__()
Return a human-readable string representation of the state.
Source code in src/fire_prox/state.py
50 51 52 | |
testing
FirestoreProjectCleanupError
Bases: RuntimeError
Raised when the Firestore emulator project could not be deleted.
Source code in src/fire_prox/testing/__init__.py
67 68 | |
FirestoreTestHarness
Utility that cleans up the Firestore emulator project before and after tests.
Source code in src/fire_prox/testing/__init__.py
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | |
async_demo_client()
Create an async demo Firestore client.
If NOTEBOOK_CI environment variable is set, returns a standard async testing client. Otherwise, returns a client configured for the developer emulator (port 9090).
Source code in src/fire_prox/testing/__init__.py
55 56 57 58 59 60 61 62 63 64 65 | |
async_testing_client()
Create an asynchronous Firestore client configured to connect to the emulator.
Source code in src/fire_prox/testing/__init__.py
21 22 23 24 25 26 | |
check_emulator()
Check if the Firestore emulator is running.
Source code in src/fire_prox/testing/__init__.py
30 31 32 33 34 35 36 37 38 39 40 41 | |
cleanup_firestore(project_id=DEFAULT_PROJECT_ID, db_or_client=None)
Delete all documents in the given project on the Firestore emulator.
Source code in src/fire_prox/testing/__init__.py
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | |
demo_client()
Create a demo Firestore client.
If NOTEBOOK_CI environment variable is set, returns a standard testing client. Otherwise, returns a client configured for the developer emulator (port 9090).
Source code in src/fire_prox/testing/__init__.py
43 44 45 46 47 48 49 50 51 52 53 | |
firestore_harness(project_id=DEFAULT_PROJECT_ID)
Context manager that ensures Firestore cleanup in setup/teardown.
Source code in src/fire_prox/testing/__init__.py
134 135 136 137 138 139 | |
firestore_test_harness()
Pytest fixture that yields a FirestoreTestHarness.
Source code in src/fire_prox/testing/__init__.py
148 149 150 151 152 | |
testing_client()
Create a synchronous Firestore client configured to connect to the emulator.
Source code in src/fire_prox/testing/__init__.py
13 14 15 16 17 18 | |