From 30684734106f345312f249c9ac86f700dd79c1e1 Mon Sep 17 00:00:00 2001 From: Danny Hermes Date: Thu, 25 Jun 2015 18:16:52 -0700 Subject: [PATCH] Adding base client class in core. This is pre-work in #952, but came up in #933 as well. --- gcloud/client.py | 104 +++++++++++++++++++++++++++++++++++ gcloud/connection.py | 20 +++++-- gcloud/pubsub/client.py | 57 +------------------ gcloud/pubsub/test_client.py | 49 ----------------- gcloud/test_client.py | 83 ++++++++++++++++++++++++++++ 5 files changed, 205 insertions(+), 108 deletions(-) create mode 100644 gcloud/client.py create mode 100644 gcloud/test_client.py diff --git a/gcloud/client.py b/gcloud/client.py new file mode 100644 index 000000000000..eefa74a352b8 --- /dev/null +++ b/gcloud/client.py @@ -0,0 +1,104 @@ +# Copyright 2015 Google Inc. All rights reserved. +# +# 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. + +"""gcloud client base class for interacting with API.""" + + +from gcloud.credentials import get_for_service_account_json +from gcloud.credentials import get_for_service_account_p12 + + +class Client(object): + """Client to bundle configuration needed for API requests. + + :type credentials: :class:`oauth2client.client.OAuth2Credentials` or + :class:`NoneType` + :param credentials: The OAuth2 Credentials to use for the connection + owned by this client. If not passed (and if no ``http`` + object is passed), falls back to the default inferred + from the environment. + + :type http: :class:`httplib2.Http` or class that defines ``request()``. + :param http: An optional HTTP object to make requests. If not passed, an + ``http`` object is created that is bound to the + ``credentials`` for the current object. + """ + + def __init__(self, credentials=None, http=None): + self.credentials = credentials + self.http = http + + @classmethod + def from_service_account_json(cls, json_credentials_path, *args, **kwargs): + """Factory to retrieve JSON credentials while creating client. + + :type json_credentials_path: string + :param json_credentials_path: The path to a private key file (this file + was given to you when you created the + service account). This file must contain + a JSON object with a private key and + other credentials information (downloaded + from the Google APIs console). + + :type args: tuple + :param args: Remaining positional arguments to pass to constructor. + + :type kwargs: dictionary + :param kwargs: Remaining keyword arguments to pass to constructor. + + :rtype: :class:`gcloud.pubsub.client.Client` + :returns: The client created with the retrieved JSON credentials. + :raises: class:`TypeError` if there is a conflict with the kwargs + and the credentials created by the factory. + """ + if 'credentials' in kwargs: + raise TypeError('credentials must not be in keyword arguments') + credentials = get_for_service_account_json(json_credentials_path) + kwargs['credentials'] = credentials + return cls(*args, **kwargs) + + @classmethod + def from_service_account_p12(cls, client_email, private_key_path, + *args, **kwargs): + """Factory to retrieve P12 credentials while creating client. + + .. note:: + Unless you have an explicit reason to use a PKCS12 key for your + service account, we recommend using a JSON key. + + :type client_email: string + :param client_email: The e-mail attached to the service account. + + :type private_key_path: string + :param private_key_path: The path to a private key file (this file was + given to you when you created the service + account). This file must be in P12 format. + + :type args: tuple + :param args: Remaining positional arguments to pass to constructor. + + :type kwargs: dictionary + :param kwargs: Remaining keyword arguments to pass to constructor. + + :rtype: :class:`gcloud.client.Client` + :returns: The client created with the retrieved P12 credentials. + :raises: class:`TypeError` if there is a conflict with the kwargs + and the credentials created by the factory. + """ + if 'credentials' in kwargs: + raise TypeError('credentials must not be in keyword arguments') + credentials = get_for_service_account_p12(client_email, + private_key_path) + kwargs['credentials'] = credentials + return cls(*args, **kwargs) diff --git a/gcloud/connection.py b/gcloud/connection.py index bc6505d45519..d61df0458b26 100644 --- a/gcloud/connection.py +++ b/gcloud/connection.py @@ -135,10 +135,12 @@ def from_service_account_json(cls, json_credentials_path, *args, **kwargs): :rtype: :class:`gcloud.connection.Connection` :returns: The connection created with the retrieved JSON credentials. + :raises: class:`TypeError` if there is a conflict with the kwargs + and the credentials created by the factory. """ - credentials = get_for_service_account_json(json_credentials_path) if 'credentials' in kwargs: raise TypeError('credentials must not be in keyword arguments') + credentials = get_for_service_account_json(json_credentials_path) kwargs['credentials'] = credentials return cls(*args, **kwargs) @@ -167,11 +169,13 @@ def from_service_account_p12(cls, client_email, private_key_path, :rtype: :class:`gcloud.connection.Connection` :returns: The connection created with the retrieved P12 credentials. + :raises: class:`TypeError` if there is a conflict with the kwargs + and the credentials created by the factory. """ - credentials = get_for_service_account_p12(client_email, - private_key_path) if 'credentials' in kwargs: raise TypeError('credentials must not be in keyword arguments') + credentials = get_for_service_account_p12(client_email, + private_key_path) kwargs['credentials'] = credentials return cls(*args, **kwargs) @@ -179,13 +183,21 @@ def from_service_account_p12(cls, client_email, private_key_path, def from_environment(cls, *args, **kwargs): """Factory to retrieve implicit credentials while creating connection. + :type args: tuple + :param args: Remaining positional arguments to pass to constructor. + + :type kwargs: dictionary + :param kwargs: Remaining keyword arguments to pass to constructor. + :rtype: :class:`gcloud.connection.Connection` :returns: The connection created with the retrieved implicit credentials. + :raises: class:`TypeError` if there is a conflict with the kwargs + and the credentials created by the factory. """ - credentials = get_credentials() if 'credentials' in kwargs: raise TypeError('credentials must not be in keyword arguments') + credentials = get_credentials() kwargs['credentials'] = credentials return cls(*args, **kwargs) diff --git a/gcloud/pubsub/client.py b/gcloud/pubsub/client.py index cca81da176c6..8e934e1d42c6 100644 --- a/gcloud/pubsub/client.py +++ b/gcloud/pubsub/client.py @@ -16,15 +16,14 @@ from gcloud._helpers import _get_production_project +from gcloud.client import Client as BaseClient from gcloud.credentials import get_credentials -from gcloud.credentials import get_for_service_account_json -from gcloud.credentials import get_for_service_account_p12 from gcloud.pubsub.connection import Connection from gcloud.pubsub.subscription import Subscription from gcloud.pubsub.topic import Topic -class Client(object): +class Client(BaseClient): """Client to bundle configuration needed for API requests. :type project: string @@ -59,58 +58,6 @@ def __init__(self, project=None, credentials=None, http=None): credentials = get_credentials() self.connection = Connection(credentials=credentials, http=http) - @classmethod - def from_service_account_json(cls, json_credentials_path, project=None): - """Factory to retrieve JSON credentials while creating client. - - :type json_credentials_path: string - :param json_credentials_path: The path to a private key file (this file - was given to you when you created the - service account). This file must contain - a JSON object with a private key and - other credentials information (downloaded - from the Google APIs console). - - :type project: string - :param project: the project which the client acts on behalf of. Will be - passed when creating a topic. If not passed, falls - back to the default inferred from the environment. - - :rtype: :class:`gcloud.pubsub.client.Client` - :returns: The client created with the retrieved JSON credentials. - """ - credentials = get_for_service_account_json(json_credentials_path) - return cls(project=project, credentials=credentials) - - @classmethod - def from_service_account_p12(cls, client_email, private_key_path, - project=None): - """Factory to retrieve P12 credentials while creating client. - - .. note:: - Unless you have an explicit reason to use a PKCS12 key for your - service account, we recommend using a JSON key. - - :type client_email: string - :param client_email: The e-mail attached to the service account. - - :type private_key_path: string - :param private_key_path: The path to a private key file (this file was - given to you when you created the service - account). This file must be in P12 format. - - :type project: string - :param project: the project which the client acts on behalf of. Will be - passed when creating a topic. If not passed, falls - back to the default inferred from the environment. - - :rtype: :class:`gcloud.pubsub.client.Client` - :returns: The client created with the retrieved P12 credentials. - """ - credentials = get_for_service_account_p12(client_email, - private_key_path) - return cls(project=project, credentials=credentials) - def list_topics(self, page_size=None, page_token=None): """List topics for the project associated with this client. diff --git a/gcloud/pubsub/test_client.py b/gcloud/pubsub/test_client.py index f0f1d30a81be..d3d0cf45e71d 100644 --- a/gcloud/pubsub/test_client.py +++ b/gcloud/pubsub/test_client.py @@ -82,55 +82,6 @@ def test_ctor_explicit(self): self.assertTrue(client_obj.connection._credentials is CREDS) self.assertEqual(CREDS._scopes, SCOPE) - def test_from_service_account_json(self): - from gcloud._testing import _Monkey - from gcloud.pubsub import client - from gcloud.pubsub.connection import Connection - - PROJECT = 'PROJECT' - KLASS = self._getTargetClass() - CREDS = _Credentials() - _CALLED = [] - - def mock_creds(arg1): - _CALLED.append((arg1,)) - return CREDS - - BOGUS_ARG = object() - with _Monkey(client, get_for_service_account_json=mock_creds): - client_obj = KLASS.from_service_account_json( - BOGUS_ARG, project=PROJECT) - - self.assertEqual(client_obj.project, PROJECT) - self.assertTrue(isinstance(client_obj.connection, Connection)) - self.assertTrue(client_obj.connection._credentials is CREDS) - self.assertEqual(_CALLED, [(BOGUS_ARG,)]) - - def test_from_service_account_p12(self): - from gcloud._testing import _Monkey - from gcloud.pubsub import client - from gcloud.pubsub.connection import Connection - - PROJECT = 'PROJECT' - KLASS = self._getTargetClass() - CREDS = _Credentials() - _CALLED = [] - - def mock_creds(arg1, arg2): - _CALLED.append((arg1, arg2)) - return CREDS - - BOGUS_ARG1 = object() - BOGUS_ARG2 = object() - with _Monkey(client, get_for_service_account_p12=mock_creds): - client_obj = KLASS.from_service_account_p12( - BOGUS_ARG1, BOGUS_ARG2, project=PROJECT) - - self.assertEqual(client_obj.project, PROJECT) - self.assertTrue(isinstance(client_obj.connection, Connection)) - self.assertTrue(client_obj.connection._credentials is CREDS) - self.assertEqual(_CALLED, [(BOGUS_ARG1, BOGUS_ARG2)]) - def test_list_topics_no_paging(self): from gcloud.pubsub.topic import Topic PROJECT = 'PROJECT' diff --git a/gcloud/test_client.py b/gcloud/test_client.py new file mode 100644 index 000000000000..34fbe0614ce8 --- /dev/null +++ b/gcloud/test_client.py @@ -0,0 +1,83 @@ +# Copyright 2015 Google Inc. All rights reserved. +# +# 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. + +import unittest2 + + +class TestClient(unittest2.TestCase): + + def _getTargetClass(self): + from gcloud.client import Client + return Client + + def _makeOne(self, *args, **kw): + return self._getTargetClass()(*args, **kw) + + def test_ctor_explict(self): + CREDENTIALS = object() + HTTP = object() + CLIENT_OBJ = self._makeOne(credentials=CREDENTIALS, http=HTTP) + self.assertTrue(CLIENT_OBJ.credentials is CREDENTIALS) + self.assertTrue(CLIENT_OBJ.http is HTTP) + + def test_from_service_account_json(self): + from gcloud._testing import _Monkey + from gcloud import client + + KLASS = self._getTargetClass() + CREDENTIALS = object() + _CALLED = [] + + def mock_creds(arg1): + _CALLED.append((arg1,)) + return CREDENTIALS + + BOGUS_ARG = object() + with _Monkey(client, get_for_service_account_json=mock_creds): + CLIENT_OBJ = KLASS.from_service_account_json(BOGUS_ARG) + + self.assertTrue(CLIENT_OBJ.credentials is CREDENTIALS) + self.assertEqual(_CALLED, [(BOGUS_ARG,)]) + + def test_from_service_account_json_fail(self): + KLASS = self._getTargetClass() + CREDENTIALS = object() + self.assertRaises(TypeError, KLASS.from_service_account_json, None, + credentials=CREDENTIALS) + + def test_from_service_account_p12(self): + from gcloud._testing import _Monkey + from gcloud import client + + KLASS = self._getTargetClass() + CREDENTIALS = object() + _CALLED = [] + + def mock_creds(arg1, arg2): + _CALLED.append((arg1, arg2)) + return CREDENTIALS + + BOGUS_ARG1 = object() + BOGUS_ARG2 = object() + with _Monkey(client, get_for_service_account_p12=mock_creds): + CLIENT_OBJ = KLASS.from_service_account_p12(BOGUS_ARG1, BOGUS_ARG2) + + self.assertTrue(CLIENT_OBJ.credentials is CREDENTIALS) + self.assertEqual(_CALLED, [(BOGUS_ARG1, BOGUS_ARG2)]) + + def test_from_service_account_p12_fail(self): + KLASS = self._getTargetClass() + CREDENTIALS = object() + self.assertRaises(TypeError, KLASS.from_service_account_p12, None, + None, credentials=CREDENTIALS)