firebase-admin 7.5.0__tar.gz → 7.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/PKG-INFO +1 -1
  2. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/__about__.py +1 -1
  3. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/app_check.py +53 -9
  4. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/ml.py +140 -4
  5. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin.egg-info/PKG-INFO +1 -1
  6. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/test_app_check.py +136 -5
  7. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/test_ml.py +87 -0
  8. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/LICENSE +0 -0
  9. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/README.md +0 -0
  10. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/__init__.py +0 -0
  11. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/_auth_client.py +0 -0
  12. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/_auth_providers.py +0 -0
  13. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/_auth_utils.py +0 -0
  14. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/_http_client.py +0 -0
  15. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/_messaging_encoder.py +0 -0
  16. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/_messaging_utils.py +0 -0
  17. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/_retry.py +0 -0
  18. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/_rfc3339.py +0 -0
  19. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/_sseclient.py +0 -0
  20. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/_token_gen.py +0 -0
  21. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/_user_identifier.py +0 -0
  22. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/_user_import.py +0 -0
  23. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/_user_mgt.py +0 -0
  24. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/_utils.py +0 -0
  25. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/auth.py +0 -0
  26. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/credentials.py +0 -0
  27. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/db.py +0 -0
  28. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/exceptions.py +0 -0
  29. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/firestore.py +0 -0
  30. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/firestore_async.py +0 -0
  31. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/functions.py +0 -0
  32. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/instance_id.py +0 -0
  33. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/messaging.py +0 -0
  34. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/phone_number_verification.py +0 -0
  35. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/project_management.py +0 -0
  36. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/remote_config.py +0 -0
  37. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/storage.py +0 -0
  38. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin/tenant_mgt.py +0 -0
  39. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin.egg-info/SOURCES.txt +0 -0
  40. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin.egg-info/dependency_links.txt +0 -0
  41. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin.egg-info/requires.txt +0 -0
  42. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/firebase_admin.egg-info/top_level.txt +0 -0
  43. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/setup.cfg +0 -0
  44. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/setup.py +0 -0
  45. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/test_app.py +0 -0
  46. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/test_auth_providers.py +0 -0
  47. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/test_credentials.py +0 -0
  48. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/test_db.py +0 -0
  49. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/test_exceptions.py +0 -0
  50. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/test_firestore.py +0 -0
  51. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/test_firestore_async.py +0 -0
  52. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/test_functions.py +0 -0
  53. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/test_http_client.py +0 -0
  54. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/test_instance_id.py +0 -0
  55. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/test_messaging.py +0 -0
  56. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/test_phone_number_verification.py +0 -0
  57. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/test_project_management.py +0 -0
  58. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/test_remote_config.py +0 -0
  59. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/test_retry.py +0 -0
  60. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/test_rfc3339.py +0 -0
  61. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/test_sseclient.py +0 -0
  62. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/test_storage.py +0 -0
  63. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/test_tenant_mgt.py +0 -0
  64. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/test_token_gen.py +0 -0
  65. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/test_user_mgt.py +0 -0
  66. {firebase_admin-7.5.0 → firebase_admin-7.6.0}/tests/testutils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: firebase_admin
3
- Version: 7.5.0
3
+ Version: 7.6.0
4
4
  Summary: Firebase Admin Python SDK
5
5
  Home-page: https://firebase.google.com/docs/admin/setup/
6
6
  Author: Firebase
@@ -14,7 +14,7 @@
14
14
 
15
15
  """About information (version, etc) for Firebase Admin SDK."""
16
16
 
17
- __version__ = '7.5.0'
17
+ __version__ = '7.6.0'
18
18
  __title__ = 'firebase_admin'
19
19
  __author__ = 'Firebase'
20
20
  __license__ = 'Apache License 2.0'
@@ -15,38 +15,47 @@
15
15
  """Firebase App Check module."""
16
16
 
17
17
  from typing import Any, Dict
18
+ import requests
18
19
  import jwt
19
20
  from jwt import PyJWKClient, ExpiredSignatureError, InvalidTokenError, DecodeError
20
21
  from jwt import InvalidAudienceError, InvalidIssuerError, InvalidSignatureError
21
- from firebase_admin import _utils
22
+ from firebase_admin import _http_client, _utils, exceptions
22
23
 
23
24
  _APP_CHECK_ATTRIBUTE = '_app_check'
24
25
 
25
26
  def _get_app_check_service(app) -> Any:
26
27
  return _utils.get_app_service(app, _APP_CHECK_ATTRIBUTE, _AppCheckService)
27
28
 
28
- def verify_token(token: str, app=None) -> Dict[str, Any]:
29
- """Verifies a Firebase App Check token.
29
+ def verify_token(token: str, app=None, consume: bool = False) -> Dict[str, Any]:
30
+ """Verifies a Firebase App Check token, optionally consuming limited-use tokens.
30
31
 
31
32
  Args:
32
33
  token: A token from App Check.
33
34
  app: An App instance (optional).
35
+ consume: Set to ``True`` only if the token is a limited-use (one-time) token
36
+ that should be consumed upon verification (optional, defaults to ``False``).
34
37
 
