1313# limitations under the License.
1414
1515"""Firebase Admin SDK for Python."""
16- import datetime
16+
1717import json
1818import os
1919import 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
2025
21- from google .auth .credentials import Credentials as GoogleAuthCredentials
22- from google .auth .exceptions import DefaultCredentialsError
2326from firebase_admin import credentials
2427from firebase_admin .__about__ import __version__
2528
29+ __all__ = (
30+ 'App' ,
31+ 'delete_app' ,
32+ 'get_app' ,
33+ 'initialize_app' ,
34+ )
2635
27- _apps = {}
36+ _T = TypeVar ('_T' )
37+
38+ _apps : dict [str , 'App' ] = {}
2839_apps_lock = threading .RLock ()
29- _clock = datetime .datetime .utcnow
3040
3141_DEFAULT_APP_NAME = '[DEFAULT]'
3242_FIREBASE_CONFIG_ENV_VAR = 'FIREBASE_CONFIG'
3343_CONFIG_VALID_KEYS = ['databaseAuthVariableOverride' , 'databaseURL' , 'httpTimeout' , 'projectId' ,
3444 'storageBucket' ]
3545
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' :
3751 """Initializes and returns a new App instance.
3852
3953 Creates a new App instance using the specified options
@@ -86,7 +100,7 @@ def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME):
86100 'you call initialize_app().' )
87101
88102
89- def delete_app (app ) :
103+ def delete_app (app : 'App' ) -> None :
90104 """Gracefully deletes an App instance.
91105
92106 Args:
@@ -113,7 +127,7 @@ def delete_app(app):
113127 'second argument.' )
114128
115129
116- def get_app (name = _DEFAULT_APP_NAME ):
130+ def get_app (name : str = _DEFAULT_APP_NAME ) -> 'App' :
117131 """Retrieves an App instance by name.
118132
119133 Args:
@@ -147,7 +161,7 @@ def get_app(name=_DEFAULT_APP_NAME):
147161class _AppOptions :
148162 """A collection of configuration options for an App."""
149163
150- def __init__ (self , options ) :
164+ def __init__ (self , options : Optional [ dict [ str , Any ]]) -> None :
151165 if options is None :
152166 options = self ._load_from_environment ()
153167
@@ -157,11 +171,16 @@ def __init__(self, options):
157171 'Options must be a dictionary.' )
158172 self ._options = options
159173
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 ]:
161180 """Returns the option identified by the provided key."""
162181 return self ._options .get (key , default )
163182
164- def _load_from_environment (self ):
183+ def _load_from_environment (self ) -> dict [ str , Any ] :
165184 """Invoked when no options are passed to __init__, loads options from FIREBASE_CONFIG.
166185
167186 If the value of the FIREBASE_CONFIG environment variable starts with "{" an attempt is made
@@ -194,7 +213,12 @@ class App:
194213 common to all Firebase APIs.
195214 """
196215
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 :
198222 """Constructs a new App using the provided name and options.
199223
200224 Args:
@@ -211,7 +235,7 @@ def __init__(self, name, credential, options):
211235 'non-empty string.' )
212236 self ._name = name
213237
214- if isinstance (credential , GoogleAuthCredentials ):
238+ if isinstance (credential , google . auth . credentials . Credentials ):
215239 self ._credential = credentials ._ExternalCredentials (credential ) # pylint: disable=protected-access
216240 elif isinstance (credential , credentials .Base ):
217241 self ._credential = credential
@@ -220,37 +244,38 @@ def __init__(self, name, credential, options):
220244 'with a valid credential instance.' )
221245 self ._options = _AppOptions (options )
222246 self ._lock = threading .RLock ()
223- self ._services = {}
247+ self ._services : Optional [ dict [ str , Any ]] = {}
224248
225249 App ._validate_project_id (self ._options .get ('projectId' ))
226- self ._project_id_initialized = False
250+ self ._project_id_initialized : bool = False
227251
228252 @classmethod
229- def _validate_project_id (cls , project_id ) :
253+ def _validate_project_id (cls , project_id : Optional [ Any ]) -> Optional [ str ] :
230254 if project_id is not None and not isinstance (project_id , str ):
231255 raise ValueError (
232256 f'Invalid project ID: "{ project_id } ". project ID must be a string.' )
257+ return project_id
233258
234259 @property
235- def name (self ):
260+ def name (self ) -> str :
236261 return self ._name
237262
238263 @property
239- def credential (self ):
264+ def credential (self ) -> credentials . Base :
240265 return self ._credential
241266
242267 @property
243- def options (self ):
268+ def options (self ) -> _AppOptions :
244269 return self ._options
245270
246271 @property
247- def project_id (self ):
272+ def project_id (self ) -> Optional [ str ] :
248273 if not self ._project_id_initialized :
249274 self ._project_id = self ._lookup_project_id ()
250275 self ._project_id_initialized = True
251276 return self ._project_id
252277
253- def _lookup_project_id (self ):
278+ def _lookup_project_id (self ) -> Optional [ str ] :
254279 """Looks up the Firebase project ID associated with an App.
255280
256281 If a ``projectId`` is specified in app options, it is returned. Then tries to
@@ -264,16 +289,16 @@ def _lookup_project_id(self):
264289 project_id = self ._options .get ('projectId' )
265290 if not project_id :
266291 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 ):
269294 pass
270295 if not project_id :
271296 project_id = os .environ .get ('GOOGLE_CLOUD_PROJECT' ,
272297 os .environ .get ('GCLOUD_PROJECT' ))
273298 App ._validate_project_id (self ._options .get ('projectId' ))
274299 return project_id
275300
276- def _get_service (self , name , initializer ) :
301+ def _get_service (self , name : str , initializer : Callable [[ 'App' ], _T ]) -> _T :
277302 """Returns the service instance identified by the given name.
278303
279304 Services are functional entities exposed by the Admin SDK (e.g. auth, database). Each
@@ -303,15 +328,16 @@ def _get_service(self, name, initializer):
303328 self ._services [name ] = initializer (self )
304329 return self ._services [name ]
305330
306- def _cleanup (self ):
331+ def _cleanup (self ) -> None :
307332 """Cleans up any services associated with this App.
308333
309334 Checks whether each service contains a close() method, and calls it if available.
310335 This is to be called when an App is being deleted, thus ensuring graceful termination of
311336 any services started by the App.
312337 """
313338 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