Skip to content

Commit 84b4c25

Browse files
committed
add type annotations
1 parent d84c39d commit 84b4c25

33 files changed

+2805
-1322
lines changed

firebase_admin/__init__.py

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,31 @@
1717
import json
1818
import os
1919
import threading
20+
import typing
2021

2122
from google.auth.credentials import Credentials as GoogleAuthCredentials
2223
from google.auth.exceptions import DefaultCredentialsError
2324
from firebase_admin import credentials
2425
from firebase_admin.__about__ import __version__
26+
from firebase_admin import _typing
2527

2628

27-
_apps = {}
29+
_T = typing.TypeVar("_T")
30+
31+
_apps: typing.Dict[str, "App"] = {}
2832
_apps_lock = threading.RLock()
29-
_clock = datetime.datetime.utcnow
33+
_clock = lambda: datetime.datetime.now(datetime.timezone.utc)
3034

3135
_DEFAULT_APP_NAME = '[DEFAULT]'
3236
_FIREBASE_CONFIG_ENV_VAR = 'FIREBASE_CONFIG'
3337
_CONFIG_VALID_KEYS = ['databaseAuthVariableOverride', 'databaseURL', 'httpTimeout', 'projectId',
3438
'storageBucket']
3539

36-
def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME):
40+
def initialize_app(
41+
credential: typing.Optional[_typing.CredentialLike] = None,
42+
options: typing.Optional[typing.Dict[str, typing.Any]] = None,
43+
name: str = _DEFAULT_APP_NAME
44+
) -> "App":
3745
"""Initializes and returns a new App instance.
3846
3947
Creates a new App instance using the specified options
@@ -86,7 +94,7 @@ def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME):
8694
'you call initialize_app().').format(name))
8795

8896

89-
def delete_app(app):
97+
def delete_app(app: "App") -> None:
9098
"""Gracefully deletes an App instance.
9199
92100
Args:
@@ -114,7 +122,7 @@ def delete_app(app):
114122
'second argument.').format(app.name))
115123

116124

117-
def get_app(name=_DEFAULT_APP_NAME):
125+
def get_app(name: str = _DEFAULT_APP_NAME) -> "App":
118126
"""Retrieves an App instance by name.
119127
120128
Args:
@@ -148,7 +156,7 @@ def get_app(name=_DEFAULT_APP_NAME):
148156
class _AppOptions:
149157
"""A collection of configuration options for an App."""
150158

151-
def __init__(self, options):
159+
def __init__(self, options: typing.Optional[typing.Dict[str, typing.Any]]) -> None:
152160
if options is None:
153161
options = self._load_from_environment()
154162

@@ -157,11 +165,16 @@ def __init__(self, options):
157165
'must be a dictionary.'.format(type(options)))
158166
self._options = options
159167

160-
def get(self, key, default=None):
168+
@typing.overload
169+
def get(self, key: str, default: None = None) -> typing.Optional[typing.Any]: ...
170+
# possible issue: needs return Any | _T ?
171+
@typing.overload
172+
def get(self, key: str, default: _T) -> _T: ...
173+
def get(self, key: str, default: typing.Any = None) -> typing.Optional[typing.Any]:
161174
"""Returns the option identified by the provided key."""
162175
return self._options.get(key, default)
163176

164-
def _load_from_environment(self):
177+
def _load_from_environment(self) -> typing.Dict[str, typing.Any]:
165178
"""Invoked when no options are passed to __init__, loads options from FIREBASE_CONFIG.
166179
167180
If the value of the FIREBASE_CONFIG environment variable starts with "{" an attempt is made
@@ -193,7 +206,12 @@ class App:
193206
common to all Firebase APIs.
194207
"""
195208

196-
def __init__(self, name, credential, options):
209+
def __init__(
210+
self,
211+
name: str,
212+
credential: _typing.CredentialLike,
213+
options: typing.Optional[typing.Dict[str, typing.Any]]
214+
) -> None:
197215
"""Constructs a new App using the provided name and options.
198216
199217
Args:
@@ -218,37 +236,37 @@ def __init__(self, name, credential, options):
218236
'with a valid credential instance.')
219237
self._options = _AppOptions(options)
220238
self._lock = threading.RLock()
221-
self._services = {}
239+
self._services: typing.Optional[typing.Dict[str, typing.Any]] = {}
222240

223241
App._validate_project_id(self._options.get('projectId'))
224242
self._project_id_initialized = False
225243

226244
@classmethod
227-
def _validate_project_id(cls, project_id):
245+
def _validate_project_id(cls, project_id: typing.Optional[str]) -> None:
228246
if project_id is not None and not isinstance(project_id, str):
229247
raise ValueError(
230248
'Invalid project ID: "{0}". project ID must be a string.'.format(project_id))
231249

232250
@property
233-
def name(self):
251+
def name(self) -> str:
234252
return self._name
235253

236254
@property
237-
def credential(self):
255+
def credential(self) -> credentials.Base:
238256
return self._credential
239257

240258
@property
241-
def options(self):
259+
def options(self) -> _AppOptions:
242260
return self._options
243261

244262
@property
245-
def project_id(self):
263+
def project_id(self) -> typing.Optional[str]:
246264
if not self._project_id_initialized:
247265
self._project_id = self._lookup_project_id()
248266
self._project_id_initialized = True
249267
return self._project_id
250268

251-
def _lookup_project_id(self):
269+
def _lookup_project_id(self) -> typing.Optional[str]:
252270
"""Looks up the Firebase project ID associated with an App.
253271
254272
If a ``projectId`` is specified in app options, it is returned. Then tries to
@@ -259,10 +277,10 @@ def _lookup_project_id(self):
259277
Returns:
260278
str: A project ID string or None.
261279
"""
262-
project_id = self._options.get('projectId')
280+
project_id: typing.Optional[str] = self._options.get('projectId')
263281
if not project_id:
264282
try:
265-
project_id = self._credential.project_id
283+
project_id = getattr(self._credential, "project_id")
266284
except (AttributeError, DefaultCredentialsError):
267285
pass
268286
if not project_id:
@@ -271,7 +289,7 @@ def _lookup_project_id(self):
271289
App._validate_project_id(self._options.get('projectId'))
272290
return project_id
273291

274-
def _get_service(self, name, initializer):
292+
def _get_service(self, name: str, initializer: _typing.ServiceInitializer[_T]) -> _T:
275293
"""Returns the service instance identified by the given name.
276294
277295
Services are functional entities exposed by the Admin SDK (e.g. auth, database). Each
@@ -301,15 +319,16 @@ def _get_service(self, name, initializer):
301319
self._services[name] = initializer(self)
302320
return self._services[name]
303321

304-
def _cleanup(self):
322+
def _cleanup(self) -> None:
305323
"""Cleans up any services associated with this App.
306324
307325
Checks whether each service contains a close() method, and calls it if available.
308326
This is to be called when an App is being deleted, thus ensuring graceful termination of
309327
any services started by the App.
310328
"""
311329
with self._lock:
312-
for service in self._services.values():
313-
if hasattr(service, 'close') and hasattr(service.close, '__call__'):
314-
service.close()
330+
if self._services:
331+
for service in self._services.values():
332+
if hasattr(service, 'close') and hasattr(service.close, '__call__'):
333+
service.close()
315334
self._services = None

0 commit comments

Comments
 (0)