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

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

71 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"""Client for interacting with the Google Cloud Firestore API. 

16 

17This is the base from which all interactions with the API occur. 

18 

19In the hierarchy of API concepts 

20 

21* a :class:`~google.cloud.firestore_v1.client.Client` owns a 

22 :class:`~google.cloud.firestore_v1.async_collection.AsyncCollectionReference` 

23* a :class:`~google.cloud.firestore_v1.client.Client` owns a 

24 :class:`~google.cloud.firestore_v1.async_document.AsyncDocumentReference` 

25""" 

26from __future__ import annotations 

27 

28from typing import ( 

29 TYPE_CHECKING, 

30 Any, 

31 AsyncGenerator, 

32 Iterable, 

33 List, 

34 Optional, 

35 Union, 

36) 

37 

38from google.api_core import gapic_v1 

39from google.api_core import retry_async as retries 

40 

41from google.cloud.firestore_v1.async_batch import AsyncWriteBatch 

42from google.cloud.firestore_v1.async_collection import AsyncCollectionReference 

43from google.cloud.firestore_v1.async_document import ( 

44 AsyncDocumentReference, 

45 DocumentSnapshot, 

46) 

47from google.cloud.firestore_v1.async_query import AsyncCollectionGroup 

48from google.cloud.firestore_v1.async_transaction import AsyncTransaction 

49from google.cloud.firestore_v1.base_client import _parse_batch_get # type: ignore 

50from google.cloud.firestore_v1.base_client import _CLIENT_INFO, BaseClient, _path_helper 

51from google.cloud.firestore_v1.base_transaction import MAX_ATTEMPTS 

52from google.cloud.firestore_v1.field_path import FieldPath 

53from google.cloud.firestore_v1.services.firestore import ( 

54 async_client as firestore_client, 

55) 

56from google.cloud.firestore_v1.services.firestore.transports import ( 

57 grpc_asyncio as firestore_grpc_transport, 

58) 

59 

60if TYPE_CHECKING: # pragma: NO COVER 

61 import datetime 

62 

63 from google.cloud.firestore_v1.bulk_writer import BulkWriter 

64 

65 

66class AsyncClient(BaseClient): 

67 """Client for interacting with Google Cloud Firestore API. 

68 

69 .. note:: 

70 

71 Since the Cloud Firestore API requires the gRPC transport, no 

72 ``_http`` argument is accepted by this class. 

73 

74 Args: 

75 project (Optional[str]): The project which the client acts on behalf 

76 of. If not passed, falls back to the default inferred 

77 from the environment. 

78 credentials (Optional[~google.auth.credentials.Credentials]): The 

79 OAuth2 Credentials to use for this client. If not passed, falls 

80 back to the default inferred from the environment. 

81 database (Optional[str]): The database name that the client targets. 

82 For now, :attr:`DEFAULT_DATABASE` (the default value) is the 

83 only valid database. 

84 client_info (Optional[google.api_core.gapic_v1.client_info.ClientInfo]): 

85 The client info used to send a user-agent string along with API 

86 requests. If ``None``, then default info will be used. Generally, 

87 you only need to set this if you're developing your own library 

88 or partner tool. 

89 client_options (Union[dict, google.api_core.client_options.ClientOptions]): 

90 Client options used to set user options on the client. API Endpoint 

91 should be set through client_options. 

92 """ 

93 

94 def __init__( 

95 self, 

96 project=None, 

97 credentials=None, 

98 database=None, 

99 client_info=_CLIENT_INFO, 

100 client_options=None, 

101 ) -> None: 

102 super(AsyncClient, self).__init__( 

103 project=project, 

104 credentials=credentials, 

105 database=database, 

106 client_info=client_info, 

107 client_options=client_options, 

108 ) 

109 

110 def _to_sync_copy(self): 

111 from google.cloud.firestore_v1.client import Client 

112 

113 if not getattr(self, "_sync_copy", None): 

114 self._sync_copy = Client( 

115 project=self.project, 

116 credentials=self._credentials, 

117 database=self._database, 

118 client_info=self._client_info, 

119 client_options=self._client_options, 

120 ) 

121 return self._sync_copy 

122 

123 @property 

124 def _firestore_api(self): 

125 """Lazy-loading getter GAPIC Firestore API. 

126 Returns: 

127 :class:`~google.cloud.gapic.firestore.v1`.async_firestore_client.FirestoreAsyncClient: 

128 The GAPIC client with the credentials of the current client. 

129 """ 

130 return self._firestore_api_helper( 

131 firestore_grpc_transport.FirestoreGrpcAsyncIOTransport, 

132 firestore_client.FirestoreAsyncClient, 

133 firestore_client, 

134 ) 

135 

136 @property 

137 def _target(self): 

