Repository: googleapis/google-cloud-python

Issue:
Firestore: get_all removes .id key for non existing DocumentSnapshot
When I use the `get_all` function to load documents and some are non-existing the DocumentSnapshot which are non-existing lose their `.id` key. That is unfortunate as I don't know which document lost its ID. However, it's still possible to call `.exists`.

Requesting the exact same non-existing object from the database, also return a DocumentSnapshot while this time, the `.id` can be referenced and exists.


#### Environment details

1.) Firestore
2.) Linux-4.14.104-78.84.amzn1.x86_64-x86_64-with-glibc2.9
3.)  Python 3.6.5 :: Anaconda custom (64-bit)
4.) google-cloud-firestore==0.31.0

#### Steps to reproduce


#### Code example

```python

print(db.collection("something").document('thisDoesNotExists').get().exists)
# produces: False
print(db.collection("something").document('thisDoesNotExists').get().id)
# produces: thisDoesNotExists
print(list(db.get_all([db.collection("something").document('thisDoesNotExists')]))[0].exists)
# produces: False
print(list(db.get_all([db.collection("something").document('thisDoesNotExists')]))[0].id)
# produces: See Stack trace
```

#### Stack trace
```
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-218-5094e5147636> in <module>()
      2 print(db.collection("something").document('thisDoesNotExists').get().id)
      3 print(list(db.get_all([db.collection("something").document('thisDoesNotExists')]))[0].exists)
----> 4 print(list(db.get_all([db.collection("something").document('thisDoesNotExists')]))[0].id)

~/anaconda3/envs/python3/lib/python3.6/site-packages/google/cloud/firestore_v1beta1/document.py in id(self)
    592             str: The last component of the path of the document.
    593         """
--> 594         return self._reference.id
    595 
    596     @property

AttributeError: 'NoneType' object has no attribute 'id'
```



Discussion:
Per the [API reference](google.firestore.v1.BatchGetDocumentsResponse.FIELDS.string.google.firestore.v1.BatchGetDocumentsResponse), we need to update the `_parse_batch_get` method to pick out the values needed to create a reference from the `missing` value.

Resolving patch:
diff --git a/firestore/google/cloud/firestore_v1/client.py b/firestore/google/cloud/firestore_v1/client.py
--- a/firestore/google/cloud/firestore_v1/client.py
+++ b/firestore/google/cloud/firestore_v1/client.py
@@ -520,8 +520,9 @@ def _parse_batch_get(get_doc_response, reference_map, client):
             update_time=get_doc_response.found.update_time,
         )
     elif result_type == "missing":
+        reference = _get_reference(get_doc_response.missing, reference_map)
         snapshot = DocumentSnapshot(
-            None,
+            reference,
             None,
             exists=False,
             read_time=get_doc_response.read_time,


Test patch:
diff --git a/firestore/tests/system.py b/firestore/tests/system.py
--- a/firestore/tests/system.py
+++ b/firestore/tests/system.py
@@ -769,6 +769,7 @@ def test_get_all(client, cleanup):
     assert snapshots[0].exists
     assert snapshots[1].exists
     assert not snapshots[2].exists
+
     snapshots = [snapshot for snapshot in snapshots if snapshot.exists]
     id_attr = operator.attrgetter("id")
     snapshots.sort(key=id_attr)
diff --git a/firestore/tests/unit/v1/test_client.py b/firestore/tests/unit/v1/test_client.py
--- a/firestore/tests/unit/v1/test_client.py
+++ b/firestore/tests/unit/v1/test_client.py
@@ -594,11 +594,16 @@ def test_found(self):
         self.assertEqual(snapshot.update_time, update_time)
 
     def test_missing(self):
+        from google.cloud.firestore_v1.document import DocumentReference
+
         ref_string = self._dummy_ref_string()
         response_pb = _make_batch_response(missing=ref_string)
-
-        snapshot = self._call_fut(response_pb, {})
+        document = DocumentReference("fizz", "bazz", client=mock.sentinel.client)
+        reference_map = {ref_string: document}
+        snapshot = self._call_fut(response_pb, reference_map)
         self.assertFalse(snapshot.exists)
+        self.assertEqual(snapshot.id, "bazz")
+        self.assertIsNone(snapshot._data)
 
     def test_unset_result_type(self):
         response_pb = _make_batch_response()