|
18 | 18 | associated with Firebase apps. This requires the ``google-cloud-firestore`` Python module.
|
19 | 19 | """
|
20 | 20 |
|
21 |
| -from typing import Type |
22 |
| - |
23 |
| -from firebase_admin import ( |
24 |
| - App, |
25 |
| - _utils, |
26 |
| -) |
27 |
| -from firebase_admin.credentials import Base |
| 21 | +from __future__ import annotations |
| 22 | +from typing import Optional, Dict |
| 23 | +from firebase_admin import App |
| 24 | +from firebase_admin import _utils |
28 | 25 |
|
29 | 26 | try:
|
30 |
| - from google.cloud import firestore # type: ignore # pylint: disable=import-error,no-name-in-module |
| 27 | + from google.cloud import firestore |
| 28 | + from google.cloud.firestore_v1.base_client import DEFAULT_DATABASE |
31 | 29 | existing = globals().keys()
|
32 | 30 | for key, value in firestore.__dict__.items():
|
33 | 31 | if not key.startswith('_') and key not in existing:
|
34 | 32 | globals()[key] = value
|
35 |
| -except ImportError: |
| 33 | +except ImportError as error: |
36 | 34 | raise ImportError('Failed to import the Cloud Firestore library for Python. Make sure '
|
37 |
| - 'to install the "google-cloud-firestore" module.') |
| 35 | + 'to install the "google-cloud-firestore" module.') from error |
| 36 | + |
38 | 37 |
|
39 | 38 | _FIRESTORE_ASYNC_ATTRIBUTE: str = '_firestore_async'
|
40 | 39 |
|
41 | 40 |
|
42 |
| -def client(app: App = None) -> firestore.AsyncClient: |
| 41 | +def client(app: Optional[App] = None, database_id: Optional[str] = None) -> firestore.AsyncClient: |
43 | 42 | """Returns an async client that can be used to interact with Google Cloud Firestore.
|
44 | 43 |
|
45 | 44 | Args:
|
46 |
| - app: An App instance (optional). |
| 45 | + app: An App instance (optional). |
| 46 | + database_id: The database ID of the Google Cloud Firestore database to be used. |
| 47 | + Defaults to the default Firestore database ID if not specified or an empty string |
| 48 | + (optional). |
47 | 49 |
|
48 | 50 | Returns:
|
49 |
| - google.cloud.firestore.Firestore_Async: A `Firestore Async Client`_. |
| 51 | + google.cloud.firestore.Firestore_Async: A `Firestore Async Client`_. |
50 | 52 |
|
51 | 53 | Raises:
|
52 |
| - ValueError: If a project ID is not specified either via options, credentials or |
53 |
| - environment variables, or if the specified project ID is not a valid string. |
| 54 | + ValueError: If the specified database ID is not a valid string, or if a project ID is not |
| 55 | + specified either via options, credentials or environment variables, or if the specified |
| 56 | + project ID is not a valid string. |
54 | 57 |
|
55 |
| - .. _Firestore Async Client: https://googleapis.dev/python/firestore/latest/client.html |
| 58 | + .. _Firestore Async Client: https://cloud.google.com/python/docs/reference/firestore/latest/\ |
| 59 | + google.cloud.firestore_v1.async_client.AsyncClient |
56 | 60 | """
|
57 |
| - fs_client = _utils.get_app_service( |
58 |
| - app, _FIRESTORE_ASYNC_ATTRIBUTE, _FirestoreAsyncClient.from_app) |
59 |
| - return fs_client.get() |
60 |
| - |
61 |
| - |
62 |
| -class _FirestoreAsyncClient: |
63 |
| - """Holds a Google Cloud Firestore Async Client instance.""" |
64 |
| - |
65 |
| - def __init__(self, credentials: Type[Base], project: str) -> None: |
66 |
| - self._client = firestore.AsyncClient(credentials=credentials, project=project) |
67 |
| - |
68 |
| - def get(self) -> firestore.AsyncClient: |
69 |
| - return self._client |
70 |
| - |
71 |
| - @classmethod |
72 |
| - def from_app(cls, app: App) -> "_FirestoreAsyncClient": |
73 |
| - # Replace remove future reference quotes by importing annotations in Python 3.7+ b/238779406 |
74 |
| - """Creates a new _FirestoreAsyncClient for the specified app.""" |
75 |
| - credentials = app.credential.get_credential() |
76 |
| - project = app.project_id |
77 |
| - if not project: |
78 |
| - raise ValueError( |
79 |
| - 'Project ID is required to access Firestore. Either set the projectId option, ' |
80 |
| - 'or use service account credentials. Alternatively, set the GOOGLE_CLOUD_PROJECT ' |
81 |
| - 'environment variable.') |
82 |
| - return _FirestoreAsyncClient(credentials, project) |
| 61 | + # Validate database_id |
| 62 | + if database_id is not None and not isinstance(database_id, str): |
| 63 | + raise ValueError(f'database_id "{database_id}" must be a string or None.') |
| 64 | + |
| 65 | + fs_service = _utils.get_app_service(app, _FIRESTORE_ASYNC_ATTRIBUTE, _FirestoreAsyncService) |
| 66 | + return fs_service.get_client(database_id) |
| 67 | + |
| 68 | +class _FirestoreAsyncService: |
| 69 | + """Service that maintains a collection of firestore async clients.""" |
| 70 | + |
| 71 | + def __init__(self, app: App) -> None: |
| 72 | + self._app: App = app |
| 73 | + self._clients: Dict[str, firestore.AsyncClient] = {} |
| 74 | + |
| 75 | + def get_client(self, database_id: Optional[str]) -> firestore.AsyncClient: |
| 76 | + """Creates an async client based on the database_id. These clients are cached.""" |
| 77 | + database_id = database_id or DEFAULT_DATABASE |
| 78 | + if database_id not in self._clients: |
| 79 | + # Create a new client and cache it in _clients |
| 80 | + credentials = self._app.credential.get_credential() |
| 81 | + project = self._app.project_id |
| 82 | + if not project: |
| 83 | + raise ValueError( |
| 84 | + 'Project ID is required to access Firestore. Either set the projectId option, ' |
| 85 | + 'or use service account credentials. Alternatively, set the ' |
| 86 | + 'GOOGLE_CLOUD_PROJECT environment variable.') |
| 87 | + |
| 88 | + fs_client = firestore.AsyncClient( |
| 89 | + credentials=credentials, project=project, database=database_id) |
| 90 | + self._clients[database_id] = fs_client |
| 91 | + |
| 92 | + return self._clients[database_id] |
0 commit comments