138 """Return the target (where the API is). 

139 Eg. "firestore.googleapis.com" 

140 

141 Returns: 

142 str: The location of the API. 

143 """ 

144 return self._target_helper(firestore_client.FirestoreAsyncClient) 

145 

146 def collection(self, *collection_path: str) -> AsyncCollectionReference: 

147 """Get a reference to a collection. 

148 

149 For a top-level collection: 

150 

151 .. code-block:: python 

152 

153 >>> client.collection('top') 

154 

155 For a sub-collection: 

156 

157 .. code-block:: python 

158 

159 >>> client.collection('mydocs/doc/subcol') 

160 >>> # is the same as 

161 >>> client.collection('mydocs', 'doc', 'subcol') 

162 

163 Sub-collections can be nested deeper in a similar fashion. 

164 

165 Args: 

166 collection_path: Can either be 

167 

168 * A single ``/``-delimited path to a collection 

169 * A tuple of collection path segments 

170 

171 Returns: 

172 :class:`~google.cloud.firestore_v1.async_collection.AsyncCollectionReference`: 

173 A reference to a collection in the Firestore database. 

174 """ 

175 return AsyncCollectionReference(*_path_helper(collection_path), client=self) 

176 

177 def collection_group(self, collection_id: str) -> AsyncCollectionGroup: 

178 """ 

179 Creates and returns a new AsyncQuery that includes all documents in the 

180 database that are contained in a collection or subcollection with the 

181 given collection_id. 

182 

183 .. code-block:: python 

184 

185 >>> query = client.collection_group('mygroup') 

186 

187 Args: 

188 collection_id (str) Identifies the collections to query over. 

189 

190 Every collection or subcollection with this ID as the last segment of its 

191 path will be included. Cannot contain a slash. 

192 

193 Returns: 

194 :class:`~google.cloud.firestore_v1.async_query.AsyncCollectionGroup`: 

195 The created AsyncQuery. 

196 """ 

197 return AsyncCollectionGroup(self._get_collection_reference(collection_id)) 

198 

199 def document(self, *document_path: str) -> AsyncDocumentReference: 

200 """Get a reference to a document in a collection. 

201 

202 For a top-level document: 

203 

204 .. code-block:: python 

205 

206 >>> client.document('collek/shun') 

207 >>> # is the same as 

208 >>> client.document('collek', 'shun') 

209 

210 For a document in a sub-collection: 

211 

212 .. code-block:: python 

213 

214 >>> client.document('mydocs/doc/subcol/child') 

215 >>> # is the same as 

216 >>> client.document('mydocs', 'doc', 'subcol', 'child') 

217 

218 Documents in sub-collections can be nested deeper in a similar fashion. 

219 

220 Args: 

221 document_path: Can either be 

222 

223 * A single ``/``-delimited path to a document 

224 * A tuple of document path segments 

225 

226 Returns: 

227 :class:`~google.cloud.firestore_v1.document.AsyncDocumentReference`: 

228 A reference to a document in a collection. 

229 """ 

230 return AsyncDocumentReference( 

231 *self._document_path_helper(*document_path), client=self 

232 ) 

233 

234 async def get_all( 

235 self, 

236 references: List[AsyncDocumentReference], 

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

238 transaction: AsyncTransaction | None = None, 

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

240 timeout: float | None = None, 

241 *, 

242 read_time: datetime.datetime | None = None, 

243 ) -> AsyncGenerator[DocumentSnapshot, Any]: 

244 """Retrieve a batch of documents. 

245 

246 .. note:: 

247 

248 Documents returned by this method are not guaranteed to be 

249 returned in the same order that they are given in ``references``. 

250 

251 .. note:: 

252 

253 If multiple ``references`` refer to the same document, the server 

254 will only return one result. 

255 

256 See :meth:`~google.cloud.firestore_v1.client.Client.field_path` for 

257 more information on **field paths**. 

258 

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

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

261 allowed). 

262 

263 Args: 

264 references (List[.AsyncDocumentReference, ...]): Iterable of document 

265 references to be retrieved. 

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

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

268 projection of document fields in the returned results. If 

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

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

271 An existing transaction that these ``references`` will be 

272 retrieved in. 

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

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

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

276 system-specified value. 

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

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

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

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

281 

282 Yields: 

283 .DocumentSnapshot: The next document snapshot that fulfills the 

284 query, or :data:`None` if the document does not exist. 

285 """ 

286 request, reference_map, kwargs = self._prep_get_all( 

287 references, field_paths, transaction, retry, timeout, read_time 

288 ) 

289 

290 response_iterator = await self._firestore_api.batch_get_documents( 

291 request=request, 

292 metadata=self._rpc_metadata, 

293 **kwargs, 

294 ) 

295 

296 async for get_doc_response in response_iterator: 

297 yield _parse_batch_get(get_doc_response, reference_map, self) 

298 

