Skip to content

Commit 42889a5

Browse files
committed
Implement 'pubsub.client.Client'.
Wraps: - 'pubsub.api' functions - 'pubsub.topic.Topic' (via a factory / proxy) - 'pubsub.subscription.Subscription' (via a factory / proxy) See: #861 (comment)
1 parent 2bf11ca commit 42889a5

File tree

5 files changed

+564
-0
lines changed

5 files changed

+564
-0
lines changed

docs/pubsub-usage.rst

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,3 +265,88 @@ Fetch messages for a pull subscription without blocking (none pending):
265265
>>> messages = [recv[1] for recv in received]
266266
>>> [message.id for message in messages]
267267
[]
268+
269+
Using Clients
270+
-------------
271+
272+
A :class:`gcloud.pubsub.client.Client` instance explicitly manages a
273+
:class:`gcloud.pubsub.connection.Connection` and an associated project
274+
ID. Applications can then use the APIs which might otherwise take a
275+
``connection`` or ``project`` parameter, knowing that the values configured
276+
in the client will be passed.
277+
278+
Create a client using the defaults from the environment:
279+
280+
.. doctest::
281+
282+
>>> from gcloud.pubsub.client import Client
283+
>>> client = Client()
284+
285+
Create a client using an explicit connection, but the default project:
286+
287+
.. doctest::
288+
289+
>>> from gcloud.pubsub.client import Client
290+
>>> from gcloud.pubsub.connection import Connection
291+
>>> connection = Connection.from_service_account_json('/path/to/creds.json')
292+
>>> client = Client(connection=connection)
293+
294+
Create a client using an explicit project ID, but the connetion inferred
295+
from the environment:
296+
297+
.. doctest::
298+
299+
>>> from gcloud.pubsub.client import Client
300+
>>> client = Client(project='your-project-id')
301+
302+
Listing topics using a client (note that the client's connection
303+
is used to make the request, and its project is passed as a parameter):
304+
305+
.. doctest::
306+
307+
>>> from gcloud.pubsub.client import Client
308+
>>> client = Client(project='your-project-id')
309+
>>> topics, next_page_token = client.list_topics() # API request
310+
311+
Listing subscriptions using a client (note that the client's connection
312+
is used to make the request, and its project is passed as a parameter):
313+
314+
.. doctest::
315+
316+
>>> from gcloud.pubsub.client import Client
317+
>>> client = Client(project='your-project-id')
318+
>>> topics, next_page_token = client.list_topics() # API request
319+
>>> subscription, next_page_tokens = list_subscriptions() # API request
320+
321+
Instantiate a topic using a client (note that the client's project is passed
322+
through to the topic constructor, and that the returned object is a proxy
323+
which ensures that an API requests made via the topic use the client's
324+
connection):
325+
326+
.. doctest::
327+
328+
>>> from gcloud.pubsub.client import Client
329+
>>> client = Client(project='your-project-id')
330+
>>> topic = client.topic('topic-name')
331+
>>> topic.exists() # API request
332+
False
333+
>>> topic.create() # API request
334+
>>> topic.exists() # API request
335+
True
336+
337+
Instantiate a subscription using a client (note that the client's project is
338+
passed through to the subscription constructor, and that the returned object
339+
is a proxy which ensures that an API requests made via the subscription use
340+
the client's connection):
341+
342+
.. doctest::
343+
344+
>>> from gcloud.pubsub.client import Client
345+
>>> client = Client(project='your-project-id')
346+
>>> topic = client.topic('topic-name')
347+
>>> subscription = topic.subscription('subscription-name')
348+
>>> subscription.exists() # API request
349+
False
350+
>>> subscription.create() # API request
351+
>>> subscription.exists() # API request
352+
True

gcloud/_helpers.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
1616
This module is not part of the public API surface of `gcloud`.
1717
"""
18+
19+
import functools
20+
import inspect
1821
import os
1922
import socket
2023

@@ -238,3 +241,34 @@ def __init__(self, project=None, implicit=False):
238241

239242

240243
_DEFAULTS = _DefaultsContainer(implicit=True)
244+
245+
246+
class _ClientProxy(object):
247+
"""Proxy for :class:`gcloud.pubsub.topic.Topic`.
248+
249+
:param wrapped: Domain instance being proxied.
250+
251+
:param client: Client used to pass connection / project as needed to
252+
methods of ``wrapped``.
253+
"""
254+
def __init__(self, wrapped, client):
255+
self._wrapped = wrapped
256+
self._client = client
257+
258+
def __getattr__(self, name):
259+
"""Proxy to wrapped object.
260+
261+
Pass 'connection' and 'project' from our client to methods which take
262+
either / both.
263+
"""
264+
found = getattr(self._wrapped, name)
265+
if inspect.ismethod(found):
266+
args, _, _ = inspect.getargs(found.__code__)
267+
curried = {}
268+
if 'connection' in args:
269+
curried['connection'] = self._client.connection
270+
if 'project' in args:
271+
curried['project'] = self._client.project
272+
if curried:
273+
found = functools.partial(found, **curried)
274+
return found

gcloud/pubsub/client.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Copyright 2015 Google Inc. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Convenience proxies
16+
17+
Define wrappers for ``api`` functions, :class:`gcloud.pubsub.topic.Topic`, and
18+
:class:`gcloud.pubsub.subscription.Subscription`, passing the memoized
19+
connection / project as needed.
20+
"""
21+
22+
from gcloud._helpers import get_default_project
23+
from gcloud._helpers import _ClientProxy
24+
from gcloud.pubsub._implicit_environ import _require_connection
25+
from gcloud.pubsub import api
26+
from gcloud.pubsub.subscription import Subscription
27+
from gcloud.pubsub.topic import Topic
28+
29+
30+
class Client(object):
31+
"""Wrap :mod:`gcloud.pubsub` API objects.
32+
33+
:type connection: :class:`gcloud.pubsub.connection.Connection` or None
34+
:param connection: The configured connection. Defaults to one inferred
35+
from the environment.
36+
37+
:type project: str or None
38+
:param connection: The configured project. Defaults to the value inferred
39+
from the environment.
40+
"""
41+
42+
def __init__(self, connection=None, project=None):
43+
self.connection = _require_connection(connection)
44+
if project is None:
45+
project = get_default_project()
46+
self.project = project
47+
48+
def topic(self, name):
49+
"""Proxy for :class:`gcloud.pubsub.topic.Topic`.
50+
51+
:type name: string
52+
:param name: the name of the topic
53+
54+
:rtype: :class:`_Topic`
55+
:returns: a proxy for a newly created Topic, using the passed name
56+
and the client's project.
57+
"""
58+
topic = Topic(name, self.project)
59+
return _Topic(topic, self)
60+
61+
def list_topics(self, page_size=None, page_token=None):
62+
"""Proxy for :func:`gcloud.pubsub.api.list_topics`.
63+
64+
Passes configured connection and project.
65+
"""
66+
topics, next_page_token = api.list_topics(
67+
page_size=page_size,
68+
page_token=page_token,
69+
connection=self.connection,
70+
project=self.project)
71+
proxies = [_Topic(topic, self) for topic in topics]
72+
return proxies, next_page_token
73+
74+
def list_subscriptions(self, page_size=None, page_token=None,
75+
topic_name=None):
76+
"""Proxy for :func:`gcloud.pubsub.api.list_subscriptions`.
77+
78+
Passes configured connection and project.
79+
"""
80+
subscriptions, next_page_token = api.list_subscriptions(
81+
page_size=page_size,
82+
page_token=page_token,
83+
topic_name=topic_name,
84+
connection=self.connection,
85+
project=self.project)
86+
topics = dict([(sub.topic.name, _Topic(sub.topic, self))
87+
for sub in subscriptions])
88+
proxies = [
89+
_Subscription(sub, self, topics[sub.topic.name])
90+
for sub in subscriptions]
91+
return proxies, next_page_token
92+
93+
94+
class _Topic(_ClientProxy):
95+
"""Proxy for :class:`gcloud.pubsub.topic.Topic`.
96+
97+
:type wrapped: :class:`gcloud.pubsub.topic.Topic`
98+
:param wrapped: Topic being proxied.
99+
100+
:type client: :class:`gcloud.pubsub.client.Client`
101+
:param client: Client used to pass connection / project.
102+
"""
103+
def subscription(self, name, ack_deadline=None, push_endpoint=None):
104+
""" Proxy through to :class:`gcloud.pubsub.subscription.Subscription`.
105+
106+
:rtype: :class:`_Subscription`
107+
"""
108+
subscription = Subscription(
109+
name,
110+
self._wrapped,
111+
ack_deadline=ack_deadline,
112+
push_endpoint=push_endpoint)
113+
return _Subscription(subscription, self._client, self)
114+
115+
116+
class _Subscription(_ClientProxy):
117+
"""Proxy for :class:`gcloud.pubsub.subscription.Subscription`.
118+
119+
:type wrapped: :class:`gcloud.pubsub.topic.Subscription`
120+
:param wrapped: Subscription being proxied.
121+
122+
:type client: :class:`gcloud.pubsub.client.Client`
123+
:param client: Client used to pass connection / project.
124+
125+
:type topic: :class:`gcloud.pubsub.client._Topic`
126+
:param topic: proxy for the wrapped subscription's topic.
127+
"""
128+
def __init__(self, wrapped, client, topic):
129+
super(_Subscription, self).__init__(wrapped, client)
130+
self.topic = topic

0 commit comments

Comments
 (0)