From 28717953103e7a3cbd92e5ae950009dd387f0262 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Tue, 31 Mar 2015 17:06:07 -0400 Subject: [PATCH 1/2] Return Subscription instances from 'pubsub.api.list_subscriptions'. --- gcloud/pubsub/api.py | 60 +++++++++++++++++++---- gcloud/pubsub/subscription.py | 2 +- gcloud/pubsub/test_api.py | 76 ++++++++++++++++++------------ gcloud/pubsub/test_subscription.py | 3 +- gcloud/pubsub/test_topic.py | 4 ++ gcloud/pubsub/topic.py | 7 ++- 6 files changed, 112 insertions(+), 40 deletions(-) diff --git a/gcloud/pubsub/api.py b/gcloud/pubsub/api.py index 56eaa21b4c87..f91fb63dc0f8 100644 --- a/gcloud/pubsub/api.py +++ b/gcloud/pubsub/api.py @@ -16,6 +16,7 @@ from gcloud._helpers import get_default_project from gcloud.pubsub._implicit_environ import get_default_connection +from gcloud.pubsub.subscription import Subscription from gcloud.pubsub.topic import Topic @@ -65,10 +66,8 @@ def list_topics(page_size=None, page_token=None, path = '/projects/%s/topics' % project resp = connection.api_request(method='GET', path=path, query_params=params) - topics = [] - for full_name in [topic['name'] for topic in resp['topics']]: - _, t_project, _, name = full_name.split('/') - topics.append(Topic(name, t_project, connection)) + topics = [_topic_from_resource(resource, connection) + for resource in resp['topics']] return topics, resp.get('nextPageToken') @@ -102,9 +101,9 @@ def list_subscriptions(page_size=None, page_token=None, topic_name=None, defaults to the connection inferred from the environment. - :rtype: dict - :returns: keys include ``subscriptions`` (a list of subscription mappings) - and ``nextPageToken`` (a string: if non-empty, indicates that + :rtype: tuple, (list, str) + :returns: list of :class:`gcloud.pubsub.subscription.Subscription`, plus a + "next page token" string: if not None, indicates that more topics can be retrieved with another call (pass that value as ``page_token``). """ @@ -127,4 +126,49 @@ def list_subscriptions(page_size=None, page_token=None, topic_name=None, else: path = '/projects/%s/topics/%s/subscriptions' % (project, topic_name) - return connection.api_request(method='GET', path=path, query_params=params) + resp = connection.api_request(method='GET', path=path, query_params=params) + topics = {} + subscriptions = [_subscription_from_resource(resource, topics, connection) + for resource in resp['subscriptions']] + return subscriptions, resp.get('nextPageToken') + + +def _topic_from_resource(resource, connection): + """Construct a topic given its full path-like name. + + :type resource: dict + :param resource: topic resource representation returned from the API + + :type connection: :class:`gcloud.pubsub.connection.Connection` + :param connection: connection to use for the topic. + + :rtype: :class:`gcloud.pubsub.topic.Topic` + """ + _, project, _, name = resource['name'].split('/') + return Topic(name, project, connection) + + +def _subscription_from_resource(resource, topics, connection): + """Construct a topic given its full path-like name. + + :type resource: string + :param resource: subscription resource representation returned from the API + + :type topics: dict, full_name -> :class:`gcloud.pubsub.topic.Topic` + :param topics: the topics to which subscriptions have been bound + + :type connection: :class:`gcloud.pubsub.connection.Connection` + :param connection: connection to use for the topic. + + :rtype: :class:`gcloud.pubsub.subscription.Subscription` + """ + t_name = resource['topic'] + topic = topics.get(t_name) + if topic is None: + topic = topics[t_name] = _topic_from_resource({'name': t_name}, + connection) + _, _, _, name = resource['name'].split('/') + ack_deadline = resource.get('ackDeadlineSeconds') + push_config = resource.get('pushConfig', {}) + push_endpoint = push_config.get('pushEndpoint') + return Subscription(name, topic, ack_deadline, push_endpoint) diff --git a/gcloud/pubsub/subscription.py b/gcloud/pubsub/subscription.py index 4d438c3010fc..2fab37924ea6 100644 --- a/gcloud/pubsub/subscription.py +++ b/gcloud/pubsub/subscription.py @@ -55,7 +55,7 @@ def create(self): See: https://cloud.google.com/pubsub/reference/rest/v1beta2/projects/subscriptions/create """ - data = {'topic': self.topic.path} + data = {'topic': self.topic.full_name} if self.ack_deadline is not None: data['ackDeadline'] = self.ack_deadline diff --git a/gcloud/pubsub/test_api.py b/gcloud/pubsub/test_api.py index cca0defe1bda..e48622b37529 100644 --- a/gcloud/pubsub/test_api.py +++ b/gcloud/pubsub/test_api.py @@ -97,23 +97,23 @@ def _callFUT(self, *args, **kw): 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 + from gcloud.pubsub.subscription import Subscription PROJECT = 'PROJECT' - SUB_NAME = 'topic_name' + SUB_NAME = 'subscription_name' SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) TOPIC_NAME = 'topic_name' TOPIC_PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME) - TOKEN = 'TOKEN' - returned = {'subscriptions': [{'name': SUB_PATH, 'topic': TOPIC_PATH}], - 'nextPageToken': TOKEN} + SUB_INFO = [{'name': SUB_PATH, 'topic': TOPIC_PATH}] + returned = {'subscriptions': SUB_INFO} conn = _Connection(returned) with _monkey_base_defaults(project=PROJECT): with _monkey_defaults(connection=conn): - response = self._callFUT() - subscriptions = response['subscriptions'] + subscriptions, next_page_token = self._callFUT() self.assertEqual(len(subscriptions), 1) - self.assertEqual(subscriptions[0], - {'name': SUB_PATH, 'topic': TOPIC_PATH}) - self.assertEqual(response['nextPageToken'], TOKEN) + self.assertTrue(isinstance(subscriptions[0], Subscription)) + self.assertEqual(subscriptions[0].name, SUB_NAME) + self.assertEqual(subscriptions[0].topic.name, TOPIC_NAME) + self.assertEqual(next_page_token, None) self.assertEqual(len(conn._requested), 1) req = conn._requested[0] self.assertEqual(req['method'], 'GET') @@ -121,24 +121,33 @@ def test_w_implicit_connection_wo_paging(self): self.assertEqual(req['query_params'], {}) def test_w_explicit_connection_and_project_w_paging(self): + from gcloud.pubsub.subscription import Subscription PROJECT = 'PROJECT' - SUB_NAME = 'topic_name' + SUB_NAME = 'subscription_name' SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) TOPIC_NAME = 'topic_name' TOPIC_PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME) + ACK_DEADLINE = 42 + PUSH_ENDPOINT = 'https://push.example.com/endpoint' TOKEN1 = 'TOKEN1' TOKEN2 = 'TOKEN2' SIZE = 1 - returned = {'subscriptions': [{'name': SUB_PATH, 'topic': TOPIC_PATH}], - 'nextPageToken': TOKEN2} + SUB_INFO = [{'name': SUB_PATH, + 'topic': TOPIC_PATH, + 'ackDeadlineSeconds': ACK_DEADLINE, + 'pushConfig': {'pushEndpoint': PUSH_ENDPOINT}}] + returned = {'subscriptions': SUB_INFO, 'nextPageToken': TOKEN2} conn = _Connection(returned) - response = self._callFUT(SIZE, TOKEN1, - project=PROJECT, connection=conn) - subscriptions = response['subscriptions'] + subscriptions, next_page_token = self._callFUT(SIZE, TOKEN1, + project=PROJECT, + connection=conn) self.assertEqual(len(subscriptions), 1) - self.assertEqual(subscriptions[0], - {'name': SUB_PATH, 'topic': TOPIC_PATH}) - self.assertEqual(response['nextPageToken'], TOKEN2) + self.assertTrue(isinstance(subscriptions[0], Subscription)) + self.assertEqual(subscriptions[0].name, SUB_NAME) + self.assertEqual(subscriptions[0].topic.name, TOPIC_NAME) + self.assertEqual(subscriptions[0].ack_deadline, ACK_DEADLINE) + self.assertEqual(subscriptions[0].push_endpoint, PUSH_ENDPOINT) + self.assertEqual(next_page_token, TOKEN2) self.assertEqual(len(conn._requested), 1) req = conn._requested[0] self.assertEqual(req['method'], 'GET') @@ -147,22 +156,31 @@ def test_w_explicit_connection_and_project_w_paging(self): {'pageSize': SIZE, 'pageToken': TOKEN1}) def test_w_topic_name(self): + from gcloud.pubsub.subscription import Subscription PROJECT = 'PROJECT' - SUB_NAME = 'topic_name' - SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME) + SUB_NAME_1 = 'subscription_1' + SUB_PATH_1 = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME_1) + SUB_NAME_2 = 'subscription_2' + SUB_PATH_2 = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME_2) TOPIC_NAME = 'topic_name' TOPIC_PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME) + SUB_INFO = [{'name': SUB_PATH_1, 'topic': TOPIC_PATH}, + {'name': SUB_PATH_2, 'topic': TOPIC_PATH}] TOKEN = 'TOKEN' - returned = {'subscriptions': [{'name': SUB_PATH, 'topic': TOPIC_PATH}], - 'nextPageToken': TOKEN} + returned = {'subscriptions': SUB_INFO, 'nextPageToken': TOKEN} conn = _Connection(returned) - response = self._callFUT(topic_name=TOPIC_NAME, - project=PROJECT, connection=conn) - subscriptions = response['subscriptions'] - self.assertEqual(len(subscriptions), 1) - self.assertEqual(subscriptions[0], - {'name': SUB_PATH, 'topic': TOPIC_PATH}) - self.assertEqual(response['nextPageToken'], TOKEN) + subscriptions, next_page_token = self._callFUT(topic_name=TOPIC_NAME, + project=PROJECT, + connection=conn) + self.assertEqual(len(subscriptions), 2) + self.assertTrue(isinstance(subscriptions[0], Subscription)) + self.assertEqual(subscriptions[0].name, SUB_NAME_1) + self.assertEqual(subscriptions[0].topic.name, TOPIC_NAME) + self.assertTrue(isinstance(subscriptions[1], Subscription)) + self.assertEqual(subscriptions[1].name, SUB_NAME_2) + self.assertEqual(subscriptions[1].topic.name, TOPIC_NAME) + self.assertTrue(subscriptions[1].topic is subscriptions[0].topic) + self.assertEqual(next_page_token, TOKEN) self.assertEqual(len(conn._requested), 1) req = conn._requested[0] self.assertEqual(req['method'], 'GET') diff --git a/gcloud/pubsub/test_subscription.py b/gcloud/pubsub/test_subscription.py index cc9147231b03..eb5145cd989d 100644 --- a/gcloud/pubsub/test_subscription.py +++ b/gcloud/pubsub/test_subscription.py @@ -296,4 +296,5 @@ def __init__(self, name, project, connection): self.name = name self.project = project self.connection = connection - self.path = 'projects/%s/topics/%s' % (project, name) + self.full_name = 'projects/%s/topics/%s' % (project, name) + self.path = '/projects/%s/topics/%s' % (project, name) diff --git a/gcloud/pubsub/test_topic.py b/gcloud/pubsub/test_topic.py index a6d96ac96f50..273087821f2a 100644 --- a/gcloud/pubsub/test_topic.py +++ b/gcloud/pubsub/test_topic.py @@ -35,6 +35,8 @@ def test_ctor_wo_inferred_project_or_connection(self): topic = self._makeOne(TOPIC_NAME) self.assertEqual(topic.name, TOPIC_NAME) self.assertEqual(topic.project, PROJECT) + self.assertEqual(topic.full_name, + 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME)) self.assertTrue(topic.connection is conn) def test_ctor_w_explicit_project_and_connection(self): @@ -44,6 +46,8 @@ def test_ctor_w_explicit_project_and_connection(self): topic = self._makeOne(TOPIC_NAME, project=PROJECT, connection=conn) self.assertEqual(topic.name, TOPIC_NAME) self.assertEqual(topic.project, PROJECT) + self.assertEqual(topic.full_name, + 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME)) self.assertTrue(topic.connection is conn) def test_create(self): diff --git a/gcloud/pubsub/topic.py b/gcloud/pubsub/topic.py index bbb0d7ed2b38..018963657e00 100644 --- a/gcloud/pubsub/topic.py +++ b/gcloud/pubsub/topic.py @@ -50,10 +50,15 @@ def __init__(self, name, project=None, connection=None): self.project = project self.connection = connection + @property + def full_name(self): + """Fully-qualified name used in topic / subscription APIs""" + return 'projects/%s/topics/%s' % (self.project, self.name) + @property def path(self): """URL path for the topic's APIs""" - return '/projects/%s/topics/%s' % (self.project, self.name) + return '/%s' % (self.full_name) def create(self): """API call: create the topic via a PUT request From 3637ceed92fd8e012d6271c206cc0dd1552a6287 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Fri, 3 Apr 2015 12:21:19 -0400 Subject: [PATCH 2/2] Add regression tests for 'Subscription.create'/'api.list_subscriptions'. --- gcloud/pubsub/__init__.py | 1 + regression/pubsub.py | 56 ++++++++++++++++++++++++++++++++------- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/gcloud/pubsub/__init__.py b/gcloud/pubsub/__init__.py index ffc96aa890c8..a707f2218125 100644 --- a/gcloud/pubsub/__init__.py +++ b/gcloud/pubsub/__init__.py @@ -29,6 +29,7 @@ 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.api import list_subscriptions from gcloud.pubsub.api import list_topics from gcloud.pubsub.connection import Connection diff --git a/regression/pubsub.py b/regression/pubsub.py index 7df330986787..92d2e4b2becd 100644 --- a/regression/pubsub.py +++ b/regression/pubsub.py @@ -18,6 +18,7 @@ from gcloud import _helpers from gcloud import pubsub +from gcloud.pubsub.subscription import Subscription from gcloud.pubsub.topic import Topic @@ -25,7 +26,7 @@ pubsub.set_defaults() -class TestPubsubTopics(unittest2.TestCase): +class TestPubsub(unittest2.TestCase): def setUp(self): self.to_delete = [] @@ -35,13 +36,13 @@ def tearDown(self): doomed.delete() def test_create_topic(self): - new_topic_name = 'a-new-topic' - topic = Topic(new_topic_name) + TOPIC_NAME = 'a-new-topic' + topic = Topic(TOPIC_NAME) self.assertFalse(topic.exists()) topic.create() self.to_delete.append(topic) self.assertTrue(topic.exists()) - self.assertEqual(topic.name, new_topic_name) + self.assertEqual(topic.name, TOPIC_NAME) def test_list_topics(self): topics_to_create = [ @@ -49,7 +50,6 @@ def test_list_topics(self): 'newer%d' % (1000 * time.time(),), 'newest%d' % (1000 * time.time(),), ] - created_topics = [] for topic_name in topics_to_create: topic = Topic(topic_name) topic.create() @@ -58,7 +58,45 @@ def test_list_topics(self): # Retrieve the topics. all_topics, _ = pubsub.list_topics() project_id = pubsub.get_default_project() - created_topics = [topic for topic in all_topics - if topic.name in topics_to_create and - topic.project == project_id] - self.assertEqual(len(created_topics), len(topics_to_create)) + created = [topic for topic in all_topics + if topic.name in topics_to_create and + topic.project == project_id] + self.assertEqual(len(created), len(topics_to_create)) + + def test_create_subscription(self): + TOPIC_NAME = 'subscribe-me' + topic = Topic(TOPIC_NAME) + self.assertFalse(topic.exists()) + topic.create() + self.to_delete.append(topic) + SUBSCRIPTION_NAME = 'subscribing-now' + subscription = Subscription(SUBSCRIPTION_NAME, topic) + self.assertFalse(subscription.exists()) + subscription.create() + self.to_delete.append(subscription) + self.assertTrue(subscription.exists()) + self.assertEqual(subscription.name, SUBSCRIPTION_NAME) + self.assertTrue(subscription.topic is topic) + + def test_list_subscriptions(self): + TOPIC_NAME = 'subscribe-me' + topic = Topic(TOPIC_NAME) + self.assertFalse(topic.exists()) + topic.create() + self.to_delete.append(topic) + subscriptions_to_create = [ + 'new%d' % (1000 * time.time(),), + 'newer%d' % (1000 * time.time(),), + 'newest%d' % (1000 * time.time(),), + ] + for subscription_name in subscriptions_to_create: + subscription = Subscription(subscription_name, topic) + subscription.create() + self.to_delete.append(subscription) + + # Retrieve the subscriptions. + all_subscriptions, _ = pubsub.list_subscriptions() + created = [subscription for subscription in all_subscriptions + if subscription.name in subscriptions_to_create and + subscription.topic.name == TOPIC_NAME] + self.assertEqual(len(created), len(subscriptions_to_create))