Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/google/cloud/firestore_v1/document.py: 46%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

50 statements  

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. 

14 

15"""Classes for representing documents for the Google Cloud Firestore API.""" 

16from __future__ import annotations 

17import datetime 

18import logging 

19from typing import Any, Callable, Generator, Iterable 

20 

21from google.api_core import gapic_v1 

22from google.api_core import retry as retries 

23from google.cloud._helpers import _datetime_to_pb_timestamp # type: ignore 

24from google.protobuf.timestamp_pb2 import Timestamp 

25 

26from google.cloud.firestore_v1 import _helpers 

27from google.cloud.firestore_v1.base_document import ( 

28 BaseDocumentReference, 

29 DocumentSnapshot, 

30 _first_write_result, 

31) 

32from google.cloud.firestore_v1.types import write 

33from google.cloud.firestore_v1.watch import Watch 

34 

35logger = logging.getLogger(__name__) 

36 

37 

38class DocumentReference(BaseDocumentReference): 

39 """A reference to a document in a Firestore database. 

40 

41 The document may already exist or can be created by this class. 

42 

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. 

52 

53 Raises: 

54 ValueError: if 

55 

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 """ 

62 

63 def __init__(self, *path, **kwargs) -> None: 

64 super(DocumentReference, self).__init__(*path, **kwargs) 

65 

66 def create( 

67 self, 

68 document_data: dict, 

69 retry: retries.Retry | object | None = gapic_v1.method.DEFAULT, 

70 timeout: float | None = None, 

71 ) -> write.WriteResult: 

72 """Create a document in the Firestore database. 

73 

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 

80 

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. 

88 

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. 

93 

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) 

101 

102 def set( 

103 self, 

104 document_data: dict, 

105 merge: bool = False, 

106 retry: retries.Retry | object | None = gapic_v1.method.DEFAULT, 

107 timeout: float | None = None, 

108 ) -> write.WriteResult: 

109 """Create / replace / merge a document in the Firestore database. 

110 

111 - To "upsert" a document (create if it doesn't exist, replace completely 

112 if it does), leave the ``merge`` argument at its default: 

113 

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 

120 

121 - To "merge" ``document_data`` with an existing document (creating if 

122 the document does not exist), pass ``merge`` as True``: 

123 

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 

129 

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. 

133 

134 

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: 

138 

139 

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 

146 

147 For more information on field paths, see 

148 :meth:`~google.cloud.firestore_v1.base_client.BaseClient.field_path`. 

149 

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. 

160 

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) 

169 

170 def update( 

171 self, 

172 field_updates: dict, 

173 option: _helpers.WriteOption | None = None, 

174 retry: retries.Retry | object | None = gapic_v1.method.DEFAULT, 

175 timeout: float | None = None, 

176 ) -> write.WriteResult: 

