From 78606e3daa9997f565f063c00255f7f749bec533 Mon Sep 17 00:00:00 2001 From: jkyle109 Date: Tue, 5 Jul 2022 14:44:21 -0400 Subject: [PATCH 1/5] feat(firestore): Expose Async Firestore Client. --- firebase_admin/firestore_async.py | 76 +++++++++++++++++++++++++++++ tests/test_firestore_async.py | 81 +++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 firebase_admin/firestore_async.py create mode 100644 tests/test_firestore_async.py diff --git a/firebase_admin/firestore_async.py b/firebase_admin/firestore_async.py new file mode 100644 index 000000000..d6841a1d5 --- /dev/null +++ b/firebase_admin/firestore_async.py @@ -0,0 +1,76 @@ +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Cloud Firestore Async module. + +This module contains utilities for accessing the Google Cloud Firestore databases associated with +Firebase apps. This requires the ``google-cloud-firestore`` Python module. +""" + +try: + from google.cloud import firestore # pylint: disable=import-error,no-name-in-module + existing = globals().keys() + for key, value in firestore.__dict__.items(): + if not key.startswith('_') and key not in existing: + globals()[key] = value +except ImportError: + raise ImportError('Failed to import the Cloud Firestore library for Python. Make sure ' + 'to install the "google-cloud-firestore" module.') + +from firebase_admin import _utils + + +_FIRESTORE_ASYNC_ATTRIBUTE = '_firestore_async' + + +def client(app=None): + """Returns a client that can be used to interact with Google Cloud Firestore. + + Args: + app: An App instance (optional). + + Returns: + google.cloud.firestore.Firestore_Async: A `Firestore Async Client`_. + + Raises: + ValueError: If a project ID is not specified either via options, credentials or + environment variables, or if the specified project ID is not a valid string. + + .. _Firestore Async Client: https://googleapis.dev/python/firestore/latest/client.html + """ + fs_client = _utils.get_app_service( + app, _FIRESTORE_ASYNC_ATTRIBUTE, _FirestoreAsyncClient.from_app) + return fs_client.get() + + +class _FirestoreAsyncClient: + """Holds a Google Cloud Firestore Async Client instance.""" + + def __init__(self, credentials, project): + self._client = firestore.AsyncClient(credentials=credentials, project=project) + + def get(self): + return self._client + + @classmethod + def from_app(cls, app): + """Creates a new _FirestoreAsyncClient for the specified app.""" + credentials = app.credential.get_credential() + project = app.project_id + if not project: + raise ValueError( + 'Project ID is required to access Firestore. Either set the projectId option, ' + 'or use service account credentials. Alternatively, set the GOOGLE_CLOUD_PROJECT ' + 'environment variable.') + return _FirestoreAsyncClient(credentials, project) diff --git a/tests/test_firestore_async.py b/tests/test_firestore_async.py new file mode 100644 index 000000000..7b4c2b0af --- /dev/null +++ b/tests/test_firestore_async.py @@ -0,0 +1,81 @@ +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for firebase_admin.firestore_async.""" + +import platform + +import pytest + +import firebase_admin +from firebase_admin import credentials +try: + from firebase_admin import firestore_async +except ImportError: + pass +from tests import testutils + + +@pytest.mark.skipif( + platform.python_implementation() == 'PyPy', + reason='Firestore is not supported on PyPy') +class TestFirestoreAsync: + """Test class Firestore Async APIs.""" + + def teardown_method(self, method): + del method + testutils.cleanup_apps() + + def test_no_project_id(self): + def evaluate(): + firebase_admin.initialize_app(testutils.MockCredential()) + with pytest.raises(ValueError): + firestore_async.client() + testutils.run_without_project_id(evaluate) + + def test_project_id(self): + cred = credentials.Certificate(testutils.resource_filename('service_account.json')) + firebase_admin.initialize_app(cred, {'projectId': 'explicit-project-id'}) + client = firestore_async.client() + assert client is not None + assert client.project == 'explicit-project-id' + + def test_project_id_with_explicit_app(self): + cred = credentials.Certificate(testutils.resource_filename('service_account.json')) + app = firebase_admin.initialize_app(cred, {'projectId': 'explicit-project-id'}) + client = firestore_async.client(app=app) + assert client is not None + assert client.project == 'explicit-project-id' + + def test_service_account(self): + cred = credentials.Certificate(testutils.resource_filename('service_account.json')) + firebase_admin.initialize_app(cred) + client = firestore_async.client() + assert client is not None + assert client.project == 'mock-project-id' + + def test_service_account_with_explicit_app(self): + cred = credentials.Certificate(testutils.resource_filename('service_account.json')) + app = firebase_admin.initialize_app(cred) + client = firestore_async.client(app=app) + assert client is not None + assert client.project == 'mock-project-id' + + def test_geo_point(self): + geo_point = firestore_async.GeoPoint(10, 20) # pylint: disable=no-member + assert geo_point.latitude == 10 + assert geo_point.longitude == 20 + + def test_server_timestamp(self): + assert firestore_async.SERVER_TIMESTAMP is not None # pylint: disable=no-member From fe565e862dab668afaf3f4ba7d21e929dd742924 Mon Sep 17 00:00:00 2001 From: jkyle109 Date: Mon, 11 Jul 2022 18:03:19 -0400 Subject: [PATCH 2/5] fix: Added type hints and defintion wording changes --- firebase_admin/firestore_async.py | 30 ++++++++++++++++++------------ tests/test_firestore_async.py | 2 +- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/firebase_admin/firestore_async.py b/firebase_admin/firestore_async.py index d6841a1d5..c30a3a8f6 100644 --- a/firebase_admin/firestore_async.py +++ b/firebase_admin/firestore_async.py @@ -1,4 +1,4 @@ -# Copyright 2017 Google Inc. +# Copyright 2022 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,12 +14,21 @@ """Cloud Firestore Async module. -This module contains utilities for accessing the Google Cloud Firestore databases associated with -Firebase apps. This requires the ``google-cloud-firestore`` Python module. +This module contains utilities for asynchronusly accessing the Google Cloud Firestore databases +associated with Firebase apps. This requires the ``google-cloud-firestore`` Python module. """ +from __future__ import annotations +from typing import Type + +from firebase_admin import ( + App, + _utils, +) +from firebase_admin.credentials import Base + try: - from google.cloud import firestore # pylint: disable=import-error,no-name-in-module + from google.cloud import firestore # type: ignore # pylint: disable=import-error,no-name-in-module existing = globals().keys() for key, value in firestore.__dict__.items(): if not key.startswith('_') and key not in existing: @@ -28,14 +37,11 @@ raise ImportError('Failed to import the Cloud Firestore library for Python. Make sure ' 'to install the "google-cloud-firestore" module.') -from firebase_admin import _utils - - -_FIRESTORE_ASYNC_ATTRIBUTE = '_firestore_async' +_FIRESTORE_ASYNC_ATTRIBUTE: str = '_firestore_async' -def client(app=None): - """Returns a client that can be used to interact with Google Cloud Firestore. +def client(app: App = None) -> firestore.AsyncClient: + """Returns an async client that can be used to interact with Google Cloud Firestore. Args: app: An App instance (optional). @@ -57,14 +63,14 @@ def client(app=None): class _FirestoreAsyncClient: """Holds a Google Cloud Firestore Async Client instance.""" - def __init__(self, credentials, project): + def __init__(self, credentials: Type[Base], project: str) -> None: self._client = firestore.AsyncClient(credentials=credentials, project=project) def get(self): return self._client @classmethod - def from_app(cls, app): + def from_app(cls, app: App) -> _FirestoreAsyncClient: """Creates a new _FirestoreAsyncClient for the specified app.""" credentials = app.credential.get_credential() project = app.project_id diff --git a/tests/test_firestore_async.py b/tests/test_firestore_async.py index 7b4c2b0af..0fb17c813 100644 --- a/tests/test_firestore_async.py +++ b/tests/test_firestore_async.py @@ -1,4 +1,4 @@ -# Copyright 2017 Google Inc. +# Copyright 2022 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From a1c783872d98322be1a540777c183b5a1f6a36b2 Mon Sep 17 00:00:00 2001 From: jkyle109 Date: Tue, 12 Jul 2022 12:07:08 -0400 Subject: [PATCH 3/5] fix: removed future annotations until Python 3.6 is depreciated. --- firebase_admin/firestore_async.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/firebase_admin/firestore_async.py b/firebase_admin/firestore_async.py index c30a3a8f6..0cf98dab4 100644 --- a/firebase_admin/firestore_async.py +++ b/firebase_admin/firestore_async.py @@ -18,7 +18,6 @@ associated with Firebase apps. This requires the ``google-cloud-firestore`` Python module. """ -from __future__ import annotations from typing import Type from firebase_admin import ( @@ -70,7 +69,7 @@ def get(self): return self._client @classmethod - def from_app(cls, app: App) -> _FirestoreAsyncClient: + def from_app(cls, app: App) -> "_FirestoreAsyncClient": """Creates a new _FirestoreAsyncClient for the specified app.""" credentials = app.credential.get_credential() project = app.project_id From bad8e6d5186a05e19ae9c560afa739df66794ab9 Mon Sep 17 00:00:00 2001 From: jkyle109 Date: Tue, 12 Jul 2022 15:25:48 -0400 Subject: [PATCH 4/5] fix: added missed type and clarifying comment for Python 3.6 type hinting. --- firebase_admin/firestore_async.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/firebase_admin/firestore_async.py b/firebase_admin/firestore_async.py index 0cf98dab4..ccefbcf42 100644 --- a/firebase_admin/firestore_async.py +++ b/firebase_admin/firestore_async.py @@ -65,11 +65,12 @@ class _FirestoreAsyncClient: def __init__(self, credentials: Type[Base], project: str) -> None: self._client = firestore.AsyncClient(credentials=credentials, project=project) - def get(self): + def get(self) -> firestore.AsyncClient: return self._client @classmethod def from_app(cls, app: App) -> "_FirestoreAsyncClient": + # Replace remove future reference quotes by importing annotations in Python 3.7+ (b/238779406) """Creates a new _FirestoreAsyncClient for the specified app.""" credentials = app.credential.get_credential() project = app.project_id From 0ca6bc743e213938227c783e2cda30eafdb2b84e Mon Sep 17 00:00:00 2001 From: jkyle109 Date: Tue, 12 Jul 2022 16:00:08 -0400 Subject: [PATCH 5/5] fix: lint --- firebase_admin/firestore_async.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase_admin/firestore_async.py b/firebase_admin/firestore_async.py index ccefbcf42..a63d5a761 100644 --- a/firebase_admin/firestore_async.py +++ b/firebase_admin/firestore_async.py @@ -70,7 +70,7 @@ def get(self) -> firestore.AsyncClient: @classmethod def from_app(cls, app: App) -> "_FirestoreAsyncClient": - # Replace remove future reference quotes by importing annotations in Python 3.7+ (b/238779406) + # Replace remove future reference quotes by importing annotations in Python 3.7+ b/238779406 """Creates a new _FirestoreAsyncClient for the specified app.""" credentials = app.credential.get_credential() project = app.project_id