35
38
  Returns:
36
- Dict[str, Any]: The token's decoded claims.
39
+ Dict[str, Any]: The token's decoded claims. If ``consume`` is ``True``, the dictionary
40
+ also includes an ``already_consumed`` boolean key indicating whether the token was
41
+ previously consumed.
37
42
 
38
43
  Raises:
39
- ValueError: If the app's ``project_id`` is invalid or unspecified,
40
- or if the token's headers or payload are invalid.
44
+ ValueError: If ``consume`` is not a boolean, or if the app's ``project_id``
45
+ is invalid or unspecified, or if the token's headers or payload are invalid.
46
+ FirebaseError: If an error occurs while communicating with the App Check service.
41
47
  PyJWKClientError: If PyJWKClient fails to fetch a valid signing key.
42
48
  """
43
- return _get_app_check_service(app).verify_token(token)
49
+ return _get_app_check_service(app).verify_token(token, consume=consume)
44
50
 
45
51
  class _AppCheckService:
46
52
  """Service class that implements Firebase App Check functionality."""
47
53
 
48
54
  _APP_CHECK_ISSUER = 'https://firebaseappcheck.googleapis.com/'
49
55
  _JWKS_URL = 'https://firebaseappcheck.googleapis.com/v1/jwks'
56
+ _VERIFY_URL_FORMAT = (
57
+ 'https://firebaseappcheck.googleapis.com/v1/projects/{project_id}:verifyAppCheckToken'
58
+ )
50
59
  _project_id = None
51
60
  _scoped_project_id = None
52
61
  _jwks_client = None
@@ -68,11 +77,15 @@ class _AppCheckService:
68
77
  # Default lifespan is 300 seconds (5 minutes) so we change it to 21600 seconds (6 hours).
69
78
  self._jwks_client = PyJWKClient(
70
79
  self._JWKS_URL, lifespan=21600, headers=self._APP_CHECK_HEADERS)
80
+ timeout = app.options.get('httpTimeout', _http_client.DEFAULT_TIMEOUT_SECONDS)
81
+ self._http_client = _http_client.JsonHttpClient(
82
+ credential=app.credential.get_credential(), timeout=timeout)
71
83
 
72
84
 
73
- def verify_token(self, token: str) -> Dict[str, Any]:
74
- """Verifies a Firebase App Check token."""
85
+ def verify_token(self, token: str, consume: bool = False) -> Dict[str, Any]:
86
+ """Verifies a Firebase App Check token, optionally consuming limited-use tokens."""
75
87
  _Validators.check_string("app check token", token)
88
+ _Validators.check_boolean("consume", consume)
76
89
 
77
90
  # Obtain the Firebase App Check Public Keys
78
91
  # Note: It is not recommended to hard code these keys as they rotate,
@@ -87,8 +100,32 @@ class _AppCheckService:
87
100
  ) from exception
88
101
 
89
102
  verified_claims['app_id'] = verified_claims.get('sub')
103
+
104
+ if consume:
105
+ verified_claims['already_consumed'] = self._verify_replay_protection(token)
106
+
90
107
  return verified_claims
91
108
 
109
+ def _verify_replay_protection(self, token: str) -> bool:
110
+ """Verifies replay protection with the backend and returns the alreadyConsumed status."""
111
+ url = self._VERIFY_URL_FORMAT.format(project_id=self._project_id)
112
+ try:
113
+ body = self._http_client.body('post', url, json={'app_check_token': token})
114
+ except requests.exceptions.RequestException as error:
115
+ raise _utils.handle_platform_error_from_requests(error)
116
+ except ValueError as error:
117
+ raise exceptions.UnknownError(
118
+ f'Unexpected response from App Check service: {error}'
119
+ ) from error
120
+
121
+ if not isinstance(body, dict):
122
+ raise exceptions.UnknownError(
123
+ 'Unexpected response from App Check service. '
124
+ f'Expected a JSON object, but got {type(body).__name__}.'
125
+ )
126
+
127
+ return bool(body.get('alreadyConsumed', False))
128
+
92
129
  def _has_valid_token_headers(self, headers: Any) -> None:
93
130
  """Checks whether the token has valid headers for App Check."""
94
131
  # Ensure the token's header has type JWT
@@ -159,3 +196,10 @@ class _Validators:
159
196
  raise ValueError(f'{label} "{value}" must be a non-empty string.')
160
197
  if not isinstance(value, str):
161
198
  raise ValueError(f'{label} "{value}" must be a string.')
199
+
200
+ @classmethod
201
+ def check_boolean(cls, label: str, value: Any):
202
+ """Checks if the given value is a boolean."""
203
+ if not isinstance(value, bool):
204
+ raise ValueError(f'{label} must be a boolean.')
205
+ return value
@@ -14,16 +14,25 @@
14
14
 
15
15
  """Firebase ML module.
16
16
 
17
+ .. deprecated::
18
+ Firebase ML is deprecated and will be shut down on June 15, 2027.
19
+ To host custom models, you must migrate to another solution. You can use Cloud Storage for
20
+ Firebase as an alternative for hosting custom models. For more info, see
21
+ https://firebase.google.com/docs/ml/migrate-to-cloud-storage
22
+
17
23
  This module contains functions for creating, updating, getting, listing,
