diff --git a/gcloud/pubsub/__init__.py b/gcloud/pubsub/__init__.py index 70df328f0987..fb9965ac8b90 100644 --- a/gcloud/pubsub/__init__.py +++ b/gcloud/pubsub/__init__.py @@ -12,4 +12,66 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""GCloud Pubsub API wrapper.""" +"""GCloud Pubsub API wrapper. + + +The main concepts with this API are: + +- :class:`gcloud.pubsub.topic.Topic` represents an endpoint to which messages + can be published using the Cloud Storage Pubsub API. + +- :class:`gcloud.pubsub.subscription.Subscription` represents a named + subscription (either pull or push) to a topic. +""" + +from gcloud._helpers import get_default_project +from gcloud._helpers import set_default_project +from gcloud.connection import get_scoped_connection +from gcloud.pubsub import _implicit_environ +from gcloud.pubsub._implicit_environ import get_default_connection +from gcloud.pubsub.connection import Connection + + +SCOPE = ('https://www.googleapis.com/auth/pubsub', + 'https://www.googleapis.com/auth/cloud-platform') + + +def set_default_connection(connection=None): + """Set default connection either explicitly or implicitly as fall-back. + + :type connection: :class:`gcloud.pubsub.connection.Connection` + :param connection: A connection provided to be the default. + """ + _implicit_environ._DEFAULTS.connection = connection or get_connection() + + +def set_defaults(project=None, connection=None): + """Set defaults either explicitly or implicitly as fall-back. + + Uses the arguments to call the individual default methods. + + :type project: string + :param project: Optional. The name of the project to connect to. + + :type connection: :class:`gcloud.pubsub.connection.Connection` + :param connection: Optional. A connection provided to be the default. + """ + set_default_project(project=project) + set_default_connection(connection=connection) + + +def get_connection(): + """Shortcut method to establish a connection to Cloud Storage. + + Use this if you are going to access several buckets with the same + set of credentials: + + >>> from gcloud import pubsub + >>> connection = pubsub.get_connection() + >>> bucket1 = pubsub.get_bucket('bucket1', connection=connection) + >>> bucket2 = pubsub.get_bucket('bucket2', connection=connection) + + :rtype: :class:`gcloud.pubsub.connection.Connection` + :returns: A connection defined with the proper credentials. + """ + return get_scoped_connection(Connection, SCOPE) diff --git a/gcloud/pubsub/_implicit_environ.py b/gcloud/pubsub/_implicit_environ.py new file mode 100644 index 000000000000..649b6f6528b8 --- /dev/null +++ b/gcloud/pubsub/_implicit_environ.py @@ -0,0 +1,41 @@ +# 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. + +"""Module to provide implicit behavior based on enviroment. + +Allows the pubsub package to infer the default connection from the enviroment. +""" + + +class _DefaultsContainer(object): + """Container for defaults. + + :type connection: :class:`gcloud.pubsub.connection.Connection` + :param connection: Persistent implied connection from environment. + """ + + def __init__(self, connection=None): + self.connection = connection + + +def get_default_connection(): + """Get default connection. + + :rtype: :class:`gcloud.pubsub.connection.Connection` or ``NoneType`` + :returns: The default connection if one has been set. + """ + return _DEFAULTS.connection + + +_DEFAULTS = _DefaultsContainer() diff --git a/gcloud/pubsub/_testing.py b/gcloud/pubsub/_testing.py new file mode 100644 index 000000000000..26a69ec95a3a --- /dev/null +++ b/gcloud/pubsub/_testing.py @@ -0,0 +1,33 @@ +# Copyright 2014 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. + +"""Shared pubsub testing utilities.""" + +from gcloud._testing import _Monkey +from gcloud.pubsub import _implicit_environ +from gcloud.pubsub._implicit_environ import _DefaultsContainer + + +def _monkey_defaults(*args, **kwargs): + mock_defaults = _DefaultsContainer(*args, **kwargs) + return _Monkey(_implicit_environ, _DEFAULTS=mock_defaults) + + +def _setup_defaults(test_case, *args, **kwargs): + test_case._replaced_defaults = _implicit_environ._DEFAULTS + _implicit_environ._DEFAULTS = _DefaultsContainer(*args, **kwargs) + + +def _tear_down_defaults(test_case): + _implicit_environ._DEFAULTS = test_case._replaced_defaults diff --git a/gcloud/pubsub/api.py b/gcloud/pubsub/api.py index 32b11f1d286e..56f826ffecd9 100644 --- a/gcloud/pubsub/api.py +++ b/gcloud/pubsub/api.py @@ -14,6 +14,9 @@ """ Define API functions (not bound to classes).""" +from gcloud._helpers import get_default_project +from gcloud.pubsub._implicit_environ import get_default_connection + def list_topics(page_size=None, page_token=None, project=None, connection=None): @@ -45,6 +48,12 @@ def list_topics(page_size=None, page_token=None, more topics can be retrieved with another call (pass that value as ``page_token``). """ + if project is None: + project = get_default_project() + + if connection is None: + connection = get_default_connection() + params = {} if page_size is not None: @@ -93,6 +102,12 @@ def list_subscriptions(page_size=None, page_token=None, topic_name=None, more topics can be retrieved with another call (pass that value as ``page_token``). """ + if project is None: + project = get_default_project() + + if connection is None: + connection = get_default_connection() + params = {} if page_size is not None: diff --git a/gcloud/pubsub/test___init__.py b/gcloud/pubsub/test___init__.py new file mode 100644 index 000000000000..19197c7105f3 --- /dev/null +++ b/gcloud/pubsub/test___init__.py @@ -0,0 +1,114 @@ +# 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 Test_set_default_connection(unittest2.TestCase): + + def setUp(self): + from gcloud.pubsub._testing import _setup_defaults + _setup_defaults(self) + + def tearDown(self): + from gcloud.pubsub._testing import _tear_down_defaults + _tear_down_defaults(self) + + def _callFUT(self, connection=None): + from gcloud.pubsub import set_default_connection + return set_default_connection(connection=connection) + + def test_set_explicit(self): + from gcloud.pubsub import _implicit_environ + + self.assertEqual(_implicit_environ.get_default_connection(), None) + fake_cnxn = object() + self._callFUT(connection=fake_cnxn) + self.assertEqual(_implicit_environ.get_default_connection(), fake_cnxn) + + def test_set_implicit(self): + from gcloud._testing import _Monkey + from gcloud import pubsub + from gcloud.pubsub import _implicit_environ + + self.assertEqual(_implicit_environ.get_default_connection(), None) + + fake_cnxn = object() + _called_args = [] + _called_kwargs = [] + + def mock_get_connection(*args, **kwargs): + _called_args.append(args) + _called_kwargs.append(kwargs) + return fake_cnxn + + with _Monkey(pubsub, get_connection=mock_get_connection): + self._callFUT() + + self.assertEqual(_implicit_environ.get_default_connection(), fake_cnxn) + self.assertEqual(_called_args, [()]) + self.assertEqual(_called_kwargs, [{}]) + + +class Test_set_defaults(unittest2.TestCase): + + def _callFUT(self, project=None, connection=None): + from gcloud.pubsub import set_defaults + return set_defaults(project=project, connection=connection) + + def test_it(self): + from gcloud._testing import _Monkey + from gcloud import pubsub + + PROJECT = object() + CONNECTION = object() + + SET_PROJECT_CALLED = [] + + def call_set_project(project=None): + SET_PROJECT_CALLED.append(project) + + SET_CONNECTION_CALLED = [] + + def call_set_connection(connection=None): + SET_CONNECTION_CALLED.append(connection) + + with _Monkey(pubsub, + set_default_connection=call_set_connection, + set_default_project=call_set_project): + self._callFUT(project=PROJECT, connection=CONNECTION) + + self.assertEqual(SET_PROJECT_CALLED, [PROJECT]) + self.assertEqual(SET_CONNECTION_CALLED, [CONNECTION]) + + +class Test_get_connection(unittest2.TestCase): + + def _callFUT(self, *args, **kw): + from gcloud.pubsub import get_connection + return get_connection(*args, **kw) + + def test_it(self): + from gcloud import credentials + from gcloud.pubsub import SCOPE + from gcloud.pubsub.connection import Connection + from gcloud.test_credentials import _Client + from gcloud._testing import _Monkey + client = _Client() + with _Monkey(credentials, client=client): + found = self._callFUT() + self.assertTrue(isinstance(found, Connection)) + self.assertTrue(found._credentials is client._signed) + self.assertEqual(found._credentials._scopes, SCOPE) + self.assertTrue(client._get_app_default_called) diff --git a/gcloud/pubsub/test__implicit_environ.py b/gcloud/pubsub/test__implicit_environ.py new file mode 100644 index 000000000000..9061c2bb6c89 --- /dev/null +++ b/gcloud/pubsub/test__implicit_environ.py @@ -0,0 +1,25 @@ +# Copyright 2014 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 Test_get_default_connection(unittest2.TestCase): + + def _callFUT(self): + from gcloud.storage._implicit_environ import get_default_connection + return get_default_connection() + + def test_wo_override(self): + self.assertTrue(self._callFUT() is None) diff --git a/gcloud/pubsub/test_api.py b/gcloud/pubsub/test_api.py index a244a5cd7392..430f6afbbe33 100644 --- a/gcloud/pubsub/test_api.py +++ b/gcloud/pubsub/test_api.py @@ -39,7 +39,29 @@ def test_w_explicit_connection_no_paging(self): self.assertEqual(req['path'], '/projects/%s/topics' % PROJECT) self.assertEqual(req['query_params'], {}) - def test_w_explicit_connection_w_paging(self): + def test_w_implicit_connection_and_project_wo_paging(self): + from gcloud._testing import _monkey_defaults as _monkey_base_defaults + from gcloud.pubsub._testing import _monkey_defaults + TOPIC_NAME = 'topic_name' + PROJECT = 'PROJECT' + TOKEN = 'TOKEN' + returned = {'topics': [{'name': TOPIC_NAME}], + 'nextPageToken': TOKEN} + conn = _Connection(returned) + with _monkey_base_defaults(project=PROJECT): + with _monkey_defaults(connection=conn): + response = self._callFUT() + topics = response['topics'] + self.assertEqual(len(topics), 1) + self.assertEqual(topics[0], {'name': TOPIC_NAME}) + self.assertEqual(response['nextPageToken'], TOKEN) + self.assertEqual(len(conn._requested), 1) + req = conn._requested[0] + self.assertEqual(req['method'], 'GET') + self.assertEqual(req['path'], '/projects/%s/topics' % PROJECT) + self.assertEqual(req['query_params'], {}) + + def test_w_explicit_connection_and_project_w_paging(self): TOPIC_NAME = 'topic_name' PROJECT = 'PROJECT' TOKEN1 = 'TOKEN1' @@ -67,7 +89,9 @@ def _callFUT(self, *args, **kw): from gcloud.pubsub.api import list_subscriptions return list_subscriptions(*args, **kw) - def test_w_explicit_connection_no_paging(self): + def test_w_implicit_connection_wo_paging(self): + from gcloud._testing import _monkey_defaults as _monkey_base_defaults + from gcloud.pubsub._testing import _monkey_defaults PROJECT = 'PROJECT' SUB_NAME = 'topic_name' SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) @@ -77,7 +101,9 @@ def test_w_explicit_connection_no_paging(self): returned = {'subscriptions': [{'name': SUB_PATH, 'topic': TOPIC_PATH}], 'nextPageToken': TOKEN} conn = _Connection(returned) - response = self._callFUT(project=PROJECT, connection=conn) + with _monkey_base_defaults(project=PROJECT): + with _monkey_defaults(connection=conn): + response = self._callFUT() subscriptions = response['subscriptions'] self.assertEqual(len(subscriptions), 1) self.assertEqual(subscriptions[0], @@ -89,7 +115,7 @@ def test_w_explicit_connection_no_paging(self): self.assertEqual(req['path'], '/projects/%s/subscriptions' % PROJECT) self.assertEqual(req['query_params'], {}) - def test_w_explicit_connection_w_paging(self): + def test_w_explicit_connection_and_project_w_paging(self): PROJECT = 'PROJECT' SUB_NAME = 'topic_name' SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) diff --git a/gcloud/pubsub/test_topic.py b/gcloud/pubsub/test_topic.py index 30a745ecda2c..a6d96ac96f50 100644 --- a/gcloud/pubsub/test_topic.py +++ b/gcloud/pubsub/test_topic.py @@ -25,11 +25,17 @@ def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) def test_ctor_wo_inferred_project_or_connection(self): + from gcloud._testing import _monkey_defaults as _monkey_base_defaults + from gcloud.pubsub._testing import _monkey_defaults TOPIC_NAME = 'topic_name' - topic = self._makeOne(TOPIC_NAME) + PROJECT = 'PROJECT' + conn = _Connection() + with _monkey_base_defaults(project=PROJECT): + with _monkey_defaults(connection=conn): + topic = self._makeOne(TOPIC_NAME) self.assertEqual(topic.name, TOPIC_NAME) - self.assertEqual(topic.project, None) - self.assertEqual(topic.connection, None) + self.assertEqual(topic.project, PROJECT) + self.assertTrue(topic.connection is conn) def test_ctor_w_explicit_project_and_connection(self): TOPIC_NAME = 'topic_name' diff --git a/gcloud/pubsub/topic.py b/gcloud/pubsub/topic.py index 1d3b5eda246a..d9a7b9b16ecb 100644 --- a/gcloud/pubsub/topic.py +++ b/gcloud/pubsub/topic.py @@ -16,7 +16,9 @@ import base64 +from gcloud._helpers import get_default_project from gcloud.exceptions import NotFound +from gcloud.pubsub._implicit_environ import get_default_connection class Topic(object): @@ -40,6 +42,10 @@ class Topic(object): environment. """ def __init__(self, name, project=None, connection=None): + if project is None: + project = get_default_project() + if connection is None: + connection = get_default_connection() self.name = name self.project = project self.connection = connection