feat: add async support (AsyncMockFirestore) ported from mdowds#62#5
Open
Some1Somewhere wants to merge 1 commit into
Open
feat: add async support (AsyncMockFirestore) ported from mdowds#62#5Some1Somewhere wants to merge 1 commit into
Some1Somewhere wants to merge 1 commit into
Conversation
Ported from mdowds/python-mock-firestore PR mdowds#62 (author: anna-hope), taking only the additive async layer and adapting it to this fork's sync API instead of PR mdowds#62's refactored one: - async_query uses Query._process_snapshots (extracted from Query.stream, behavior unchanged) instead of PR mdowds#62's _process_field_filters/_process_pagination - async_collection.get consumes its async stream instead of inheriting the sync list(stream) implementation; where() supports FieldFilter like this fork's sync where() - async_document.set mirrors this fork's set(merge=True) semantics (flatten_for_merge, __name__ stamping) with proper awaits - MockFirestore._ensure_path uses isinstance so async subclasses route document/collection paths correctly - consume_async_iterable helper added to _helpers
There was a problem hiding this comment.
Pull request overview
This PR ports upstream async Firestore-mock support into this fork by adding AsyncMockFirestore plus async variants of collection/document/query/transaction, while keeping the existing sync implementation and storage model intact.
Changes:
- Added async client + reference/query/transaction modules and exported them from
mockfirestore.__init__. - Added
consume_async_iterablehelper to support consuming async generators in API and tests. - Introduced a new async test suite and added
aiounittestto minimal dev requirements.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
mockfirestore/async_client.py |
Adds AsyncMockFirestore async client wrapper around the existing in-memory store. |
mockfirestore/async_collection.py |
Adds async collection reference with async streaming/get and query builders. |
mockfirestore/async_document.py |
Adds async document reference methods (get/set/update/delete) and async subcollection creation. |
mockfirestore/async_query.py |
Adds async query streaming/get using the shared snapshot processing logic. |
mockfirestore/async_transaction.py |
Adds async transaction commit and async iterables for reads. |
mockfirestore/_helpers.py |
Adds consume_async_iterable helper and extends typing imports. |
mockfirestore/__init__.py |
Exports the new async public API types. |
mockfirestore/query.py |
Extracts shared snapshot processing into _process_snapshots for reuse by async queries. |
mockfirestore/client.py |
Adjusts _ensure_path to use isinstance for correct async-path resolution. |
requirements-dev-minimal.txt |
Adds aiounittest for async tests. |
tests/test_async_collection_reference.py |
New async tests for collection reference behaviors (stream/query/cursors/etc). |
tests/test_async_document_reference.py |
New async tests for document reference behaviors (get/set/update/delete/transforms). |
tests/test_async_mock_client.py |
New async tests for client-level APIs (get_all, collections). |
tests/test_async_query.py |
New async tests for query .get() behavior. |
tests/test_async_transaction.py |
New async tests for transaction read/write operations. |
Comments suppressed due to low confidence (1)
mockfirestore/async_transaction.py:45
AsyncTransactionrelies on inheriting the synchronousTransaction.commit()method, which returns a coroutine here (because_commitis async). That works if callers always remember toawait, but it is an easy footgun and makes the async API surface ambiguous. Also,__aexit__currently leaves the transaction in-progress with pending write ops when an exception occurs in the context block; cleaning up on error prevents state leakage.
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
await self.commit()
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+11
to
+14
| def document(self, path: str) -> AsyncDocumentReference: | ||
| doc = super().document(path) | ||
| assert isinstance(doc, AsyncDocumentReference) | ||
| return doc |
Comment on lines
+54
to
+56
| def limit(self, limit_amount: int) -> AsyncQuery: | ||
| query = AsyncQuery(self, limit=limit_amount) | ||
| return query |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds async support (
AsyncMockFirestore+ async reference classes) to this fork, ported from upstream mdowds/python-mock-firestore#62 (credit: @anna-hope), which was never merged upstream.Ported (additive only):
async_client.py,async_collection.py,async_document.py,async_query.py,async_transaction.pyconsume_async_iterablehelper added to_helpers.py__init__.py(AsyncMockFirestore,AsyncDocumentReference,AsyncCollectionReference,AsyncQuery,AsyncTransaction)aiounittestadded torequirements-dev-minimal.txt)What was deliberately NOT taken from PR mdowds#62
PR mdowds#62 also refactored the sync classes (
client.py,collection.py,query.py,transaction.py,_helpers.py) and black-reformatted the whole package. Both were skipped: this fork has diverged on exactly those files with semantic fixes (DELETE_FIELD-doesn't-fail, don't-drop-empty-dicts,set(merge=True),collection.getgenerator fix, query iteration) that a wholesale merge would clobber.Adaptations needed to fit this fork's sync API
The async layer was written against PR mdowds#62's refactored sync classes, so four surgical adaptations were required:
async_query.pycalledQuery._process_field_filters/_process_pagination, which only exist in PR Add async support and format with black mdowds/python-mock-firestore#62's refactor. Instead, the filtering/ordering/cursor/limit body of this fork'sQuery.streamwas extracted intoQuery._process_snapshots(pure extract-method; sync behavior unchanged) and the async query reuses it.async_collection.py::getinheritedlist(self.stream()), which breaks whenstreamis an async generator — nowawait consume_async_iterable(self.stream()). Itswhere()also gained this fork'sfilter=FieldFilterkeyword support to match the sync signature.async_document.py::setre-implemented PR Add async support and format with black mdowds/python-mock-firestore#62-era merge logic, which would bypass this fork'sset(merge=True)fix and break with unawaited coroutines (the syncsetcallsself.update, which dispatches to the async override). It now mirrors this fork's merge branch (flatten_for_merge+__name__stamping) with proper awaits.client.py::_ensure_pathused exacttype() in (...)checks, which misrouteAsyncMockFirestore/AsyncDocumentReferencewhen resolving multi-segment paths. Changed toisinstance— behavior-identical for the sync classes (CollectionReferenceis not a subclass of either type).AsyncMockFirestore shares the sync in-memory store
Yes — verified empirically.
AsyncMockFirestoresubclassesMockFirestore; all reads/writes (sync and async reference classes alike) operate on the same plain-dict_datastore. Consequences for consumers:MockFirestoreview pointed at the same store lets test fixtures keep seeding synchronously while production code under test reads viaawait:Test results
pytest, sync + async)test_document_update_nestedFieldDotNotationMultipleNestedWithTransformerfails identically onmaster(verified: 1 failed, 36 passed there), and the async file carries a verbatim twin of that test. Not introduced by this PR.pytest)tests/test_attachment_manager.py::test_upload_attachments_real(local playwright Chromium binary missing).pytest -rA)Sync test files are untouched, and the sync suite result is identical to
master— the port is additive.🤖 Generated with Claude Code