8000 Merge pull request #794 from tseaver/744-pubsub-return_subscription_i… · googleapis/google-cloud-python@18a8330 · GitHub
[go: up one dir, main page]

Skip to content

Commit 18a8330

Browse files
committed
Merge pull request #794 from tseaver/744-pubsub-return_subscription_inst_from_list_subscriptions
#744: return Subscription instances from `pubsub.list_subscriptions()`
2 parents 1536f09 + 3637cee commit 18a8330

File tree

8 files changed

+160
-49
lines changed

8 files changed

+160
-49
lines changed

gcloud/pubsub/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from gcloud.connection import get_scoped_connection
3030
from gcloud.pubsub import _implicit_environ
3131
from gcloud.pubsub._implicit_environ import get_default_connection
32+
from gcloud.pubsub.api import list_subscriptions
3233
from gcloud.pubsub.api import list_topics
3334
from gcloud.pubsub.connection import Connection
3435

gcloud/pubsub/api.py

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from gcloud._helpers import get_default_project
1818
from gcloud.pubsub._implicit_environ import get_default_connection
19+
from gcloud.pubsub.subscription import Subscription
1920
from gcloud.pubsub.topic import Topic
2021

2122

@@ -65,10 +66,8 @@ def list_topics(page_size=None, page_token=None,
6566
628C
6667
path = '/projects/%s/topics' % project
6768
resp = connection.api_request(method='GET', path=path, query_params=params)
68-
topics = []
69-
for full_name in [topic['name'] for topic in resp['topics']]:
70-
_, t_project, _, name = full_name.split('/')
71-
topics.append(Topic(name, t_project, connection))
69+
topics = [_topic_from_resource(resource, connection)
70+
for resource in resp['topics']]
7271
return topics, resp.get('nextPageToken')
7372

7473

@@ -102,9 +101,9 @@ def list_subscriptions(page_size=None, page_token=None, topic_name=None,
102101
defaults to the connection inferred from the
103102
environment.
104103
105-
:rtype: dict
106-
:returns: keys include ``subscriptions`` (a list of subscription mappings)
107-
and ``nextPageToken`` (a string: if non-empty, indicates that
104+
:rtype: tuple, (list, str)
105+
:returns: list of :class:`gcloud.pubsub.subscription.Subscription`, plus a
106+
"next page token" string: if not None, indicates that
108107
more topics can be retrieved with another call (pass that
109108
value as ``page_token``).
110109
"""
@@ -127,4 +126,49 @@ def list_subscriptions(page_size=None, page_token=None, topic_name=None,
127126
else:
128127
path = '/projects/%s/topics/%s/subscriptions' % (project, topic_name)
129128

130-
return connection.api_request(method='GET', path=path, query_params=params)
129+
resp = connection.api_request(method='GET', path=path, query_params=params)
130+
topics = {}
131+
subscriptions = [_subscription_from_resource(resource, topics, connection)
132+
for resource in resp['subscriptions']]
133+
return subscriptions, resp.get('nextPageToken')
134+
135+
136+
def _topic_from_resource(resource, connection):
137+
"""Construct a topic given its full path-like name.
138+
139+
:type resource: dict
140+
:param resource: topic resource representation returned from the API
141+
142+
:type connection: :class:`gcloud.pubsub.connection.Connection`
143+
:param connection: connection to use for the topic.
144+
145+
:rtype: :class:`gcloud.pubsub.topic.Topic`
146+
"""
147+
_, project, _, name = resource['name'].split('/')
148+
return Topic(name, project, connection)
149+
150+
151+
def _subscription_from_resource(resource, topics, connection):
152+
"""Construct a topic given its full path-like name.
153+
154+
:type resource: string
155+
:param resource: subscription resource representation returned from the API
156+
157+
:type topics: dict, full_name -> :class:`gcloud.pubsub.topic.Topic`
158+
:param topics: the topics to which subscriptions have been bound
159+
160+
:type connection: :class:`gcloud.pubsub.connection.Connection`
161+
:param connection: connection to use for the topic.
162+
163+
:rtype: :class:`gcloud.pubsub.subscription.Subscription`
164+
"""
165+
t_name = resource['topic']
166+
topic = topics.get(t_name)
167+
if topic is None:
168+
topic = topics[t_name] = _topic_from_resource({'name': t_name},
169+
connection)
170+
_, _, _, name = resource['name'].split('/')
171+
ack_deadline = resource.get('ackDeadlineSeconds')
172+
push_config = resource.get('pushConfig', {})
173+
push_endpoint = push_config.get('pushEndpoint')
174+
return Subscription(name, topic, ack_deadline, push_endpoint)

gcloud/pubsub/subscription.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def create(self):
5555
See:
5656
https://cloud.google.com/pubsub/reference/rest/v1beta2/projects/subscriptions/create
5757
"""
58-
data = {'topic': self.topic.path}
58+
data = {'topic': self.topic.full_name}
5959

6060
if self.ack_deadline is not None:
6161
data['ackDeadline'] = self.ack_deadline

gcloud/pubsub/test_api.py

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -97,48 +97,57 @@ def _callFUT(self, *args, **kw):
9797
def test_w_implicit_connection_wo_paging(self):
9898
from gcloud._testing import _monkey_defaults as _monkey_base_defaults
9999
from gcloud.pubsub._testing import _monkey_defaults
100+
from gcloud.pubsub.subscription import Subscription
100101
PROJECT = 'PROJECT'
101-
SUB_NAME = 'topic_name'
102+
SUB_NAME = 'subscription_name'
102103
SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME)
103104
TOPIC_NAME = 'topic_name'
104105
TOPIC_PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME)
105-
TOKEN = 'TOKEN'
106-
returned = {'subscriptions': [{'name': SUB_PATH, 'topic': TOPIC_PATH}],
107-
'nextPageToken': TOKEN}
106+
SUB_INFO = [{'name': SUB_PATH, 'topic': TOPIC_PATH}]
107+
returned = {'subscriptions': SUB_INFO}
108108
conn = _Connection(returned)
109109
with _monkey_base_defaults(project=PROJECT):
110110
with _monkey_defaults(connection=conn):
111-
response = self._callFUT()
112-
subscriptions = response['subscriptions']
111+
subscriptions, next_page_token = self._callFUT()
113112
self.assertEqual(len(subscriptions), 1)
114-
self.assertEqual(subscriptions[0],
115-
{'name': SUB_PATH, 'topic': TOPIC_PATH})
116-
self.assertEqual(response['nextPageToken'], TOKEN)
113+
self.assertTrue(isinstance(subscriptions[0], Subscription))
114+
self.assertEqual(subscriptions[0].name, SUB_NAME)
115+
self.assertEqual(subscriptions[0].topic.name, TOPIC_NAME)
116+
self.assertEqual(next_page_token, None)
117117
self.assertEqual(len(conn._requested), 1)
118118
req = conn._requested[0]
119119
self.assertEqual(req['method'], 'GET')
120120
self.assertEqual(req['path'], '/projects/%s/subscriptions' % PROJECT)
121121
self.assertEqual(req['query_params'], {})
122122

123123
def test_w_explicit_connection_and_project_w_paging(self):
124+
from gcloud.pubsub.subscription import Subscription
124125 EED3
PROJECT = 'PROJECT'
125-
SUB_NAME = 'topic_name'
126+
SUB_NAME = 'subscription_name'
126127
SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME)
127128
TOPIC_NAME = 'topic_name'
128129
TOPIC_PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME)
130+
ACK_DEADLINE = 42
131+
PUSH_ENDPOINT = 'https://push.example.com/endpoint'
129132
TOKEN1 = 'TOKEN1'
130133
TOKEN2 = 'TOKEN2'
131134
SIZE = 1
132-
returned = {'subscriptions': [{'name': SUB_PATH, 'topic': TOPIC_PATH}],
133-
'nextPageToken': TOKEN2}
135+
SUB_INFO = [{'name': SUB_PATH,
136+
'topic': TOPIC_PATH,
137+
'ackDeadlineSeconds': ACK_DEADLINE,
138+
'pushConfig': {'pushEndpoint': PUSH_ENDPOINT}}]
139+
returned = {'subscriptions': SUB_INFO, 'nextPageToken': TOKEN2}
134140
conn = _Connection(returned)
135-
response = self._callFUT(SIZE, TOKEN1,
136-
project=PROJECT, connection=conn)
137-
subscriptions = response['subscriptions']
141+
subscriptions, next_page_token = self._callFUT(SIZE, TOKEN1,
142+
project=PROJECT,
143+
connection=conn)
138144
self.assertEqual(len(subscriptions), 1)
139-
self.assertEqual(subscriptions[0],
140-
{'name': SUB_PATH, 'topic': TOPIC_PATH})
141-
self.assertEqual(response['nextPageToken'], TOKEN2)
145+
self.assertTrue(isinstance(subscriptions[0], Subscription))
146+
self.assertEqual(subscriptions[0].name, SUB_NAME)
147+
self.assertEqual(subscriptions[0].topic.name, TOPIC_NAME)
148+
self.assertEqual(subscriptions[0].ack_deadline, ACK_DEADLINE)
149+
self.assertEqual(subscriptions[0].push_endpoint, PUSH_ENDPOINT)
150+
self.assertEqual(next_page_token, TOKEN2)
142151
self.assertEqual(len(conn._requested), 1)
143152
req = conn._requested[0]
144153
self.assertEqual(req['method'], 'GET')
@@ -147,22 +156,31 @@ def test_w_explicit_connection_and_project_w_paging(self):
147156
{'pageSize': SIZE, 'pageToken': TOKEN1})
148157