177 """Update an existing document in the Firestore database. 

178 

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. 

182 

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 

187 

188 .. code-block:: python 

189 

190 >>> snapshot = document.get() 

191 >>> snapshot.to_dict() 

192 { 

193 'foo': { 

194 'bar': 'baz', 

195 }, 

196 'other': True, 

197 } 

198 

199 stored on the server. If the field name is used in the update: 

200 

201 .. code-block:: python 

202 

203 >>> field_updates = { 

204 ... 'foo': { 

205 ... 'quux': 800, 

206 ... }, 

207 ... } 

208 >>> document.update(field_updates) 

209 

210 then all of ``foo`` will be overwritten on the server and the new 

211 value will be 

212 

213 .. code-block:: python 

214 

215 >>> snapshot = document.get() 

216 >>> snapshot.to_dict() 

217 { 

218 'foo': { 

219 'quux': 800, 

220 }, 

221 'other': True, 

222 } 

223 

224 On the other hand, if a ``.``-delimited **field path** is used in the 

225 update: 

226 

227 .. code-block:: python 

228 

229 >>> field_updates = { 

230 ... 'foo.quux': 800, 

231 ... } 

232 >>> document.update(field_updates) 

233 

234 then only ``foo.quux`` will be updated on the server and the 

235 field ``foo.bar`` will remain intact: 

236 

237 .. code-block:: python 

238 

239 >>> snapshot = document.get() 

240 >>> snapshot.to_dict() 

241 { 

242 'foo': { 

243 'bar': 'baz', 

244 'quux': 800, 

245 }, 

246 'other': True, 

247 } 

248 

249 .. warning:: 

250 

251 A **field path** can only be used as a top-level key in 

252 ``field_updates``. 

253 

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 

257 

258 .. code-block:: python 

259 

260 >>> field_updates = { 

261 ... 'other': firestore.DELETE_FIELD, 

262 ... } 

263 >>> document.update(field_updates) 

264 

265 would update the value on the server to: 

266 

267 .. code-block:: python 

268 

269 >>> snapshot = document.get() 

270 >>> snapshot.to_dict() 

271 { 

272 'foo': { 

273 'bar': 'baz', 

274 }, 

275 } 

276 

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 

282 

283 .. code-block:: python 

284 

285 >>> field_updates = { 

286 ... 'foo.now': firestore.SERVER_TIMESTAMP, 

287 ... } 

288 >>> document.update(field_updates) 

289 

290 would update the value on the server to: 

291 

292 .. code-block:: python 

293 

294 >>> snapshot = document.get() 

295 >>> snapshot.to_dict() 

296 { 

297 'foo': { 

298 'bar': 'baz', 

299 'now': datetime.datetime(2012, ...), 

300 }, 

301 'other': True, 

302 } 

303 

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. 

314 

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. 

319 

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) 

327 

328 def delete( 

329 self, 

330 option: _helpers.WriteOption | None = None, 

331 retry: retries.Retry | object | None = gapic_v1.method.DEFAULT, 

332 timeout: float | None = None, 

333 ) -> Timestamp: 

334 """Delete the current document in the Firestore database. 

335 

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. 

344 

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) 

353 

354 commit_response = self._client._firestore_api.commit( 

355 request=request, 

356 metadata=self._client._rpc_metadata, 

357 **kwargs, 

358 ) 

359 

360 return commit_response.commit_time 

361 

362 def get( 

363 self, 

364 field_paths: Iterable[str] | None = None, 

365 transaction=None, 

366 retry: retries.Retry | object | None = gapic_v1.method.DEFAULT, 

367 timeout: float | None = None, 

368 *, 

369 read_time: datetime.datetime | None = None, 

370 ) -> DocumentSnapshot: 

371 """Retrieve a snapshot of the current document. 

372 

373 See :meth:`~google.cloud.firestore_v1.base_client.BaseClient.field_path` for 

374 more information on **field paths**. 

375 

376 If a ``transaction`` is used and it already has write operations 

377 added, this method cannot be used (i.e. read-after-write is not 

378 allowed). 

379 

380 Args: 

381 field_paths (Optional[Iterable[str, ...]]): An iterable of field 

382 paths (``.``-delimited list of field names) to use as a 

383 projection of document fields in the returned results. If 

384 no value is provided, all fields will be returned. 

385 transaction (Optional[:class:`~google.cloud.firestore_v1.transaction.Transaction`]): 

386 An existing transaction that this reference 

387 will be retrieved in. 

388 retry (google.api_core.retry.Retry): Designation of what errors, if an y, 

389 should be retried. Defaults to a system-specified policy. 

390 timeout (float): The timeout for this request. Defaults to a 

391 system-specified value. 

392 read_time (Optional[datetime.datetime]): If set, reads documents as they were at the given 

393 time. This must be a timestamp within the past one hour, or if Point-in-Time Recovery 

394 is enabled, can additionally be a whole minute timestamp within the past 7 days. If no 

395 timezone is specified in the :class:`datetime.datetime` object, it is assumed to be UTC. 

396 

397 Returns: 

398 :class:`~google.cloud.firestore_v1.base_document.DocumentSnapshot`: 

399 A snapshot of the current document. If the document does not 

400 exist at the time of the snapshot is taken, the snapshot's 

401 :attr:`reference`, :attr:`data`, :attr:`update_time`, and 

402 :attr:`create_time` attributes will all be ``None`` and 

403 its :attr:`exists` attribute will be ``False``. 

404 """ 