18
24
  deleting, publishing and unpublishing Firebase ML models.
19
25
  """
20
26
 
27
+ # pylint: disable=too-many-lines
21
28
 
22
29
  import datetime
30
+ import os
23
31
  import re
32
+ import sys
24
33
  import time
25
- import os
26
34
  from urllib import parse
35
+ import warnings
27
36
 
28
37
  import requests
29
38
 
@@ -46,6 +55,30 @@ try:
46
55
  except ImportError:
47
56
  _TF_ENABLED = False
48
57
 
58
+ _DEPRECATION_WARNING = (
59
+ 'Firebase ML is deprecated and will be shut down on June 15, 2027. '
60
+ 'To host custom models, you must migrate to another solution. You can use Cloud Storage for '
61
+ 'Firebase as an alternative for hosting custom models. For more info, see '
62
+ 'https://firebase.google.com/docs/ml/migrate-to-cloud-storage'
63
+ )
64
+
65
+
66
+ def _is_internal_call():
67
+ """Checks if the current call originated from within the ML module."""
68
+ try:
69
+ # pylint: disable=protected-access
70
+ frame = sys._getframe(2)
71
+ while frame:
72
+ if frame.f_code.co_name in (
73
+ 'from_dict', '_update_from_dict', 'handle_operation', 'list_models'
74
+ ):
75
+ return True
76
+ frame = frame.f_back
77
+ except (AttributeError, ValueError):
78
+ pass
79
+ return False
80
+
81
+
49
82
  _ML_ATTRIBUTE = '_ml'
50
83
  _MAX_PAGE_SIZE = 100
51
84
  _MODEL_ID_PATTERN = re.compile(r'^[A-Za-z0-9_-]{1,60}$')
@@ -77,6 +110,12 @@ def _get_ml_service(app):
77
110
  def create_model(model, app=None):
78
111
  """Creates a model in the current Firebase project.
79
112
 
113
+ .. deprecated::
114
+ Firebase ML is deprecated and will be shut down on June 15, 2027.
115
+ To host custom models, you must migrate to another solution. You can use Cloud Storage for
116
+ Firebase as an alternative for hosting custom models. For more info, see
117
+ https://firebase.google.com/docs/ml/migrate-to-cloud-storage
118
+
80
119
  Args:
81
120
  model: An ml.Model to create.
82
121
  app: A Firebase app instance (or None to use the default app).
@@ -84,6 +123,8 @@ def create_model(model, app=None):
84
123
  Returns:
85
124
  Model: The model that was created in Firebase ML.
86
125
  """
126
+ if not _is_internal_call():
127
+ warnings.warn(_DEPRECATION_WARNING, DeprecationWarning, stacklevel=2)
87
128
  ml_service = _get_ml_service(app)
88
129
  return Model.from_dict(ml_service.create_model(model), app=app)
89
130
 
@@ -91,6 +132,12 @@ def create_model(model, app=None):
91
132
  def update_model(model, app=None):
92
133
  """Updates a model's metadata or model file.
93
134
 
135
+ .. deprecated::
136
+ Firebase ML is deprecated and will be shut down on June 15, 2027.
137
+ To host custom models, you must migrate to another solution. You can use Cloud Storage for
138
+ Firebase as an alternative for hosting custom models. For more info, see
139
+ https://firebase.google.com/docs/ml/migrate-to-cloud-storage
140
+
94
141
  Args:
95
142
  model: The ml.Model to update.
96
143
  app: A Firebase app instance (or None to use the default app).
@@ -98,6 +145,8 @@ def update_model(model, app=None):
98
145
  Returns:
99
146
  Model: The updated model.
100
147
  """
148
+ if not _is_internal_call():
149
+ warnings.warn(_DEPRECATION_WARNING, DeprecationWarning, stacklevel=2)
101
150
  ml_service = _get_ml_service(app)
102
151
  return Model.from_dict(ml_service.update_model(model), app=app)
103
152
 
@@ -105,6 +154,12 @@ def update_model(model, app=None):
105
154
  def publish_model(model_id, app=None):
106
155
  """Publishes a Firebase ML model.
107
156
 
157
+ .. deprecated::
158
+ Firebase ML is deprecated and will be shut down on June 15, 2027.
159
+ To host custom models, you must migrate to another solution. You can use Cloud Storage for
160
+ Firebase as an alternative for hosting custom models. For more info, see
161
+ https://firebase.google.com/docs/ml/migrate-to-cloud-storage
162
+
108
163
  A published model can be downloaded to client apps.
109
164
 
110
165
  Args:
@@ -114,6 +169,8 @@ def publish_model(model_id, app=None):
114
169
  Returns:
115
170
  Model: The published model.
116
171
  """
172
+ if not _is_internal_call():
173
+ warnings.warn(_DEPRECATION_WARNING, DeprecationWarning, stacklevel=2)
117
174
  ml_service = _get_ml_service(app)
118
175
  return Model.from_dict(ml_service.set_published(model_id, publish=True), app=app)
119
176
 
@@ -121,6 +178,12 @@ def publish_model(model_id, app=None):
121
178
  def unpublish_model(model_id, app=None):
122
179
  """Unpublishes a Firebase ML model.
