Skip to content

Commit

Permalink
Merge pull request #794 from tseaver/744-pubsub-return_subscription_i…
Browse files Browse the repository at this point in the history
…nst_from_list_subscriptions

#744: return Subscription instances from `pubsub.list_subscriptions()`
  • Loading branch information
tseaver committed Apr 6, 2015
2 parents 1536f09 + 3637cee commit 18a8330
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 49 deletions.
1 change: 1 addition & 0 deletions gcloud/pubsub/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
60 changes: 52 additions & 8 deletions gcloud/pubsub/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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')


Expand Down Expand Up @@ -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``).
"""
Expand All @@ -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)
2 changes: 1 addition & 1 deletion gcloud/pubsub/subscription.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
76 changes: 47 additions & 29 deletions gcloud/pubsub/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,48 +97,57 @@ 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')
self.assertEqual(req['path'], '/projects/%s/subscriptions' % PROJECT)
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')
Expand All @@ -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')
Expand Down
3 changes: 2 additions & 1 deletion gcloud/pubsub/test_subscription.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
4 changes: 4 additions & 0 deletions gcloud/pubsub/test_topic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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):
Expand Down
7 changes: 6 additions & 1 deletion gcloud/pubsub/topic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
56 changes: 47 additions & 9 deletions regression/pubsub.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@

from gcloud import _helpers
from gcloud import pubsub
from gcloud.pubsub.subscription import Subscription
from gcloud.pubsub.topic import Topic


_helpers._PROJECT_ENV_VAR_NAME = 'GCLOUD_TESTS_PROJECT_ID'
pubsub.set_defaults()


class TestPubsubTopics(unittest2.TestCase):
class TestPubsub(unittest2.TestCase):

def setUp(self):
self.to_delete = []
Expand All @@ -35,21 +36,20 @@ 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 = [
'new%d' % (1000 * time.time(),),
'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()
Expand All @@ -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))

0 comments on commit 18a8330

Please sign in to comment.