Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/google/cloud/firestore_v1/async_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

46 statements  

1# Copyright 2020 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 AsyncGenerator, Iterable 

20 

21from google.api_core import gapic_v1 

22from google.api_core import retry_async 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 

33 

34logger = logging.getLogger(__name__) 

35 

36 

37class AsyncDocumentReference(BaseDocumentReference): 

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

39 

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

41 

42 Args: 

43 path (Tuple[str, ...]): The components in the document path. 

44 This is a series of strings representing each collection and 

45 sub-collection ID, as well as the document IDs for any documents 

46 that contain a sub-collection (as well as the base document). 

47 kwargs (dict): The keyword arguments for the constructor. The only 

48 supported keyword is ``client`` and it must be a 

49 :class:`~google.cloud.firestore_v1.client.Client`. It represents 

50 the client that created this document reference. 

51 

52 Raises: 

53 ValueError: if 

54 

55 * the ``path`` is empty 

56 * there are an even number of elements 

57 * a collection ID in ``path`` is not a string 

58 * a document ID in ``path`` is not a string 

59 TypeError: If a keyword other than ``client`` is used. 

60 """ 

61 

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

63 super(AsyncDocumentReference, self).__init__(*path, **kwargs) 

64 

65 async def create( 

66 self, 

67 document_data: dict, 

68 retry: retries.AsyncRetry | object | None = gapic_v1.method.DEFAULT, 

69 timeout: float | None = None, 

70 ) -> write.WriteResult: 

71 """Create the current document in the Firestore database. 

72 

73 Args: 

74 document_data (dict): Property names and values to use for 

75 creating a document. 

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

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

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

79 system-specified value. 

80 

81 Returns: 

82 :class:`~google.cloud.firestore_v1.types.WriteResult`: 

83 The write result corresponding to the committed document. 

84 A write result contains an ``update_time`` field. 

85 

86 Raises: 

87 :class:`google.cloud.exceptions.Conflict`: 

88 If the document already exists. 

89 """ 

90 batch, kwargs = self._prep_create(document_data, retry, timeout) 

91 write_results = await batch.commit(**kwargs) 

92 return _first_write_result(write_results) 

93 

94 async def set( 

95 self, 

96 document_data: dict, 

97 merge: bool = False, 

98 retry: retries.AsyncRetry | object | None = gapic_v1.method.DEFAULT, 

99 timeout: float | None = None, 

100 ) -> write.WriteResult: 

101 """Replace the current document in the Firestore database. 

102 

103 A write ``option`` can be specified to indicate preconditions of 

104 the "set" operation. If no ``option`` is specified and this document 

105 doesn't exist yet, this method will create it. 

106 

107 Overwrites all content for the document with the fields in 

108 ``document_data``. This method performs almost the same functionality 

109 as :meth:`create`. The only difference is that this method doesn't 

110 make any requirements on the existence of the document (unless 

111 ``option`` is used), whereas as :meth:`create` will fail if the 

112 document already exists. 

113 

114 Args: 

115 document_data (dict): Property names and values to use for 

116 replacing a document. 

117 merge (Optional[bool] or Optional[List<apispec>]): 

118 If True, apply merging instead of overwriting the state 

119 of the document. 

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

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

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

123 system-specified value. 

124 

125 Returns: 

126 :class:`~google.cloud.firestore_v1.types.WriteResult`: 

127 The write result corresponding to the committed document. A write 

128 result contains an ``update_time`` field. 

