13
13
# limitations under the License.
14
14
15
15
"""Firebase Admin SDK for Python."""
16
- import datetime
16
+
17
17
import json
18
18
import os
19
19
import threading
20
+ from collections .abc import Callable
21
+ from typing import Any , Optional , TypeVar , Union , overload
22
+
23
+ import google .auth .credentials
24
+ import google .auth .exceptions
20
25
21
- from google .auth .credentials import Credentials as GoogleAuthCredentials
22
- from google .auth .exceptions import DefaultCredentialsError
23
26
from firebase_admin import credentials
24
27
from firebase_admin .__about__ import __version__
25
28
29
+ __all__ = (
30
+ 'App' ,
31
+ 'delete_app' ,
32
+ 'get_app' ,
33
+ 'initialize_app' ,
34
+ )
35
+
36
+ _T = TypeVar ('_T' )
26
37
27
- _apps = {}
38
+ _apps : dict [ str , 'App' ] = {}
28
39
_apps_lock = threading .RLock ()
29
- _clock = datetime .datetime .utcnow
30
40
31
41
_DEFAULT_APP_NAME = '[DEFAULT]'
32
42
_FIREBASE_CONFIG_ENV_VAR = 'FIREBASE_CONFIG'
33
43
_CONFIG_VALID_KEYS = ['databaseAuthVariableOverride' , 'databaseURL' , 'httpTimeout' , 'projectId' ,
34
44
'storageBucket' ]
35
45
36
- def initialize_app (credential = None , options = None , name = _DEFAULT_APP_NAME ):
46
+ def initialize_app (
47
+ credential : Optional [Union [credentials .Base , google .auth .credentials .Credentials ]] = None ,
48
+ options : Optional [dict [str , Any ]] = None ,
49
+ name : str = _DEFAULT_APP_NAME ,
50
+ ) -> 'App' :
37
51
"""Initializes and returns a new App instance.
38
52
39
53
Creates a new App instance using the specified options
@@ -86,7 +100,7 @@ def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME):
86
100
'you call initialize_app().' )
87
101
88
102
89
- def delete_app (app ) :
103
+ def delete_app (app : 'App' ) -> None :
90
104
"""Gracefully deletes an App instance.
91
105
92
106
Args:
@@ -113,7 +127,7 @@ def delete_app(app):
113
127
'second argument.' )
114
128
115
129
116
- def get_app (name = _DEFAULT_APP_NAME ):
130
+ def get_app (name : str = _DEFAULT_APP_NAME ) -> 'App' :
117
131
"""Retrieves an App instance by name.
118
132
119
133
Args:
@@ -147,7 +161,7 @@ def get_app(name=_DEFAULT_APP_NAME):
147
161
class _AppOptions :
148
162
"""A collection of configuration options for an App."""
149
163
150
- def __init__ (self , options ) :
164
+ def __init__ (self , options : Optional [ dict [ str , Any ]]) -> None :
151
165
if options is None :
152
166
options = self ._load_from_environment ()
153
167
@@ -157,11 +171,15 @@ def __init__(self, options):
157
171
'Options must be a dictionary.' )
158
172
self ._options = options
159
173
160
- def get (self , key , default = None ):
174
+ @overload
175
+ def get (self , key : str , default : None = None ) -> Optional [Any ]: ...
176
+ @overload
177
+ def get (self , key : str , default : _T ) -> _T : ...
178
+ def get (self , key : str , default : Optional [Any ] = None ) -> Optional [Any ]:
161
179
"""Returns the option identified by the provided key."""
162
180
return self ._options .get (key , default )
163
181
164
- def _load_from_environment (self ):
182
+ def _load_from_environment (self ) -> dict [ str , Any ] :
165
183
"""Invoked when no options are passed to __init__, loads options from FIREBASE_CONFIG.
166
184
167
185
If the value of the FIREBASE_CONFIG environment variable starts with "{" an attempt is made
@@ -194,7 +212,12 @@ class App:
194
212
common to all Firebase APIs.
195
213
"""
196
214
197
- def __init__ (self , name , credential , options ):
215
+ def __init__ (
216
+ self ,
217
+ name : str ,
218
+ credential : Union [credentials .Base , google .auth .credentials .Credentials ],
219
+ options : Optional [dict [str , Any ]],
220
+ ) -> None :
198
221
"""Constructs a new App using the provided name and options.
199
222
200
223
Args:
@@ -211,7 +234,7 @@ def __init__(self, name, credential, options):
211
234
'non-empty string.' )
212
235
self ._name = name
213
236
214
- if isinstance (credential , GoogleAuthCredentials ):
237
+ if isinstance (credential , google . auth . credentials . Credentials ):
215
238
self ._credential = credentials ._ExternalCredentials (credential ) # pylint: disable=protected-access
216
239
elif isinstance (credential , credentials .Base ):
217
240
self ._credential = credential
@@ -220,37 +243,38 @@ def __init__(self, name, credential, options):
220
243
'with a valid credential instance.' )
221
244
self ._options = _AppOptions (options )
222
245
self ._lock = threading .RLock ()
223
- self ._services = {}
246
+ self ._services : Optional [ dict [ str , Any ]] = {}
224
247
225
248
App ._validate_project_id (self ._options .get ('projectId' ))
226
- self ._project_id_initialized = False
249
+ self ._project_id_initialized : bool = False
227
250
228
- @classmethod
229
- def _validate_project_id (cls , project_id ) :
251
+ @staticmethod
252
+ def _validate_project_id (project_id : Optional [ Any ]) -> Optional [ str ] :
230
253
if project_id is not None and not isinstance (project_id , str ):
231
254
raise ValueError (
232
255
f'Invalid project ID: "{ project_id } ". project ID must be a string.' )
256
+ return project_id
233
257
234
258
@property
235
- def name (self ):
259
+ def name (self ) -> str :
236
260
return self ._name
237
261
238
262
@property
239
- def credential (self ):
263
+ def credential (self ) -> credentials . Base :
240
264
return self ._credential
241
265
242
266
@property
243
- def options (self ):
267
+ def options (self ) -> _AppOptions :
244
268
return self ._options
245
269
246
270
@property
247
- def project_id (self ):
271
+ def project_id (self ) -> Optional [ str ] :
248
272
if not self ._project_id_initialized :
249
273
self ._project_id = self ._lookup_project_id ()
250
274
self ._project_id_initialized = True
251
275
return self ._project_id
252
276
253
- def _lookup_project_id (self ):
277
+ def _lookup_project_id (self ) -> Optional [ str ] :
254
278
"""Looks up the Firebase project ID associated with an App.
255
279
256
280
If a ``projectId`` is specified in app options, it is returned. Then tries to
@@ -264,16 +288,16 @@ def _lookup_project_id(self):
264
288
project_id = self ._options .get ('projectId' )
265
289
if not project_id :
266
290
try :
267
- project_id = self ._credential . project_id
268
- except (AttributeError , DefaultCredentialsError ):
291
+ project_id = getattr ( self ._credential , ' project_id' )
292
+ except (AttributeError , google . auth . exceptions . DefaultCredentialsError ):
269
293
pass
270
294
if not project_id :
271
295
project_id = os .environ .get ('GOOGLE_CLOUD_PROJECT' ,
272
296
os .environ .get ('GCLOUD_PROJECT' ))
273
297
App ._validate_project_id (self ._options .get ('projectId' ))
274
298
return project_id
275
299
276
- def _get_service (self , name , initializer ) :
300
+ def _get_service (self , name : str , initializer : Callable [[ 'App' ], _T ]) -> _T :
277
301
"""Returns the service instance identified by the given name.
278
302
279
303
Services are functional entities exposed by the Admin SDK (e.g. auth, database). Each
@@ -303,15 +327,17 @@ def _get_service(self, name, initializer):
303
327
self ._services [name ] = initializer (self )
304
328
return self ._services [name ]
305
329
306
- def _cleanup (self ):
330
+ def _cleanup (self ) -> None :
307
331
"""Cleans up any services associated with this App.
308
332
309
333
Checks whether each service contains a close() method, and calls it if available.
310
334
This is to be called when an App is being deleted, thus ensuring graceful termination of
311
335
any services started by the App.
312
336
"""
313
337
with self ._lock :
338
+ if self ._services is None :
339
+ return None
314
340
for service in self ._services .values ():
315
- if hasattr (service , 'close' ) and hasattr (service .close , '__call__' ):
341
+ if hasattr (service , 'close' ) and callable (service .close ):
316
342
service .close ()
317
343
self ._services = None
0 commit comments