1# -*- coding: utf-8 -*-
2# Copyright 2025 Google LLC
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16import abc
17from typing import Awaitable, Callable, Dict, Optional, Sequence, Union
18
19from google.cloud.firestore_v1 import gapic_version as package_version
20
21import google.auth # type: ignore
22import google.api_core
23from google.api_core import exceptions as core_exceptions
24from google.api_core import gapic_v1
25from google.api_core import retry as retries
26from google.auth import credentials as ga_credentials # type: ignore
27from google.oauth2 import service_account # type: ignore
28import google.protobuf
29
30from google.cloud.firestore_v1.types import document
31from google.cloud.firestore_v1.types import document as gf_document
32from google.cloud.firestore_v1.types import firestore
33from google.cloud.location import locations_pb2 # type: ignore
34from google.longrunning import operations_pb2 # type: ignore
35from google.protobuf import empty_pb2 # type: ignore
36
37DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo(
38 gapic_version=package_version.__version__
39)
40
41if hasattr(DEFAULT_CLIENT_INFO, "protobuf_runtime_version"): # pragma: NO COVER
42 DEFAULT_CLIENT_INFO.protobuf_runtime_version = google.protobuf.__version__
43
44
45class FirestoreTransport(abc.ABC):
46 """Abstract transport class for Firestore."""
47
48 AUTH_SCOPES = (
49 "https://www.googleapis.com/auth/cloud-platform",
50 "https://www.googleapis.com/auth/datastore",
51 )
52
53 DEFAULT_HOST: str = "firestore.googleapis.com"
54
55 def __init__(
56 self,
57 *,
58 host: str = DEFAULT_HOST,
59 credentials: Optional[ga_credentials.Credentials] = None,
60 credentials_file: Optional[str] = None,
61 scopes: Optional[Sequence[str]] = None,
62 quota_project_id: Optional[str] = None,
63 client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
64 always_use_jwt_access: Optional[bool] = False,
65 api_audience: Optional[str] = None,
66 **kwargs,
67 ) -> None:
68 """Instantiate the transport.
69
70 Args:
71 host (Optional[str]):
72 The hostname to connect to (default: 'firestore.googleapis.com').
73 credentials (Optional[google.auth.credentials.Credentials]): The
74 authorization credentials to attach to requests. These
75 credentials identify the application to the service; if none
76 are specified, the client will attempt to ascertain the
77 credentials from the environment.
78 credentials_file (Optional[str]): Deprecated. A file with credentials that can
79 be loaded with :func:`google.auth.load_credentials_from_file`.
80 This argument is mutually exclusive with credentials. This argument will be
81 removed in the next major version of this library.
82 scopes (Optional[Sequence[str]]): A list of scopes.
83 quota_project_id (Optional[str]): An optional project to use for billing
84 and quota.
85 client_info (google.api_core.gapic_v1.client_info.ClientInfo):
86 The client info used to send a user-agent string along with
87 API requests. If ``None``, then default info will be used.
88 Generally, you only need to set this if you're developing
89 your own client library.
90 always_use_jwt_access (Optional[bool]): Whether self signed JWT should
91 be used for service account credentials.
92 """
93
94 scopes_kwargs = {"scopes": scopes, "default_scopes": self.AUTH_SCOPES}
95
96 # Save the scopes.
97 self._scopes = scopes
98 if not hasattr(self, "_ignore_credentials"):
99 self._ignore_credentials: bool = False
100
101 # If no credentials are provided, then determine the appropriate
102 # defaults.
103 if credentials and credentials_file:
104 raise core_exceptions.DuplicateCredentialArgs(
105 "'credentials_file' and 'credentials' are mutually exclusive"
106 )
107
108 if credentials_file is not None:
109 credentials, _ = google.auth.load_credentials_from_file(
110 credentials_file, **scopes_kwargs, quota_project_id=quota_project_id
111 )
112 elif credentials is None and not self._ignore_credentials:
113 credentials, _ = google.auth.default(
114 **scopes_kwargs, quota_project_id=quota_project_id
115 )
116 # Don't apply audience if the credentials file passed from user.
117 if hasattr(credentials, "with_gdch_audience"):
118 credentials = credentials.with_gdch_audience(
119 api_audience if api_audience else host
120 )
121
122 # If the credentials are service account credentials, then always try to use self signed JWT.
123 if (
124 always_use_jwt_access
125 and isinstance(credentials, service_account.Credentials)
126 and hasattr(service_account.Credentials, "with_always_use_jwt_access")
127 ):
128 credentials = credentials.with_always_use_jwt_access(True)
129
130 # Save the credentials.
131 self._credentials = credentials
132
133 # Save the hostname. Default to port 443 (HTTPS) if none is specified.
134 if ":" not in host:
135 host += ":443"
136 self._host = host
137
138 @property
139 def host(self):
140 return self._host
141
142 def _prep_wrapped_messages(self, client_info):
143 # Precompute the wrapped methods.
144 self._wrapped_methods = {
145 self.get_document: gapic_v1.method.wrap_method(
146 self.get_document,
147 default_retry=retries.Retry(
148 initial=0.1,
149 maximum=60.0,
150 multiplier=1.3,
151 predicate=retries.if_exception_type(
152 core_exceptions.DeadlineExceeded,
153 core_exceptions.InternalServerError,
154 core_exceptions.ResourceExhausted,
155 core_exceptions.ServiceUnavailable,
156 ),
157 deadline=60.0,
158 ),
159 default_timeout=60.0,
160 client_info=client_info,
161 ),
162 self.list_documents: gapic_v1.method.wrap_method(
163 self.list_documents,
164 default_retry=retries.Retry(
165 initial=0.1,
166 maximum=60.0,
167 multiplier=1.3,
168 predicate=retries.if_exception_type(
169 core_exceptions.DeadlineExceeded,
170 core_exceptions.InternalServerError,
171 core_exceptions.ResourceExhausted,
172 core_exceptions.ServiceUnavailable,
173 ),
174 deadline=60.0,
175 ),
176 default_timeout=60.0,
177 client_info=client_info,
178 ),
179 self.update_document: gapic_v1.method.wrap_method(
180 self.update_document,
181 default_retry=retries.Retry(
182 initial=0.1,
183 maximum=60.0,
184 multiplier=1.3,
185 predicate=retries.if_exception_type(
186 core_exceptions.ResourceExhausted,
187 core_exceptions.ServiceUnavailable,
188 ),
189 deadline=60.0,
190 ),
191 default_timeout=60.0,
192 client_info=client_info,
193 ),
194 self.delete_document: gapic_v1.method.wrap_method(
195 self.delete_document,
196 default_retry=retries.Retry(
197 initial=0.1,
198 maximum=60.0,
199 multiplier=1.3,
200 predicate=retries.if_exception_type(
201 core_exceptions.DeadlineExceeded,
202 core_exceptions.InternalServerError,
203 core_exceptions.ResourceExhausted,
204 core_exceptions.ServiceUnavailable,
205 ),
206 deadline=60.0,
207 ),
208 default_timeout=60.0,
209 client_info=client_info,
210 ),
211 self.batch_get_documents: gapic_v1.method.wrap_method(
212 self.batch_get_documents,
213 default_retry=retries.Retry(
214 initial=0.1,
215 maximum=60.0,
216 multiplier=1.3,
217 predicate=retries.if_exception_type(
218 core_exceptions.DeadlineExceeded,
219 core_exceptions.InternalServerError,
220 core_exceptions.ResourceExhausted,
221 core_exceptions.ServiceUnavailable,
222 ),
223 deadline=300.0,
224 ),
225 default_timeout=300.0,
226 client_info=client_info,
227 ),
228 self.begin_transaction: gapic_v1.method.wrap_method(
229 self.begin_transaction,
230 default_retry=retries.Retry(
231 initial=0.1,
232 maximum=60.0,
233 multiplier=1.3,
234 predicate=retries.if_exception_type(
235 core_exceptions.DeadlineExceeded,
236 core_exceptions.InternalServerError,
237 core_exceptions.ResourceExhausted,
238 core_exceptions.ServiceUnavailable,
239 ),
240 deadline=60.0,
241 ),
242 default_timeout=60.0,
243 client_info=client_info,
244 ),
245 self.commit: gapic_v1.method.wrap_method(
246 self.commit,
247 default_retry=retries.Retry(
248 initial=0.1,
249 maximum=60.0,
250 multiplier=1.3,
251 predicate=retries.if_exception_type(
252 core_exceptions.ResourceExhausted,
253 core_exceptions.ServiceUnavailable,
254 ),
255 deadline=60.0,
256 ),
257 default_timeout=60.0,
258 client_info=client_info,
259 ),
260 self.rollback: gapic_v1.method.wrap_method(
261 self.rollback,
262 default_retry=retries.Retry(
263 initial=0.1,
264 maximum=60.0,
265 multiplier=1.3,
266 predicate=retries.if_exception_type(
267 core_exceptions.DeadlineExceeded,
268 core_exceptions.InternalServerError,
269 core_exceptions.ResourceExhausted,
270 core_exceptions.ServiceUnavailable,
271 ),
272 deadline=60.0,
273 ),
274 default_timeout=60.0,
275 client_info=client_info,
276 ),
277 self.run_query: gapic_v1.method.wrap_method(
278 self.run_query,
279 default_retry=retries.Retry(
280 initial=0.1,
281 maximum=60.0,
282 multiplier=1.3,
283 predicate=retries.if_exception_type(
284 core_exceptions.DeadlineExceeded,
285 core_exceptions.InternalServerError,
286 core_exceptions.ResourceExhausted,
287 core_exceptions.ServiceUnavailable,
288 ),
289 deadline=300.0,
290 ),
291 default_timeout=300.0,
292 client_info=client_info,
293 ),
294 self.run_aggregation_query: gapic_v1.method.wrap_method(
295 self.run_aggregation_query,
296 default_retry=retries.Retry(
297 initial=0.1,
298 maximum=60.0,
299 multiplier=1.3,
300 predicate=retries.if_exception_type(
301 core_exceptions.DeadlineExceeded,
302 core_exceptions.InternalServerError,
303 core_exceptions.ResourceExhausted,
304 core_exceptions.ServiceUnavailable,
305 ),
306 deadline=300.0,
307 ),
308 default_timeout=300.0,
309 client_info=client_info,
310 ),
311 self.partition_query: gapic_v1.method.wrap_method(
312 self.partition_query,
313 default_retry=retries.Retry(
314 initial=0.1,
315 maximum=60.0,
316 multiplier=1.3,
317 predicate=retries.if_exception_type(
318 core_exceptions.DeadlineExceeded,
319 core_exceptions.InternalServerError,
320 core_exceptions.ResourceExhausted,
321 core_exceptions.ServiceUnavailable,
322 ),
323 deadline=300.0,
324 ),
325 default_timeout=300.0,
326 client_info=client_info,
327 ),
328 self.write: gapic_v1.method.wrap_method(
329 self.write,
330 default_timeout=86400.0,
331 client_info=client_info,
332 ),
333 self.listen: gapic_v1.method.wrap_method(
334 self.listen,
335 default_retry=retries.Retry(
336 initial=0.1,
337 maximum=60.0,
338 multiplier=1.3,
339 predicate=retries.if_exception_type(
340 core_exceptions.DeadlineExceeded,
341 core_exceptions.InternalServerError,
342 core_exceptions.ResourceExhausted,
343 core_exceptions.ServiceUnavailable,
344 ),
345 deadline=86400.0,
346 ),
347 default_timeout=86400.0,
348 client_info=client_info,
349 ),
350 self.list_collection_ids: gapic_v1.method.wrap_method(
351 self.list_collection_ids,
352 default_retry=retries.Retry(
353 initial=0.1,
354 maximum=60.0,
355 multiplier=1.3,
356 predicate=retries.if_exception_type(
357 core_exceptions.DeadlineExceeded,
358 core_exceptions.InternalServerError,
359 core_exceptions.ResourceExhausted,
360 core_exceptions.ServiceUnavailable,
361 ),
362 deadline=60.0,
363 ),
364 default_timeout=60.0,
365 client_info=client_info,
366 ),
367 self.batch_write: gapic_v1.method.wrap_method(
368 self.batch_write,
369 default_retry=retries.Retry(
370 initial=0.1,
371 maximum=60.0,
372 multiplier=1.3,
373 predicate=retries.if_exception_type(
374 core_exceptions.Aborted,
375 core_exceptions.ResourceExhausted,
376 core_exceptions.ServiceUnavailable,
377 ),
378 deadline=60.0,
379 ),
380 default_timeout=60.0,
381 client_info=client_info,
382 ),
383 self.create_document: gapic_v1.method.wrap_method(
384 self.create_document,
385 default_retry=retries.Retry(
386 initial=0.1,
387 maximum=60.0,
388 multiplier=1.3,
389 predicate=retries.if_exception_type(
390 core_exceptions.ResourceExhausted,
391 core_exceptions.ServiceUnavailable,
392 ),
393 deadline=60.0,
394 ),
395 default_timeout=60.0,
396 client_info=client_info,
397 ),
398 self.cancel_operation: gapic_v1.method.wrap_method(
399 self.cancel_operation,
400 default_timeout=None,
401 client_info=client_info,
402 ),
403 self.delete_operation: gapic_v1.method.wrap_method(
404 self.delete_operation,
405 default_timeout=None,
406 client_info=client_info,
407 ),
408 self.get_operation: gapic_v1.method.wrap_method(
409 self.get_operation,
410 default_timeout=None,
411 client_info=client_info,
412 ),
413 self.list_operations: gapic_v1.method.wrap_method(
414 self.list_operations,
415 default_timeout=None,
416 client_info=client_info,
417 ),
418 }
419
420 def close(self):
421 """Closes resources associated with the transport.
422
423 .. warning::
424 Only call this method if the transport is NOT shared
425 with other clients - this may cause errors in other clients!
426 """
427 raise NotImplementedError()
428
429 @property
430 def get_document(
431 self,
432 ) -> Callable[
433 [firestore.GetDocumentRequest],
434 Union[document.Document, Awaitable[document.Document]],
435 ]:
436 raise NotImplementedError()
437
438 @property
439 def list_documents(
440 self,
441 ) -> Callable[
442 [firestore.ListDocumentsRequest],
443 Union[
444 firestore.ListDocumentsResponse, Awaitable[firestore.ListDocumentsResponse]
445 ],
446 ]:
447 raise NotImplementedError()
448
449 @property
450 def update_document(
451 self,
452 ) -> Callable[
453 [firestore.UpdateDocumentRequest],
454 Union[gf_document.Document, Awaitable[gf_document.Document]],
455 ]:
456 raise NotImplementedError()
457
458 @property
459 def delete_document(
460 self,
461 ) -> Callable[
462 [firestore.DeleteDocumentRequest],
463 Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]],
464 ]:
465 raise NotImplementedError()
466
467 @property
468 def batch_get_documents(
469 self,
470 ) -> Callable[
471 [firestore.BatchGetDocumentsRequest],
472 Union[
473 firestore.BatchGetDocumentsResponse,
474 Awaitable[firestore.BatchGetDocumentsResponse],
475 ],
476 ]:
477 raise NotImplementedError()
478
479 @property
480 def begin_transaction(
481 self,
482 ) -> Callable[
483 [firestore.BeginTransactionRequest],
484 Union[
485 firestore.BeginTransactionResponse,
486 Awaitable[firestore.BeginTransactionResponse],
487 ],
488 ]:
489 raise NotImplementedError()
490
491 @property
492 def commit(
493 self,
494 ) -> Callable[
495 [firestore.CommitRequest],
496 Union[firestore.CommitResponse, Awaitable[firestore.CommitResponse]],
497 ]:
498 raise NotImplementedError()
499
500 @property
501 def rollback(
502 self,
503 ) -> Callable[
504 [firestore.RollbackRequest], Union[empty_pb2.Empty, Awaitable[empty_pb2.Empty]]
505 ]:
506 raise NotImplementedError()
507
508 @property
509 def run_query(
510 self,
511 ) -> Callable[
512 [firestore.RunQueryRequest],
513 Union[firestore.RunQueryResponse, Awaitable[firestore.RunQueryResponse]],
514 ]:
515 raise NotImplementedError()
516
517 @property
518 def run_aggregation_query(
519 self,
520 ) -> Callable[
521 [firestore.RunAggregationQueryRequest],
522 Union[
523 firestore.RunAggregationQueryResponse,
524 Awaitable[firestore.RunAggregationQueryResponse],
525 ],
526 ]:
527 raise NotImplementedError()
528
529 @property
530 def partition_query(
531 self,
532 ) -> Callable[
533 [firestore.PartitionQueryRequest],
534 Union[
535 firestore.PartitionQueryResponse,
536 Awaitable[firestore.PartitionQueryResponse],
537 ],
538 ]:
539 raise NotImplementedError()
540
541 @property
542 def write(
543 self,
544 ) -> Callable[
545 [firestore.WriteRequest],
546 Union[firestore.WriteResponse, Awaitable[firestore.WriteResponse]],
547 ]:
548 raise NotImplementedError()
549
550 @property
551 def listen(
552 self,
553 ) -> Callable[
554 [firestore.ListenRequest],
555 Union[firestore.ListenResponse, Awaitable[firestore.ListenResponse]],
556 ]:
557 raise NotImplementedError()
558
559 @property
560 def list_collection_ids(
561 self,
562 ) -> Callable[
563 [firestore.ListCollectionIdsRequest],
564 Union[
565 firestore.ListCollectionIdsResponse,
566 Awaitable[firestore.ListCollectionIdsResponse],
567 ],
568 ]:
569 raise NotImplementedError()
570
571 @property
572 def batch_write(
573 self,
574 ) -> Callable[
575 [firestore.BatchWriteRequest],
576 Union[firestore.BatchWriteResponse, Awaitable[firestore.BatchWriteResponse]],
577 ]:
578 raise NotImplementedError()
579
580 @property
581 def create_document(
582 self,
583 ) -> Callable[
584 [firestore.CreateDocumentRequest],
585 Union[document.Document, Awaitable[document.Document]],
586 ]:
587 raise NotImplementedError()
588
589 @property
590 def list_operations(
591 self,
592 ) -> Callable[
593 [operations_pb2.ListOperationsRequest],
594 Union[
595 operations_pb2.ListOperationsResponse,
596 Awaitable[operations_pb2.ListOperationsResponse],
597 ],
598 ]:
599 raise NotImplementedError()
600
601 @property
602 def get_operation(
603 self,
604 ) -> Callable[
605 [operations_pb2.GetOperationRequest],
606 Union[operations_pb2.Operation, Awaitable[operations_pb2.Operation]],
607 ]:
608 raise NotImplementedError()
609
610 @property
611 def cancel_operation(
612 self,
613 ) -> Callable[[operations_pb2.CancelOperationRequest], None,]:
614 raise NotImplementedError()
615
616 @property
617 def delete_operation(
618 self,
619 ) -> Callable[[operations_pb2.DeleteOperationRequest], None,]:
620 raise NotImplementedError()
621
622 @property
623 def kind(self) -> str:
624 raise NotImplementedError()
625
626
627__all__ = ("FirestoreTransport",)