Skip to content

Commit 1536e33

Browse files
authored
feat(firestore): Expose Async Firestore Client. (#621)
* feat(firestore): Expose Async Firestore Client. * fix: Added type hints and defintion wording changes * fix: removed future annotations until Python 3.6 is depreciated. * fix: added missed type and clarifying comment for Python 3.6 type hinting. * fix: lint
1 parent 6d826fd commit 1536e33

File tree

2 files changed

+163
-0
lines changed

2 files changed

+163
-0
lines changed

firebase_admin/firestore_async.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Copyright 2022 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Cloud Firestore Async module.
16+
17+
This module contains utilities for asynchronusly accessing the Google Cloud Firestore databases
18+
associated with Firebase apps. This requires the ``google-cloud-firestore`` Python module.
19+
"""
20+
21+
from typing import Type
22+
23+
from firebase_admin import (
24+
App,
25+
_utils,
26+
)
27+
from firebase_admin.credentials import Base
28+
29+
try:
30+
from google.cloud import firestore # type: ignore # pylint: disable=import-error,no-name-in-module
31+
existing = globals().keys()
32+
for key, value in firestore.__dict__.items():
33+
if not key.startswith('_') and key not in existing:
34+
globals()[key] = value
35+
except ImportError:
36+
raise ImportError('Failed to import the Cloud Firestore library for Python. Make sure '
37+
'to install the "google-cloud-firestore" module.')
38+
39+
_FIRESTORE_ASYNC_ATTRIBUTE: str = '_firestore_async'
40+
41+
42+
def client(app: App = None) -> firestore.AsyncClient:
43+
"""Returns an async client that can be used to interact with Google Cloud Firestore.
44+
45+
Args:
46+
app: An App instance (optional).
47+
48+
Returns:
49+
google.cloud.firestore.Firestore_Async: A `Firestore Async Client`_.
50+
51+
Raises:
52+
ValueError: If a project ID is not specified either via options, credentials or
53+
environment variables, or if the specified project ID is not a valid string.
54+
55+
.. _Firestore Async Client: https://googleapis.dev/python/firestore/latest/client.html
56+
"""
57+
fs_client = _utils.get_app_service(
58+
app, _FIRESTORE_ASYNC_ATTRIBUTE, _FirestoreAsyncClient.from_app)
59+
return fs_client.get()
60+
61+
62+
class _FirestoreAsyncClient:
63+
"""Holds a Google Cloud Firestore Async Client instance."""
64+
65+
def __init__(self, credentials: Type[Base], project: str) -> None:
66+
self._client = firestore.AsyncClient(credentials=credentials, project=project)
67+
68+
def get(self) -> firestore.AsyncClient:
69+
return self._client
70+
71+
@classmethod
72+
def from_app(cls, app: App) -> "_FirestoreAsyncClient":
73+
# Replace remove future reference quotes by importing annotations in Python 3.7+ b/238779406
74+
"""Creates a new _FirestoreAsyncClient for the specified app."""
75+
credentials = app.credential.get_credential()
76+
project = app.project_id
77+
if not project:
78+
raise ValueError(
79+
'Project ID is required to access Firestore. Either set the projectId option, '
80+
'or use service account credentials. Alternatively, set the GOOGLE_CLOUD_PROJECT '
81+
'environment variable.')
82+
return _FirestoreAsyncClient(credentials, project)

tests/test_firestore_async.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Copyright 2022 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Tests for firebase_admin.firestore_async."""
16+
17+
import platform
18+
19+
import pytest
20+
21+
import firebase_admin
22+
from firebase_admin import credentials
23+
try:
24+
from firebase_admin import firestore_async
25+
except ImportError:
26+
pass
27+
from tests import testutils
28+
29+
30+
@pytest.mark.skipif(
31+
platform.python_implementation() == 'PyPy',
32+
reason='Firestore is not supported on PyPy')
33+
class TestFirestoreAsync:
34+
"""Test class Firestore Async APIs."""
35+
36+
def teardown_method(self, method):
37+
del method
38+
testutils.cleanup_apps()
39+
40+
def test_no_project_id(self):
41+
def evaluate():
42+
firebase_admin.initialize_app(testutils.MockCredential())
43+
with pytest.raises(ValueError):
44+
firestore_async.client()
45+
testutils.run_without_project_id(evaluate)
46+
47+
def test_project_id(self):
48+
cred = credentials.Certificate(testutils.resource_filename('service_account.json'))
49+
firebase_admin.initialize_app(cred, {'projectId': 'explicit-project-id'})
50+
client = firestore_async.client()
51+
assert client is not None
52+
assert client.project == 'explicit-project-id'
53+
54+
def test_project_id_with_explicit_app(self):
55+
cred = credentials.Certificate(testutils.resource_filename('service_account.json'))
56+
app = firebase_admin.initialize_app(cred, {'projectId': 'explicit-project-id'})
57+
client = firestore_async.client(app=app)
58+
assert client is not None
59+
assert client.project == 'explicit-project-id'
60+
61+
def test_service_account(self):
62+
cred = credentials.Certificate(testutils.resource_filename('service_account.json'))
63+
firebase_admin.initialize_app(cred)
64+
client = firestore_async.client()
65+
assert client is not None
66+
assert client.project == 'mock-project-id'
67+
68+
def test_service_account_with_explicit_app(self):
69+
cred = credentials.Certificate(testutils.resource_filename('service_account.json'))
70+
app = firebase_admin.initialize_app(cred)
71+
client = firestore_async.client(app=app)
72+
assert client is not None
73+
assert client.project == 'mock-project-id'
74+
75+
def test_geo_point(self):
76+
geo_point = firestore_async.GeoPoint(10, 20) # pylint: disable=no-member
77+
assert geo_point.latitude == 10
78+
assert geo_point.longitude == 20
79+
80+
def test_server_timestamp(self):
81+
assert firestore_async.SERVER_TIMESTAMP is not None # pylint: disable=no-member

0 commit comments

Comments
 (0)