299 async def collections( 

300 self, 

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

302 timeout: float | None = None, 

303 *, 

304 read_time: datetime.datetime | None = None, 

305 ) -> AsyncGenerator[AsyncCollectionReference, Any]: 

306 """List top-level collections of the client's database. 

307 

308 Args: 

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

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

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

312 system-specified value. 

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

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

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

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

317 

318 Returns: 

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

320 iterator of subcollections of the current document. 

321 """ 

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

323 iterator = await self._firestore_api.list_collection_ids( 

324 request=request, 

325 metadata=self._rpc_metadata, 

326 **kwargs, 

327 ) 

328 

329 async for collection_id in iterator: 

330 yield self.collection(collection_id) 

331 

332 async def recursive_delete( 

333 self, 

334 reference: Union[AsyncCollectionReference, AsyncDocumentReference], 

335 *, 

336 bulk_writer: Optional["BulkWriter"] = None, 

337 chunk_size: int = 5000, 

338 ) -> int: 

339 """Deletes documents and their subcollections, regardless of collection 

340 name. 

341 

342 Passing an AsyncCollectionReference leads to each document in the 

343 collection getting deleted, as well as all of their descendents. 

344 

345 Passing an AsyncDocumentReference deletes that one document and all of 

346 its descendents. 

347 

348 Args: 

349 reference (Union[ 

350 :class:`@google.cloud.firestore_v1.async_collection.CollectionReference`, 

351 :class:`@google.cloud.firestore_v1.async_document.DocumentReference`, 

352 ]) 

353 The reference to be deleted. 

354 

355 bulk_writer (Optional[:class:`@google.cloud.firestore_v1.bulk_writer.BulkWriter`]) 

356 The BulkWriter used to delete all matching documents. Supply this 

357 if you want to override the default throttling behavior. 

358 """ 

359 if bulk_writer is None: 

360 bulk_writer = self.bulk_writer() 

361 

362 return await self._recursive_delete( 

363 reference, 

364 bulk_writer=bulk_writer, 

365 chunk_size=chunk_size, 

366 ) 

367 

368 async def _recursive_delete( 

369 self, 

370 reference: Union[AsyncCollectionReference, AsyncDocumentReference], 

371 bulk_writer: "BulkWriter", 

372 *, 

373 chunk_size: int = 5000, 

374 depth: int = 0, 

375 ) -> int: 

376 """Recursion helper for `recursive_delete.""" 

377 

378 num_deleted: int = 0 

379 

380 if isinstance(reference, AsyncCollectionReference): 

381 chunk: List[DocumentSnapshot] 

382 async for chunk in reference.recursive().select( 

383 [FieldPath.document_id()] 

384 )._chunkify(chunk_size): 

385 doc_snap: DocumentSnapshot 

386 for doc_snap in chunk: 

387 num_deleted += 1 

388 bulk_writer.delete(doc_snap.reference) 

389 

390 elif isinstance(reference, AsyncDocumentReference): 

391 col_ref: AsyncCollectionReference 

392 async for col_ref in reference.collections(): 

393 num_deleted += await self._recursive_delete( 

394 col_ref, 

395 bulk_writer=bulk_writer, 

396 depth=depth + 1, 

397 chunk_size=chunk_size, 

398 ) 

399 num_deleted += 1 

400 bulk_writer.delete(reference) 

401 

402 else: 

403 raise TypeError( 

404 f"Unexpected type for reference: {reference.__class__.__name__}" 

405 ) 

406 

407 if depth == 0: 

408 bulk_writer.close() 

409 

410 return num_deleted 

411 

412 def batch(self) -> AsyncWriteBatch: 

413 """Get a batch instance from this client. 

414 

415 Returns: 

416 :class:`~google.cloud.firestore_v1.async_batch.AsyncWriteBatch`: 

417 A "write" batch to be used for accumulating document changes and 

418 sending the changes all at once. 

419 """ 

420 return AsyncWriteBatch(self) 

421 

422 def transaction( 

423 self, max_attempts: int = MAX_ATTEMPTS, read_only: bool = False 

424 ) -> AsyncTransaction: 

425 """Get a transaction that uses this client. 

426 

427 See :class:`~google.cloud.firestore_v1.async_transaction.AsyncTransaction` for 

428 more information on transactions and the constructor arguments. 

429 

430 Args: 

431 kwargs (Dict[str, Any]): The keyword arguments (other than 

432 ``client``) to pass along to the 

433 :class:`~google.cloud.firestore_v1.async_transaction.AsyncTransaction` 

434 constructor. 

435 

436 Returns: 

437 :class:`~google.cloud.firestore_v1.async_transaction.AsyncTransaction`: 

438 A transaction attached to this client. 

439 """ 

440 return AsyncTransaction(self, max_attempts=max_attempts, read_only=read_only)