Skip to content

Commit 6cb4b7c

Browse files
committed
Added multi db support for firestore and firestore_async
1 parent c044729 commit 6cb4b7c

File tree

2 files changed

+100
-76
lines changed

2 files changed

+100
-76
lines changed

firebase_admin/firestore.py

Lines changed: 49 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,59 +18,74 @@
1818
Firebase apps. This requires the ``google-cloud-firestore`` Python module.
1919
"""
2020

21+
from __future__ import annotations
22+
from typing import Optional, Dict
23+
from firebase_admin import App
24+
from firebase_admin import _utils
25+
2126
try:
22-
from google.cloud import firestore # 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
2329
existing = globals().keys()
2430
for key, value in firestore.__dict__.items():
2531
if not key.startswith('_') and key not in existing:
2632
globals()[key] = value
27-
except ImportError:
33+
except ImportError as error:
2834
raise ImportError('Failed to import the Cloud Firestore library for Python. Make sure '
29-
'to install the "google-cloud-firestore" module.')
30-
31-
from firebase_admin import _utils
35+
'to install the "google-cloud-firestore" module.') from error
3236

3337

3438
_FIRESTORE_ATTRIBUTE = '_firestore'
3539

3640

37-
def client(app=None) -> firestore.Client:
41+
def client(app: Optional[App] = None, database_id: Optional[str] = None) -> firestore.Client:
3842
"""Returns a client that can be used to interact with Google Cloud Firestore.
3943
4044
Args:
4145
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 if not specified or empty string (optional).
4248
4349
Returns:
4450
google.cloud.firestore.Firestore: A `Firestore Client`_.
4551
4652
Raises:
47-
ValueError: If a project ID is not specified either via options, credentials or
48-
environment variables, or if the specified project ID is not a valid string.
53+
ValueError: If the database ID is not None or a string or a project ID is not specified
54+
either via options, credentials or environment variables, or if the specified
55+
project ID is not a valid string.
4956
50-
.. _Firestore Client: https://googlecloudplatform.github.io/google-cloud-python/latest\
51-
/firestore/client.html
57+
.. _Firestore Client: https://cloud.google.com/python/docs/reference/firestore/latest/\
58+
google.cloud.firestore_v1.client.Client
5259
"""
53-
fs_client = _utils.get_app_service(app, _FIRESTORE_ATTRIBUTE, _FirestoreClient.from_app)
54-
return fs_client.get()
55-
56-
57-
class _FirestoreClient:
58-
"""Holds a Google Cloud Firestore client instance."""
59-
60-
def __init__(self, credentials, project):
61-
self._client = firestore.Client(credentials=credentials, project=project)
62-
63-
def get(self):
64-
return self._client
65-
66-
@classmethod
67-
def from_app(cls, app):
68-
"""Creates a new _FirestoreClient for the specified app."""
69-
credentials = app.credential.get_credential()
70-
project = app.project_id
71-
if not project:
72-
raise ValueError(
73-
'Project ID is required to access Firestore. Either set the projectId option, '
74-
'or use service account credentials. Alternatively, set the GOOGLE_CLOUD_PROJECT '
75-
'environment variable.')
76-
return _FirestoreClient(credentials, project)
60+
# Validate database_id
61+
if database_id is not None and not isinstance(database_id, str):
62+
raise ValueError(f'database_id "{database_id}" must be a string or None.')
63+
fs_service = _utils.get_app_service(app, _FIRESTORE_ATTRIBUTE, _FirestoreService)
64+
return fs_service.get_client(database_id)
65+
66+
67+
class _FirestoreService:
68+
"""Service that maintains a collection of firestore clients."""
69+
70+
def __init__(self, app: App) -> None:
71+
self._app: App = app
72+
self._clients: Dict[str, firestore.Client] = {}
73+
74+
def get_client(self, database_id: Optional[str]) -> firestore.Client:
75+
"""Creates a client based on the database_id. These clients are cached."""
76+
database_id = database_id or DEFAULT_DATABASE
77+
if database_id not in self._clients:
78+
# Create a new client and cache it in _clients
79+
credentials = self._app.credential.get_credential()
80+
project = self._app.project_id
81+
if not project:
82+
raise ValueError(
83+
'Project ID is required to access Firestore. Either set the projectId option, '
84+
'or use service account credentials. Alternatively, set the '
85+
'GOOGLE_CLOUD_PROJECT environment variable.')
86+
87+
fs_client = firestore.Client(
88+
credentials=credentials, project=project, database=database_id)
89+
self._clients[database_id] = fs_client
90+
91+
return self._clients[database_id]

firebase_admin/firestore_async.py

Lines changed: 51 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -18,65 +18,74 @@
1818
associated with Firebase apps. This requires the ``google-cloud-firestore`` Python module.
1919
"""
2020

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
2825

2926
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
3129
existing = globals().keys()
3230
for key, value in firestore.__dict__.items():
3331
if not key.startswith('_') and key not in existing:
3432
globals()[key] = value
35-
except ImportError:
33+
except ImportError as error:
3634
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+
3837

3938
_FIRESTORE_ASYNC_ATTRIBUTE: str = '_firestore_async'
4039

4140

42-
def client(app: App = None) -> firestore.AsyncClient:
41+
def client(app: Optional[App] = None, database_id: Optional[str] = None) -> firestore.AsyncClient:
4342
"""Returns an async client that can be used to interact with Google Cloud Firestore.
4443
4544
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 if not specified or empty string (optional).
4748
4849
Returns:
49-
google.cloud.firestore.Firestore_Async: A `Firestore Async Client`_.
50+
google.cloud.firestore.Firestore_Async: A `Firestore Async Client`_.
5051
5152
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.
53+
ValueError: If the database ID is not None or a string or a project ID is not specified
54+
either via options, credentials or environment variables, or if the specified
55+
project ID is not a valid string.
5456
55-
.. _Firestore Async Client: https://googleapis.dev/python/firestore/latest/client.html
57+
.. _Firestore Async Client: https://cloud.google.com/python/docs/reference/firestore/latest/\
58+
google.cloud.firestore_v1.async_client.AsyncClient
5659
"""
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)
60+
# Validate database_id
61+
if database_id is not None and not isinstance(database_id, str):
62+
raise ValueError(f'database_id "{database_id}" must be a string or None.')
63+
64+
fs_service = _utils.get_app_service(app, _FIRESTORE_ASYNC_ATTRIBUTE, _FirestoreAsyncService)
65+
return fs_service.get_client(database_id)
66+
67+
class _FirestoreAsyncService:
68+
"""Service that maintains a collection of firestore async clients."""
69+
70+
def __init__(self, app: App) -> None:
71+
self._app: App = app
72+
self._clients: Dict[str, firestore.AsyncClient] = {}
73+
74+
def get_client(self, database_id: Optional[str]) -> firestore.AsyncClient:
75+
"""Creates an async client based on the database_id. These clients are cached."""
76+
database_id = database_id or DEFAULT_DATABASE
77+
if database_id not in self._clients:
78+
# Create a new client and cache it in _clients
79+
credentials = self._app.credential.get_credential()
80+
project = self._app.project_id
81+
if not project:
82+
raise ValueError(
83+
'Project ID is required to access Firestore. Either set the projectId option, '
84+
'or use service account credentials. Alternatively, set the '
85+
'GOOGLE_CLOUD_PROJECT environment variable.')
86+
87+
fs_client = firestore.AsyncClient(
88+
credentials=credentials, project=project, database=database_id)
89+
self._clients[database_id] = fs_client
90+
91+
return self._clients[database_id]

0 commit comments

Comments
 (0)