123
180
 
181
+ .. deprecated::
182
+ Firebase ML is deprecated and will be shut down on June 15, 2027.
183
+ To host custom models, you must migrate to another solution. You can use Cloud Storage for
184
+ Firebase as an alternative for hosting custom models. For more info, see
185
+ https://firebase.google.com/docs/ml/migrate-to-cloud-storage
186
+
124
187
  Args:
125
188
  model_id: The id of the model to unpublish.
126
189
  app: A Firebase app instance (or None to use the default app).
@@ -128,6 +191,8 @@ def unpublish_model(model_id, app=None):
128
191
  Returns:
129
192
  Model: The unpublished model.
130
193
  """
194
+ if not _is_internal_call():
195
+ warnings.warn(_DEPRECATION_WARNING, DeprecationWarning, stacklevel=2)
131
196
  ml_service = _get_ml_service(app)
132
197
  return Model.from_dict(ml_service.set_published(model_id, publish=False), app=app)
133
198
 
@@ -135,6 +200,12 @@ def unpublish_model(model_id, app=None):
135
200
  def get_model(model_id, app=None):
136
201
  """Gets the model specified by the given ID.
137
202
 
203
+ .. deprecated::
204
+ Firebase ML is deprecated and will be shut down on June 15, 2027.
205
+ To host custom models, you must migrate to another solution. You can use Cloud Storage for
206
+ Firebase as an alternative for hosting custom models. For more info, see
207
+ https://firebase.google.com/docs/ml/migrate-to-cloud-storage
208
+
138
209
  Args:
139
210
  model_id: The id of the model to get.
140
211
  app: A Firebase app instance (or None to use the default app).
@@ -142,6 +213,8 @@ def get_model(model_id, app=None):
142
213
  Returns:
143
214
  Model: The requested model.
144
215
  """
216
+ if not _is_internal_call():
217
+ warnings.warn(_DEPRECATION_WARNING, DeprecationWarning, stacklevel=2)
145
218
  ml_service = _get_ml_service(app)
146
219
  return Model.from_dict(ml_service.get_model(model_id), app=app)
147
220
 
@@ -149,6 +222,12 @@ def get_model(model_id, app=None):
149
222
  def list_models(list_filter=None, page_size=None, page_token=None, app=None):
150
223
  """Lists the current project's models.
151
224
 
225
+ .. deprecated::
226
+ Firebase ML is deprecated and will be shut down on June 15, 2027.
227
+ To host custom models, you must migrate to another solution. You can use Cloud Storage for
228
+ Firebase as an alternative for hosting custom models. For more info, see
229
+ https://firebase.google.com/docs/ml/migrate-to-cloud-storage
230
+
152
231
  Args:
153
232
  list_filter: a list filter string such as ``tags:'tag_1'``. None will return all models.
154
233
  page_size: A number between 1 and 100 inclusive that specifies the maximum
@@ -160,6 +239,8 @@ def list_models(list_filter=None, page_size=None, page_token=None, app=None):
160
239
  Returns:
161
240
  ListModelsPage: A (filtered) list of models.
162
241
  """
242
+ if not _is_internal_call():
243
+ warnings.warn(_DEPRECATION_WARNING, DeprecationWarning, stacklevel=2)
163
244
  ml_service = _get_ml_service(app)
164
245
  return ListModelsPage(
165
246
  ml_service.list_models, list_filter, page_size, page_token, app=app)
@@ -168,10 +249,18 @@ def list_models(list_filter=None, page_size=None, page_token=None, app=None):
168
249
  def delete_model(model_id, app=None):
169
250
  """Deletes a model from the current project.
170
251
 
252
+ .. deprecated::
253
+ Firebase ML is deprecated and will be shut down on June 15, 2027.
254
+ To host custom models, you must migrate to another solution. You can use Cloud Storage for
255
+ Firebase as an alternative for hosting custom models. For more info, see
256
+ https://firebase.google.com/docs/ml/migrate-to-cloud-storage
257
+
171
258
  Args:
172
259
  model_id: The id of the model you wish to delete.
173
260
  app: A Firebase app instance (or None to use the default app).
174
261
  """
262
+ if not _is_internal_call():
263
+ warnings.warn(_DEPRECATION_WARNING, DeprecationWarning, stacklevel=2)
175
264
  ml_service = _get_ml_service(app)
176
265
  ml_service.delete_model(model_id)
177
266
 
@@ -179,12 +268,20 @@ def delete_model(model_id, app=None):
179
268
  class Model:
180
269
  """A Firebase ML Model object.
181
270
 
271
+ .. deprecated::
272
+ Firebase ML is deprecated and will be shut down on June 15, 2027.
273
+ To host custom models, you must migrate to another solution. You can use Cloud Storage for
274
+ Firebase as an alternative for hosting custom models. For more info, see
275
+ https://firebase.google.com/docs/ml/migrate-to-cloud-storage
276
+
182
277
  Args:
