Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'Topic.list_subscriptions'. #1580

Merged
merged 4 commits into from
Mar 9, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/pubsub-usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,8 @@ List subscriptions for a topic:

>>> from gcloud import pubsub
>>> client = pubsub.Client()
>>> subscriptions, next_page_token = client.list_subscriptions(
... topic_name='topic_name') # API request
>>> topic = client.topic('topic_name')
>>> subscriptions, next_page_token = topic.list_subscriptions() # API request
>>> [subscription.name for subscription in subscriptions]
['subscription_name']

Expand Down
31 changes: 31 additions & 0 deletions gcloud/pubsub/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,34 @@ def topic_name_from_path(path, project):
'project from resource.')

return path_parts[3]


def subscription_name_from_path(path, project):
"""Validate a subscription URI path and get the subscription name.

:type path: string
:param path: URI path for a subscription API request.

:type project: string
:param project: The project associated with the request. It is
included for validation purposes.

:rtype: string
:returns: subscription name parsed from ``path``.
:raises: :class:`ValueError` if the ``path`` is ill-formed or if
the project from the ``path`` does not agree with the
``project`` passed in.
"""
# PATH = 'projects/%s/subscriptions/%s' % (PROJECT, TOPIC_NAME)

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

path_parts = path.split('/')
if (len(path_parts) != 4 or path_parts[0] != 'projects' or
path_parts[2] != 'subscriptions'):

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

raise ValueError(
'Expected path to be of the form '
'projects/{project}/subscriptions/{subscription_name}')
if (len(path_parts) != 4 or path_parts[0] != 'projects' or
path_parts[2] != 'subscriptions' or path_parts[1] != project):

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

raise ValueError('Project from client should agree with '
'project from resource.')

return path_parts[3]
13 changes: 2 additions & 11 deletions gcloud/pubsub/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ def list_topics(self, page_size=None, page_token=None):
for resource in resp.get('topics', ())]
return topics, resp.get('nextPageToken')