129 """ 

130 batch, kwargs = self._prep_set(document_data, merge, retry, timeout) 

131 write_results = await batch.commit(**kwargs) 

132 return _first_write_result(write_results) 

133 

134 async def update( 

135 self, 

136 field_updates: dict, 

137 option: _helpers.WriteOption | None = None, 

138 retry: retries.AsyncRetry | object | None = gapic_v1.method.DEFAULT, 

139 timeout: float | None = None, 

140 ) -> write.WriteResult: 

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

142 

143 By default, this method verifies that the document exists on the 

144 server before making updates. A write ``option`` can be specified to 

145 override these preconditions. 

146 

147 Each key in ``field_updates`` can either be a field name or a 

148 **field path** (For more information on **field paths**, see 

149 :meth:`~google.cloud.firestore_v1.client.Client.field_path`.) To 

150 illustrate this, consider a document with 

151 

152 .. code-block:: python 

153 

154 >>> snapshot = await document.get() 

155 >>> snapshot.to_dict() 

156 { 

157 'foo': { 

158 'bar': 'baz', 

159 }, 

160 'other': True, 

161 } 

162 

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

164 

165 .. code-block:: python 

166 

167 >>> field_updates = { 

168 ... 'foo': { 

169 ... 'quux': 800, 

170 ... }, 

171 ... } 

172 >>> await document.update(field_updates) 

173 

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

175 value will be 

176 

177 .. code-block:: python 

178 

179 >>> snapshot = await document.get() 

180 >>> snapshot.to_dict() 

181 { 

182 'foo': { 

183 'quux': 800, 

184 }, 

185 'other': True, 

186 } 

187 

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

189 update: 

190 

191 .. code-block:: python 

192 

193 >>> field_updates = { 

194 ... 'foo.quux': 800, 

195 ... } 

196 >>> await document.update(field_updates) 

197 

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

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

200 

201 .. code-block:: python 

202 

203 >>> snapshot = await document.get() 

204 >>> snapshot.to_dict() 

205 { 

206 'foo': { 

207 'bar': 'baz', 

208 'quux': 800, 

209 }, 

210 'other': True, 

211 } 

212 

213 .. warning:: 

214 

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

216 ``field_updates``. 

217 

218 To delete / remove a field from an existing document, use the 

219 :attr:`~google.cloud.firestore_v1.transforms.DELETE_FIELD` sentinel. 

220 So with the example above, sending 

221 

222 .. code-block:: python 

223 

224 >>> field_updates = { 

225 ... 'other': firestore.DELETE_FIELD, 

226 ... } 

227 >>> await document.update(field_updates) 

228 

229 would update the value on the server to: 

230 

231 .. code-block:: python 

232 

233 >>> snapshot = await document.get() 

234 >>> snapshot.to_dict() 

235 { 

236 'foo': { 

237 'bar': 'baz', 

238 }, 

239 } 

240 

241 To set a field to the current time on the server when the 

242 update is received, use the 

243 :attr:`~google.cloud.firestore_v1.transforms.SERVER_TIMESTAMP` 

244 sentinel. 

245 Sending 

246 

247 .. code-block:: python 

248 

249 >>> field_updates = { 

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

251 ... } 

252 >>> await document.update(field_updates) 

253 

254 would update the value on the server to: 

255 

256 .. code-block:: python 

257 

258 >>> snapshot = await document.get() 

259 >>> snapshot.to_dict() 

260 { 

261 'foo': { 

262 'bar': 'baz', 

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

264 }, 

265 'other': True, 

266 } 

267 

268 Args: 

269 field_updates (dict): Field names or paths to update and values 

270 to update with. 

271 option (Optional[:class:`~google.cloud.firestore_v1.client.WriteOption`]): 

272 A write option to make assertions / preconditions on the server 

273 state of the document before applying changes. 

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

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

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

277 system-specified value. 

278 

279 Returns: 

280 :class:`~google.cloud.firestore_v1.types.WriteResult`: 

281 The write result corresponding to the updated document. A write 

282 result contains an ``update_time`` field. 

283 

284 Raises: 

285 :class:`google.cloud.exceptions.NotFound`: 

286 If the document does not exist. 

287 """ 

288 batch, kwargs = self._prep_update(field_updates, option, retry, timeout) 

289 write_results = await batch.commit(**kwargs) 