405 from google.cloud.firestore_v1.base_client import _parse_batch_get 

406 

407 request, kwargs = self._prep_batch_get( 

408 field_paths, transaction, retry, timeout, read_time 

409 ) 

410 

411 response_iter = self._client._firestore_api.batch_get_documents( 

412 request=request, 

413 metadata=self._client._rpc_metadata, 

414 **kwargs, 

415 ) 

416 

417 get_doc_response = next(response_iter, None) 

418 

419 if get_doc_response is not None: 

420 return _parse_batch_get( 

421 get_doc_response=get_doc_response, 

422 reference_map={self._document_path: self}, 

423 client=self._client, 

424 ) 

425 

426 logger.warning( 

427 "`batch_get_documents` unexpectedly returned empty " 

428 "stream. Expected one object.", 

429 ) 

430 

431 return DocumentSnapshot( 

432 self, 

433 None, 

434 exists=False, 

435 read_time=_datetime_to_pb_timestamp(datetime.datetime.now()), 

436 create_time=None, 

437 update_time=None, 

438 ) 

439 

440 def collections( 

441 self, 

442 page_size: int | None = None, 

443 retry: retries.Retry | object | None = gapic_v1.method.DEFAULT, 

444 timeout: float | None = None, 

445 *, 

446 read_time: datetime.datetime | None = None, 

447 ) -> Generator[Any, Any, None]: 

448 """List subcollections of the current document. 

449 

450 Args: 

451 page_size (Optional[int]]): The maximum number of collections 

452 in each page of results from this request. Non-positive values 

453 are ignored. Defaults to a sensible value set by the API. 

454 retry (google.api_core.retry.Retry): Designation of what errors, if any, 

455 should be retried. Defaults to a system-specified policy. 

456 timeout (float): The timeout for this request. Defaults to a 

457 system-specified value. 

458 read_time (Optional[datetime.datetime]): If set, reads documents as they were at the given 

459 time. This must be a timestamp within the past one hour, or if Point-in-Time Recovery 

460 is enabled, can additionally be a whole minute timestamp within the past 7 days. If no 

461 timezone is specified in the :class:`datetime.datetime` object, it is assumed to be UTC. 

462 

463 Returns: 

464 Sequence[:class:`~google.cloud.firestore_v1.collection.CollectionReference`]: 

465 iterator of subcollections of the current document. If the 

466 document does not exist at the time of `snapshot`, the 

467 iterator will be empty 

468 """ 

469 request, kwargs = self._prep_collections(page_size, retry, timeout, read_time) 

470 

471 iterator = self._client._firestore_api.list_collection_ids( 

472 request=request, 

473 metadata=self._client._rpc_metadata, 

474 **kwargs, 

475 ) 

476 

477 for collection_id in iterator: 

478 yield self.collection(collection_id) 

479 

480 def on_snapshot(self, callback: Callable) -> Watch: 

481 """Watch this document. 

482 

483 This starts a watch on this document using a background thread. The 

484 provided callback is run on the snapshot. 

485 

486 Args: 

487 callback(Callable[[:class:`~google.cloud.firestore_v1.base_document.DocumentSnapshot`], NoneType]): 

488 a callback to run when a change occurs 

489 

490 Example: 

491 

492 .. code-block:: python 

493 

494 from google.cloud import firestore_v1 

495 

496 db = firestore_v1.Client() 

497 collection_ref = db.collection(u'users') 

498 

499 def on_snapshot(document_snapshot, changes, read_time): 

500 doc = document_snapshot 

501 print(u'{} => {}'.format(doc.id, doc.to_dict())) 

502 

503 doc_ref = db.collection(u'users').document( 

504 u'alovelace' + unique_resource_id()) 

505 

506 # Watch this document 

507 doc_watch = doc_ref.on_snapshot(on_snapshot) 

508 

509 # Terminate this watch 

510 doc_watch.unsubscribe() 

511 """ 

512 return Watch.for_document(self, callback, DocumentSnapshot)