8000 Implementing a Database API for Python Admin SDK (#31) · mc-lovin/firebase-admin-python@2246418 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2246418

Browse files
authored
Implementing a Database API for Python Admin SDK (firebase#31)
* Implemented get_token() method for App * Adding db client * Fleshed out the DatabaseReference type * Implemented the full DB API * Added unit tests for DB queries * Support for service cleanup; More tests * Further API cleanup * Code cleanup and more test cases * Fixing test for Python 3 * More python3 fixes * Get/set priority * Implementing query filters * Implemented query filters * Implemented a Query abstraction * Adding integration tests for DB API * Adding license headers to new files; Using the same testutils from unit tests * Added integration tests for comlpex queries * More integration tests for complex queries * Some documentation and tests * Improved error handling; More integration tests * Updated API docs * Implement support for sorting query results * Support for sorting lists * Fixed the sorting implementation and updated test cases * Braking index ties by comparing their keys * Updated integration tests to check for result order * Updated docstrings * Added newlines at the end of test data files * Updated documentation; Fixed a bug in service cleanup logic; Other minor nits.
1 parent f7240de commit 2246418

File tree

12 files changed

+1770
-22
lines changed

12 files changed

+1770
-22
lines changed

firebase_admin/__init__.py

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
"""Firebase Admin SDK for Python."""
16+
import datetime
1617
import threading
1718

1819
import six
@@ -26,8 +27,10 @@
2627

2728
_apps = {}
2829
_apps_lock = threading.RLock()
30+
_clock = datetime.datetime.utcnow
2931

3032
_DEFAULT_APP_NAME = '[DEFAULT]'
33+
_CLOCK_SKEW_SECONDS = 300
3134

3235

3336
def initialize_app(credential=None, options=None, name=_DEFAULT_APP_NAME):
@@ -91,6 +94,7 @@ def delete_app(app):
9194
with _apps_lock:
9295
if _apps.get(app.name) is app:
9396
del _apps[app.name]
97+
app._cleanup() # pylint: disable=protected-access
9498
return
9599
if app.name == _DEFAULT_APP_NAME:
96100
raise ValueError(
@@ -145,12 +149,16 @@ def __init__(self, options):
145149
'must be a dictionary.'.format(type(options)))
146150
self._options = options
147151

152+
def get(self, key):
153+
"""Returns the option identified by the provided key."""
154+
return self._options.get(key)
155+
148156

149157
class App(object):
150158
"""The entry point for Firebase Python SDK.
151159
152-
Represents a Firebase app, while holding the configuration and state
153-
common to all Firebase APIs.
160+
Represents a Firebase app, while holding the configuration and state
161+
common to all Firebase APIs.
154162
"""
155163

156164
def __init__(self, name, credential, options):
@@ -174,6 +182,9 @@ def __init__(self, name, credential, options):
174182
'with a valid credential instance.')
175183
self._credential = credential
176184
self._options = _AppOptions(options)
185+
self._token = None
186+
self._lock = threading.RLock()
187+
self._services = {}
177188

178189
@property
179190
def name(self):
@@ -186,3 +197,65 @@ def credential(self):
186197
@property
187198
def options(self):
188199
return self._options
200+
201+
def get_token(self):
202+
"""Returns an OAuth2 bearer token.
203+
204+
This method may return a cached token. But it handles cache invalidation, and therefore
205+
is guaranteed to always return unexpired tokens.
206+
207+
Returns:
208+
string: An unexpired OAuth2 token.
209+
"""
210+
if not self._token_valid():
211+
self._token = self._credential.get_access_token()
212+
return self._token.access_token
213+
214+
def _token_valid(self):
215+
if self._token is None:
216+
return False
217+
skewed_expiry = self._token.expiry - datetime.timedelta(seconds=_CLOCK_SKEW_SECONDS)
218+
return _clock() < skewed_expiry
219+
220+
def _get_service(self, name, initializer):
221+
"""Returns the service instance identified by the given name.
222+
223+
Services are functional entities exposed by the Admin SDK (e.g. auth, database). Each
224+
service instance is associated with exactly one App. If the named service
225+
instance does not exist yet, _get_service() calls the provided initializer function to
226+
create the service instance. The created instance will be cached, so that subsequent
227+
calls would always fetch it from the cache.
228+
229+
Args:
230+
name: Name of the service to retrieve.
231+
initializer: A function that can be used to initialize a service for the first time.
232+
233+
Returns:
234+
object: The specified service instance.
235+
236+
Raises:
237+
ValueError: If the provided name is invalid, or if the App is already deleted.
238+
"""
239+
if not name or not isinstance(name, six.string_types):
240+
raise ValueError(
241+
'Illegal name argument: "{0}". Name must be a non-empty string.'.format(name))
242+
with self._lock:
243+
if self._services is None:
244+
raise ValueError(
245+
'Service requested from deleted Firebase App: "{0}".'.format(self._name))
246+
if name not in self._services:
247+
self._services[name] = initializer(self)
248+
return self._services[name]
249+
250+
def _cleanup(self):
251+
"""Cleans up any services associated with this App.
252+
253+
Checks whether each service contains a close() method, and calls it if available.
254+
This is to be called when an App is being deleted, thus ensuring graceful termination of
255+
any services started by the App.
256+
"""
257+
with self._lock:
258+
for service in self._services.values():
259+
if hasattr(service, 'close') and hasattr(service.close, '__call__'):
260+
service.close()
261+
self._services = None

firebase_admin/auth.py

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
import google.oauth2.id_token
2828
import six
2929

30-
import firebase_admin
3130
from firebase_admin import credentials
31+
from firebase_admin import utils
3232

3333
_auth_lock = threading.Lock()
3434

@@ -39,20 +39,6 @@
3939
GCLOUD_PROJECT_ENV_VAR = 'GCLOUD_PROJECT'
4040

4141

42-
def _get_initialized_app(app):
43-
if app is None:
44-
return firebase_admin.get_app()
45-
elif isinstance(app, firebase_admin.App):
46-
initialized_app = firebase_admin.get_app(app.name)
47-
if app is not initialized_app:
48-
raise ValueError('Illegal app argument. App instance not '
49-
'initialized via the firebase module.')
50-
return app
51-
else:
52-
raise ValueError('Illegal app argument. Argument must be of type '
53-
' firebase_admin.App, but given "{0}".'.format(type(app)))
54-
55-
5642
def _get_token_generator(app):
5743
"""Returns a _TokenGenerator instance for an App.
5844
@@ -69,11 +55,7 @@ def _get_token_generator(app):
6955
Raises:
7056
ValueError: If the app argument is invalid.
7157
"""
72-
app = _get_initialized_app(app)
73-
with _auth_lock:
74-
if not hasattr(app, _AUTH_ATTRIBUTE):
75-
setattr(app, _AUTH_ATTRIBUTE, _TokenGenerator(app))
76-
return getattr(app, _AUTH_ATTRIBUTE)
58+
return utils.get_app_service(app, _AUTH_ATTRIBUTE, _TokenGenerator)
7759

7860

7961
def create_custom_token(uid, developer_claims=None, app=None):

0 commit comments

Comments
 (0)
0