183
278
  display_name: The display name of your model - used to identify your model in code.
184
279
  tags: Optional list of strings associated with your model. Can be used in list queries.
185
280
  model_format: A subclass of ModelFormat. (e.g. TFLiteFormat) Specifies the model details.
186
281
  """
187
282
  def __init__(self, display_name=None, tags=None, model_format=None):
283
+ if not _is_internal_call():
284
+ warnings.warn(_DEPRECATION_WARNING, DeprecationWarning, stacklevel=2)
188
285
  self._app = None # Only needed for wait_for_unlo
189
286
  self._data = {}
190
287
  self._model_format = None
@@ -342,7 +439,14 @@ class Model:
342
439
 
343
440
 
344
441
  class ModelFormat:
345
- """Abstract base class representing a Model Format such as TFLite."""
442
+ """Abstract base class representing a Model Format such as TFLite.
443
+
444
+ .. deprecated::
445
+ Firebase ML is deprecated and will be shut down on June 15, 2027.
446
+ To host custom models, you must migrate to another solution. You can use Cloud Storage for
447
+ Firebase as an alternative for hosting custom models. For more info, see
448
+ https://firebase.google.com/docs/ml/migrate-to-cloud-storage
449
+ """
346
450
  def as_dict(self, for_upload=False):
347
451
  """Returns a serializable representation of the object."""
348
452
  raise NotImplementedError
@@ -351,10 +455,18 @@ class ModelFormat:
351
455
  class TFLiteFormat(ModelFormat):
352
456
  """Model format representing a TFLite model.
353
457
 
458
+ .. deprecated::
459
+ Firebase ML is deprecated and will be shut down on June 15, 2027.
460
+ To host custom models, you must migrate to another solution. You can use Cloud Storage for
461
+ Firebase as an alternative for hosting custom models. For more info, see
462
+ https://firebase.google.com/docs/ml/migrate-to-cloud-storage
463
+
354
464
  Args:
355
465
  model_source: A TFLiteModelSource sub class. Specifies the details of the model source.
356
466
  """
357
467
  def __init__(self, model_source=None):
468
+ if not _is_internal_call():
469
+ warnings.warn(_DEPRECATION_WARNING, DeprecationWarning, stacklevel=2)
358
470
  self._data = {}
359
471
  self._model_source = None
360
472
 
@@ -412,7 +524,14 @@ class TFLiteFormat(ModelFormat):
412
524
 
413
525
 
414
526
  class TFLiteModelSource:
415
- """Abstract base class representing a model source for TFLite format models."""
527
+ """Abstract base class representing a model source for TFLite format models.
528
+
529
+ .. deprecated::
530
+ Firebase ML is deprecated and will be shut down on June 15, 2027.
531
+ To host custom models, you must migrate to another solution. You can use Cloud Storage for
532
+ Firebase as an alternative for hosting custom models. For more info, see
533
+ https://firebase.google.com/docs/ml/migrate-to-cloud-storage
534
+ """
416
535
  def as_dict(self, for_upload=False):
417
536
  """Returns a serializable representation of the object."""
418
537
  raise NotImplementedError
@@ -466,11 +585,20 @@ class _CloudStorageClient:
466
585
 
467
586
 
468
587
  class TFLiteGCSModelSource(TFLiteModelSource):
469
- """TFLite model source representing a tflite model file stored in GCS."""
588
+ """TFLite model source representing a tflite model file stored in GCS.
589
+
590
+ .. deprecated::
591
+ Firebase ML is deprecated and will be shut down on June 15, 2027.
592
+ To host custom models, you must migrate to another solution. You can use Cloud Storage for
593
+ Firebase as an alternative for hosting custom models. For more info, see
594
+ https://firebase.google.com/docs/ml/migrate-to-cloud-storage
595
+ """
470
596
 
471
597
  _STORAGE_CLIENT = _CloudStorageClient()
472
598
 
473
599
  def __init__(self, gcs_tflite_uri, app=None):
600
+ if not _is_internal_call():
601
+ warnings.warn(_DEPRECATION_WARNING, DeprecationWarning, stacklevel=2)
474
602
  self._app = app
475
603
  self._gcs_tflite_uri = _validate_gcs_tflite_uri(gcs_tflite_uri)
476
604
 
@@ -600,12 +728,20 @@ class TFLiteGCSModelSource(TFLiteModelSource):
600
728
  class ListModelsPage:
601
729
  """Represents a page of models in a Firebase project.
602
730
 
731
+ .. deprecated::
732
+ Firebase ML is deprecated and will be shut down on June 15, 2027.
733
+ To host custom models, you must migrate to another solution. You can use Cloud Storage for
734
+ Firebase as an alternative for hosting custom models. For more info, see
735
+ https://firebase.google.com/docs/ml/migrate-to-cloud-storage
736
+
603
737
  Provides methods for traversing the models included in this page, as well as
604
738
  retrieving subsequent pages of models. The iterator returned by
605
739
  ``iterate_all()`` can be used to iterate through all the models in the
606
740
  Firebase project starting from this page.
