Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/google/cloud/firestore_v1/document.py: 46%
48 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-09 06:27 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-09 06:27 +0000
1# Copyright 2017 Google LLC All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
15"""Classes for representing documents for the Google Cloud Firestore API."""
16import datetime
17import logging
19from google.api_core import gapic_v1
20from google.api_core import retry as retries
21from google.cloud._helpers import _datetime_to_pb_timestamp # type: ignore
23from google.cloud.firestore_v1.base_document import (
24 BaseDocumentReference,
25 DocumentSnapshot,
26 _first_write_result,
27)
28from google.cloud.firestore_v1 import _helpers
29from google.cloud.firestore_v1.types import write
30from google.cloud.firestore_v1.watch import Watch
31from google.protobuf.timestamp_pb2 import Timestamp
32from typing import Any, Callable, Generator, Iterable
35logger = logging.getLogger(__name__)
38class DocumentReference(BaseDocumentReference):
39 """A reference to a document in a Firestore database.
41 The document may already exist or can be created by this class.
43 Args:
44 path (Tuple[str, ...]): The components in the document path.
45 This is a series of strings representing each collection and
46 sub-collection ID, as well as the document IDs for any documents
47 that contain a sub-collection (as well as the base document).
48 kwargs (dict): The keyword arguments for the constructor. The only
49 supported keyword is ``client`` and it must be a
50 :class:`~google.cloud.firestore_v1.client.Client`. It represents
51 the client that created this document reference.
53 Raises:
54 ValueError: if
56 * the ``path`` is empty
57 * there are an even number of elements
58 * a collection ID in ``path`` is not a string
59 * a document ID in ``path`` is not a string
60 TypeError: If a keyword other than ``client`` is used.
61 """
63 def __init__(self, *path, **kwargs) -> None:
64 super(DocumentReference, self).__init__(*path, **kwargs)
66 def create(
67 self,
68 document_data: dict,
69 retry: retries.Retry = gapic_v1.method.DEFAULT,
70 timeout: float = None,
71 ) -> write.WriteResult:
72 """Create a document in the Firestore database.
74 >>> document_data = {"a": 1, "b": {"c": "Two"}}
75 >>> document.get().to_dict() is None # does not exist
76 True
77 >>> document.create(document_data)
78 >>> document.get().to_dict() == document_data # exists
79 True
81 Args:
82 document_data (dict): Property names and values to use for
83 creating a document.
84 retry (google.api_core.retry.Retry): Designation of what errors, if any,
85 should be retried. Defaults to a system-specified policy.
86 timeout (float): The timeout for this request. Defaults to a
87 system-specified value.
89 Returns:
90 :class:`~google.cloud.firestore_v1.types.WriteResult`:
91 The write result corresponding to the committed document.
92 A write result contains an ``update_time`` field.
94 Raises:
95 :class:`google.cloud.exceptions.Conflict`:
96 If the document already exists.
97 """
98 batch, kwargs = self._prep_create(document_data, retry, timeout)
99 write_results = batch.commit(**kwargs)
100 return _first_write_result(write_results)
102 def set(
103 self,
104 document_data: dict,
105 merge: bool = False,
106 retry: retries.Retry = gapic_v1.method.DEFAULT,
107 timeout: float = None,
108 ) -> write.WriteResult:
109 """Create / replace / merge a document in the Firestore database.
111 - To "upsert" a document (create if it doesn't exist, replace completely
112 if it does), leave the ``merge`` argument at its default:
114 >>> document_data = {"a": 1, "b": {"c": "Two"}}
115 >>> document.get().to_dict() is None # document exists
116 False
117 >>> document.set(document_data)
118 >>> document.get().to_dict() == document_data # exists
119 True
121 - To "merge" ``document_data`` with an existing document (creating if
122 the document does not exist), pass ``merge`` as True``:
124 >>> document_data = {"a": 1, "b": {"c": "Two"}}
125 >>> document.get().to_dict() == {"d": "Three", "b": {}} # exists
126 >>> document.set(document_data, merge=True)
127 >>> document.get().to_dict() == {"a": 1, "d": "Three", "b": {"c": "Two"}}
128 True
130 In this case, existing documents with top-level keys which are
131 not present in ``document_data`` (``"d"``) will preserve the values
132 of those keys.
135 - To merge only specific fields of ``document_data`` with existing
136 documents (creating if the document does not exist), pass ``merge``
137 as a list of field paths:
140 >>> document_data = {"a": 1, "b": {"c": "Two"}}
141 >>> document.get().to_dict() == {"b": {"c": "One", "d": "Four" }} # exists
142 True
143 >>> document.set(document_data, merge=["b.c"])
144 >>> document.get().to_dict() == {"b": {"c": "Two", "d": "Four" }}
145 True
147 For more information on field paths, see
148 :meth:`~google.cloud.firestore_v1.base_client.BaseClient.field_path`.
150 Args:
151 document_data (dict): Property names and values to use for
152 replacing a document.
153 merge (Optional[bool] or Optional[List<fieldpath>]):
154 If True, apply merging instead of overwriting the state
155 of the document.
156 retry (google.api_core.retry.Retry): Designation of what errors, if any,
157 should be retried. Defaults to a system-specified policy.
158 timeout (float): The timeout for this request. Defaults to a
159 system-specified value.
161 Returns:
162 :class:`~google.cloud.firestore_v1.types.WriteResult`:
163 The write result corresponding to the committed document. A write
164 result contains an ``update_time`` field.
165 """
166 batch, kwargs = self._prep_set(document_data, merge, retry, timeout)
167 write_results = batch.commit(**kwargs)
168 return _first_write_result(write_results)
170 def update(
171 self,
172 field_updates: dict,
173 option: _helpers.WriteOption = None,
174 retry: retries.Retry = gapic_v1.method.DEFAULT,
175 timeout: float = None,
176 ) -> write.WriteResult:
177 """Update an existing document in the Firestore database.
179 By default, this method verifies that the document exists on the
180 server before making updates. A write ``option`` can be specified to
181 override these preconditions.
183 Each key in ``field_updates`` can either be a field name or a
184 **field path** (For more information on field paths, see
185 :meth:`~google.cloud.firestore_v1.base_client.BaseClient.field_path`.)
186 To illustrate this, consider a document with
188 .. code-block:: python
190 >>> snapshot = document.get()
191 >>> snapshot.to_dict()
192 {
193 'foo': {
194 'bar': 'baz',
195 },
196 'other': True,
197 }
199 stored on the server. If the field name is used in the update:
201 .. code-block:: python
203 >>> field_updates = {
204 ... 'foo': {
205 ... 'quux': 800,
206 ... },
207 ... }
208 >>> document.update(field_updates)
210 then all of ``foo`` will be overwritten on the server and the new
211 value will be
213 .. code-block:: python
215 >>> snapshot = document.get()
216 >>> snapshot.to_dict()
217 {
218 'foo': {
219 'quux': 800,
220 },
221 'other': True,
222 }
224 On the other hand, if a ``.``-delimited **field path** is used in the
225 update:
227 .. code-block:: python
229 >>> field_updates = {
230 ... 'foo.quux': 800,
231 ... }
232 >>> document.update(field_updates)
234 then only ``foo.quux`` will be updated on the server and the
235 field ``foo.bar`` will remain intact:
237 .. code-block:: python
239 >>> snapshot = document.get()
240 >>> snapshot.to_dict()
241 {
242 'foo': {
243 'bar': 'baz',
244 'quux': 800,
245 },
246 'other': True,
247 }
249 .. warning::
251 A **field path** can only be used as a top-level key in
252 ``field_updates``.
254 To delete / remove a field from an existing document, use the
255 :attr:`~google.cloud.firestore_v1.transforms.DELETE_FIELD` sentinel.
256 So with the example above, sending
258 .. code-block:: python
260 >>> field_updates = {
261 ... 'other': firestore.DELETE_FIELD,
262 ... }
263 >>> document.update(field_updates)
265 would update the value on the server to:
267 .. code-block:: python
269 >>> snapshot = document.get()
270 >>> snapshot.to_dict()
271 {
272 'foo': {
273 'bar': 'baz',
274 },
275 }
277 To set a field to the current time on the server when the
278 update is received, use the
279 :attr:`~google.cloud.firestore_v1.transforms.SERVER_TIMESTAMP`
280 sentinel.
281 Sending
283 .. code-block:: python
285 >>> field_updates = {
286 ... 'foo.now': firestore.SERVER_TIMESTAMP,
287 ... }
288 >>> document.update(field_updates)
290 would update the value on the server to:
292 .. code-block:: python
294 >>> snapshot = document.get()
295 >>> snapshot.to_dict()
296 {
297 'foo': {
298 'bar': 'baz',
299 'now': datetime.datetime(2012, ...),
300 },
301 'other': True,
302 }
304 Args:
305 field_updates (dict): Field names or paths to update and values
306 to update with.
307 option (Optional[:class:`~google.cloud.firestore_v1.client.WriteOption`]):
308 A write option to make assertions / preconditions on the server
309 state of the document before applying changes.
310 retry (google.api_core.retry.Retry): Designation of what errors, if any,
311 should be retried. Defaults to a system-specified policy.
312 timeout (float): The timeout for this request. Defaults to a
313 system-specified value.
315 Returns:
316 :class:`~google.cloud.firestore_v1.types.WriteResult`:
317 The write result corresponding to the updated document. A write
318 result contains an ``update_time`` field.
320 Raises:
321 :class:`google.cloud.exceptions.NotFound`:
322 If the document does not exist.
323 """
324 batch, kwargs = self._prep_update(field_updates, option, retry, timeout)
325 write_results = batch.commit(**kwargs)
326 return _first_write_result(write_results)
328 def delete(
329 self,
330 option: _helpers.WriteOption = None,
331 retry: retries.Retry = gapic_v1.method.DEFAULT,
332 timeout: float = None,
333 ) -> Timestamp:
334 """Delete the current document in the Firestore database.
336 Args:
337 option (Optional[:class:`~google.cloud.firestore_v1.client.WriteOption`]):
338 A write option to make assertions / preconditions on the server
339 state of the document before applying changes.
340 retry (google.api_core.retry.Retry): Designation of what errors, if any,
341 should be retried. Defaults to a system-specified policy.
342 timeout (float): The timeout for this request. Defaults to a
343 system-specified value.
345 Returns:
346 :class:`google.protobuf.timestamp_pb2.Timestamp`:
347 The time that the delete request was received by the server.
348 If the document did not exist when the delete was sent (i.e.
349 nothing was deleted), this method will still succeed and will
350 still return the time that the request was received by the server.
351 """
352 request, kwargs = self._prep_delete(option, retry, timeout)
354 commit_response = self._client._firestore_api.commit(
355 request=request,
356 metadata=self._client._rpc_metadata,
357 **kwargs,
358 )
360 return commit_response.commit_time
362 def get(
363 self,
364 field_paths: Iterable[str] = None,
365 transaction=None,
366 retry: retries.Retry = gapic_v1.method.DEFAULT,
367 timeout: float = None,
368 ) -> DocumentSnapshot:
369 """Retrieve a snapshot of the current document.
371 See :meth:`~google.cloud.firestore_v1.base_client.BaseClient.field_path` for
372 more information on **field paths**.
374 If a ``transaction`` is used and it already has write operations
375 added, this method cannot be used (i.e. read-after-write is not
376 allowed).
378 Args:
379 field_paths (Optional[Iterable[str, ...]]): An iterable of field
380 paths (``.``-delimited list of field names) to use as a
381 projection of document fields in the returned results. If
382 no value is provided, all fields will be returned.
383 transaction (Optional[:class:`~google.cloud.firestore_v1.transaction.Transaction`]):
384 An existing transaction that this reference
385 will be retrieved in.
386 retry (google.api_core.retry.Retry): Designation of what errors, if an y,
387 should be retried. Defaults to a system-specified policy.
388 timeout (float): The timeout for this request. Defaults to a
389 system-specified value.
391 Returns:
392 :class:`~google.cloud.firestore_v1.base_document.DocumentSnapshot`:
393 A snapshot of the current document. If the document does not
394 exist at the time of the snapshot is taken, the snapshot's
395 :attr:`reference`, :attr:`data`, :attr:`update_time`, and
396 :attr:`create_time` attributes will all be ``None`` and
397 its :attr:`exists` attribute will be ``False``.
398 """
399 from google.cloud.firestore_v1.base_client import _parse_batch_get
401 request, kwargs = self._prep_batch_get(field_paths, transaction, retry, timeout)
403 response_iter = self._client._firestore_api.batch_get_documents(
404 request=request,
405 metadata=self._client._rpc_metadata,
406 **kwargs,
407 )
409 get_doc_response = next(response_iter, None)
411 if get_doc_response is not None:
412 return _parse_batch_get(
413 get_doc_response=get_doc_response,
414 reference_map={self._document_path: self},
415 client=self._client,
416 )
418 logger.warning(
419 "`batch_get_documents` unexpectedly returned empty "
420 "stream. Expected one object.",
421 )
423 return DocumentSnapshot(
424 self,
425 None,
426 exists=False,
427 read_time=_datetime_to_pb_timestamp(datetime.datetime.now()),
428 create_time=None,
429 update_time=None,
430 )
432 def collections(
433 self,
434 page_size: int = None,
435 retry: retries.Retry = gapic_v1.method.DEFAULT,
436 timeout: float = None,
437 ) -> Generator[Any, Any, None]:
438 """List subcollections of the current document.
440 Args:
441 page_size (Optional[int]]): The maximum number of collections
442 in each page of results from this request. Non-positive values
443 are ignored. Defaults to a sensible value set by the API.
444 retry (google.api_core.retry.Retry): Designation of what errors, if any,
445 should be retried. Defaults to a system-specified policy.
446 timeout (float): The timeout for this request. Defaults to a
447 system-specified value.
449 Returns:
450 Sequence[:class:`~google.cloud.firestore_v1.collection.CollectionReference`]:
451 iterator of subcollections of the current document. If the
452 document does not exist at the time of `snapshot`, the
453 iterator will be empty
454 """
455 request, kwargs = self._prep_collections(page_size, retry, timeout)
457 iterator = self._client._firestore_api.list_collection_ids(
458 request=request,
459 metadata=self._client._rpc_metadata,
460 **kwargs,
461 )
463 for collection_id in iterator:
464 yield self.collection(collection_id)
466 def on_snapshot(self, callback: Callable) -> Watch:
467 """Watch this document.
469 This starts a watch on this document using a background thread. The
470 provided callback is run on the snapshot.
472 Args:
473 callback(Callable[[:class:`~google.cloud.firestore_v1.base_document.DocumentSnapshot`], NoneType]):
474 a callback to run when a change occurs
476 Example:
478 .. code-block:: python
480 from google.cloud import firestore_v1
482 db = firestore_v1.Client()
483 collection_ref = db.collection(u'users')
485 def on_snapshot(document_snapshot, changes, read_time):
486 doc = document_snapshot
487 print(u'{} => {}'.format(doc.id, doc.to_dict()))
489 doc_ref = db.collection(u'users').document(
490 u'alovelace' + unique_resource_id())
492 # Watch this document
493 doc_watch = doc_ref.on_snapshot(on_snapshot)
495 # Terminate this watch
496 doc_watch.unsubscribe()
497 """
498 return Watch.for_document(self, callback, DocumentSnapshot)