Skip to content

Commit 065424a

Browse files
pijushcsjonathanedeylahirumarambaPijush Chakrabortyrathovarun1032
authored
Fixing SSRC Typos and Minor Bugs (#841)
* Changes for percent comparison * Fixing semantic version issues with invalid version * Fixing Config values must retrun default values from invalid get operations * Updating tolerance for percentage evaluation * Removing dependency changes from fix branch * Updating ServerConfig methods as per review changes * Updating comments and vars for readability * Added unit and integration tests * Refactor and add unit test * Implementation for Fetching and Caching Server Side Remote Config (#825) * Minor update to API signature * Updating init params for ServerTemplateData * Adding validation errors and test * Removing parameter groups * Addressing PR comments and fixing async flow during fetch call * Fixing lint issues --------- Co-authored-by: Jonathan Edey <[email protected]> Co-authored-by: Lahiru Maramba <[email protected]> Co-authored-by: Pijush Chakraborty <[email protected]> Co-authored-by: varun rathore <[email protected]> Co-authored-by: Varun Rathore <[email protected]>
1 parent 6f9591b commit 065424a

32 files changed

+826
-425
lines changed

.github/dependabot.yml

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "pip"
4+
directory: "/"
5+
schedule:
6+
interval: "weekly"

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
strategy:
99
fail-fast: false
1010
matrix:
11-
python: ['3.8', '3.9', '3.10', '3.11', '3.12', 'pypy3.8']
11+
python: ['3.8', '3.9', '3.10', '3.11', '3.12', 'pypy3.9']
1212

1313
steps:
1414
- uses: actions/checkout@v4

.github/workflows/release.yml

+1
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ jobs:
108108
uses: actions/[email protected]
109109
with:
110110
name: dist
111+
path: dist
111112

112113
- name: Publish preflight check
113114
id: preflight

firebase_admin/__about__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
"""About information (version, etc) for Firebase Admin SDK."""
1616

17-
__version__ = '6.5.0'
17+
__version__ = '6.6.0'
1818
__title__ = 'firebase_admin'
1919
__author__ = 'Firebase'
2020
__license__ = 'Apache License 2.0'

firebase_admin/__init__.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import os
1919
import threading
2020

21+
from google.auth.credentials import Credentials as GoogleAuthCredentials
2122
from google.auth.exceptions import DefaultCredentialsError
2223
from firebase_admin import credentials
2324
from firebase_admin.__about__ import __version__
@@ -208,10 +209,13 @@ def __init__(self, name, credential, options):
208209
'non-empty string.'.format(name))
209210
self._name = name
210211

211-
if not isinstance(credential, credentials.Base):
212+
if isinstance(credential, GoogleAuthCredentials):
213+
self._credential = credentials._ExternalCredentials(credential) # pylint: disable=protected-access
214+
elif isinstance(credential, credentials.Base):
215+
self._credential = credential
216+
else:
212217
raise ValueError('Illegal Firebase credential provided. App must be initialized '
213218
'with a valid credential instance.')
214-
self._credential = credential
215219
self._options = _AppOptions(options)
216220
self._lock = threading.RLock()
217221
self._services = {}

firebase_admin/_http_client.py

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import requests
2222
from requests.packages.urllib3.util import retry # pylint: disable=import-error
2323

24+
from firebase_admin import _utils
2425

2526
if hasattr(retry.Retry.DEFAULT, 'allowed_methods'):
2627
_ANY_METHOD = {'allowed_methods': None}
@@ -36,6 +37,9 @@
3637

3738
DEFAULT_TIMEOUT_SECONDS = 120
3839

40+
METRICS_HEADERS = {
41+
'X-GOOG-API-CLIENT': _utils.get_metrics_header(),
42+
}
3943

4044
class HttpClient:
4145
"""Base HTTP client used to make HTTP calls.
@@ -72,6 +76,7 @@ def __init__(
7276

7377
if headers:
7478
self._session.headers.update(headers)
79+
self._session.headers.update(METRICS_HEADERS)
7580
if retries:
7681
self._session.mount('http://', requests.adapters.HTTPAdapter(max_retries=retries))
7782
self._session.mount('https://', requests.adapters.HTTPAdapter(max_retries=retries))

firebase_admin/_utils.py

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"""Internal utilities common to all modules."""
1616

1717
import json
18+
from platform import python_version
1819

1920
import google.auth
2021
import requests
@@ -75,6 +76,8 @@
7576
16: exceptions.UNAUTHENTICATED,
7677
}
7778

79+
def get_metrics_header():
80+
return f'gl-python/{python_version()} fire-admin/{firebase_admin.__version__}'
7881

7982
def _get_initialized_app(app):
8083
"""Returns a reference to an initialized App instance."""

firebase_admin/app_check.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ class _AppCheckService:
5151
_scoped_project_id = None
5252
_jwks_client = None
5353

54+
_APP_CHECK_HEADERS = {
55+
'X-GOOG-API-CLIENT': _utils.get_metrics_header(),
56+
}
57+
5458
def __init__(self, app):
5559
# Validate and store the project_id to validate the JWT claims
5660
self._project_id = app.project_id
@@ -62,7 +66,8 @@ def __init__(self, app):
6266
'GOOGLE_CLOUD_PROJECT environment variable.')
6367
self._scoped_project_id = 'projects/' + app.project_id
6468
# Default lifespan is 300 seconds (5 minutes) so we change it to 21600 seconds (6 hours).
65-
self._jwks_client = PyJWKClient(self._JWKS_URL, lifespan=21600)
69+
self._jwks_client = PyJWKClient(
70+
self._JWKS_URL, lifespan=21600, headers=self._APP_CHECK_HEADERS)
6671

6772

6873
def verify_token(self, token: str) -> Dict[str, Any]:

firebase_admin/credentials.py

+14
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import pathlib
1919

2020
import google.auth
21+
from google.auth.credentials import Credentials as GoogleAuthCredentials
2122
from google.auth.transport import requests
2223
from google.oauth2 import credentials
2324
from google.oauth2 import service_account
@@ -58,6 +59,19 @@ def get_credential(self):
5859
"""Returns the Google credential instance used for authentication."""
5960
raise NotImplementedError
6061

62+
class _ExternalCredentials(Base):
63+
"""A wrapper for google.auth.credentials.Credentials typed credential instances"""
64+
65+
def __init__(self, credential: GoogleAuthCredentials):
66+
super(_ExternalCredentials, self).__init__()
67+
self._g_credential = credential
68+
69+
def get_credential(self):
70+
"""Returns the underlying Google Credential
71+
72+
Returns:
73+
google.auth.credentials.Credentials: A Google Auth credential instance."""
74+
return self._g_credential
6175

6276
class Certificate(Base):
6377
"""A credential initialized from a JSON certificate keyfile."""

firebase_admin/firestore.py

+52-36
Original file line numberDiff line numberDiff line change
@@ -18,59 +18,75 @@
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:
41-
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).
4249
4350
Returns:
44-
google.cloud.firestore.Firestore: A `Firestore Client`_.
51+
google.cloud.firestore.Firestore: A `Firestore Client`_.
4552
4653
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.
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.
4957
50-
.. _Firestore Client: https://googlecloudplatform.github.io/google-cloud-python/latest\
51-
/firestore/client.html
58+
.. _Firestore Client: https://cloud.google.com/python/docs/reference/firestore/latest/\
59+
google.cloud.firestore_v1.client.Client
5260
"""
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)
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+
fs_service = _utils.get_app_service(app, _FIRESTORE_ATTRIBUTE, _FirestoreService)
65+
return fs_service.get_client(database_id)
66+
67+
68+
class _FirestoreService:
69+
"""Service that maintains a collection of firestore clients."""
70+
71+
def __init__(self, app: App) -> None:
72+
self._app: App = app
73+
self._clients: Dict[str, firestore.Client] = {}
74+
75+
def get_client(self, database_id: Optional[str]) -> firestore.Client:
76+
"""Creates a 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.Client(
89+
credentials=credentials, project=project, database=database_id)
90+
self._clients[database_id] = fs_client
91+
92+
return self._clients[database_id]

firebase_admin/firestore_async.py

+52-42
Original file line numberDiff line numberDiff line change
@@ -18,65 +18,75 @@
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 ID if not specified or an empty string
48+
(optional).
4749
4850
Returns:
49-
google.cloud.firestore.Firestore_Async: A `Firestore Async Client`_.
51+
google.cloud.firestore.Firestore_Async: A `Firestore Async Client`_.
5052
5153
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.
5457
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
5660
"""
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

Comments
 (0)