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

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

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

59from google.cloud.firestore_v1.async_pipeline import AsyncPipeline 

60from google.cloud.firestore_v1.pipeline_source import PipelineSource 

61 

62if TYPE_CHECKING: # pragma: NO COVER 

63 import datetime 

64 

65 from google.cloud.firestore_v1.bulk_writer import BulkWriter 

66 

67 

68class AsyncClient(BaseClient): 

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

70 

71 .. note:: 

72 

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

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

75 

76 Args: 

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

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

79 from the environment. 

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

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

82 back to the default inferred from the environment. 

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

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

85 only valid database. 

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

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

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

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

90 or partner tool. 

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

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

93 should be set through client_options. 

94 """ 

95 

96 def __init__( 

97 self, 

98 project=None, 

99 credentials=None, 

100 database=None, 

101 client_info=_CLIENT_INFO, 

102 client_options=None, 

103 ) -> None: 

104 super(AsyncClient, self).__init__( 

105 project=project, 

106 credentials=credentials, 

107 database=database, 

108 client_info=client_info, 

109 client_options=client_options, 

110 ) 

111 

112 def _to_sync_copy(self): 

113 from google.cloud.firestore_v1.client import Client 

114 

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

116 self._sync_copy = Client( 

117 project=self.project, 

118 credentials=self._credentials, 

119 database=self._database, 

120 client_info=self._client_info, 

121 client_options=self._client_options, 

122 ) 

123 return self._sync_copy 

124 

125 @property 

126 def _firestore_api(self): 

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

128 Returns: 

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

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

131 """ 

132 return self._firestore_api_helper( 

133 firestore_grpc_transport.FirestoreGrpcAsyncIOTransport, 

134 firestore_client.FirestoreAsyncClient, 

135 firestore_client, 

136 ) 

137 

138 @property 

139 def _target(self): 

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

141 Eg. "firestore.googleapis.com" 

142 

143 Returns: 

144 str: The location of the API. 

145 """ 

146 return self._target_helper(firestore_client.FirestoreAsyncClient) 

147 

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

149 """Get a reference to a collection. 

150 

151 For a top-level collection: 

152 

153 .. code-block:: python 

154 

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

156 

157 For a sub-collection: 

158 

159 .. code-block:: python 

160 

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

162 >>> # is the same as 

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

164 

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

166 

167 Args: 

168 collection_path: Can either be 

169 

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

171 * A tuple of collection path segments 

172 

173 Returns: 

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

175 A reference to a collection in the Firestore database. 

176 """ 

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

178 

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

180 """ 

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

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

183 given collection_id. 

184 

185 .. code-block:: python 

186 

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

188 

189 Args: 

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

191 

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

193 path will be included. Cannot contain a slash. 

194 

195 Returns: 

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

197 The created AsyncQuery. 

198 """ 

199 return AsyncCollectionGroup(self._get_collection_reference(collection_id)) 

200 

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

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

203 

204 For a top-level document: 

205 

206 .. code-block:: python 

207 

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

209 >>> # is the same as 

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

211 

212 For a document in a sub-collection: 

213 

214 .. code-block:: python 

215 

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

217 >>> # is the same as 

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

219 

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

221 

222 Args: 

223 document_path: Can either be 

224 

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

226 * A tuple of document path segments 

227 

228 Returns: 

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

230 A reference to a document in a collection. 

231 """ 

232 return AsyncDocumentReference( 

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

234 ) 

235 

236 async def get_all( 

237 self, 

238 references: List[AsyncDocumentReference], 

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

240 transaction: AsyncTransaction | None = None, 

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

242 timeout: float | None = None, 

243 *, 

244 read_time: datetime.datetime | None = None, 

245 ) -> AsyncGenerator[DocumentSnapshot, Any]: 

246 """Retrieve a batch of documents. 

247 

248 .. note:: 

249 

250 Documents returned by this method are not guaranteed to be 

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

252 

253 .. note:: 

254 

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

256 will only return one result. 

257 

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

259 more information on **field paths**. 

260 

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

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

263 allowed). 

264 

265 Args: 

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

267 references to be retrieved. 

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

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

270 projection of document fields in the returned results. If 

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

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

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

274 retrieved in. 

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

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

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

278 system-specified value. 

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

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

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

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

283 

284 Yields: 

285 .DocumentSnapshot: The next document snapshot that fulfills the 

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

287 """ 