149158
def test_w_topic_name(self):
159+
from gcloud.pubsub.subscription import Subscription
150160
PROJECT = 'PROJECT'
151-
SUB_NAME = 'topic_name'
152-
SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME)
161+
SUB_NAME_1 = 'subscription_1'
162+
SUB_PATH_1 = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME_1)
163+
SUB_NAME_2 = 'subscription_2'
164+
SUB_PATH_2 = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME_2)
153165
TOPIC_NAME = 'topic_name'
154166
TOPIC_PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME)
167+
SUB_INFO = [{'name': SUB_PATH_1, 'topic': TOPIC_PATH},
168+
{'name': SUB_PATH_2, 'topic': TOPIC_PATH}]
155169
TOKEN = 'TOKEN'
156-
returned = {'subscriptions': [{'name': SUB_PATH, 'topic': TOPIC_PATH}],
157-
'nextPageToken': TOKEN}
170+
returned = {'subscriptions': SUB_INFO, 'nextPageToken': TOKEN}
158171
conn = _Connection(returned)
159-
response = self._callFUT(topic_name=TOPIC_NAME,
160-
project=PROJECT, connection=conn)
161-
subscriptions = response['subscriptions']
162-
self.assertEqual(len(subscriptions), 1)
163-
self.assertEqual(subscriptions[0],
164-
{'name': SUB_PATH, 'topic': TOPIC_PATH})
165-
self.assertEqual(response['nextPageToken'], TOKEN)
172+
subscriptions, next_page_token = self._callFUT(topic_name=TOPIC_NAME,
173+
project=PROJECT,
174+
connection=conn)
175+
self.assertEqual(len(subscriptions), 2)
176+
self.assertTrue(isinstance(subscriptions[0], Subscription))
177+
self.assertEqual(subscriptions[0].name, SUB_NAME_1)
178+
self.assertEqual(subscriptions[0].topic.name, TOPIC_NAME)
179+
self.assertTrue(isinstance(subscriptions[1], Subscription))
180+
self.assertEqual(subscriptions[1].name, SUB_NAME_2)
181+
self.assertEqual(subscriptions[1].topic.name, TOPIC_NAME)
182+
self.assertTrue(subscriptions[1].topic is subscriptions[0].topic)
183+
self.assertEqual(next_page_token, TOKEN)
166184
self.assertEqual(len(conn._requested), 1)
167185
req = conn._requested[0]
168186
self.assertEqual(req['method'], 'GET')

