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+ )
35+
36+ _T = TypeVar ('_T' )
2637
27- _apps = {}
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,15 @@ 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+ @overload
177+ def get (self , key : str , default : _T ) -> Union [Any , _T ]: ...
178+ def get (self , key : str , default : Optional [Any ] = None ) -> Optional [Any ]:
161179 """Returns the option identified by the provided key."""
162180 return self ._options .get (key , default )
163181
164- def _load_from_environment (self ):
182+ def _load_from_environment (self ) -> dict [ str , Any ] :
165183 """Invoked when no options are passed to __init__, loads options from FIREBASE_CONFIG.
166184
167185 If the value of the FIREBASE_CONFIG environment variable starts with "{" an attempt is made
@@ -194,7 +212,12 @@ class App:
194212 common to all Firebase APIs.
195213 """
196214
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 :
198221 """Constructs a new App using the provided name and options.
199222
200223 Args:
@@ -211,7 +234,7 @@ def __init__(self, name, credential, options):
211234 'non-empty string.' )
212235 self ._name = name
213236
214- if isinstance (credential , GoogleAuthCredentials ):
237+ if isinstance (credential , google . auth . credentials . Credentials ):
215238 self ._credential = credentials ._ExternalCredentials (credential ) # pylint: disable=protected-access
216239 elif isinstance (credential , credentials .Base ):
217240 self ._credential = credential
@@ -220,37 +243,38 @@ def __init__(self, name, credential, options):
220243 'with a valid credential instance.' )
221244 self ._options = _AppOptions (options )
222245 self ._lock = threading .RLock ()
223- self ._services = {}
246+ self ._services : Optional [ dict [ str , Any ]] = {}
224247
225248 App ._validate_project_id (self ._options .get ('projectId' ))
226- self ._project_id_initialized = False
249+ self ._project_id_initialized : bool = False
227250
228- @classmethod
229- def _validate_project_id (cls , project_id ) :
251+ @staticmethod
252+ def _validate_project_id (project_id : Optional [ Any ]) -> Optional [ str ] :
230253 if project_id is not None and not isinstance (project_id , str ):
231254 raise ValueError (
232255 f'Invalid project ID: "{ project_id } ". project ID must be a string.' )
256+ return project_id
233257
234258 @property
235- def name (self ):
259+ def name (self ) -> str :
236260 return self ._name
237261
238262 @property
239- def credential (self ):
263+ def credential (self ) -> credentials . Base :
240264 return self ._credential
241265
242266 @property
243- def options (self ):
267+ def options (self ) -> _AppOptions :
244268 return self ._options
245269
246270 @property
247- def project_id (self ):
271+ def project_id (self ) -> Optional [ str ] :
248272 if not self ._project_id_initialized :
249273 self ._project_id = self ._lookup_project_id ()
250274 self ._project_id_initialized = True
251275 return self ._project_id
252276
253- def _lookup_project_id (self ):
277+ def _lookup_project_id (self ) -> Optional [ str ] :
254278 """Looks up the Firebase project ID associated with an App.
255279
256280 If a ``projectId`` is specified in app options, it is returned. Then tries to
@@ -264,16 +288,16 @@ def _lookup_project_id(self):
264288 project_id = self ._options .get ('projectId' )
265289 if not project_id :
266290 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 ):
269293 pass
270294 if not project_id :
271295 project_id = os .environ .get ('GOOGLE_CLOUD_PROJECT' ,
272296 os .environ .get ('GCLOUD_PROJECT' ))
273297 App ._validate_project_id (self ._options .get ('projectId' ))
274298 return project_id
275299
276- def _get_service (self , name , initializer ) :
300+ def _get_service (self , name : str , initializer : Callable [[ 'App' ], _T ]) -> _T :
277301 """Returns the service instance identified by the given name.
278302
279303 Services are functional entities exposed by the Admin SDK (e.g. auth, database). Each
@@ -303,15 +327,17 @@ def _get_service(self, name, initializer):
303327 self ._services [name ] = initializer (self )
304328 return self ._services [name ]
305329
306- def _cleanup (self ):
330+ def _cleanup (self ) -> None :
307331 """Cleans up any services associated with this App.
308332
309333 Checks whether each service contains a close() method, and calls it if available.
310334 This is to be called when an App is being deleted, thus ensuring graceful termination of
311335 any services started by the App.
312336 """
313337 with self ._lock :
338+ if self ._services is None :
339+ return None
314340 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 ):
316342 service .close ()
317343 self ._services = None
0 commit comments