8000 Firestore API (#79) · Megabytemb/firebase-admin-python@0502423 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0502423

Browse files
authored
Firestore API (firebase#79)
* Firestore integration * Merged with master; Firestore module now using the project ID plumbed in via App * Correcting a mistyped error message * Added Firestore * Fixing some test and lint errors * Installing enum34 when running below Python 3.4
1 parent 3b6f727 commit 0502423

File tree

8 files changed

+227
-2
lines changed

8 files changed

+227
-2
lines changed

firebase_admin/credentials.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525

2626
_request = requests.Request()
2727
_scopes = [
28+
'https://www.googleapis.com/auth/cloud-platform',
29+
'https://www.googleapis.com/auth/datastore',
2830
'https://www.googleapis.com/auth/devstorage.read_write',
2931
'https://www.googleapis.com/auth/firebase',
3032
'https://www.googleapis.com/auth/identitytoolkit',

firebase_admin/firestore.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Copyright 2017 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 module.
16+
17+
This module contains utilities for accessing the Google Cloud Firestore databases associated with
18+
Firebase apps. This requires the google-cloud-firestore Python module.
19+
"""
20+
21+
try:
22+
from google.cloud import firestore # pylint: disable=import-error,no-name-in-module
23+
existing = globals().keys()
24+
for key, value in firestore.__dict__.items():
25+
if not key.startswith('_') and key not in existing:
26+
globals()[key] = value
27+
except ImportError:
28+
raise ImportError('Failed to import the Cloud Firestore library for Python. Make sure '
29+
'to install the "google-cloud-firestore" module.')
30+
31+
import six
32+
33+
from firebase_admin import _utils
34+
35+
36+
_FIRESTORE_ATTRIBUTE = '_firestore'
37+
38+
39+
def client(app=None):
40+
"""Returns a client that can be used to interact with Google Cloud Firestore.
41+
42+
Args:
43+
app: An App instance (optional).
44+
45+
Returns:
46+
google.cloud.firestore.Firestore: A Firestore database client.
47+
48+
Raises:
49+
ValueError: If a project ID is not specified either via options, credentials or
50+
environment variables, or if the specified project ID is not a valid string.
51+
"""
52+
fs_client = _utils.get_app_service(app, _FIRESTORE_ATTRIBUTE, _FirestoreClient.from_app)
53+
return fs_client.get()
54+
55+
56+
class _FirestoreClient(object):
57+
"""Holds a Google Cloud Firestore client instance."""
58+
59+
def __init__(self, credentials, project):
60+
self._client = firestore.Client(credentials=credentials, project=project)
61+
62+
def get(self):
63+
return self._client
64+
65+
@classmethod
66+
def from_app(cls, app):
67+
"""Creates a new _FirestoreClient for the specified app."""
68+
credentials = app.credential.get_credential()
69+
project = app.project_id
70+
if not project:
71+
raise ValueError(
72+
'Project ID is required to access Firestore. Either set the projectId option, '
73+
'or use service account credentials. Alternatively, set the GCLOUD_PROJECT '
74+
'environment variable.')
75+
elif not isinstance(project, six.string_types):
76+
raise ValueError(
77+
'Invalid project ID: "{0}". project ID must be a string.'.format(project))
78+
return _FirestoreClient(credentials, project)

firebase_admin/storage.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"""Firebase Cloud Storage module.
1616
1717
This module contains utilities for accessing Google Cloud Storage buckets associated with
18-
Firebase apps. This requires installing the google-cloud-storage Python module separately.
18+
Firebase apps. This requires the google-cloud-storage Python module.
1919
"""
2020

2121
# pylint: disable=import-error,no-name-in-module

integration/test_firestore.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Copyright 2017 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+
"""Integration tests for firebase_admin.firestore module."""
16+
import datetime
17+
import pytest
18+
19+
from google.cloud import exceptions # pylint: disable=import-error,no-name-in-module
20+
21+
from firebase_admin import firestore
22+
23+
24+
def test_firestore():
25+
client = firestore.client()
26+
expected = {
27+
'name': u'Mountain View',
28+
'country': u'USA',
29+
'population': 77846,
30+
'capital': False
31+
}
32+
doc = client.collection('cities').document()
33+
doc.set(expected)
34+
35+
data = doc.get().to_dict()
36+
assert data == expected
37+
38+
doc.delete()
39+
with pytest.raises(exceptions.NotFound):
40+
doc.get()
41+
42+
def test_server_timestamp():
43+
client = firestore.client()
44+
expected = {
45+
'name': u'Mountain View',
46+
'timestamp': firestore.SERVER_TIMESTAMP # pylint: disable=no-member
47+
}
48+
doc = client.collection('cities').document()
49+
doc.set(expected)
50+
51+
data = doc.get().to_dict()
52+
assert isinstance(data['timestamp'], datetime.datetime)
53+
doc.delete()

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ pytest-cov >= 2.4.0
44
tox >= 2.6.0
55

66
google-auth >= 1.1.0
7+
google-cloud-firestore >= 0.27.0
78
google-cloud-storage >= 1.2.0
89
requests >= 2.13.0
910
six >= 1.6.1
11+
enum34 >= 1.0.4; python_version < '3.4'

setup.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,16 @@
3333
'to integrate Firebase into their services and applications.')
3434
install_requires = [
3535
'google-auth>=1.1.0',
36-
'google-cloud-storage>=1.2.0',
36+
'google-cloud-firestore>=0.27.0',
37+
'google-cloud-storage>=1.2.0',
3738
'requests>=2.13.0',
3839
'six>=1.6.1'
3940
]
4041

42+
extras_require = {
43+
':python_version<"3.4"': ('enum34>=1.0.4',),
44+
}
45+
4146
version = firebase_admin.__version__
4247

4348
setup(
@@ -49,6 +54,7 @@
4954
author='Firebase',
5055
license='Apache License 2.0',
5156
keywords='firebase cloud development',
57+
extras_require=extras_require,
5258
install_requires=install_requires,
5359
packages=find_packages(exclude=['tests']),
5460
classifiers=[

tests/test_firestore.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Copyright 2017 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."""
16+
17+
import os
18+
19+
import pytest
20+
21+
import firebase_admin
22+
from firebase_admin import credentials
23+
from firebase_admin import firestore
24+
from tests import testutils
25+
26+
27+
def teardown_function():
28+
testutils.cleanup_apps()
29+
30+
def test_no_project_id():
31+
env_var = 'GCLOUD_PROJECT'
32+
gcloud_project = os.environ.get(env_var)
33+
if gcloud_project:
34+
del os.environ[env_var]
35+
try:
36+
firebase_admin.initialize_app(testutils.MockCredential())
37+
with pytest.raises(ValueError):
38+
firestore.client()
39+
finally:
40+
if gcloud_project:
41+
os.environ[env_var] = gcloud_project
42+
43+
def test_project_id():
44+
cred = credentials.Certificate(testutils.resource_filename('service_account.json'))
45+
firebase_admin.initialize_app(cred, {'projectId': 'explicit-project-id'})
46+
client = firestore.client()
47+
assert client is not None
48+
assert client.project == 'explicit-project-id'
49+
50+
def test_project_id_with_explicit_app():
51+
cred = credentials.Certificate(testutils.resource_filename('service_account.json'))
52+
app = firebase_admin.initialize_app(cred, {'projectId': 'explicit-project-id'})
53+
client = firestore.client(app=app)
54+
assert client is not None
55+
assert client.project == 'explicit-project-id'
56+
57+
def test_service_account():
58+
cred = credentials.Certificate(testutils.resource_filename('service_account.json'))
59+
firebase_admin.initialize_app(cred)
60+
client = firestore.client()
61+
assert client is not None
62+
assert client.project == 'mock-project-id'
63+
64+
def test_service_account_with_explicit_app():
65+
cred = credentials.Certificate(testutils.resource_filename('service_account.json'))
66+
app = firebase_admin.initialize_app(cred)
67+
client = firestore.client(app=app)
68+
assert client is not None
69+
assert client.project == 'mock-project-id'
70+
71+
def test_geo_point():
72+
geo_point = firestore.GeoPoint(10, 20) # pylint: disable=no-member
73+
assert geo_point.latitude == 10
74+
assert geo_point.longitude == 20
75+
76+
def test_server_timestamp():
77+
assert firestore.SERVER_TIMESTAMP is not None # pylint: disable=no-member

tests/test_storage.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,10 @@ def test_valid_name():
4343
bucket = storage.bucket('foo')
4444
assert bucket is not None
4545
assert bucket.name == 'foo'
46+
47+
def test_valid_name_with_explicit_app():
48+
# Should not make RPC calls.
49+
app = firebase_admin.get_app()
50+
bucket = storage.bucket('foo', app=app)
51+
assert bucket is not None
52+
assert bucket.name == 'foo'

0 commit comments

Comments
 (0)
0