gcloud/pubsub/test_subscription.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,4 +296,5 @@ def __init__(self, name, project, connection):
296296
self.name = name
297297
self.project = project
298298
self.connection = connection
299-
self.path = 'projects/%s/topics/%s' % (project, name)
299+
self.full_name = 'projects/%s/topics/%s' % (project, name)
300+
self.path = '/projects/%s/topics/%s' % (project, name)

gcloud/pubsub/test_topic.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ def test_ctor_wo_inferred_project_or_connection(self):
3535
topic = self._makeOne(TOPIC_NAME)
3636
self.assertEqual(topic.name, TOPIC_NAME)
3737
self.assertEqual(topic.project, PROJECT)
38+
self.assertEqual(topic.full_name,
39+
'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME))
3840
self.assertTrue(topic.connection is conn)
3941

4042
def test_ctor_w_explicit_project_and_connection(self):
@@ -44,6 +46,8 @@ def test_ctor_w_explicit_project_and_connection(self):
4446
topic = self._makeOne(TOPIC_NAME, project=PROJECT, connection=conn)
4547
self.assertEqual(topic.name, TOPIC_NAME)
4648
self.assertEqual(topic.project, PROJECT)
49+
self.assertEqual(topic.full_name,
50+
'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME))
4751
self.assertTrue(topic.connection is conn)
4852

4953
def test_create(self):

gcloud/pubsub/topic.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,15 @@ def __init__(self, name, project=None, connection=None):
5050
self.project = project
5151
self.connection = connection
5252

53+
@property
54+
def full_name(self):
55+
"""Fully-qualified name used in topic / subscription APIs"""
56+
return 'projects/%s/topics/%s' % (self.project, self.name)
57+
5358
@property
5459
def path(self):
5560
"""URL path for the topic's APIs"""
56-
return '/projects/%s/topics/%s' % (self.project, self.name)
61+
return '/%s' % (self.full_name)
5762

5863
def create(self):
5964
"""API call: create the topic via a PUT request

regression/pubsub.py

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@
1818

1919
from gcloud import _helpers
2020
from gcloud import pubsub
21+
from gcloud.pubsub.subscription import Subscription
2122
from gcloud.pubsub.topic import Topic
2223

2324

2425
_helpers._PROJECT_ENV_VAR_NAME = 'GCLOUD_TESTS_PROJECT_ID'
2526
pubsub.set_defaults()
2627

2728

28-
class TestPubsubTopics(unittest2.TestCase):
29+
class TestPubsub(unittest2.TestCase):
2930

3031
def setUp(self):
3132
self.to_delete = []
@@ -35,21 +36,20 @@ def tearDown(self):
3536
doomed.delete()
3637

3738
def test_create_topic(self):
38-
new_topic_name = 'a-new-topic'
39-
topic = Topic(new_topic_name)
39+
TOPIC_NAME = 'a-new-topic'
40+
topic = Topic(TOPIC_NAME)
4041
self.assertFalse(topic.exists())
4142
topic.create()
4243
self.to_delete.append(topic)
4344
self.assertTrue(topic.exists())
44-
self.assertEqual(topic.name, new_topic_name)
45+
self.assertEqual(topic.name, TOPIC_NAME)
4546

4647
def test_list_topics(self):
4748
topics_to_create = [
4849
'new%d' % (1000 * time.time(),),
4950
'newer%d' % (1000 * time.time(),),
5051
'newest%d' % (1000 * time.time(),),
5152
]
52-
created_topics = []
5353
for topic_name in topics_to_create:
5454
topic = Topic(topic_name)
5555
topic.create()
@@ -58,7 +58,45 @@ def test_list_topics(self):
5858
# Retrieve the topics.
5959
all_topics, _ = pubsub.list_topics()
6060
project_id = pubsub.get_default_project()
61-
created_topics = [topic for topic in all_topics
62-
if topic.name in topics_to_create and
63-
topic.project == project_id]
64-
self.assertEqual(len(created_topics), len(topics_to_create))
61+
created = [topic for topic in all_topics
62+
if topic.name in topics_to_create and
63+
topic.project == project_id]
64+
self.assertEqual(len(created), len(topics_to_create))
65+
66+
def test_create_subscription(self):
67+
TOPIC_NAME = 'subscribe-me'
68+
topic = Topic(TOPIC_NAME)
69+
self.assertFalse(topic.exists())
70+
topic.create()
71+
self.to_delete.append(topic)
72+
SUBSCRIPTION_NAME = 'subscribing-now'
73+
subscription = Subscription(SUBSCRIPTION_NAME, topic)
74+
self.assertFalse(subscription.exists())
75+
subscription.create()
76+
self.to_delete.append(subscription)
77+
self.assertTrue(subscription.exists())
78+
self.assertEqual(subscription.name, SUBSCRIPTION_NAME)
79+
self.assertTrue(subscription.topic is topic)
80+
81+
def test_list_subscriptions(self):
82+
TOPIC_NAME = 'subscribe-me'
83+
topic = Topic(TOPIC_NAME)
84+
self.assertFalse(topic.exists())
85+
topic.create()
86+
self.to_delete.append(topic)
87+
subscriptions_to_create = [
88+
'new%d' % (1000 * time.time(),),
89+
'newer%d' % (1000 * time.time(),),
90+
'newest%d' % (1000 * time.time(),),
91+
]
92+
for subscription_name in subscriptions_to_create:
93+
subscription = Subscription(subscription_name, topic)
94+
subscription.create()
95+
self.to_delete.append(subscription)
96+
97+
# Retrieve the subscriptions.
98+
all_subscriptions, _ = pubsub.list_subscriptions()
99+
created = [subscription for subscription in all_subscriptions
100+
if subscription.name in subscriptions_to_create and
101+
subscription.topic.name == TOPIC_NAME]
102+
self.assertEqual(len(created), len(subscriptions_to_create))

0 commit comments

Comments
 (0)
0