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

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

16import datetime 

17import logging 

18 

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 

22 

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 

33 

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 = gapic_v1.method.DEFAULT, 

70 timeout: float = 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 = gapic_v1.method.DEFAULT, 

107 timeout: float = 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, 

174 retry: retries.Retry = gapic_v1.method.DEFAULT, 

175 timeout: float = 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, 

331 retry: retries.Retry = gapic_v1.method.DEFAULT, 

332 timeout: float = 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, 

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. 

370 

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

372 more information on **field paths**. 

373 

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). 

377 

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. 

390 

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 

400 

401 request, kwargs = self._prep_batch_get(field_paths, transaction, retry, timeout) 

402 

403 response_iter = self._client._firestore_api.batch_get_documents( 

404 request=request, 

405 metadata=self._client._rpc_metadata, 

406 **kwargs, 

407 ) 

408 

409 get_doc_response = next(response_iter, None) 

410 

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 ) 

417 

418 logger.warning( 

419 "`batch_get_documents` unexpectedly returned empty " 

420 "stream. Expected one object.", 

421 ) 

422 

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 ) 

431 

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. 

439 

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. 

448 

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) 

456 

457 iterator = self._client._firestore_api.list_collection_ids( 

458 request=request, 

459 metadata=self._client._rpc_metadata, 

460 **kwargs, 

461 ) 

462 

463 for collection_id in iterator: 

464 yield self.collection(collection_id) 

465 

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

467 """Watch this document. 

468 

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

470 provided callback is run on the snapshot. 

471 

472 Args: 

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

474 a callback to run when a change occurs 

475 

476 Example: 

477 

478 .. code-block:: python 

479 

480 from google.cloud import firestore_v1 

481 

482 db = firestore_v1.Client() 

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

484 

485 def on_snapshot(document_snapshot, changes, read_time): 

486 doc = document_snapshot 

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

488 

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

490 u'alovelace' + unique_resource_id()) 

491 

492 # Watch this document 

493 doc_watch = doc_ref.on_snapshot(on_snapshot) 

494 

495 # Terminate this watch 

496 doc_watch.unsubscribe() 

497 """ 

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