def list_subscriptions(self, page_size=None, page_token=None,
topic_name=None):
def list_subscriptions(self, page_size=None, page_token=None):
"""List subscriptions for the project associated with this client.

See:
Expand All @@ -99,10 +98,6 @@ def list_subscriptions(self, page_size=None, page_token=None,
passed, the API will return the first page of
topics.

:type topic_name: string
:param topic_name: limit results to subscriptions bound to the given
topic.

:rtype: tuple, (list, str)
:returns: list of :class:`gcloud.pubsub.subscription.Subscription`,
plus a "next page token" string: if not None, indicates that
Expand All @@ -117,11 +112,7 @@ def list_subscriptions(self, page_size=None, page_token=None,
if page_token is not None:
params['pageToken'] = page_token

if topic_name is None:
path = '/projects/%s/subscriptions' % (self.project,)
else:
path = '/projects/%s/topics/%s/subscriptions' % (self.project,
topic_name)
path = '/projects/%s/subscriptions' % (self.project,)

resp = self.connection.api_request(method='GET', path=path,
query_params=params)
Expand Down
32 changes: 32 additions & 0 deletions gcloud/pubsub/test__helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,35 @@ def test_valid_data(self):
PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME)
topic_name = self._callFUT(PATH, PROJECT)
self.assertEqual(topic_name, TOPIC_NAME)


class Test_subscription_name_from_path(unittest2.TestCase):

def _callFUT(self, path, project):
from gcloud.pubsub._helpers import subscription_name_from_path
return subscription_name_from_path(path, project)

def test_invalid_path_length(self):
PATH = 'projects/foo'
PROJECT = None
self.assertRaises(ValueError, self._callFUT, PATH, PROJECT)

def test_invalid_path_format(self):
TOPIC_NAME = 'TOPIC_NAME'
PROJECT = 'PROJECT'
PATH = 'foo/%s/bar/%s' % (PROJECT, TOPIC_NAME)
self.assertRaises(ValueError, self._callFUT, PATH, PROJECT)

def test_invalid_project(self):
TOPIC_NAME = 'TOPIC_NAME'
PROJECT1 = 'PROJECT1'
PROJECT2 = 'PROJECT2'
PATH = 'projects/%s/subscriptions/%s' % (PROJECT1, TOPIC_NAME)
self.assertRaises(ValueError, self._callFUT, PATH, PROJECT2)

def test_valid_data(self):
TOPIC_NAME = 'TOPIC_NAME'
PROJECT = 'PROJECT'
PATH = 'projects/%s/subscriptions/%s' % (PROJECT, TOPIC_NAME)
subscription_name = self._callFUT(PATH, PROJECT)
self.assertEqual(subscription_name, TOPIC_NAME)
41 changes: 0 additions & 41 deletions gcloud/pubsub/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,47 +196,6 @@ def test_list_subscriptions_w_missing_key(self):
self.assertEqual(req['path'], '/projects/%s/subscriptions' % PROJECT)
self.assertEqual(req['query_params'], {})

def test_list_subscriptions_with_topic_name(self):
from gcloud.pubsub.subscription import Subscription
PROJECT = 'PROJECT'
CREDS = _Credentials()

CLIENT_OBJ = self._makeOne(project=PROJECT, credentials=CREDS)

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': SUB_INFO, 'nextPageToken': TOKEN}
# Replace the connection on the client with one of our own.
CLIENT_OBJ.connection = _Connection(RETURNED)

# Execute request.
subscriptions, next_page_token = CLIENT_OBJ.list_subscriptions(
topic_name=TOPIC_NAME)
# Test values are correct.
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(CLIENT_OBJ.connection._requested), 1)
req = CLIENT_OBJ.connection._requested[0]
self.assertEqual(req['method'], 'GET')
self.assertEqual(req['path'],
'/projects/%s/topics/%s/subscriptions'
% (PROJECT, TOPIC_NAME))
self.assertEqual(req['query_params'], {})

def test_topic(self):
PROJECT = 'PROJECT'
TOPIC_NAME = 'TOPIC_NAME'
Expand Down
111 changes: 109 additions & 2 deletions gcloud/pubsub/test_topic.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,15 +336,122 @@ def test_subscription(self):
TOPIC_NAME = 'topic_name'
PROJECT = 'PROJECT'
CLIENT = _Client(project=PROJECT)
topic = self._makeOne(TOPIC_NAME,
client=CLIENT)
topic = self._makeOne(TOPIC_NAME, client=CLIENT)

SUBSCRIPTION_NAME = 'subscription_name'
subscription = topic.subscription(SUBSCRIPTION_NAME)
self.assertTrue(isinstance(subscription, Subscription))
self.assertEqual(subscription.name, SUBSCRIPTION_NAME)
self.assertTrue(subscription.topic is topic)

def test_list_subscriptions_no_paging(self):
from gcloud.pubsub.subscription import Subscription
TOPIC_NAME = 'topic_name'
PROJECT = 'PROJECT'
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'
SUBS_LIST = [SUB_PATH_1, SUB_PATH_2]
TOKEN = 'TOKEN'
RETURNED = {'subscriptions': SUBS_LIST, 'nextPageToken': TOKEN}

conn = _Connection(RETURNED)
CLIENT = _Client(project=PROJECT, connection=conn)
topic = self._makeOne(TOPIC_NAME, client=CLIENT)

# Execute request.
subscriptions, next_page_token = topic.list_subscriptions()
# Test values are correct.
self.assertEqual(len(subscriptions), 2)

subscription = subscriptions[0]
self.assertTrue(isinstance(subscription, Subscription))
self.assertEqual(subscriptions[0].name, SUB_NAME_1)
self.assertTrue(subscription.topic is topic)

subscription = subscriptions[1]
self.assertTrue(isinstance(subscription, Subscription))
self.assertEqual(subscriptions[1].name, SUB_NAME_2)
self.assertTrue(subscription.topic is topic)

self.assertEqual(next_page_token, TOKEN)
self.assertEqual(len(conn._requested), 1)
req = conn._requested[0]
self.assertEqual(req['method'], 'GET')
self.assertEqual(req['path'],
'/projects/%s/topics/%s/subscriptions'
% (PROJECT, TOPIC_NAME))
self.assertEqual(req['query_params'], {})

def test_list_subscriptions_with_paging(self):
from gcloud.pubsub.subscription import Subscription
TOPIC_NAME = 'topic_name'
PROJECT = 'PROJECT'
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'
SUBS_LIST = [SUB_PATH_1, SUB_PATH_2]
PAGE_SIZE = 10
TOKEN = 'TOKEN'
RETURNED = {'subscriptions': SUBS_LIST}

conn = _Connection(RETURNED)
CLIENT = _Client(project=PROJECT, connection=conn)
topic = self._makeOne(TOPIC_NAME, client=CLIENT)

# Execute request.
subscriptions, next_page_token = topic.list_subscriptions(
page_size=PAGE_SIZE, page_token=TOKEN)
# Test values are correct.
self.assertEqual(len(subscriptions), 2)

subscription = subscriptions[0]
self.assertTrue(isinstance(subscription, Subscription))
self.assertEqual(subscriptions[0].name, SUB_NAME_1)
self.assertTrue(subscription.topic is topic)

subscription = subscriptions[1]
self.assertTrue(isinstance(subscription, Subscription))
self.assertEqual(subscriptions[1].name, SUB_NAME_2)
self.assertTrue(subscription.topic is topic)

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/topics/%s/subscriptions'
% (PROJECT, TOPIC_NAME))
self.assertEqual(req['query_params'],
{'pageSize': PAGE_SIZE, 'pageToken': TOKEN})

def test_list_subscriptions_missing_key(self):
TOPIC_NAME = 'topic_name'
PROJECT = 'PROJECT'
TOPIC_NAME = 'topic_name'

conn = _Connection({})
CLIENT = _Client(project=PROJECT, connection=conn)
topic = self._makeOne(TOPIC_NAME, client=CLIENT)

# Execute request.
subscriptions, next_page_token = topic.list_subscriptions()
# Test values are correct.
self.assertEqual(len(subscriptions), 0)
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/topics/%s/subscriptions'
% (PROJECT, TOPIC_NAME))
self.assertEqual(req['query_params'], {})


class TestBatch(unittest2.TestCase):

Expand Down
46 changes: 46 additions & 0 deletions gcloud/pubsub/topic.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from gcloud._helpers import _datetime_to_rfc3339
from gcloud._helpers import _NOW
from gcloud.exceptions import NotFound
from gcloud.pubsub._helpers import subscription_name_from_path
from gcloud.pubsub._helpers import topic_name_from_path
from gcloud.pubsub.subscription import Subscription

Expand Down Expand Up @@ -212,6 +213,51 @@ def delete(self, client=None):
client = self._require_client(client)
client.connection.api_request(method='DELETE', path=self.path)

def list_subscriptions(self, page_size=None, page_token=None, client=None):
"""List subscriptions for the project associated with this client.

