Skip to content

Commit 068089e

Browse files
authored
Refactor 'Document.get' to use the 'GetDocument' API. (#6534)
Update conformance test to actually run for 'get'. Toward #6533.
1 parent d8a24f8 commit 068089e

File tree

4 files changed

+119
-74
lines changed

4 files changed

+119
-74
lines changed

google/cloud/firestore_v1beta1/document.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818

1919
import six
2020

21+
from google.api_core import exceptions
2122
from google.cloud.firestore_v1beta1 import _helpers
23+
from google.cloud.firestore_v1beta1.proto import common_pb2
2224
from google.cloud.firestore_v1beta1.watch import Watch
2325

2426

@@ -423,9 +425,37 @@ def get(self, field_paths=None, transaction=None):
423425
if isinstance(field_paths, six.string_types):
424426
raise ValueError(
425427
"'field_paths' must be a sequence of paths, not a string.")
426-
snapshot_generator = self._client.get_all(
427-
[self], field_paths=field_paths, transaction=transaction)
428-
return _consume_single_get(snapshot_generator)
428+
429+
if field_paths is not None:
430+
mask = common_pb2.DocumentMask(field_paths=sorted(field_paths))
431+
else:
432+
mask = None
433+
434+
firestore_api = self._client._firestore_api
435+
try:
436+
document_pb = firestore_api.get_document(
437+
self._document_path,
438+
mask=mask,
439+
transaction=_helpers.get_transaction_id(transaction),
440+
metadata=self._client._rpc_metadata)
441+
except exceptions.NotFound:
442+
data = None
443+
exists = False
444+
create_time = None
445+
update_time = None
446+
else:
447+
data = _helpers.decode_dict(document_pb.fields, self._client)
448+
exists = True
449+
create_time = document_pb.create_time
450+
update_time = document_pb.update_time
451+
452+
return DocumentSnapshot(
453+
reference=self,
454+
data=data,
455+
exists=exists,
456+
read_time=None, # No server read_time available
457+
create_time=create_time,
458+
update_time=update_time)
429459

430460
def collections(self, page_size=None):
431461
"""List subcollections of the current document.

tests/system.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,6 @@ def test_document_get(client, cleanup):
413413
write_result = document.create(data)
414414
snapshot = document.get()
415415
check_snapshot(snapshot, document, data, write_result)
416-
assert_timestamp_less(snapshot.create_time, snapshot.read_time)
417416

418417

419418
def test_document_delete(client, cleanup):

tests/unit/test_cross_language.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import pytest
2222

2323
from google.protobuf import text_format
24+
from google.cloud.firestore_v1beta1.proto import document_pb2
2425
from google.cloud.firestore_v1beta1.proto import firestore_pb2
2526
from google.cloud.firestore_v1beta1.proto import test_pb2
2627
from google.cloud.firestore_v1beta1.proto import write_pb2
@@ -170,19 +171,18 @@ def test_create_testprotos(test_proto):
170171
@pytest.mark.parametrize('test_proto', _GET_TESTPROTOS)
171172
def test_get_testprotos(test_proto):
172173
testcase = test_proto.get
173-
# XXX this stub currently does nothing because no get testcases have
174-
# is_error; taking this bit out causes the existing tests to fail
175-
# due to a lack of batch getting
176-
try:
177-
testcase.is_error
178-
except AttributeError:
179-
return
180-
else: # pragma: NO COVER
181-
testcase = test_proto.get
182-
firestore_api = _mock_firestore_api()
183-
client, document = _make_client_document(firestore_api, testcase)
184-
call = functools.partial(document.get, None, None)
185-
_run_testcase(testcase, call, firestore_api, client)
174+
firestore_api = mock.Mock(spec=['get_document'])
175+
response = document_pb2.Document()
176+
firestore_api.get_document.return_value = response
177+
client, document = _make_client_document(firestore_api, testcase)
178+
179+
document.get() # No '.textprotos' for errors, field_paths.
180+
181+
firestore_api.get_document.assert_called_once_with(
182+
document._document_path,
183+
mask=None,
184+
transaction=None,
185+
metadata=client._rpc_metadata)
186186

187187

188188
@pytest.mark.parametrize('test_proto', _SET_TESTPROTOS)

tests/unit/test_document.py

Lines changed: 73 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -463,74 +463,90 @@ def test_delete_with_option(self):
463463
)
464464
self._delete_helper(last_update_time=timestamp_pb)
465465

466-
def test_get_w_single_field_path(self):
467-
client = mock.Mock(spec=[])
466+
def _get_helper(
467+
self, field_paths=None, use_transaction=False, not_found=False):
468+
from google.api_core.exceptions import NotFound
469+
from google.cloud.firestore_v1beta1.proto import common_pb2
470+
from google.cloud.firestore_v1beta1.proto import document_pb2
471+
from google.cloud.firestore_v1beta1.transaction import Transaction
468472

469-
document = self._make_one('yellow', 'mellow', client=client)
470-
with self.assertRaises(ValueError):
471-
document.get('foo')
473+
# Create a minimal fake GAPIC with a dummy response.
474+
create_time = 123
475+
update_time = 234
476+
firestore_api = mock.Mock(spec=['get_document'])
477+
response = mock.create_autospec(document_pb2.Document)
478+
response.fields = {}
479+
response.create_time = create_time
480+
response.update_time = update_time
481+
482+
if not_found:
483+
firestore_api.get_document.side_effect = NotFound('testing')
484+
else:
485+
firestore_api.get_document.return_value = response
472486

473-
def test_get_success(self):
474-
# Create a minimal fake client with a dummy response.
475-
response_iterator = iter([mock.sentinel.snapshot])
476-
client = mock.Mock(spec=['get_all'])
477-
client.get_all.return_value = response_iterator
487+
client = _make_client('donut-base')
488+
client._firestore_api_internal = firestore_api
478489

479-
# Actually make a document and call get().
480-
document = self._make_one('yellow', 'mellow', client=client)
481-
snapshot = document.get()
490+
document = self._make_one('where', 'we-are', client=client)
482491

483-
# Verify the response and the mocks.
484-
self.assertIs(snapshot, mock.sentinel.snapshot)
485-
client.get_all.assert_called_once_with(
486-
[document], field_paths=None, transaction=None)
492+
if use_transaction:
493+
transaction = Transaction(client)
494+
transaction_id = transaction._id = b'asking-me-2'
495+
else:
496+
transaction = None
497+
498+
snapshot = document.get(
499+
field_paths=field_paths, transaction=transaction)
500+
501+
self.assertIs(snapshot.reference, document)
502+
if not_found:
503+
self.assertIsNone(snapshot._data)
504+
self.assertFalse(snapshot.exists)
505+
self.assertIsNone(snapshot.read_time)
506+
self.assertIsNone(snapshot.create_time)
507+
self.assertIsNone(snapshot.update_time)
508+
else:
509+
self.assertEqual(snapshot.to_dict(), {})
510+
self.assertTrue(snapshot.exists)
511+
self.assertIsNone(snapshot.read_time)
512+
self.assertIs(snapshot.create_time, create_time)
513+
self.assertIs(snapshot.update_time, update_time)
514+
515+
# Verify the request made to the API
516+
if field_paths is not None:
517+
mask = common_pb2.DocumentMask(field_paths=sorted(field_paths))
518+
else:
519+
mask = None
487520

488-
def test_get_with_transaction(self):
489-
from google.cloud.firestore_v1beta1.client import Client
490-
from google.cloud.firestore_v1beta1.transaction import Transaction
521+
if use_transaction:
522+
expected_transaction_id = transaction_id
523+
else:
524+
expected_transaction_id = None
491525

492-
# Create a minimal fake client with a dummy response.
493-
response_iterator = iter([mock.sentinel.snapshot])
494-
client = mock.create_autospec(Client, instance=True)
495-
client.get_all.return_value = response_iterator
526+
firestore_api.get_document.assert_called_once_with(
527+
document._document_path,
528+
mask=mask,
529+
transaction=expected_transaction_id,
530+
metadata=client._rpc_metadata)
496531

497-
# Actually make a document and call get().
498-
document = self._make_one('yellow', 'mellow', client=client)
499-
transaction = Transaction(client)
500-
transaction._id = b'asking-me-2'
501-
snapshot = document.get(transaction=transaction)
532+
def test_get_not_found(self):
533+
self._get_helper(not_found=True)
502534

503-
# Verify the response and the mocks.
504-
self.assertIs(snapshot, mock.sentinel.snapshot)
505-
client.get_all.assert_called_once_with(
506-
[document], field_paths=None, transaction=transaction)
535+
def test_get_default(self):
536+
self._get_helper()
507537

508-
def test_get_not_found(self):
509-
from google.cloud.firestore_v1beta1.document import DocumentSnapshot
538+
def test_get_w_string_field_path(self):
539+
with self.assertRaises(ValueError):
540+
self._get_helper(field_paths='foo')
510541

511-
# Create a minimal fake client with a dummy response.
512-
read_time = 123
513-
expected = DocumentSnapshot(None, None, False, read_time, None, None)
514-
response_iterator = iter([expected])
515-
client = mock.Mock(
516-
_database_string='sprinklez',
517-
spec=['_database_string', 'get_all'])
518-
client.get_all.return_value = response_iterator
519-
520-
# Actually make a document and call get().
521-
document = self._make_one('house', 'cowse', client=client)
522-
field_paths = ['x.y', 'x.z', 't']
523-
snapshot = document.get(field_paths=field_paths)
524-
self.assertIsNone(snapshot.reference)
525-
self.assertIsNone(snapshot._data)
526-
self.assertFalse(snapshot.exists)
527-
self.assertEqual(snapshot.read_time, expected.read_time)
528-
self.assertIsNone(snapshot.create_time)
529-
self.assertIsNone(snapshot.update_time)
542+
def test_get_with_field_path(self):
543+
self._get_helper(field_paths=['foo'])
530544

531-
# Verify the response and the mocks.
532-
client.get_all.assert_called_once_with(
533-
[document], field_paths=field_paths, transaction=None)
545+
def test_get_with_multiple_field_paths(self):
546+
self._get_helper(field_paths=['foo', 'bar.baz'])
547+
548+
def test_get_with_transaction(self):
549+
self._get_helper(use_transaction=True)
534550

535551
def _collections_helper(self, page_size=None):
536552
from google.api_core.page_iterator import Iterator

0 commit comments

Comments
 (0)