290 return _first_write_result(write_results) 

291 

292 async def delete( 

293 self, 

294 option: _helpers.WriteOption | None = None, 

295 retry: retries.AsyncRetry | object | None = gapic_v1.method.DEFAULT, 

296 timeout: float | None = None, 

297 ) -> Timestamp: 

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

299 

300 Args: 

301 option (Optional[:class:`~google.cloud.firestore_v1.client.WriteOption`]): 

302 A write option to make assertions / preconditions on the server 

303 state of the document before applying changes. 

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

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

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

307 system-specified value. 

308 

309 Returns: 

310 :class:`google.protobuf.timestamp_pb2.Timestamp`: 

311 The time that the delete request was received by the server. 

312 If the document did not exist when the delete was sent (i.e. 

313 nothing was deleted), this method will still succeed and will 

314 still return the time that the request was received by the server. 

315 """ 

316 request, kwargs = self._prep_delete(option, retry, timeout) 

317 

318 commit_response = await self._client._firestore_api.commit( 

319 request=request, 

320 metadata=self._client._rpc_metadata, 

321 **kwargs, 

322 ) 

323 

324 return commit_response.commit_time 

325 

326 async def get( 

327 self, 

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

329 transaction=None, 

330 retry: retries.AsyncRetry | object | None = gapic_v1.method.DEFAULT, 

331 timeout: float | None = None, 

332 *, 

333 read_time: datetime.datetime | None = None, 

334 ) -> DocumentSnapshot: 

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

336 

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

338 more information on **field paths**. 

339 

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

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

342 allowed). 

343 

344 Args: 

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

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

347 projection of document fields in the returned results. If 

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

349 transaction (Optional[:class:`~google.cloud.firestore_v1.async_transaction.AsyncTransaction`]): 

350 An existing transaction that this reference 

351 will be retrieved in. 

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

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

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

355 system-specified value. 

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

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

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

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

360 

361 Returns: 

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

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

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

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

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

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

368 """ 

369 from google.cloud.firestore_v1.base_client import _parse_batch_get 

370 

371 request, kwargs = self._prep_batch_get( 

372 field_paths, transaction, retry, timeout, read_time 

373 ) 

374 

375 response_iter = await self._client._firestore_api.batch_get_documents( 

376 request=request, 

377 metadata=self._client._rpc_metadata, 

378 **kwargs, 

379 ) 

380 

381 async for resp in response_iter: 

382 # Immediate return as the iterator should only ever have one item. 

383 return _parse_batch_get( 

384 get_doc_response=resp, 

385 reference_map={self._document_path: self}, 

386 client=self._client, 

387 ) 

388 

389 logger.warning( 

390 "`batch_get_documents` unexpectedly returned empty " 

391 "stream. Expected one object.", 

392 ) 

393 

394 return DocumentSnapshot( 

395 self, 

396 None, 

397 exists=False, 

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

399 create_time=None, 

400 update_time=None, 

401 ) 

402 

403 async def collections( 

404 self, 

405 page_size: int | None = None, 

406 retry: retries.AsyncRetry | object | None = gapic_v1.method.DEFAULT, 

407 timeout: float | None = None, 

408 *, 

409 read_time: datetime.datetime | None = None, 

410 ) -> AsyncGenerator: 

411 """List subcollections of the current document. 

412 

413 Args: 

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

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

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

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

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

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

420 system-specified value. 

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

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

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

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

425 

426 Returns: 

427 Sequence[:class:`~google.cloud.firestore_v1.async_collection.AsyncCollectionReference`]: 

428 iterator of subcollections of the current document. If the 

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

430 iterator will be empty 

431 """ 

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

433 

434 iterator = await self._client._firestore_api.list_collection_ids( 

435 request=request, 

436 metadata=self._client._rpc_metadata, 

437 **kwargs, 

438 ) 

439 

440 async for collection_id in iterator: 

441 yield self.collection(collection_id)