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
+ )
26
35
27
- _apps = {}
36
+ _T = TypeVar ('_T' )
37
+
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,16 @@ 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
+ # possible issue: needs return Any | _T ?
177
+ @overload
178
+ def get (self , key : str , default : _T ) -> _T : ...
179
+ def get (self , key : str , default : Optional [Any ] = None ) -> Optional [Any ]:
161
180
"""Returns the option identified by the provided key."""
162
181
return self ._options .get (key , default )
163
182
164
- def _load_from_environment (self ):
183
+ def _load_from_environment (self ) -> dict [ str , Any ] :
165
184
"""Invoked when no options are passed to __init__, loads options from FIREBASE_CONFIG.
166
185
167
186
If the value of the FIREBASE_CONFIG environment variable starts with "{" an attempt is made
@@ -194,7 +213,12 @@ class App:
194
213
common to all Firebase APIs.
195
214
"""
196
215
197
- def __init__ (self , name , credential , options ):
216
+ def __init__ (
217
+ self ,
218
+ name : str ,
219
+ credential : Union [credentials .Base , google .auth .credentials .Credentials ],
220
+ options : Optional [dict [str , Any ]],
221
+ ) -> None :
198
222
"""Constructs a new App using the provided name and options.
199
223
200
224
Args:
@@ -211,7 +235,7 @@ def __init__(self, name, credential, options):
211
235
'non-empty string.' )
212
236
self ._name = name
213
237
214
- if isinstance (credential , GoogleAuthCredentials ):
238
+ if isinstance (credential , google . auth . credentials . Credentials ):
215
239
self ._credential = credentials ._ExternalCredentials (credential ) # pylint: disable=protected-access
216
240
elif isinstance (credential , credentials .Base ):
217
241
self ._credential = credential
@@ -220,37 +244,38 @@ def __init__(self, name, credential, options):
220
244
'with a valid credential instance.' )
221
245
self ._options = _AppOptions (options )
222
246
self ._lock = threading .RLock ()
223
- self ._services = {}
247
+ self ._services : Optional [ dict [ str , Any ]] = {}
224
248
225
249
App ._validate_project_id (self ._options .get ('projectId' ))
226
- self ._project_id_initialized = False
250
+ self ._project_id_initialized : bool = False
227
251
228
252
@classmethod
229
- def _validate_project_id (cls , project_id ) :
253
+ def _validate_project_id (cls , project_id : Optional [ Any ]) -> Optional [ str ] :
230
254
if project_id is not None and not isinstance (project_id , str ):
231
255
raise ValueError (
232
256
f'Invalid project ID: "{ project_id } ". project ID must be a string.' )
257
+ return project_id
233
258
234
259
@property
235
- def name (self ):
260
+ def name (self ) -> str :
236
261
return self ._name
237
262
238
263
@property
239
- def credential (self ):
264
+ def credential (self ) -> credentials . Base :
240
265
return self ._credential
241
266
242
267
@property
243
- def options (self ):
268
+ def options (self ) -> _AppOptions :
244
269
return self ._options
245
270
246
271
@property
247
- def project_id (self ):
272
+ def project_id (self ) -> Optional [ str ] :
248
273
if not self ._project_id_initialized :
249
274
self ._project_id = self ._lookup_project_id ()
250
275
self ._project_id_initialized = True
251
276
return self ._project_id
252
277
253
- def _lookup_project_id (self ):
278
+ def _lookup_project_id (self ) -> Optional [ str ] :
254
279
"""Looks up the Firebase project ID associated with an App.
255
280
256
281
If a ``projectId`` is specified in app options, it is returned. Then tries to
@@ -264,16 +289,16 @@ def _lookup_project_id(self):
264
289
project_id = self ._options .get ('projectId' )
265
290
if not project_id :
266
291
try :
267
- project_id = self ._credential . project_id
268
- except (AttributeError , DefaultCredentialsError ):
292
+ project_id = getattr ( self ._credential , ' project_id' )
293
+ except (AttributeError , google . auth . exceptions . DefaultCredentialsError ):
269
294
pass
270
295
if not project_id :
271
296
project_id = os .environ .get ('GOOGLE_CLOUD_PROJECT' ,
272
297
os .environ .get ('GCLOUD_PROJECT' ))
273
298
App ._validate_project_id (self ._options .get ('projectId' ))
274
299
return project_id
275
300
276
- def _get_service (self , name , initializer ) :
301
+ def _get_service (self , name : str , initializer : Callable [[ 'App' ], _T ]) -> _T :
277
302
"""Returns the service instance identified by the given name.
278
303
279
304
Services are functional entities exposed by the Admin SDK (e.g. auth, database). Each
@@ -303,15 +328,16 @@ def _get_service(self, name, initializer):
303
328
self ._services [name ] = initializer (self )
304
329
return self ._services [name ]
305
330
306
- def _cleanup (self ):
331
+ def _cleanup (self ) -> None :
307
332
"""Cleans up any services associated with this App.
308
333
309
334
Checks whether each service contains a close() method, and calls it if available.
310
335
This is to be called when an App is being deleted, thus ensuring graceful termination of
311
336
any services started by the App.
312
337
"""
313
338
with self ._lock :
314
- for service in self ._services .values ():
315
- if hasattr (service , 'close' ) and hasattr (service .close , '__call__' ):
316
- service .close ()
317
- self ._services = None
339
+ if self ._services is not None :
340
+ for service in self ._services .values ():
341
+ if hasattr (service , 'close' ) and hasattr (service .close , '__call__' ):
342
+ service .close ()
343
+ self ._services = None
0 commit comments