From 738ffd12dabc1ba5d28e1a4e831b185e69f26e35 Mon Sep 17 00:00:00 2001 From: Mike Koss Date: Fri, 9 Dec 2016 15:37:49 -0800 Subject: [PATCH] Adding emulator host and _api property to Firestore client. Also some cosmetic renames in values helper. --- firestore/google/cloud/firestore/_values.py | 14 ++++---- firestore/google/cloud/firestore/client.py | 39 ++++++++++++++++++++- firestore/google/cloud/firestore/path.py | 1 + firestore/unit_tests/test_client.py | 5 +++ firestore/unit_tests/test_path.py | 4 +-- 5 files changed, 53 insertions(+), 10 deletions(-) diff --git a/firestore/google/cloud/firestore/_values.py b/firestore/google/cloud/firestore/_values.py index 6896c1e292f7..c1580ac6e187 100644 --- a/firestore/google/cloud/firestore/_values.py +++ b/firestore/google/cloud/firestore/_values.py @@ -80,14 +80,14 @@ def encode_value(val): if isinstance(val, dict): try: - return Value(entity_value=Entity(properties=_encode_dict(val))) + return Value(entity_value=Entity(properties=encode_dict(val))) except DepthError: raise ValueError( 'Dictionary is deeper than %d levels or contains a cycle: %r' % (MAX_DEPTH, val)) raise TypeError('Cannot convert type (%s) to a Firestore Value.' % - type(val)) + (type(val),)) def decode_value(val): @@ -131,9 +131,9 @@ def decode_value(val): if value_type == 'entity_value': return document.Document(None, None, - _decode_dict(val.entity_value.properties)) + decode_dict(val.entity_value.properties)) - raise ValueError('Unknown Value type: %r' % value_type) + raise ValueError('Unknown Value type: %r' % (value_type,)) def _is_ascii(text): @@ -154,7 +154,7 @@ class DepthError(ValueError): pass -def _encode_dict(dict_input, depth=1): +def encode_dict(dict_input, depth=1): """Encode a dictionary of Python native values, into a dictionary of Firestore protobuf ``Values``. @@ -176,13 +176,13 @@ def _encode_dict(dict_input, depth=1): for name, value in six.iteritems(dict_input): if isinstance(value, dict): result[name] = Value( - entity_value=Entity(properties=_encode_dict(value, depth + 1))) + entity_value=Entity(properties=encode_dict(value, depth + 1))) else: result[name] = encode_value(value) return result -def _decode_dict(dict_input): +def decode_dict(dict_input): """Decode a dictionary of Firestore protobuf Values, into a dictionary of Python native values. diff --git a/firestore/google/cloud/firestore/client.py b/firestore/google/cloud/firestore/client.py index 5293a37c4cdd..d68b95c8aa3f 100644 --- a/firestore/google/cloud/firestore/client.py +++ b/firestore/google/cloud/firestore/client.py @@ -14,10 +14,16 @@ """Firestore database SDK Client.""" +import grpc + +from google.cloud._helpers import make_secure_channel +from google.cloud._http import DEFAULT_USER_AGENT from google.cloud.client import Client as _BaseClient from google.cloud.client import _ClientProjectMixin from google.cloud.credentials import get_credentials +from google.cloud.gapic.firestore.v1alpha1 import datastore_api + ALL_SCOPES = ('https://www.googleapis.com/auth/cloud-platform', 'https://www.googleapis.com/auth/datastore') @@ -35,9 +41,13 @@ class Client(_BaseClient, _ClientProjectMixin): :param credentials: (optional) The OAuth2 Credentials to use for this client. If not provided, defaults to the Google Application Default Credentials. + + :type emulator_host: str + :param emulator_host: (optional) Host name of local Firestore emulator + (e.g., 'localhost:12345'). """ - def __init__(self, project=None, credentials=None): + def __init__(self, project=None, credentials=None, emulator_host=None): if credentials is None: credentials = get_credentials() @@ -48,3 +58,30 @@ def __init__(self, project=None, credentials=None): _ClientProjectMixin.__init__(self, project=project) _BaseClient.__init__(self, credentials=credentials) + self.database_id = None + self._api = _make_firestore_api(self._connection, emulator_host) + + +def _make_firestore_api(connection, emulator_host): + """Create a Firestore gRPC helper library. + + :type connection: :class:`~google.cloud._http.Connection` + :param connection: Connection to Google Cloud Platform. + + :type emulator_host: str + :param emulator_host: (optional) Host name of local Firestore emulator + (e.g., 'localhost:12345'). + + :rtype: + :class:`google.cloud.gapic.firestore.v1alpha1.datastore_api.DatastoreApi` + :returns: A gRPC api helper class. + """ + if emulator_host is not None: + channel = grpc.insecure_channel(emulator_host) + else: + channel = make_secure_channel( + connection.credentials, + DEFAULT_USER_AGENT, + datastore_api.DatastoreApi.SERVICE_ADDRESS) + + return datastore_api.DatastoreApi(channel=channel) diff --git a/firestore/google/cloud/firestore/path.py b/firestore/google/cloud/firestore/path.py index 61696120bbb8..634cb1d1c8e7 100644 --- a/firestore/google/cloud/firestore/path.py +++ b/firestore/google/cloud/firestore/path.py @@ -126,6 +126,7 @@ def child(self, *parts): """ return Path(*(self._parts + parts)) + @property def parent(self): """Create a Path to the parent Document or Collection. diff --git a/firestore/unit_tests/test_client.py b/firestore/unit_tests/test_client.py index 9cdaac0bb050..4f11f9e9dc16 100644 --- a/firestore/unit_tests/test_client.py +++ b/firestore/unit_tests/test_client.py @@ -29,6 +29,11 @@ def test_constructor(self): client = self._make_one('my-project-id') self.assertEqual(client.project, 'my-project-id') + def test_constructor_emulator(self): + client = self._make_one('my-project-id', + emulator_host='localhost:12345') + self.assertEqual(client.project, 'my-project-id') + def test_empty_constructor(self): import mock diff --git a/firestore/unit_tests/test_path.py b/firestore/unit_tests/test_path.py index 889e8289e573..7474df50645d 100644 --- a/firestore/unit_tests/test_path.py +++ b/firestore/unit_tests/test_path.py @@ -76,8 +76,8 @@ def test_child(self): def test_parent(self): path1 = self._make_one('my-collection') path2 = self._make_one('my-collection', 'my-document') - self.assertEqual(path2.parent(), path1) - self.assertNotEqual(path1.parent(), path2) + self.assertEqual(path2.parent, path1) + self.assertNotEqual(path1.parent, path2) def test_kind(self): path1 = self._make_one('my-collection')