Skip to content

Commit d6807f5

Browse files
committed
add type annotations
1 parent c9dce80 commit d6807f5

33 files changed

+3309
-1413
lines changed

firebase_admin/__init__.py

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,41 @@
1313
# limitations under the License.
1414

1515
"""Firebase Admin SDK for Python."""
16-
import datetime
16+
1717
import json
1818
import os
1919
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
2025

21-
from google.auth.credentials import Credentials as GoogleAuthCredentials
22-
from google.auth.exceptions import DefaultCredentialsError
2326
from firebase_admin import credentials
2427
from 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):
147161
class _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

Comments
 (0)