See:
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics.subscriptions/list

:type page_size: int
:param page_size: maximum number of topics to return, If not passed,
defaults to a value set by the API.

:type page_token: string
:param page_token: opaque marker for the next "page" of topics. If not
passed, the API will return the first page of
topics.

:type client: :class:`gcloud.pubsub.client.Client` or ``NoneType``
:param client: the client to use. If not passed, falls back to the
``client`` stored on the current topic.

: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``).
"""
client = self._require_client(client)
params = {}

if page_size is not None:
params['pageSize'] = page_size

if page_token is not None:
params['pageToken'] = page_token

path = '/projects/%s/topics/%s/subscriptions' % (
self.project, self.name)

resp = client.connection.api_request(method='GET', path=path,
query_params=params)
subscriptions = []
for sub_path in resp.get('subscriptions', ()):
sub_name = subscription_name_from_path(sub_path, self.project)
subscriptions.append(Subscription(sub_name, self))
return subscriptions, resp.get('nextPageToken')


class Batch(object):
"""Context manager: collect messages to publish via a single API call.
Expand Down
8 changes: 3 additions & 5 deletions system_tests/pubsub.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,7 @@ def test_list_subscriptions(self):
self.assertFalse(topic.exists())
topic.create()
self.to_delete.append(topic)
empty, _ = Config.CLIENT.list_subscriptions(
topic_name=DEFAULT_TOPIC_NAME)
empty, _ = topic.list_subscriptions()
self.assertEqual(len(empty), 0)
subscriptions_to_create = [
'new%d' % (1000 * time.time(),),
Expand All @@ -132,10 +131,9 @@ def test_list_subscriptions(self):
self.to_delete.append(subscription)

# Retrieve the subscriptions.
all_subscriptions, _ = Config.CLIENT.list_subscriptions()
all_subscriptions, _ = topic.list_subscriptions()
created = [subscription for subscription in all_subscriptions
if subscription.name in subscriptions_to_create and
subscription.topic.name == DEFAULT_TOPIC_NAME]
if subscription.name in subscriptions_to_create]
self.assertEqual(len(created), len(subscriptions_to_create))

def test_message_pull_mode_e2e(self):
Expand Down