288 request, reference_map, kwargs = self._prep_get_all( 

289 references, field_paths, transaction, retry, timeout, read_time 

290 ) 

291 

292 response_iterator = await self._firestore_api.batch_get_documents( 

293 request=request, 

294 metadata=self._rpc_metadata, 

295 **kwargs, 

296 ) 

297 

298 async for get_doc_response in response_iterator: 

299 yield _parse_batch_get(get_doc_response, reference_map, self) 

300 

301 async def collections( 

302 self, 

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

304 timeout: float | None = None, 

305 *, 

306 read_time: datetime.datetime | None = None, 

307 ) -> AsyncGenerator[AsyncCollectionReference, Any]: 

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

309 

310 Args: 

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

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

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

314 system-specified value. 

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

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

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

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

319 

320 Returns: 

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

322 iterator of subcollections of the current document. 

323 """ 

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

325 iterator = await self._firestore_api.list_collection_ids( 

326 request=request, 

327 metadata=self._rpc_metadata, 

328 **kwargs, 

329 ) 

330 

331 async for collection_id in iterator: 

332 yield self.collection(collection_id) 

333 

334 async def recursive_delete( 

335 self, 

336 reference: Union[AsyncCollectionReference, AsyncDocumentReference], 

337 *, 

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

339 chunk_size: int = 5000, 

340 ) -> int: 

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

342 name. 

343 

344 Passing an AsyncCollectionReference leads to each document in the 

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

346 

347 Passing an AsyncDocumentReference deletes that one document and all of 

348 its descendents. 

349 

350 Args: 

351 reference (Union[ 

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

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

354 ]) 

355 The reference to be deleted. 

356 

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

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

359 if you want to override the default throttling behavior. 

360 """ 

361 if bulk_writer is None: 

362 bulk_writer = self.bulk_writer() 

363 

364 return await self._recursive_delete( 

365 reference, 

366 bulk_writer=bulk_writer, 

367 chunk_size=chunk_size, 

368 ) 

369 

370 async def _recursive_delete( 

371 self, 

372 reference: Union[AsyncCollectionReference, AsyncDocumentReference], 

373 bulk_writer: "BulkWriter", 

374 *, 

375 chunk_size: int = 5000, 

376 depth: int = 0, 

377 ) -> int: 

378 """Recursion helper for `recursive_delete.""" 

379 

380 num_deleted: int = 0 

381 

382 if isinstance(reference, AsyncCollectionReference): 

383 chunk: List[DocumentSnapshot] 

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

385 [FieldPath.document_id()] 

386 )._chunkify(chunk_size): 

387 doc_snap: DocumentSnapshot 

388 for doc_snap in chunk: 

389 num_deleted += 1 

390 bulk_writer.delete(doc_snap.reference) 

391 

392 elif isinstance(reference, AsyncDocumentReference): 

393 col_ref: AsyncCollectionReference 

394 async for col_ref in reference.collections(): 

395 num_deleted += await self._recursive_delete( 

396 col_ref, 

397 bulk_writer=bulk_writer, 

398 depth=depth + 1, 

399 chunk_size=chunk_size, 

400 ) 

401 num_deleted += 1 

402 bulk_writer.delete(reference) 

403 

404 else: 

405 raise TypeError( 

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

407 ) 

408 

409 if depth == 0: 

410 bulk_writer.close() 

411 

412 return num_deleted 

413 

414 def batch(self) -> AsyncWriteBatch: 

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

416 

417 Returns: 

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

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

420 sending the changes all at once. 

421 """ 

422 return AsyncWriteBatch(self) 

423 

424 def transaction( 

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

426 ) -> AsyncTransaction: 

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

428 

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

430 more information on transactions and the constructor arguments. 

431 

432 Args: 

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

434 ``client``) to pass along to the 

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

436 constructor. 

437 

438 Returns: 

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

440 A transaction attached to this client. 

441 """ 

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

443 

444 @property 

445 def _pipeline_cls(self): 

446 return AsyncPipeline 

447 

448 def pipeline(self) -> PipelineSource: 

449 return PipelineSource(self)