Skip to content

Commit 4256a85

Browse files
authored
feat: asyncio system tests (#132)
* feat: make collections call backed by async * fix: failing asyncmock assertion * fix: lint * refactor: move AsyncMock to test helpers * fix: rename transactional function to avoid collision * feat: add async surface to firestore_v1 and firestore modules * feat: add pytest-asyncio to noxfile installs * feat: add transport to top level interface for client * fix: batch_get_documents invocation * fix: list_documents return type * fix: run_query invocation * fix: lint * feat: add async system tests * feat: remove Watch from async interface * rebase: v2-staging * fix: remove unused _transport property change * fix: alpha sort module imports * fix: dedup system test helpers
1 parent 35185a8 commit 4256a85

16 files changed

+1077
-169
lines changed

google/cloud/firestore.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@
1818
from google.cloud.firestore_v1 import __version__
1919
from google.cloud.firestore_v1 import ArrayRemove
2020
from google.cloud.firestore_v1 import ArrayUnion
21+
from google.cloud.firestore_v1 import AsyncClient
22+
from google.cloud.firestore_v1 import AsyncCollectionReference
23+
from google.cloud.firestore_v1 import AsyncDocumentReference
24+
from google.cloud.firestore_v1 import AsyncQuery
25+
from google.cloud.firestore_v1 import async_transactional
26+
from google.cloud.firestore_v1 import AsyncTransaction
27+
from google.cloud.firestore_v1 import AsyncWriteBatch
2128
from google.cloud.firestore_v1 import Client
2229
from google.cloud.firestore_v1 import CollectionReference
2330
from google.cloud.firestore_v1 import DELETE_FIELD
@@ -45,6 +52,13 @@
4552
"__version__",
4653
"ArrayRemove",
4754
"ArrayUnion",
55+
"AsyncClient",
56+
"AsyncCollectionReference",
57+
"AsyncDocumentReference",
58+
"AsyncQuery",
59+
"async_transactional",
60+
"AsyncTransaction",
61+
"AsyncWriteBatch",
4862
"Client",
4963
"CollectionReference",
5064
"DELETE_FIELD",

google/cloud/firestore_v1/__init__.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,28 @@
2929
from google.cloud.firestore_v1._helpers import LastUpdateOption
3030
from google.cloud.firestore_v1._helpers import ReadAfterWriteError
3131
from google.cloud.firestore_v1._helpers import WriteOption
32+
from google.cloud.firestore_v1.async_batch import AsyncWriteBatch
33+
from google.cloud.firestore_v1.async_client import AsyncClient
34+
from google.cloud.firestore_v1.async_collection import AsyncCollectionReference
35+
from google.cloud.firestore_v1.async_document import AsyncDocumentReference
36+
from google.cloud.firestore_v1.async_query import AsyncQuery
37+
from google.cloud.firestore_v1.async_transaction import async_transactional
38+
from google.cloud.firestore_v1.async_transaction import AsyncTransaction
39+
from google.cloud.firestore_v1.base_document import DocumentSnapshot
3240
from google.cloud.firestore_v1.batch import WriteBatch
3341
from google.cloud.firestore_v1.client import Client
3442
from google.cloud.firestore_v1.collection import CollectionReference
43+
from google.cloud.firestore_v1.document import DocumentReference
44+
from google.cloud.firestore_v1.query import Query
45+
from google.cloud.firestore_v1.transaction import Transaction
46+
from google.cloud.firestore_v1.transaction import transactional
3547
from google.cloud.firestore_v1.transforms import ArrayRemove
3648
from google.cloud.firestore_v1.transforms import ArrayUnion
3749
from google.cloud.firestore_v1.transforms import DELETE_FIELD
3850
from google.cloud.firestore_v1.transforms import Increment
3951
from google.cloud.firestore_v1.transforms import Maximum
4052
from google.cloud.firestore_v1.transforms import Minimum
4153
from google.cloud.firestore_v1.transforms import SERVER_TIMESTAMP
42-
from google.cloud.firestore_v1.document import DocumentReference
43-
from google.cloud.firestore_v1.document import DocumentSnapshot
44-
from google.cloud.firestore_v1.query import Query
45-
from google.cloud.firestore_v1.transaction import Transaction
46-
from google.cloud.firestore_v1.transaction import transactional
4754
from google.cloud.firestore_v1.watch import Watch
4855

4956

@@ -100,6 +107,13 @@
100107
"__version__",
101108
"ArrayRemove",
102109
"ArrayUnion",
110+
"AsyncClient",
111+
"AsyncCollectionReference",
112+
"AsyncDocumentReference",
113+
"AsyncQuery",
114+
"async_transactional",
115+
"AsyncTransaction",
116+
"AsyncWriteBatch",
103117
"Client",
104118
"CollectionReference",
105119
"DELETE_FIELD",

google/cloud/firestore_v1/async_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ async def get_all(self, references, field_paths=None, transaction=None):
242242
"""
243243
document_paths, reference_map = _reference_info(references)
244244
mask = _get_doc_mask(field_paths)
245-
response_iterator = self._firestore_api.batch_get_documents(
245+
response_iterator = await self._firestore_api.batch_get_documents(
246246
request={
247247
"database": self._database_string,
248248
"documents": document_paths,

google/cloud/firestore_v1/async_collection.py

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222
_item_to_document_ref,
2323
)
2424
from google.cloud.firestore_v1 import async_query
25-
from google.cloud.firestore_v1.watch import Watch
26-
from google.cloud.firestore_v1 import async_document
2725

2826

2927
class AsyncCollectionReference(BaseCollectionReference):
@@ -119,7 +117,8 @@ async def list_documents(self, page_size=None):
119117
},
120118
metadata=self._client._rpc_metadata,
121119
)
122-
return (_item_to_document_ref(self, i) for i in iterator)
120+
async for i in iterator:
121+
yield _item_to_document_ref(self, i)
123122

124123
async def get(self, transaction=None):
125124
"""Deprecated alias for :meth:`stream`."""
@@ -161,36 +160,3 @@ async def stream(self, transaction=None):
161160
query = async_query.AsyncQuery(self)
162161
async for d in query.stream(transaction=transaction):
163162
yield d
164-
165-
def on_snapshot(self, callback):
166-
"""Monitor the documents in this collection.
167-
168-
This starts a watch on this collection using a background thread. The
169-
provided callback is run on the snapshot of the documents.
170-
171-
Args:
172-
callback (Callable[[:class:`~google.cloud.firestore.collection.CollectionSnapshot`], NoneType]):
173-
a callback to run when a change occurs.
174-
175-
Example:
176-
from google.cloud import firestore_v1
177-
178-
db = firestore_v1.Client()
179-
collection_ref = db.collection(u'users')
180-
181-
def on_snapshot(collection_snapshot, changes, read_time):
182-
for doc in collection_snapshot.documents:
183-
print(u'{} => {}'.format(doc.id, doc.to_dict()))
184-
185-
# Watch this collection
186-
collection_watch = collection_ref.on_snapshot(on_snapshot)
187-
188-
# Terminate this watch
189-
collection_watch.unsubscribe()
190-
"""
191-
return Watch.for_query(
192-
self._query(),
193-
callback,
194-
async_document.DocumentSnapshot,
195-
async_document.AsyncDocumentReference,
196-
)

google/cloud/firestore_v1/async_document.py

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
from google.api_core import exceptions
2424
from google.cloud.firestore_v1 import _helpers
2525
from google.cloud.firestore_v1.types import common
26-
from google.cloud.firestore_v1.watch import Watch
2726

2827

2928
class AsyncDocumentReference(BaseDocumentReference):
@@ -385,39 +384,3 @@ async def collections(self, page_size=None):
385384
# iterator.document = self
386385
# iterator.item_to_value = _item_to_collection_ref
387386
# return iterator
388-
389-
def on_snapshot(self, callback):
390-
"""Watch this document.
391-
392-
This starts a watch on this document using a background thread. The
393-
provided callback is run on the snapshot.
394-
395-
Args:
396-
callback(Callable[[:class:`~google.cloud.firestore.document.DocumentSnapshot`], NoneType]):
397-
a callback to run when a change occurs
398-
399-
Example:
400-
401-
.. code-block:: python
402-
403-
from google.cloud import firestore_v1
404-
405-
db = firestore_v1.Client()
406-
collection_ref = db.collection(u'users')
407-
408-
def on_snapshot(document_snapshot, changes, read_time):
409-
doc = document_snapshot
410-
print(u'{} => {}'.format(doc.id, doc.to_dict()))
411-
412-
doc_ref = db.collection(u'users').document(
413-
u'alovelace' + unique_resource_id())
414-
415-
# Watch this document
416-
doc_watch = doc_ref.on_snapshot(on_snapshot)
417-
418-
# Terminate this watch
419-
doc_watch.unsubscribe()
420-
"""
421-
return Watch.for_document(
422-
self, callback, DocumentSnapshot, AsyncDocumentReference
423-
)

google/cloud/firestore_v1/async_query.py

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@
2727
)
2828

2929
from google.cloud.firestore_v1 import _helpers
30-
from google.cloud.firestore_v1 import async_document
31-
from google.cloud.firestore_v1.watch import Watch
3230

3331

3432
class AsyncQuery(BaseQuery):
@@ -149,7 +147,7 @@ async def stream(self, transaction=None):
149147
The next document that fulfills the query.
150148
"""
151149
parent_path, expected_prefix = self._parent._parent_info()
152-
response_iterator = self._client._firestore_api.run_query(
150+
response_iterator = await self._client._firestore_api.run_query(
153151
request={
154152
"parent": parent_path,
155153
"structured_query": self._to_protobuf(),
@@ -169,39 +167,3 @@ async def stream(self, transaction=None):
169167
)
170168
if snapshot is not None:
171169
yield snapshot
172-
173-
def on_snapshot(self, callback):
174-
"""Monitor the documents in this collection that match this query.
175-
176-
This starts a watch on this query using a background thread. The
177-
provided callback is run on the snapshot of the documents.
178-
179-
Args:
180-
callback(Callable[[:class:`~google.cloud.firestore.query.QuerySnapshot`], NoneType]):
181-
a callback to run when a change occurs.
182-
183-
Example:
184-
185-
.. code-block:: python
186-
187-
from google.cloud import firestore_v1
188-
189-
db = firestore_v1.Client()
190-
query_ref = db.collection(u'users').where("user", "==", u'Ada')
191-
192-
def on_snapshot(docs, changes, read_time):
193-
for doc in docs:
194-
print(u'{} => {}'.format(doc.id, doc.to_dict()))
195-
196-
# Watch this query
197-
query_watch = query_ref.on_snapshot(on_snapshot)
198-
199-
# Terminate this watch
200-
query_watch.unsubscribe()
201-
"""
202-
return Watch.for_query(
203-
self,
204-
callback,
205-
async_document.DocumentSnapshot,
206-
async_document.AsyncDocumentReference,
207-
)

google/cloud/firestore_v1/async_transaction.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ async def __call__(self, transaction, *args, **kwargs):
287287
raise ValueError(msg)
288288

289289

290-
def transactional(to_wrap):
290+
def async_transactional(to_wrap):
291291
"""Decorate a callable so that it runs in a transaction.
292292
293293
Args:

noxfile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ def system(session):
124124
# Install all test dependencies, then install this package into the
125125
# virtualenv's dist-packages.
126126
session.install(
127-
"mock", "pytest", "google-cloud-testutils",
127+
"mock", "pytest", "pytest-asyncio", "google-cloud-testutils",
128128
)
129129
session.install("-e", ".")
130130

tests/system/test__helpers.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import os
2+
import re
3+
from test_utils.system import unique_resource_id
4+
5+
FIRESTORE_CREDS = os.environ.get("FIRESTORE_APPLICATION_CREDENTIALS")
6+
FIRESTORE_PROJECT = os.environ.get("GCLOUD_PROJECT")
7+
RANDOM_ID_REGEX = re.compile("^[a-zA-Z0-9]{20}$")
8+
MISSING_DOCUMENT = "No document to update: "
9+
DOCUMENT_EXISTS = "Document already exists: "
10+
UNIQUE_RESOURCE_ID = unique_resource_id("-")

tests/system/test_system.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
import datetime
1616
import math
1717
import operator
18-
import os
19-
import re
2018

2119
from google.oauth2 import service_account
2220
import pytest
@@ -28,16 +26,16 @@
2826
from google.cloud._helpers import _datetime_to_pb_timestamp
2927
from google.cloud._helpers import UTC
3028
from google.cloud import firestore_v1 as firestore
31-
from test_utils.system import unique_resource_id
3229

3330
from time import sleep
3431

35-
FIRESTORE_CREDS = os.environ.get("FIRESTORE_APPLICATION_CREDENTIALS")
36-
FIRESTORE_PROJECT = os.environ.get("GCLOUD_PROJECT")
37-
RANDOM_ID_REGEX = re.compile("^[a-zA-Z0-9]{20}$")
38-
MISSING_DOCUMENT = "No document to update: "
39-
DOCUMENT_EXISTS = "Document already exists: "
40-
UNIQUE_RESOURCE_ID = unique_resource_id("-")
32+
from tests.system.test__helpers import (
33+
FIRESTORE_CREDS,
34+
FIRESTORE_PROJECT,
35+
RANDOM_ID_REGEX,
36+
MISSING_DOCUMENT,
37+
UNIQUE_RESOURCE_ID,
38+
)
4139

4240

4341
@pytest.fixture(scope=u"module")
@@ -683,7 +681,7 @@ def test_query_stream_w_offset(query_docs):
683681

684682
def test_query_with_order_dot_key(client, cleanup):
685683
db = client
686-
collection_id = "collek" + unique_resource_id("-")
684+
collection_id = "collek" + UNIQUE_RESOURCE_ID
687685
collection = db.collection(collection_id)
688686
for index in range(100, -1, -1):
689687
doc = collection.document("test_{:09d}".format(index))

0 commit comments

Comments
 (0)