607
741
  """
608
742
  def __init__(self, list_models_func, list_filter, page_size, page_token, app):
743
+ if not _is_internal_call():
744
+ warnings.warn(_DEPRECATION_WARNING, DeprecationWarning, stacklevel=2)
609
745
  self._list_models_func = list_models_func
610
746
  self._list_filter = list_filter
611
747
  self._page_size = page_size
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: firebase_admin
3
- Version: 7.5.0
3
+ Version: 7.6.0
4
4
  Summary: Firebase Admin Python SDK
5
5
  Home-page: https://firebase.google.com/docs/admin/setup/
6
6
  Author: Firebase
@@ -15,11 +15,12 @@
15
15
  """Test cases for the firebase_admin.app_check module."""
16
16
  import base64
17
17
  import pytest
18
+ import requests
18
19
 
19
20
  from jwt import PyJWK, InvalidAudienceError, InvalidIssuerError
20
21
  from jwt import ExpiredSignatureError, InvalidSignatureError
21
22
  import firebase_admin
22
- from firebase_admin import app_check
23
+ from firebase_admin import app_check, exceptions
23
24
  from tests import testutils
24
25
 
25
26
  NON_STRING_ARGS = [[], tuple(), {}, True, False, 1, 0]
@@ -58,6 +59,15 @@ class TestBatch:
58
59
  def teardown_class(cls):
59
60
  testutils.cleanup_apps()
60
61
 
62
+
63
+ @pytest.fixture
64
+ def mock_jwt(mocker):
65
+ """Mocks JWT decoding and JWKS key retrieval for a valid App Check token."""
66
+ mocker.patch("jwt.decode", return_value=JWT_PAYLOAD_SAMPLE)
67
+ mocker.patch("jwt.PyJWKClient.get_signing_key_from_jwt", return_value=PyJWK(signing_key))
68
+ mocker.patch("jwt.get_unverified_header", return_value=JWT_PAYLOAD_SAMPLE.get("headers"))
69
+
70
+
61
71
  class TestVerifyToken(TestBatch):
62
72
 
63
73
  def test_no_project_id(self):
@@ -74,6 +84,12 @@ class TestVerifyToken(TestBatch):
74
84
  expected = f'app check token "{token}" must be a string.'
75
85
  assert str(excinfo.value) == expected
76
86
 
87
+ @pytest.mark.parametrize('consume', [[], tuple(), {}, 1, 0, 'true', 'false', None])
88
+ def test_verify_token_with_non_boolean_consume_raises_error(self, consume):
89
+ with pytest.raises(ValueError) as excinfo:
90
+ app_check.verify_token('app_check_token', consume=consume)
91
+ assert str(excinfo.value) == 'consume must be a boolean.'
92
+
77
93
  def test_has_valid_token_headers(self):
78
94
  app = firebase_admin.get_app()
79
95
  app_check_service = app_check._get_app_check_service(app)
@@ -221,10 +237,8 @@ class TestVerifyToken(TestBatch):
221
237
  f'"{sub_number}" must be a string.')
222
238
  assert str(excinfo.value) == expected
223
239
 
224
- def test_verify_token(self, mocker):
225
- mocker.patch("jwt.decode", return_value=JWT_PAYLOAD_SAMPLE)
226
- mocker.patch("jwt.PyJWKClient.get_signing_key_from_jwt", return_value=PyJWK(signing_key))
227
- mocker.patch("jwt.get_unverified_header", return_value=JWT_PAYLOAD_SAMPLE.get("headers"))
240
+ @pytest.mark.usefixtures("mock_jwt")
241
+ def test_verify_token(self):
228
242
  app = firebase_admin.get_app()
229
243
 
230
244
  payload = app_check.verify_token("encoded", app)
@@ -273,3 +287,120 @@ class TestVerifyToken(TestBatch):
273
287
 
274
288
  expected = 'Token does not contain the correct "iss" (issuer).'
275
289
  assert str(excinfo.value) == expected
290
+
291
+ @pytest.mark.usefixtures("mock_jwt")
292
+ @pytest.mark.parametrize('consume_kwargs', [{}, {'consume': False}])
293
+ def test_verify_token_with_consume_false_makes_no_http_call(
294
+ self, mocker, consume_kwargs
295
+ ):
296
+ app = firebase_admin.get_app()
297
+ app_check_service = app_check._get_app_check_service(app)
298
+ mock_body = mocker.patch.object(app_check_service._http_client, "body")
299
+
300
+ payload = app_check.verify_token("encoded", app=app, **consume_kwargs)
301
+ expected = JWT_PAYLOAD_SAMPLE.copy()
302
+ expected["app_id"] = APP_ID
303
+ assert payload == expected
304
+ assert 'already_consumed' not in payload
305
+ mock_body.assert_not_called()
306
+
307
+ @pytest.mark.usefixtures("mock_jwt")
308
+ def test_verify_token_with_consume_true_not_consumed(self, mocker):
309
+ app = firebase_admin.get_app()
310
+ app_check_service = app_check._get_app_check_service(app)
311
+ mock_body = mocker.patch.object(
312
+ app_check_service._http_client, "body", return_value={"alreadyConsumed": False}
313
+ )
314
+
315
+ payload = app_check.verify_token("encoded", app=app, consume=True)
316
+ expected = JWT_PAYLOAD_SAMPLE.copy()
317
+ expected["app_id"] = APP_ID
318
+ expected["already_consumed"] = False
319
+ assert payload == expected
320
+
321
+ expected_url = (
322
+ f"https://firebaseappcheck.googleapis.com/v1/projects/{PROJECT_ID}:verifyAppCheckToken"
323
+ )
324
+ mock_body.assert_called_once_with(
325
+ "post", expected_url, json={"app_check_token": "encoded"}
326
+ )
327
+
328
+ @pytest.mark.usefixtures("mock_jwt")
329
+ def test_verify_token_with_consume_true_already_consumed(self, mocker):
330
+ app = firebase_admin.get_app()
331
+ app_check_service = app_check._get_app_check_service(app)
332
+ mock_body = mocker.patch.object(
333
+ app_check_service._http_client, "body", return_value={"alreadyConsumed": True}
334
+ )
335
+
336
+ payload = app_check.verify_token("encoded", app=app, consume=True)
337
+ expected = JWT_PAYLOAD_SAMPLE.copy()
338
+ expected["app_id"] = APP_ID
339
+ expected["already_consumed"] = True
340
+ assert payload == expected
341
+
342
+ expected_url = (
343
+ f"https://firebaseappcheck.googleapis.com/v1/projects/{PROJECT_ID}:verifyAppCheckToken"
344
+ )
345
+ mock_body.assert_called_once_with(
346
+ "post", expected_url, json={"app_check_token": "encoded"}
347
+ )
348
+
349
+ @pytest.mark.usefixtures("mock_jwt")
350
+ def test_verify_token_with_consume_true_backend_error(self, mocker):
351
+ app = firebase_admin.get_app()
352
+ app_check_service = app_check._get_app_check_service(app)
353
+
354
+ req_exc = requests.exceptions.RequestException("Backend error")
355
+ mocker.patch.object(app_check_service._http_client, "body", side_effect=req_exc)
356
+
357
+ with pytest.raises(exceptions.UnknownError) as excinfo:
358
+ app_check.verify_token("encoded", app=app, consume=True)
359
+ assert "Unknown error while making a remote service call" in str(excinfo.value)
360
+
361
+ @pytest.mark.usefixtures("mock_jwt")
362
+ def test_verify_token_with_consume_true_http_error(self, mocker):
363
+ app = firebase_admin.get_app()
364
+ app_check_service = app_check._get_app_check_service(app)
365
+
366
+ response = requests.Response()
367
+ response.status_code = 403
368
+ response._content = (
369
+ b'{"error": {"status": "PERMISSION_DENIED", "message": "Permission denied."}}'
370
+ )
371
+ http_exc = requests.exceptions.HTTPError(response=response)
372
+ mocker.patch.object(app_check_service._http_client, "body", side_effect=http_exc)
373
+
374
+ with pytest.raises(exceptions.PermissionDeniedError) as excinfo:
375
+ app_check.verify_token("encoded", app=app, consume=True)
376
+ assert "Permission denied." in str(excinfo.value)
377
+
378
+ @pytest.mark.usefixtures("mock_jwt")
379
+ @pytest.mark.parametrize('malformed_body', ['string_response', [1, 2], 123, None])
380
+ def test_verify_token_with_consume_true_malformed_response_raises_error(
381
+ self, mocker, malformed_body
382
+ ):
383
+ app = firebase_admin.get_app()
384
+ app_check_service = app_check._get_app_check_service(app)
385
+ mocker.patch.object(app_check_service._http_client, "body", return_value=malformed_body)
386
+
387
+ with pytest.raises(exceptions.UnknownError) as excinfo:
388
+ app_check.verify_token("encoded", app=app, consume=True)
389
+ assert 'Unexpected response from App Check service' in str(excinfo.value)
390
+
391
+ @pytest.mark.usefixtures("mock_jwt")
392
+ def test_verify_token_with_consume_true_json_decode_error(self, mocker):
393
+ app = firebase_admin.get_app()
394
+ app_check_service = app_check._get_app_check_service(app)
395
+ mocker.patch.object(
396
+ app_check_service._http_client,
397
+ "body",
398
+ side_effect=ValueError("Expecting value: line 1 column 1 (char 0)"),
399
+ )
400
+
401
+ with pytest.raises(exceptions.UnknownError) as excinfo:
402
+ app_check.verify_token("encoded", app=app, consume=True)
403
+ expected_msg = (
404
+ "Unexpected response from App Check service: Expecting value: line 1 column 1 (char 0)"
405
+ )
406
+ assert expected_msg in str(excinfo.value)
@@ -15,6 +15,7 @@
15
15
  """Test cases for the firebase_admin.ml module."""
16
16
 
17
17
  import json
18
+ import warnings
18
19
 
19
20
  import pytest
20
21
 
@@ -1086,3 +1087,89 @@ class TestListModels:
1086
1087
  assert len(page.models) == 0
1087
1088
  models = list(page.iterate_all())
1088
1089
  assert len(models) == 0
1090
+
1091
+
1092
+ class TestMLDeprecation:
1093
+ """Tests for verifying deprecation warnings in the ML module."""
1094
+
1095
+ DEPRECATION_MSG = (
1096
+ r'Firebase ML is deprecated and will be shut down on June 15, 2027\. '
1097
+ r'To host custom models, you must migrate to another solution\. '
1098
+ r'You can use Cloud Storage for Firebase as an alternative for hosting custom models\. '
1099
+ r'For more info, see https://firebase\.google\.com/docs/ml/migrate-to-cloud-storage'
1100
+ )
1101
+
1102
+ @classmethod
1103
+ def setup_class(cls):
1104
+ cred = testutils.MockCredential()
1105
+ firebase_admin.initialize_app(cred, {'projectId': PROJECT_ID})
1106
+ ml._MLService.POLL_BASE_WAIT_TIME_SECONDS = 0.1
1107
+ ml.TFLiteGCSModelSource._STORAGE_CLIENT = _TestStorageClient()
1108
+
1109
+ @classmethod
1110
+ def teardown_class(cls):
1111
+ testutils.cleanup_apps()
1112
+
1113
+ def test_model_init_deprecation_warning(self):
1114
+ with pytest.warns(DeprecationWarning, match=self.DEPRECATION_MSG):
1115
+ ml.Model()
1116
+
1117
+ def test_tflite_format_init_deprecation_warning(self):
1118
+ with pytest.warns(DeprecationWarning, match=self.DEPRECATION_MSG):
1119
+ ml.TFLiteFormat()
1120
+
1121
+ def test_tflite_gcs_model_source_init_deprecation_warning(self):
1122
+ with pytest.warns(DeprecationWarning, match=self.DEPRECATION_MSG):
1123
+ ml.TFLiteGCSModelSource(GCS_TFLITE_URI)
1124
+
1125
+ def test_get_model_deprecation_warning(self):
1126
+ instrument_ml_service(status=200, payload=DEFAULT_GET_RESPONSE)
1127
+ with pytest.warns(DeprecationWarning, match=self.DEPRECATION_MSG):
1128
+ ml.get_model(MODEL_ID_1)
1129
+
1130
+ def test_list_models_deprecation_warning(self):
1131
+ instrument_ml_service(status=200, payload=ONE_PAGE_LIST_RESPONSE)
1132
+ with pytest.warns(DeprecationWarning, match=self.DEPRECATION_MSG):
1133
+ ml.list_models()
1134
+
1135
+ def test_delete_model_deprecation_warning(self):
1136
+ instrument_ml_service(status=200, payload=EMPTY_RESPONSE)
1137
+ with pytest.warns(DeprecationWarning, match=self.DEPRECATION_MSG):
1138
+ ml.delete_model(MODEL_ID_1)
1139
+
1140
+ def test_create_model_deprecation_warning(self):
1141
+ instrument_ml_service(status=200, payload=OPERATION_DONE_RESPONSE)
1142
+ with pytest.warns(DeprecationWarning, match=self.DEPRECATION_MSG):
1143
+ ml.create_model(ml.Model(display_name=DISPLAY_NAME_1))
1144
+
1145
+ def test_update_model_deprecation_warning(self):
1146
+ instrument_ml_service(status=200, payload=OPERATION_DONE_RESPONSE)
1147
+ with pytest.warns(DeprecationWarning, match=self.DEPRECATION_MSG):
1148
+ ml.update_model(ml.Model(display_name=DISPLAY_NAME_1))
1149
+
1150
+ def test_publish_model_deprecation_warning(self):
1151
+ instrument_ml_service(status=200, payload=OPERATION_DONE_RESPONSE)
1152
+ with pytest.warns(DeprecationWarning, match=self.DEPRECATION_MSG):
1153
+ ml.publish_model(MODEL_ID_1)
1154
+
1155
+ def test_unpublish_model_deprecation_warning(self):
1156
+ instrument_ml_service(status=200, payload=OPERATION_DONE_RESPONSE)
1157
+ with pytest.warns(DeprecationWarning, match=self.DEPRECATION_MSG):
1158
+ ml.unpublish_model(MODEL_ID_1)
1159
+
1160
+ def test_single_warning_on_get_model(self):
1161
+ instrument_ml_service(status=200, payload=DEFAULT_GET_RESPONSE)
1162
+ with warnings.catch_warnings(record=True) as captured_warnings:
1163
+ warnings.simplefilter('always')
1164
+ ml.get_model(MODEL_ID_1)
1165
+ assert len(captured_warnings) == 1
1166
+ assert issubclass(captured_warnings[0].category, DeprecationWarning)
1167
+
1168
+ def test_single_warning_on_list_models(self):
1169
+ instrument_ml_service(status=200, payload=ONE_PAGE_LIST_RESPONSE)
1170
+ with warnings.catch_warnings(record=True) as captured_warnings:
1171
+ warnings.simplefilter('always')
1172
+ page = ml.list_models()
1173
+ _ = page.models
1174
+ assert len(captured_warnings) == 1
1175
+ assert issubclass(captured_warnings[0].category, DeprecationWarning)
File without changes
File without changes
File without changes
File without changes