Updated DB_Helper by adding firebase methods.
This commit is contained in:
parent
485cc3bbba
commit
c82121d036
1810 changed files with 537281 additions and 1 deletions
32
venv/Lib/site-packages/gcloud/pubsub/__init__.py
Normal file
32
venv/Lib/site-packages/gcloud/pubsub/__init__.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
# Copyright 2015 Google Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Google Cloud Pubsub API wrapper.
|
||||
|
||||
The main concepts with this API are:
|
||||
|
||||
- :class:`gcloud.pubsub.topic.Topic` represents an endpoint to which messages
|
||||
can be published using the Cloud Storage Pubsub API.
|
||||
|
||||
- :class:`gcloud.pubsub.subscription.Subscription` represents a named
|
||||
subscription (either pull or push) to a topic.
|
||||
"""
|
||||
|
||||
from gcloud.pubsub.client import Client
|
||||
from gcloud.pubsub.connection import Connection
|
||||
from gcloud.pubsub.subscription import Subscription
|
||||
from gcloud.pubsub.topic import Topic
|
||||
|
||||
|
||||
SCOPE = Connection.SCOPE
|
502
venv/Lib/site-packages/gcloud/pubsub/_gax.py
Normal file
502
venv/Lib/site-packages/gcloud/pubsub/_gax.py
Normal file
|
@ -0,0 +1,502 @@
|
|||
# Copyright 2016 Google Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""GAX wrapper for Pubsub API requests."""
|
||||
|
||||
# pylint: disable=import-error
|
||||
from google.gax import CallOptions
|
||||
from google.gax import INITIAL_PAGE
|
||||
from google.gax.errors import GaxError
|
||||
from google.gax.grpc import exc_to_code
|
||||
from google.pubsub.v1.pubsub_pb2 import PubsubMessage
|
||||
from google.pubsub.v1.pubsub_pb2 import PushConfig
|
||||
from grpc.beta.interfaces import StatusCode
|
||||
# pylint: enable=import-error
|
||||
|
||||
from gcloud.exceptions import Conflict
|
||||
from gcloud.exceptions import NotFound
|
||||
from gcloud._helpers import _to_bytes
|
||||
|
||||
|
||||
def _build_paging_options(page_token=None):
|
||||
"""Helper for :meth:'_PublisherAPI.list_topics' et aliae."""
|
||||
if page_token is None:
|
||||
page_token = INITIAL_PAGE
|
||||
options = {'page_token': page_token}
|
||||
return CallOptions(**options)
|
||||
|
||||
|
||||
class _PublisherAPI(object):
|
||||
"""Helper mapping publisher-related APIs.
|
||||
|
||||
:type gax_api: :class:`google.pubsub.v1.publisher_api.PublisherApi`
|
||||
:param gax_api: API object used to make GAX requests.
|
||||
"""
|
||||
def __init__(self, gax_api):
|
||||
self._gax_api = gax_api
|
||||
|
||||
def list_topics(self, project, page_size=0, page_token=None):
|
||||
"""List topics for the project associated with this API.
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/list
|
||||
|
||||
:type project: string
|
||||
:param project: project ID
|
||||
|
||||
: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.
|
||||
|
||||
:rtype: tuple, (list, str)
|
||||
:returns: list of ``Topic`` resource dicts, 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``).
|
||||
"""
|
||||
options = _build_paging_options(page_token)
|
||||
path = 'projects/%s' % (project,)
|
||||
page_iter = self._gax_api.list_topics(
|
||||
path, page_size=page_size, options=options)
|
||||
topics = [{'name': topic_pb.name} for topic_pb in page_iter.next()]
|
||||
token = page_iter.page_token or None
|
||||
return topics, token
|
||||
|
||||
def topic_create(self, topic_path):
|
||||
"""API call: create a topic
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/create
|
||||
|
||||
:type topic_path: string
|
||||
:param topic_path: fully-qualified path of the new topic, in format
|
||||
``projects/<PROJECT>/topics/<TOPIC_NAME>``.
|
||||
|
||||
:rtype: dict
|
||||
:returns: ``Topic`` resource returned from the API.
|
||||
:raises: :exc:`gcloud.exceptions.Conflict` if the topic already
|
||||
exists
|
||||
"""
|
||||
try:
|
||||
topic_pb = self._gax_api.create_topic(topic_path)
|
||||
except GaxError as exc:
|
||||
if exc_to_code(exc.cause) == StatusCode.FAILED_PRECONDITION:
|
||||
raise Conflict(topic_path)
|
||||
raise
|
||||
return {'name': topic_pb.name}
|
||||
|
||||
def topic_get(self, topic_path):
|
||||
"""API call: retrieve a topic
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/get
|
||||
|
||||
:type topic_path: string
|
||||
:param topic_path: fully-qualified path of the topic, in format
|
||||
``projects/<PROJECT>/topics/<TOPIC_NAME>``.
|
||||
|
||||
:rtype: dict
|
||||
:returns: ``Topic`` resource returned from the API.
|
||||
:raises: :exc:`gcloud.exceptions.NotFound` if the topic does not
|
||||
exist
|
||||
"""
|
||||
try:
|
||||
topic_pb = self._gax_api.get_topic(topic_path)
|
||||
except GaxError as exc:
|
||||
if exc_to_code(exc.cause) == StatusCode.NOT_FOUND:
|
||||
raise NotFound(topic_path)
|
||||
raise
|
||||
return {'name': topic_pb.name}
|
||||
|
||||
def topic_delete(self, topic_path):
|
||||
"""API call: delete a topic
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/create
|
||||
|
||||
:type topic_path: string
|
||||
:param topic_path: fully-qualified path of the new topic, in format
|
||||
``projects/<PROJECT>/topics/<TOPIC_NAME>``.
|
||||
|
||||
:rtype: dict
|
||||
:returns: ``Topic`` resource returned from the API.
|
||||
"""
|
||||
try:
|
||||
self._gax_api.delete_topic(topic_path)
|
||||
except GaxError as exc:
|
||||
if exc_to_code(exc.cause) == StatusCode.NOT_FOUND:
|
||||
raise NotFound(topic_path)
|
||||
raise
|
||||
|
||||
def topic_publish(self, topic_path, messages):
|
||||
"""API call: publish one or more messages to a topic
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/publish
|
||||
|
||||
:type topic_path: string
|
||||
:param topic_path: fully-qualified path of the topic, in format
|
||||
``projects/<PROJECT>/topics/<TOPIC_NAME>``.
|
||||
|
||||
:type messages: list of dict
|
||||
:param messages: messages to be published.
|
||||
|
||||
:rtype: list of string
|
||||
:returns: list of opaque IDs for published messages.
|
||||
:raises: :exc:`gcloud.exceptions.NotFound` if the topic does not
|
||||
exist
|
||||
"""
|
||||
options = CallOptions(is_bundling=False)
|
||||
message_pbs = [_message_pb_from_dict(message)
|
||||
for message in messages]
|
||||
try:
|
||||
result = self._gax_api.publish(topic_path, message_pbs,
|
||||
options=options)
|
||||
except GaxError as exc:
|
||||
if exc_to_code(exc.cause) == StatusCode.NOT_FOUND:
|
||||
raise NotFound(topic_path)
|
||||
raise
|
||||
return result.message_ids
|
||||
|
||||
def topic_list_subscriptions(self, topic_path, page_size=0,
|
||||
page_token=None):
|
||||
"""API call: list subscriptions bound to a topic
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics.subscriptions/list
|
||||
|
||||
:type topic_path: string
|
||||
:param topic_path: fully-qualified path of the topic, in format
|
||||
``projects/<PROJECT>/topics/<TOPIC_NAME>``.
|
||||
|
||||
:type page_size: int
|
||||
:param page_size: maximum number of subscriptions 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 subscriptions.
|
||||
If not passed, the API will return the first page
|
||||
of subscriptions.
|
||||
|
||||
:rtype: list of strings
|
||||
:returns: fully-qualified names of subscriptions for the supplied
|
||||
topic.
|
||||
:raises: :exc:`gcloud.exceptions.NotFound` if the topic does not
|
||||
exist
|
||||
"""
|
||||
options = _build_paging_options(page_token)
|
||||
try:
|
||||
page_iter = self._gax_api.list_topic_subscriptions(
|
||||
topic_path, page_size=page_size, options=options)
|
||||
except GaxError as exc:
|
||||
if exc_to_code(exc.cause) == StatusCode.NOT_FOUND:
|
||||
raise NotFound(topic_path)
|
||||
raise
|
||||
subs = page_iter.next()
|
||||
token = page_iter.page_token or None
|
||||
return subs, token
|
||||
|
||||
|
||||
class _SubscriberAPI(object):
|
||||
"""Helper mapping subscriber-related APIs.
|
||||
|
||||
:type gax_api: :class:`google.pubsub.v1.publisher_api.SubscriberApi`
|
||||
:param gax_api: API object used to make GAX requests.
|
||||
"""
|
||||
def __init__(self, gax_api):
|
||||
self._gax_api = gax_api
|
||||
|
||||
def list_subscriptions(self, project, page_size=0, page_token=None):
|
||||
"""List subscriptions for the project associated with this API.
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/list
|
||||
|
||||
:type project: string
|
||||
:param project: project ID
|
||||
|
||||
:type page_size: int
|
||||
:param page_size: maximum number of subscriptions 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 subscriptions.
|
||||
If not passed, the API will return the first page
|
||||
of subscriptions.
|
||||
|
||||
:rtype: tuple, (list, str)
|
||||
:returns: list of ``Subscription`` resource dicts, 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``).
|
||||
"""
|
||||
options = _build_paging_options(page_token)
|
||||
path = 'projects/%s' % (project,)
|
||||
page_iter = self._gax_api.list_subscriptions(
|
||||
path, page_size=page_size, options=options)
|
||||
subscriptions = [_subscription_pb_to_mapping(sub_pb)
|
||||
for sub_pb in page_iter.next()]
|
||||
token = page_iter.page_token or None
|
||||
return subscriptions, token
|
||||
|
||||
def subscription_create(self, subscription_path, topic_path,
|
||||
ack_deadline=None, push_endpoint=None):
|
||||
"""API call: create a subscription
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/create
|
||||
|
||||
:type subscription_path: string
|
||||
:param subscription_path: the fully-qualified path of the new
|
||||
subscription, in format
|
||||
``projects/<PROJECT>/subscriptions/<SUB_NAME>``.
|
||||
|
||||
:type topic_path: string
|
||||
:param topic_path: the fully-qualified path of the topic being
|
||||
subscribed, in format
|
||||
``projects/<PROJECT>/topics/<TOPIC_NAME>``.
|
||||
|
||||
:type ack_deadline: int, or ``NoneType``
|
||||
:param ack_deadline: the deadline (in seconds) by which messages pulled
|
||||
from the back-end must be acknowledged.
|
||||
|
||||
:type push_endpoint: string, or ``NoneType``
|
||||
:param push_endpoint: URL to which messages will be pushed by the
|
||||
back-end. If not set, the application must pull
|
||||
messages.
|
||||
|
||||
:rtype: dict
|
||||
:returns: ``Subscription`` resource returned from the API.
|
||||
"""
|
||||
if push_endpoint is not None:
|
||||
push_config = PushConfig(push_endpoint=push_endpoint)
|
||||
else:
|
||||
push_config = None
|
||||
|
||||
if ack_deadline is None:
|
||||
ack_deadline = 0
|
||||
|
||||
try:
|
||||
sub_pb = self._gax_api.create_subscription(
|
||||
subscription_path, topic_path, push_config, ack_deadline)
|
||||
except GaxError as exc:
|
||||
if exc_to_code(exc.cause) == StatusCode.FAILED_PRECONDITION:
|
||||
raise Conflict(topic_path)
|
||||
raise
|
||||
return _subscription_pb_to_mapping(sub_pb)
|
||||
|
||||
def subscription_get(self, subscription_path):
|
||||
"""API call: retrieve a subscription
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/get
|
||||
|
||||
:type subscription_path: string
|
||||
:param subscription_path: the fully-qualified path of the subscription,
|
||||
in format
|
||||
``projects/<PROJECT>/subscriptions/<SUB_NAME>``.
|
||||
|
||||
:rtype: dict
|
||||
:returns: ``Subscription`` resource returned from the API.
|
||||
"""
|
||||
try:
|
||||
sub_pb = self._gax_api.get_subscription(subscription_path)
|
||||
except GaxError as exc:
|
||||
if exc_to_code(exc.cause) == StatusCode.NOT_FOUND:
|
||||
raise NotFound(subscription_path)
|
||||
raise
|
||||
return _subscription_pb_to_mapping(sub_pb)
|
||||
|
||||
def subscription_delete(self, subscription_path):
|
||||
"""API call: delete a subscription
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/delete
|
||||
|
||||
:type subscription_path: string
|
||||
:param subscription_path: the fully-qualified path of the subscription,
|
||||
in format
|
||||
``projects/<PROJECT>/subscriptions/<SUB_NAME>``.
|
||||
"""
|
||||
try:
|
||||
self._gax_api.delete_subscription(subscription_path)
|
||||
except GaxError as exc:
|
||||
if exc_to_code(exc.cause) == StatusCode.NOT_FOUND:
|
||||
raise NotFound(subscription_path)
|
||||
raise
|
||||
|
||||
def subscription_modify_push_config(self, subscription_path,
|
||||
push_endpoint):
|
||||
"""API call: update push config of a subscription
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/modifyPushConfig
|
||||
|
||||
:type subscription_path: string
|
||||
:param subscription_path: the fully-qualified path of the new
|
||||
subscription, in format
|
||||
``projects/<PROJECT>/subscriptions/<SUB_NAME>``.
|
||||
|
||||
:type push_endpoint: string, or ``NoneType``
|
||||
:param push_endpoint: URL to which messages will be pushed by the
|
||||
back-end. If not set, the application must pull
|
||||
messages.
|
||||
"""
|
||||
push_config = PushConfig(push_endpoint=push_endpoint)
|
||||
try:
|
||||
self._gax_api.modify_push_config(subscription_path, push_config)
|
||||
except GaxError as exc:
|
||||
if exc_to_code(exc.cause) == StatusCode.NOT_FOUND:
|
||||
raise NotFound(subscription_path)
|
||||
raise
|
||||
|
||||
def subscription_pull(self, subscription_path, return_immediately=False,
|
||||
max_messages=1):
|
||||
"""API call: retrieve messages for a subscription
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/modifyPushConfig
|
||||
|
||||
:type subscription_path: string
|
||||
:param subscription_path: the fully-qualified path of the new
|
||||
subscription, in format
|
||||
``projects/<PROJECT>/subscriptions/<SUB_NAME>``.
|
||||
|
||||
:type return_immediately: boolean
|
||||
:param return_immediately: if True, the back-end returns even if no
|
||||
messages are available; if False, the API
|
||||
call blocks until one or more messages are
|
||||
available.
|
||||
|
||||
:type max_messages: int
|
||||
:param max_messages: the maximum number of messages to return.
|
||||
|
||||
:rtype: list of dict
|
||||
:returns: the ``receivedMessages`` element of the response.
|
||||
"""
|
||||
try:
|
||||
response_pb = self._gax_api.pull(
|
||||
subscription_path, max_messages, return_immediately)
|
||||
except GaxError as exc:
|
||||
if exc_to_code(exc.cause) == StatusCode.NOT_FOUND:
|
||||
raise NotFound(subscription_path)
|
||||
raise
|
||||
return [_received_message_pb_to_mapping(rmpb)
|
||||
for rmpb in response_pb.received_messages]
|
||||
|
||||
def subscription_acknowledge(self, subscription_path, ack_ids):
|
||||
"""API call: acknowledge retrieved messages
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/modifyPushConfig
|
||||
|
||||
:type subscription_path: string
|
||||
:param subscription_path: the fully-qualified path of the new
|
||||
subscription, in format
|
||||
``projects/<PROJECT>/subscriptions/<SUB_NAME>``.
|
||||
|
||||
:type ack_ids: list of string
|
||||
:param ack_ids: ack IDs of messages being acknowledged
|
||||
"""
|
||||
try:
|
||||
self._gax_api.acknowledge(subscription_path, ack_ids)
|
||||
except GaxError as exc:
|
||||
if exc_to_code(exc.cause) == StatusCode.NOT_FOUND:
|
||||
raise NotFound(subscription_path)
|
||||
raise
|
||||
|
||||
def subscription_modify_ack_deadline(self, subscription_path, ack_ids,
|
||||
ack_deadline):
|
||||
"""API call: update ack deadline for retrieved messages
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/modifyAckDeadline
|
||||
|
||||
:type subscription_path: string
|
||||
:param subscription_path: the fully-qualified path of the new
|
||||
subscription, in format
|
||||
``projects/<PROJECT>/subscriptions/<SUB_NAME>``.
|
||||
|
||||
:type ack_ids: list of string
|
||||
:param ack_ids: ack IDs of messages being acknowledged
|
||||
|
||||
:type ack_deadline: int
|
||||
:param ack_deadline: the deadline (in seconds) by which messages pulled
|
||||
from the back-end must be acknowledged.
|
||||
"""
|
||||
try:
|
||||
self._gax_api.modify_ack_deadline(
|
||||
subscription_path, ack_ids, ack_deadline)
|
||||
except GaxError as exc:
|
||||
if exc_to_code(exc.cause) == StatusCode.NOT_FOUND:
|
||||
raise NotFound(subscription_path)
|
||||
raise
|
||||
|
||||
|
||||
def _message_pb_from_dict(message):
|
||||
"""Helper for :meth:`_PublisherAPI.topic_publish`."""
|
||||
return PubsubMessage(data=_to_bytes(message['data']),
|
||||
attributes=message['attributes'])
|
||||
|
||||
|
||||
def _subscription_pb_to_mapping(sub_pb):
|
||||
"""Helper for :meth:`list_subscriptions`, et aliae
|
||||
|
||||
Ideally, would use a function from :mod:`protobuf.json_format`, but
|
||||
the right one isn't public. See:
|
||||
https://github.com/google/protobuf/issues/1351
|
||||
"""
|
||||
mapping = {
|
||||
'name': sub_pb.name,
|
||||
'topic': sub_pb.topic,
|
||||
'ackDeadlineSeconds': sub_pb.ack_deadline_seconds,
|
||||
}
|
||||
if sub_pb.push_config.push_endpoint != '':
|
||||
mapping['pushConfig'] = {
|
||||
'pushEndpoint': sub_pb.push_config.push_endpoint,
|
||||
}
|
||||
return mapping
|
||||
|
||||
|
||||
def _message_pb_to_mapping(message_pb):
|
||||
"""Helper for :meth:`pull`, et aliae
|
||||
|
||||
Ideally, would use a function from :mod:`protobuf.json_format`, but
|
||||
the right one isn't public. See:
|
||||
https://github.com/google/protobuf/issues/1351
|
||||
"""
|
||||
return {
|
||||
'messageId': message_pb.message_id,
|
||||
'data': message_pb.data,
|
||||
'attributes': message_pb.attributes,
|
||||
}
|
||||
|
||||
|
||||
def _received_message_pb_to_mapping(received_message_pb):
|
||||
"""Helper for :meth:`pull`, et aliae
|
||||
|
||||
Ideally, would use a function from :mod:`protobuf.json_format`, but
|
||||
the right one isn't public. See:
|
||||
https://github.com/google/protobuf/issues/1351
|
||||
"""
|
||||
return {
|
||||
'ackId': received_message_pb.ack_id,
|
||||
'message': _message_pb_to_mapping(
|
||||
received_message_pb.message),
|
||||
}
|
73
venv/Lib/site-packages/gcloud/pubsub/_helpers.py
Normal file
73
venv/Lib/site-packages/gcloud/pubsub/_helpers.py
Normal file
|
@ -0,0 +1,73 @@
|
|||
# Copyright 2015 Google Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Helper functions for shared behavior."""
|
||||
|
||||
import re
|
||||
|
||||
from gcloud._helpers import _name_from_project_path
|
||||
|
||||
|
||||
_TOPIC_TEMPLATE = re.compile(r"""
|
||||
projects/ # static prefix
|
||||
(?P<project>[^/]+) # initial letter, wordchars + hyphen
|
||||
/topics/ # static midfix
|
||||
(?P<name>[^/]+) # initial letter, wordchars + allowed punc
|
||||
""", re.VERBOSE)
|
||||
|
||||
|
||||
_SUBSCRIPTION_TEMPLATE = re.compile(r"""
|
||||
projects/ # static prefix
|
||||
(?P<project>[^/]+) # initial letter, wordchars + hyphen
|
||||
/subscriptions/ # static midfix
|
||||
(?P<name>[^/]+) # initial letter, wordchars + allowed punc
|
||||
""", re.VERBOSE)
|
||||
|
||||
|
||||
def topic_name_from_path(path, project):
|
||||
"""Validate a topic URI path and get the topic name.
|
||||
|
||||
:type path: string
|
||||
:param path: URI path for a topic API request.
|
||||
|
||||
:type project: string
|
||||
:param project: The project associated with the request. It is
|
||||
included for validation purposes.
|
||||
|
||||
:rtype: string
|
||||
:returns: Topic 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.
|
||||
"""
|
||||
return _name_from_project_path(path, project, _TOPIC_TEMPLATE)
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
return _name_from_project_path(path, project, _SUBSCRIPTION_TEMPLATE)
|
186
venv/Lib/site-packages/gcloud/pubsub/client.py
Normal file
186
venv/Lib/site-packages/gcloud/pubsub/client.py
Normal file
|
@ -0,0 +1,186 @@
|
|||
# Copyright 2015 Google Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Client for interacting with the Google Cloud Pub/Sub API."""
|
||||
|
||||
import os
|
||||
|
||||
from gcloud.client import JSONClient
|
||||
from gcloud.pubsub.connection import Connection
|
||||
from gcloud.pubsub.connection import _PublisherAPI as JSONPublisherAPI
|
||||
from gcloud.pubsub.connection import _SubscriberAPI as JSONSubscriberAPI
|
||||
from gcloud.pubsub.connection import _IAMPolicyAPI
|
||||
from gcloud.pubsub.subscription import Subscription
|
||||
from gcloud.pubsub.topic import Topic
|
||||
|
||||
try:
|
||||
from google.pubsub.v1.publisher_api import (
|
||||
PublisherApi as GeneratedPublisherAPI)
|
||||
from google.pubsub.v1.subscriber_api import (
|
||||
SubscriberApi as GeneratedSubscriberAPI)
|
||||
from gcloud.pubsub._gax import _PublisherAPI as GAXPublisherAPI
|
||||
from gcloud.pubsub._gax import _SubscriberAPI as GAXSubscriberAPI
|
||||
except ImportError: # pragma: NO COVER
|
||||
_HAVE_GAX = False
|
||||
GeneratedPublisherAPI = GAXPublisherAPI = None
|
||||
GeneratedSubscriberAPI = GAXSubscriberAPI = None
|
||||
else:
|
||||
_HAVE_GAX = True
|
||||
|
||||
|
||||
_USE_GAX = _HAVE_GAX and (os.environ.get('GCLOUD_ENABLE_GAX') is not None)
|
||||
|
||||
|
||||
class Client(JSONClient):
|
||||
"""Client to bundle configuration needed for API requests.
|
||||
|
||||
:type project: string
|
||||
:param project: the project which the client acts on behalf of. Will be
|
||||
passed when creating a topic. If not passed,
|
||||
falls back to the default inferred from the environment.
|
||||
|
||||
:type credentials: :class:`oauth2client.client.OAuth2Credentials` or
|
||||
:class:`NoneType`
|
||||
:param credentials: The OAuth2 Credentials to use for the connection
|
||||
owned by this client. If not passed (and if no ``http``
|
||||
object is passed), falls back to the default inferred
|
||||
from the environment.
|
||||
|
||||
:type http: :class:`httplib2.Http` or class that defines ``request()``.
|
||||
:param http: An optional HTTP object to make requests. If not passed, an
|
||||
``http`` object is created that is bound to the
|
||||
``credentials`` for the current object.
|
||||
"""
|
||||
|
||||
_connection_class = Connection
|
||||
_publisher_api = _subscriber_api = _iam_policy_api = None
|
||||
|
||||
@property
|
||||
def publisher_api(self):
|
||||
"""Helper for publisher-related API calls."""
|
||||
if self._publisher_api is None:
|
||||
if _USE_GAX:
|
||||
generated = GeneratedPublisherAPI()
|
||||
self._publisher_api = GAXPublisherAPI(generated)
|
||||
else:
|
||||
self._publisher_api = JSONPublisherAPI(self.connection)
|
||||
return self._publisher_api
|
||||
|
||||
@property
|
||||
def subscriber_api(self):
|
||||
"""Helper for subscriber-related API calls."""
|
||||
if self._subscriber_api is None:
|
||||
if _USE_GAX:
|
||||
generated = GeneratedSubscriberAPI()
|
||||
self._subscriber_api = GAXSubscriberAPI(generated)
|
||||
else:
|
||||
self._subscriber_api = JSONSubscriberAPI(self.connection)
|
||||
return self._subscriber_api
|
||||
|
||||
@property
|
||||
def iam_policy_api(self):
|
||||
"""Helper for IAM policy-related API calls."""
|
||||
if self._iam_policy_api is None:
|
||||
self._iam_policy_api = _IAMPolicyAPI(self.connection)
|
||||
return self._iam_policy_api
|
||||
|
||||
def list_topics(self, page_size=None, page_token=None):
|
||||
"""List topics for the project associated with this client.
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/list
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START client_list_topics]
|
||||
:end-before: [END client_list_topics]
|
||||
|
||||
: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.
|
||||
|
||||
:rtype: tuple, (list, str)
|
||||
:returns: list of :class:`gcloud.pubsub.topic.Topic`, 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``).
|
||||
"""
|
||||
api = self.publisher_api
|
||||
resources, next_token = api.list_topics(
|
||||
self.project, page_size, page_token)
|
||||
topics = [Topic.from_api_repr(resource, self)
|
||||
for resource in resources]
|
||||
return topics, next_token
|
||||
|
||||
def list_subscriptions(self, page_size=None, page_token=None):
|
||||
"""List subscriptions for the project associated with this client.
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/list
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START client_list_subscriptions]
|
||||
:end-before: [END client_list_subscriptions]
|
||||
|
||||
: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.
|
||||
|
||||
: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``).
|
||||
"""
|
||||
api = self.subscriber_api
|
||||
resources, next_token = api.list_subscriptions(
|
||||
self.project, page_size, page_token)
|
||||
topics = {}
|
||||
subscriptions = [Subscription.from_api_repr(resource, self,
|
||||
topics=topics)
|
||||
for resource in resources]
|
||||
return subscriptions, next_token
|
||||
|
||||
def topic(self, name, timestamp_messages=False):
|
||||
"""Creates a topic bound to the current client.
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START client_topic]
|
||||
:end-before: [END client_topic]
|
||||
|
||||
:type name: string
|
||||
:param name: the name of the topic to be constructed.
|
||||
|
||||
:type timestamp_messages: boolean
|
||||
:param timestamp_messages: To be passed to ``Topic`` constructor.
|
||||
|
||||
:rtype: :class:`gcloud.pubsub.topic.Topic`
|
||||
:returns: Topic created with the current client.
|
||||
"""
|
||||
return Topic(name, client=self, timestamp_messages=timestamp_messages)
|
539
venv/Lib/site-packages/gcloud/pubsub/connection.py
Normal file
539
venv/Lib/site-packages/gcloud/pubsub/connection.py
Normal file
|
@ -0,0 +1,539 @@
|
|||
# Copyright 2015 Google Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Create / interact with gcloud pubsub connections."""
|
||||
|
||||
import os
|
||||
|
||||
from gcloud import connection as base_connection
|
||||
from gcloud.environment_vars import PUBSUB_EMULATOR
|
||||
|
||||
|
||||
class Connection(base_connection.JSONConnection):
|
||||
"""A connection to Google Cloud Pubsub via the JSON REST API.
|
||||
|
||||
:type credentials: :class:`oauth2client.client.OAuth2Credentials`
|
||||
:param credentials: (Optional) The OAuth2 Credentials to use for this
|
||||
connection.
|
||||
|
||||
:type http: :class:`httplib2.Http` or class that defines ``request()``.
|
||||
:param http: (Optional) HTTP object to make requests.
|
||||
|
||||
:type api_base_url: string
|
||||
:param api_base_url: The base of the API call URL. Defaults to the value
|
||||
:attr:`Connection.API_BASE_URL`.
|
||||
"""
|
||||
|
||||
API_BASE_URL = 'https://pubsub.googleapis.com'
|
||||
"""The base of the API call URL."""
|
||||
|
||||
API_VERSION = 'v1'
|
||||
"""The version of the API, used in building the API call's URL."""
|
||||
|
||||
API_URL_TEMPLATE = '{api_base_url}/{api_version}{path}'
|
||||
"""A template for the URL of a particular API call."""
|
||||
|
||||
SCOPE = ('https://www.googleapis.com/auth/pubsub',
|
||||
'https://www.googleapis.com/auth/cloud-platform')
|
||||
"""The scopes required for authenticating as a Cloud Pub/Sub consumer."""
|
||||
|
||||
def __init__(self, credentials=None, http=None, api_base_url=None):
|
||||
super(Connection, self).__init__(credentials=credentials, http=http)
|
||||
if api_base_url is None:
|
||||
emulator_host = os.getenv(PUBSUB_EMULATOR)
|
||||
if emulator_host is None:
|
||||
api_base_url = self.__class__.API_BASE_URL
|
||||
else:
|
||||
api_base_url = 'http://' + emulator_host
|
||||
self.api_base_url = api_base_url
|
||||
|
||||
def build_api_url(self, path, query_params=None,
|
||||
api_base_url=None, api_version=None):
|
||||
"""Construct an API url given a few components, some optional.
|
||||
|
||||
Typically, you shouldn't need to use this method.
|
||||
|
||||
:type path: string
|
||||
:param path: The path to the resource.
|
||||
|
||||
:type query_params: dict or list
|
||||
:param query_params: A dictionary of keys and values (or list of
|
||||
key-value pairs) to insert into the query
|
||||
string of the URL.
|
||||
|
||||
:type api_base_url: string
|
||||
:param api_base_url: The base URL for the API endpoint.
|
||||
Typically you won't have to provide this.
|
||||
|
||||
:type api_version: string
|
||||
:param api_version: The version of the API to call.
|
||||
Typically you shouldn't provide this and instead
|
||||
use the default for the library.
|
||||
|
||||
:rtype: string
|
||||
:returns: The URL assembled from the pieces provided.
|
||||
"""
|
||||
if api_base_url is None:
|
||||
api_base_url = self.api_base_url
|
||||
return super(Connection, self.__class__).build_api_url(
|
||||
path, query_params=query_params,
|
||||
api_base_url=api_base_url, api_version=api_version)
|
||||
|
||||
|
||||
class _PublisherAPI(object):
|
||||
"""Helper mapping publisher-related APIs.
|
||||
|
||||
:type connection: :class:`Connection`
|
||||
:param connection: the connection used to make API requests.
|
||||
"""
|
||||
|
||||
def __init__(self, connection):
|
||||
self._connection = connection
|
||||
|
||||
def list_topics(self, project, page_size=None, page_token=None):
|
||||
"""API call: list topics for a given project
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/list
|
||||
|
||||
:type project: string
|
||||
:param project: project ID
|
||||
|
||||
: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.
|
||||
|
||||
:rtype: tuple, (list, str)
|
||||
:returns: list of ``Topic`` resource dicts, 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``).
|
||||
"""
|
||||
conn = self._connection
|
||||
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' % (project,)
|
||||
resp = conn.api_request(method='GET', path=path, query_params=params)
|
||||
return resp.get('topics', ()), resp.get('nextPageToken')
|
||||
|
||||
def topic_create(self, topic_path):
|
||||
"""API call: create a topic
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/create
|
||||
|
||||
:type topic_path: string
|
||||
:param topic_path: the fully-qualified path of the new topic, in format
|
||||
``projects/<PROJECT>/topics/<TOPIC_NAME>``.
|
||||
|
||||
:rtype: dict
|
||||
:returns: ``Topic`` resource returned from the API.
|
||||
"""
|
||||
conn = self._connection
|
||||
return conn.api_request(method='PUT', path='/%s' % (topic_path,))
|
||||
|
||||
def topic_get(self, topic_path):
|
||||
"""API call: retrieve a topic
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/get
|
||||
|
||||
:type topic_path: string
|
||||
:param topic_path: the fully-qualified path of the topic, in format
|
||||
``projects/<PROJECT>/topics/<TOPIC_NAME>``.
|
||||
|
||||
:rtype: dict
|
||||
:returns: ``Topic`` resource returned from the API.
|
||||
"""
|
||||
conn = self._connection
|
||||
return conn.api_request(method='GET', path='/%s' % (topic_path,))
|
||||
|
||||
def topic_delete(self, topic_path):
|
||||
"""API call: delete a topic
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/delete
|
||||
|
||||
:type topic_path: string
|
||||
:param topic_path: the fully-qualified path of the topic, in format
|
||||
``projects/<PROJECT>/topics/<TOPIC_NAME>``.
|
||||
"""
|
||||
conn = self._connection
|
||||
conn.api_request(method='DELETE', path='/%s' % (topic_path,))
|
||||
|
||||
def topic_publish(self, topic_path, messages):
|
||||
"""API call: publish one or more messages to a topic
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/publish
|
||||
|
||||
:type topic_path: string
|
||||
:param topic_path: the fully-qualified path of the topic, in format
|
||||
``projects/<PROJECT>/topics/<TOPIC_NAME>``.
|
||||
|
||||
:type messages: list of dict
|
||||
:param messages: messages to be published.
|
||||
|
||||
:rtype: list of string
|
||||
:returns: list of opaque IDs for published messages.
|
||||
"""
|
||||
conn = self._connection
|
||||
data = {'messages': messages}
|
||||
response = conn.api_request(
|
||||
method='POST', path='/%s:publish' % (topic_path,), data=data)
|
||||
return response['messageIds']
|
||||
|
||||
def topic_list_subscriptions(self, topic_path, page_size=None,
|
||||
page_token=None):
|
||||
"""API call: list subscriptions bound to a topic
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics.subscriptions/list
|
||||
|
||||
:type topic_path: string
|
||||
:param topic_path: the fully-qualified path of the topic, in format
|
||||
``projects/<PROJECT>/topics/<TOPIC_NAME>``.
|
||||
|
||||
:type page_size: int
|
||||
:param page_size: maximum number of subscriptions 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.
|
||||
|
||||
:rtype: list of strings
|
||||
:returns: fully-qualified names of subscriptions for the supplied
|
||||
topic.
|
||||
"""
|
||||
conn = self._connection
|
||||
params = {}
|
||||
|
||||
if page_size is not None:
|
||||
params['pageSize'] = page_size
|
||||
|
||||
if page_token is not None:
|
||||
params['pageToken'] = page_token
|
||||
|
||||
path = '/%s/subscriptions' % (topic_path,)
|
||||
resp = conn.api_request(method='GET', path=path, query_params=params)
|
||||
return resp.get('subscriptions', ()), resp.get('nextPageToken')
|
||||
|
||||
|
||||
class _SubscriberAPI(object):
|
||||
"""Helper mapping subscriber-related APIs.
|
||||
|
||||
:type connection: :class:`Connection`
|
||||
:param connection: the connection used to make API requests.
|
||||
"""
|
||||
|
||||
def __init__(self, connection):
|
||||
self._connection = connection
|
||||
|
||||
def list_subscriptions(self, project, page_size=None, page_token=None):
|
||||
"""API call: list subscriptions for a given project
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/list
|
||||
|
||||
:type project: string
|
||||
:param project: project ID
|
||||
|
||||
:type page_size: int
|
||||
:param page_size: maximum number of subscriptions 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 subscriptions.
|
||||
If not passed, the API will return the first page
|
||||
of subscriptions.
|
||||
|
||||
:rtype: tuple, (list, str)
|
||||
:returns: list of ``Subscription`` resource dicts, plus a
|
||||
"next page token" string: if not None, indicates that
|
||||
more subscriptions can be retrieved with another call (pass
|
||||
that value as ``page_token``).
|
||||
"""
|
||||
conn = self._connection
|
||||
params = {}
|
||||
|
||||
if page_size is not None:
|
||||
params['pageSize'] = page_size
|
||||
|
||||
if page_token is not None:
|
||||
params['pageToken'] = page_token
|
||||
|
||||
path = '/projects/%s/subscriptions' % (project,)
|
||||
resp = conn.api_request(method='GET', path=path, query_params=params)
|
||||
return resp.get('subscriptions', ()), resp.get('nextPageToken')
|
||||
|
||||
def subscription_create(self, subscription_path, topic_path,
|
||||
ack_deadline=None, push_endpoint=None):
|
||||
"""API call: create a subscription
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/create
|
||||
|
||||
:type subscription_path: string
|
||||
:param subscription_path: the fully-qualified path of the new
|
||||
subscription, in format
|
||||
``projects/<PROJECT>/subscriptions/<SUB_NAME>``.
|
||||
|
||||
:type topic_path: string
|
||||
:param topic_path: the fully-qualified path of the topic being
|
||||
subscribed, in format
|
||||
``projects/<PROJECT>/topics/<TOPIC_NAME>``.
|
||||
|
||||
:type ack_deadline: int, or ``NoneType``
|
||||
:param ack_deadline: the deadline (in seconds) by which messages pulled
|
||||
from the back-end must be acknowledged.
|
||||
|
||||
:type push_endpoint: string, or ``NoneType``
|
||||
:param push_endpoint: URL to which messages will be pushed by the
|
||||
back-end. If not set, the application must pull
|
||||
messages.
|
||||
|
||||
:rtype: dict
|
||||
:returns: ``Subscription`` resource returned from the API.
|
||||
"""
|
||||
conn = self._connection
|
||||
path = '/%s' % (subscription_path,)
|
||||
resource = {'topic': topic_path}
|
||||
|
||||
if ack_deadline is not None:
|
||||
resource['ackDeadlineSeconds'] = ack_deadline
|
||||
|
||||
if push_endpoint is not None:
|
||||
resource['pushConfig'] = {'pushEndpoint': push_endpoint}
|
||||
|
||||
return conn.api_request(method='PUT', path=path, data=resource)
|
||||
|
||||
def subscription_get(self, subscription_path):
|
||||
"""API call: retrieve a subscription
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/get
|
||||
|
||||
:type subscription_path: string
|
||||
:param subscription_path: the fully-qualified path of the subscription,
|
||||
in format
|
||||
``projects/<PROJECT>/subscriptions/<SUB_NAME>``.
|
||||
|
||||
:rtype: dict
|
||||
:returns: ``Subscription`` resource returned from the API.
|
||||
"""
|
||||
conn = self._connection
|
||||
path = '/%s' % (subscription_path,)
|
||||
return conn.api_request(method='GET', path=path)
|
||||
|
||||
def subscription_delete(self, subscription_path):
|
||||
"""API call: delete a subscription
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/delete
|
||||
|
||||
:type subscription_path: string
|
||||
:param subscription_path: the fully-qualified path of the subscription,
|
||||
in format
|
||||
``projects/<PROJECT>/subscriptions/<SUB_NAME>``.
|
||||
"""
|
||||
conn = self._connection
|
||||
path = '/%s' % (subscription_path,)
|
||||
conn.api_request(method='DELETE', path=path)
|
||||
|
||||
def subscription_modify_push_config(self, subscription_path,
|
||||
push_endpoint):
|
||||
"""API call: update push config of a subscription
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/modifyPushConfig
|
||||
|
||||
:type subscription_path: string
|
||||
:param subscription_path: the fully-qualified path of the new
|
||||
subscription, in format
|
||||
``projects/<PROJECT>/subscriptions/<SUB_NAME>``.
|
||||
|
||||
:type push_endpoint: string, or ``NoneType``
|
||||
:param push_endpoint: URL to which messages will be pushed by the
|
||||
back-end. If not set, the application must pull
|
||||
messages.
|
||||
"""
|
||||
conn = self._connection
|
||||
path = '/%s:modifyPushConfig' % (subscription_path,)
|
||||
resource = {'pushConfig': {'pushEndpoint': push_endpoint}}
|
||||
conn.api_request(method='POST', path=path, data=resource)
|
||||
|
||||
def subscription_pull(self, subscription_path, return_immediately=False,
|
||||
max_messages=1):
|
||||
"""API call: retrieve messages for a subscription
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/modifyPushConfig
|
||||
|
||||
:type subscription_path: string
|
||||
:param subscription_path: the fully-qualified path of the new
|
||||
subscription, in format
|
||||
``projects/<PROJECT>/subscriptions/<SUB_NAME>``.
|
||||
|
||||
:type return_immediately: boolean
|
||||
:param return_immediately: if True, the back-end returns even if no
|
||||
messages are available; if False, the API
|
||||
call blocks until one or more messages are
|
||||
available.
|
||||
|
||||
:type max_messages: int
|
||||
:param max_messages: the maximum number of messages to return.
|
||||
|
||||
:rtype: list of dict
|
||||
:returns: the ``receivedMessages`` element of the response.
|
||||
"""
|
||||
conn = self._connection
|
||||
path = '/%s:pull' % (subscription_path,)
|
||||
data = {
|
||||
'returnImmediately': return_immediately,
|
||||
'maxMessages': max_messages,
|
||||
}
|
||||
response = conn.api_request(method='POST', path=path, data=data)
|
||||
return response.get('receivedMessages', ())
|
||||
|
||||
def subscription_acknowledge(self, subscription_path, ack_ids):
|
||||
"""API call: acknowledge retrieved messages
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/modifyPushConfig
|
||||
|
||||
:type subscription_path: string
|
||||
:param subscription_path: the fully-qualified path of the new
|
||||
subscription, in format
|
||||
``projects/<PROJECT>/subscriptions/<SUB_NAME>``.
|
||||
|
||||
:type ack_ids: list of string
|
||||
:param ack_ids: ack IDs of messages being acknowledged
|
||||
"""
|
||||
conn = self._connection
|
||||
path = '/%s:acknowledge' % (subscription_path,)
|
||||
data = {
|
||||
'ackIds': ack_ids,
|
||||
}
|
||||
conn.api_request(method='POST', path=path, data=data)
|
||||
|
||||
def subscription_modify_ack_deadline(self, subscription_path, ack_ids,
|
||||
ack_deadline):
|
||||
"""API call: update ack deadline for retrieved messages
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/modifyAckDeadline
|
||||
|
||||
:type subscription_path: string
|
||||
:param subscription_path: the fully-qualified path of the new
|
||||
subscription, in format
|
||||
``projects/<PROJECT>/subscriptions/<SUB_NAME>``.
|
||||
|
||||
:type ack_ids: list of string
|
||||
:param ack_ids: ack IDs of messages being acknowledged
|
||||
|
||||
:type ack_deadline: int
|
||||
:param ack_deadline: the deadline (in seconds) by which messages pulled
|
||||
from the back-end must be acknowledged.
|
||||
"""
|
||||
conn = self._connection
|
||||
path = '/%s:modifyAckDeadline' % (subscription_path,)
|
||||
data = {
|
||||
'ackIds': ack_ids,
|
||||
'ackDeadlineSeconds': ack_deadline,
|
||||
}
|
||||
conn.api_request(method='POST', path=path, data=data)
|
||||
|
||||
|
||||
class _IAMPolicyAPI(object):
|
||||
"""Helper mapping IAM policy-related APIs.
|
||||
|
||||
:type connection: :class:`Connection`
|
||||
:param connection: the connection used to make API requests.
|
||||
"""
|
||||
|
||||
def __init__(self, connection):
|
||||
self._connection = connection
|
||||
|
||||
def get_iam_policy(self, target_path):
|
||||
"""API call: fetch the IAM policy for the target
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/getIamPolicy
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/getIamPolicy
|
||||
|
||||
:type target_path: string
|
||||
:param target_path: the path of the target object.
|
||||
|
||||
:rtype: dict
|
||||
:returns: the resource returned by the ``getIamPolicy`` API request.
|
||||
"""
|
||||
conn = self._connection
|
||||
path = '/%s:getIamPolicy' % (target_path,)
|
||||
return conn.api_request(method='GET', path=path)
|
||||
|
||||
def set_iam_policy(self, target_path, policy):
|
||||
"""API call: update the IAM policy for the target
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/setIamPolicy
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/setIamPolicy
|
||||
|
||||
:type target_path: string
|
||||
:param target_path: the path of the target object.
|
||||
|
||||
:type policy: dict
|
||||
:param policy: the new policy resource.
|
||||
|
||||
:rtype: dict
|
||||
:returns: the resource returned by the ``setIamPolicy`` API request.
|
||||
"""
|
||||
conn = self._connection
|
||||
wrapped = {'policy': policy}
|
||||
path = '/%s:setIamPolicy' % (target_path,)
|
||||
return conn.api_request(method='POST', path=path, data=wrapped)
|
||||
|
||||
def test_iam_permissions(self, target_path, permissions):
|
||||
"""API call: test permissions
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/testIamPermissions
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/testIamPermissions
|
||||
|
||||
:type target_path: string
|
||||
:param target_path: the path of the target object.
|
||||
|
||||
:type permissions: list of string
|
||||
:param permissions: the permissions to check
|
||||
|
||||
:rtype: dict
|
||||
:returns: the resource returned by the ``getIamPolicy`` API request.
|
||||
"""
|
||||
conn = self._connection
|
||||
wrapped = {'permissions': permissions}
|
||||
path = '/%s:testIamPermissions' % (target_path,)
|
||||
resp = conn.api_request(method='POST', path=path, data=wrapped)
|
||||
return resp.get('permissions', [])
|
259
venv/Lib/site-packages/gcloud/pubsub/iam.py
Normal file
259
venv/Lib/site-packages/gcloud/pubsub/iam.py
Normal file
|
@ -0,0 +1,259 @@
|
|||
# Copyright 2016 Google Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""PubSub API IAM policy definitions
|
||||
|
||||
For allowed roles / permissions, see:
|
||||
https://cloud.google.com/pubsub/access_control#permissions
|
||||
"""
|
||||
|
||||
# Generic IAM roles
|
||||
|
||||
OWNER_ROLE = 'roles/owner'
|
||||
"""Generic role implying all rights to an object."""
|
||||
|
||||
EDITOR_ROLE = 'roles/editor'
|
||||
"""Generic role implying rights to modify an object."""
|
||||
|
||||
VIEWER_ROLE = 'roles/viewer'
|
||||
"""Generic role implying rights to access an object."""
|
||||
|
||||
# Pubsub-specific IAM roles
|
||||
|
||||
PUBSUB_ADMIN_ROLE = 'roles/pubsub.admin'
|
||||
"""Role implying all rights to an object."""
|
||||
|
||||
PUBSUB_EDITOR_ROLE = 'roles/pubsub.editor'
|
||||
"""Role implying rights to modify an object."""
|
||||
|
||||
PUBSUB_VIEWER_ROLE = 'roles/pubsub.viewer'
|
||||
"""Role implying rights to access an object."""
|
||||
|
||||
PUBSUB_PUBLISHER_ROLE = 'roles/pubsub.publisher'
|
||||
"""Role implying rights to publish to a topic."""
|
||||
|
||||
PUBSUB_SUBSCRIBER_ROLE = 'roles/pubsub.subscriber'
|
||||
"""Role implying rights to subscribe to a topic."""
|
||||
|
||||
|
||||
# Pubsub-specific permissions
|
||||
|
||||
PUBSUB_TOPICS_CONSUME = 'pubsub.topics.consume'
|
||||
"""Permission: consume events from a subscription."""
|
||||
|
||||
PUBSUB_TOPICS_CREATE = 'pubsub.topics.create'
|
||||
"""Permission: create topics."""
|
||||
|
||||
PUBSUB_TOPICS_DELETE = 'pubsub.topics.delete'
|
||||
"""Permission: delete topics."""
|
||||
|
||||
PUBSUB_TOPICS_GET = 'pubsub.topics.get'
|
||||
"""Permission: retrieve topics."""
|
||||
|
||||
PUBSUB_TOPICS_GET_IAM_POLICY = 'pubsub.topics.getIamPolicy'
|
||||
"""Permission: retrieve subscription IAM policies."""
|
||||
|
||||
PUBSUB_TOPICS_LIST = 'pubsub.topics.list'
|
||||
"""Permission: list topics."""
|
||||
|
||||
PUBSUB_TOPICS_SET_IAM_POLICY = 'pubsub.topics.setIamPolicy'
|
||||
"""Permission: update subscription IAM policies."""
|
||||
|
||||
PUBSUB_SUBSCRIPTIONS_CONSUME = 'pubsub.subscriptions.consume'
|
||||
"""Permission: consume events from a subscription."""
|
||||
|
||||
PUBSUB_SUBSCRIPTIONS_CREATE = 'pubsub.subscriptions.create'
|
||||
"""Permission: create subscriptions."""
|
||||
|
||||
PUBSUB_SUBSCRIPTIONS_DELETE = 'pubsub.subscriptions.delete'
|
||||
"""Permission: delete subscriptions."""
|
||||
|
||||
PUBSUB_SUBSCRIPTIONS_GET = 'pubsub.subscriptions.get'
|
||||
"""Permission: retrieve subscriptions."""
|
||||
|
||||
PUBSUB_SUBSCRIPTIONS_GET_IAM_POLICY = 'pubsub.subscriptions.getIamPolicy'
|
||||
"""Permission: retrieve subscription IAM policies."""
|
||||
|
||||
PUBSUB_SUBSCRIPTIONS_LIST = 'pubsub.subscriptions.list'
|
||||
"""Permission: list subscriptions."""
|
||||
|
||||
PUBSUB_SUBSCRIPTIONS_SET_IAM_POLICY = 'pubsub.subscriptions.setIamPolicy'
|
||||
"""Permission: update subscription IAM policies."""
|
||||
|
||||
PUBSUB_SUBSCRIPTIONS_UPDATE = 'pubsub.subscriptions.update'
|
||||
"""Permission: update subscriptions."""
|
||||
|
||||
|
||||
class Policy(object):
|
||||
"""Combined IAM Policy / Bindings.
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/Shared.Types/Policy
|
||||
https://cloud.google.com/pubsub/reference/rest/Shared.Types/Binding
|
||||
|
||||
:type etag: string
|
||||
:param etag: ETag used to identify a unique of the policy
|
||||
|
||||
:type version: int
|
||||
:param version: unique version of the policy
|
||||
"""
|
||||
def __init__(self, etag=None, version=None):
|
||||
self.etag = etag
|
||||
self.version = version
|
||||
self.owners = set()
|
||||
self.editors = set()
|
||||
self.viewers = set()
|
||||
self.publishers = set()
|
||||
self.subscribers = set()
|
||||
|
||||
@staticmethod
|
||||
def user(email):
|
||||
"""Factory method for a user member.
|
||||
|
||||
:type email: string
|
||||
:param email: E-mail for this particular user.
|
||||
|
||||
:rtype: string
|
||||
:returns: A member string corresponding to the given user.
|
||||
"""
|
||||
return 'user:%s' % (email,)
|
||||
|
||||
@staticmethod
|
||||
def service_account(email):
|
||||
"""Factory method for a service account member.
|
||||
|
||||
:type email: string
|
||||
:param email: E-mail for this particular service account.
|
||||
|
||||
:rtype: string
|
||||
:returns: A member string corresponding to the given service account.
|
||||
"""
|
||||
return 'serviceAccount:%s' % (email,)
|
||||
|
||||
@staticmethod
|
||||
def group(email):
|
||||
"""Factory method for a group member.
|
||||
|
||||
:type email: string
|
||||
:param email: An id or e-mail for this particular group.
|
||||
|
||||
:rtype: string
|
||||
:returns: A member string corresponding to the given group.
|
||||
"""
|
||||
return 'group:%s' % (email,)
|
||||
|
||||
@staticmethod
|
||||
def domain(domain):
|
||||
"""Factory method for a domain member.
|
||||
|
||||
:type domain: string
|
||||
:param domain: The domain for this member.
|
||||
|
||||
:rtype: string
|
||||
:returns: A member string corresponding to the given domain.
|
||||
"""
|
||||
return 'domain:%s' % (domain,)
|
||||
|
||||
@staticmethod
|
||||
def all_users():
|
||||
"""Factory method for a member representing all users.
|
||||
|
||||
:rtype: string
|
||||
:returns: A member string representing all users.
|
||||
"""
|
||||
return 'allUsers'
|
||||
|
||||
@staticmethod
|
||||
def authenticated_users():
|
||||
"""Factory method for a member representing all authenticated users.
|
||||
|
||||
:rtype: string
|
||||
:returns: A member string representing all authenticated users.
|
||||
"""
|
||||
return 'allAuthenticatedUsers'
|
||||
|
||||
@classmethod
|
||||
def from_api_repr(cls, resource):
|
||||
"""Create a policy from the resource returned from the API.
|
||||
|
||||
:type resource: dict
|
||||
:param resource: resource returned from the ``getIamPolicy`` API.
|
||||
|
||||
:rtype: :class:`Policy`
|
||||
:returns: the parsed policy
|
||||
"""
|
||||
version = resource.get('version')
|
||||
etag = resource.get('etag')
|
||||
policy = cls(etag, version)
|
||||
for binding in resource.get('bindings', ()):
|
||||
role = binding['role']
|
||||
members = set(binding['members'])
|
||||
if role in (OWNER_ROLE, PUBSUB_ADMIN_ROLE):
|
||||
policy.owners |= members
|
||||
elif role in (EDITOR_ROLE, PUBSUB_EDITOR_ROLE):
|
||||
policy.editors |= members
|
||||
elif role in (VIEWER_ROLE, PUBSUB_VIEWER_ROLE):
|
||||
policy.viewers |= members
|
||||
elif role == PUBSUB_PUBLISHER_ROLE:
|
||||
policy.publishers |= members
|
||||
elif role == PUBSUB_SUBSCRIBER_ROLE:
|
||||
policy.subscribers |= members
|
||||
else:
|
||||
raise ValueError('Unknown role: %s' % (role,))
|
||||
return policy
|
||||
|
||||
def to_api_repr(self):
|
||||
"""Construct a Policy resource.
|
||||
|
||||
:rtype: dict
|
||||
:returns: a resource to be passed to the ``setIamPolicy`` API.
|
||||
"""
|
||||
resource = {}
|
||||
|
||||
if self.etag is not None:
|
||||
resource['etag'] = self.etag
|
||||
|
||||
if self.version is not None:
|
||||
resource['version'] = self.version
|
||||
|
||||
bindings = []
|
||||
|
||||
if self.owners:
|
||||
bindings.append(
|
||||
{'role': PUBSUB_ADMIN_ROLE,
|
||||
'members': sorted(self.owners)})
|
||||
|
||||
if self.editors:
|
||||
bindings.append(
|
||||
{'role': PUBSUB_EDITOR_ROLE,
|
||||
'members': sorted(self.editors)})
|
||||
|
||||
if self.viewers:
|
||||
bindings.append(
|
||||
{'role': PUBSUB_VIEWER_ROLE,
|
||||
'members': sorted(self.viewers)})
|
||||
|
||||
if self.publishers:
|
||||
bindings.append(
|
||||
{'role': PUBSUB_PUBLISHER_ROLE,
|
||||
'members': sorted(self.publishers)})
|
||||
|
||||
if self.subscribers:
|
||||
bindings.append(
|
||||
{'role': PUBSUB_SUBSCRIBER_ROLE,
|
||||
'members': sorted(self.subscribers)})
|
||||
|
||||
if bindings:
|
||||
resource['bindings'] = bindings
|
||||
|
||||
return resource
|
90
venv/Lib/site-packages/gcloud/pubsub/message.py
Normal file
90
venv/Lib/site-packages/gcloud/pubsub/message.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
# Copyright 2015 Google Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Define API Topics."""
|
||||
|
||||
import base64
|
||||
|
||||
from gcloud._helpers import _rfc3339_to_datetime
|
||||
|
||||
|
||||
class Message(object):
|
||||
"""Messages can be published to a topic and received by subscribers.
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/PubsubMessage
|
||||
|
||||
:type data: bytes
|
||||
:param data: the payload of the message
|
||||
|
||||
:type message_id: string
|
||||
:param message_id: An ID assigned to the message by the API.
|
||||
|
||||
:type attributes: dict or None
|
||||
:param attributes: Extra metadata associated by the publisher with the
|
||||
message.
|
||||
"""
|
||||
_service_timestamp = None
|
||||
|
||||
def __init__(self, data, message_id, attributes=None):
|
||||
self.data = data
|
||||
self.message_id = message_id
|
||||
self._attributes = attributes
|
||||
|
||||
@property
|
||||
def attributes(self):
|
||||
"""Lazily-constructed attribute dictionary"""
|
||||
if self._attributes is None:
|
||||
self._attributes = {}
|
||||
return self._attributes
|
||||
|
||||
@property
|
||||
def timestamp(self):
|
||||
"""Return sortable timestamp from attributes, if passed.
|
||||
|
||||
Allows sorting messages in publication order (assuming consistent
|
||||
clocks across all publishers).
|
||||
|
||||
:rtype: :class:`datetime.datetime`
|
||||
:returns: timestamp (in UTC timezone) parsed from RFC 3339 timestamp
|
||||
:raises: ValueError if timestamp not in ``attributes``, or if it does
|
||||
not match the RFC 3339 format.
|
||||
"""
|
||||
stamp = self.attributes.get('timestamp')
|
||||
if stamp is None:
|
||||
raise ValueError('No timestamp')
|
||||
return _rfc3339_to_datetime(stamp)
|
||||
|
||||
@property
|
||||
def service_timestamp(self):
|
||||
"""Return server-set timestamp.
|
||||
|
||||
:rtype: string
|
||||
:returns: timestamp (in UTC timezone) in RFC 3339 format
|
||||
"""
|
||||
return self._service_timestamp
|
||||
|
||||
@classmethod
|
||||
def from_api_repr(cls, api_repr):
|
||||
"""Factory: construct message from API representation.
|
||||
|
||||
:type api_repr: dict or None
|
||||
:param api_repr: The API representation of the message
|
||||
"""
|
||||
data = base64.b64decode(api_repr.get('data', b''))
|
||||
instance = cls(
|
||||
data=data, message_id=api_repr['messageId'],
|
||||
attributes=api_repr.get('attributes'))
|
||||
instance._service_timestamp = api_repr.get('publishTimestamp')
|
||||
return instance
|
422
venv/Lib/site-packages/gcloud/pubsub/subscription.py
Normal file
422
venv/Lib/site-packages/gcloud/pubsub/subscription.py
Normal file
|
@ -0,0 +1,422 @@
|
|||
# Copyright 2015 Google Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Define API Subscriptions."""
|
||||
|
||||
from gcloud.exceptions import NotFound
|
||||
from gcloud.pubsub._helpers import topic_name_from_path
|
||||
from gcloud.pubsub.iam import Policy
|
||||
from gcloud.pubsub.message import Message
|
||||
|
||||
|
||||
class Subscription(object):
|
||||
"""Subscriptions receive messages published to their topics.
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions
|
||||
|
||||
:type name: string
|
||||
:param name: the name of the subscription
|
||||
|
||||
:type topic: :class:`gcloud.pubsub.topic.Topic` or ``NoneType``
|
||||
:param topic: the topic to which the subscription belongs; if ``None``,
|
||||
the subscription's topic has been deleted.
|
||||
|
||||
:type ack_deadline: int
|
||||
:param ack_deadline: the deadline (in seconds) by which messages pulled
|
||||
from the back-end must be acknowledged.
|
||||
|
||||
:type push_endpoint: string
|
||||
:param push_endpoint: URL to which messages will be pushed by the back-end.
|
||||
If not set, the application must pull messages.
|
||||
|
||||
: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 topic.
|
||||
"""
|
||||
|
||||
_DELETED_TOPIC_PATH = '_deleted-topic_'
|
||||
"""Value of ``projects.subscriptions.topic`` when topic has been deleted.
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions#Subscription.FIELDS.topic
|
||||
"""
|
||||
|
||||
def __init__(self, name, topic=None, ack_deadline=None, push_endpoint=None,
|
||||
client=None):
|
||||
|
||||
if client is None and topic is None:
|
||||
raise TypeError("Pass only one of 'topic' or 'client'.")
|
||||
|
||||
if client is not None and topic is not None:
|
||||
raise TypeError("Pass only one of 'topic' or 'client'.")
|
||||
|
||||
self.name = name
|
||||
self.topic = topic
|
||||
self._client = client or topic._client
|
||||
self._project = self._client.project
|
||||
self.ack_deadline = ack_deadline
|
||||
self.push_endpoint = push_endpoint
|
||||
|
||||
@classmethod
|
||||
def from_api_repr(cls, resource, client, topics=None):
|
||||
"""Factory: construct a topic given its API representation
|
||||
|
||||
:type resource: dict
|
||||
:param resource: topic resource representation returned from the API
|
||||
|
||||
:type client: :class:`gcloud.pubsub.client.Client`
|
||||
:param client: Client which holds credentials and project
|
||||
configuration for a topic.
|
||||
|
||||
:type topics: dict or None
|
||||
:param topics: A mapping of topic names -> topics. If not passed,
|
||||
the subscription will have a newly-created topic.
|
||||
|
||||
:rtype: :class:`gcloud.pubsub.subscription.Subscription`
|
||||
:returns: Subscription parsed from ``resource``.
|
||||
"""
|
||||
if topics is None:
|
||||
topics = {}
|
||||
topic_path = resource['topic']
|
||||
if topic_path == cls._DELETED_TOPIC_PATH:
|
||||
topic = None
|
||||
else:
|
||||
topic = topics.get(topic_path)
|
||||
if topic is None:
|
||||
# NOTE: This duplicates behavior from Topic.from_api_repr to
|
||||
# avoid an import cycle.
|
||||
topic_name = topic_name_from_path(topic_path, client.project)
|
||||
topic = topics[topic_path] = client.topic(topic_name)
|
||||
_, _, _, name = resource['name'].split('/')
|
||||
ack_deadline = resource.get('ackDeadlineSeconds')
|
||||
push_config = resource.get('pushConfig', {})
|
||||
push_endpoint = push_config.get('pushEndpoint')
|
||||
if topic is None:
|
||||
return cls(name, ack_deadline=ack_deadline,
|
||||
push_endpoint=push_endpoint, client=client)
|
||||
return cls(name, topic, ack_deadline, push_endpoint)
|
||||
|
||||
@property
|
||||
def project(self):
|
||||
"""Project bound to the subscription."""
|
||||
return self._client.project
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
"""Fully-qualified name used in subscription APIs"""
|
||||
return 'projects/%s/subscriptions/%s' % (self.project, self.name)
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
"""URL path for the subscription's APIs"""
|
||||
return '/%s' % (self.full_name,)
|
||||
|
||||
def _require_client(self, client):
|
||||
"""Check client or verify over-ride.
|
||||
|
||||
: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 topic of the
|
||||
current subscription.
|
||||
|
||||
:rtype: :class:`gcloud.pubsub.client.Client`
|
||||
:returns: The client passed in or the currently bound client.
|
||||
"""
|
||||
if client is None:
|
||||
client = self._client
|
||||
return client
|
||||
|
||||
def create(self, client=None):
|
||||
"""API call: create the subscription via a PUT request
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/create
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START subscription_create]
|
||||
:end-before: [END subscription_create]
|
||||
|
||||
: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 subscription's topic.
|
||||
"""
|
||||
client = self._require_client(client)
|
||||
api = client.subscriber_api
|
||||
api.subscription_create(
|
||||
self.full_name, self.topic.full_name, self.ack_deadline,
|
||||
self.push_endpoint)
|
||||
|
||||
def exists(self, client=None):
|
||||
"""API call: test existence of the subscription via a GET request
|
||||
|
||||
See
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/get
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START subscription_exists]
|
||||
:end-before: [END subscription_exists]
|
||||
|
||||
: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 subscription's topic.
|
||||
"""
|
||||
client = self._require_client(client)
|
||||
api = client.subscriber_api
|
||||
try:
|
||||
api.subscription_get(self.full_name)
|
||||
except NotFound:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def reload(self, client=None):
|
||||
"""API call: sync local subscription configuration via a GET request
|
||||
|
||||
See
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/get
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START subscription_reload]
|
||||
:end-before: [END subscription_reload]
|
||||
|
||||
: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 subscription's topic.
|
||||
"""
|
||||
client = self._require_client(client)
|
||||
api = client.subscriber_api
|
||||
data = api.subscription_get(self.full_name)
|
||||
self.ack_deadline = data.get('ackDeadlineSeconds')
|
||||
push_config = data.get('pushConfig', {})
|
||||
self.push_endpoint = push_config.get('pushEndpoint')
|
||||
|
||||
def delete(self, client=None):
|
||||
"""API call: delete the subscription via a DELETE request.
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/delete
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START subscription_delete]
|
||||
:end-before: [END subscription_delete]
|
||||
|
||||
: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 subscription's topic.
|
||||
"""
|
||||
client = self._require_client(client)
|
||||
api = client.subscriber_api
|
||||
api.subscription_delete(self.full_name)
|
||||
|
||||
def modify_push_configuration(self, push_endpoint, client=None):
|
||||
"""API call: update the push endpoint for the subscription.
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/modifyPushConfig
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START subscription_push_pull]
|
||||
:end-before: [END subscription_push_pull]
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START subscription_pull_push]
|
||||
:end-before: [END subscription_pull_push]
|
||||
|
||||
:type push_endpoint: string
|
||||
:param push_endpoint: URL to which messages will be pushed by the
|
||||
back-end. If None, the application must pull
|
||||
messages.
|
||||
|
||||
: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 subscription's topic.
|
||||
"""
|
||||
client = self._require_client(client)
|
||||
api = client.subscriber_api
|
||||
api.subscription_modify_push_config(self.full_name, push_endpoint)
|
||||
self.push_endpoint = push_endpoint
|
||||
|
||||
def pull(self, return_immediately=False, max_messages=1, client=None):
|
||||
"""API call: retrieve messages for the subscription.
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/pull
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START subscription_pull]
|
||||
:end-before: [END subscription_pull]
|
||||
|
||||
:type return_immediately: boolean
|
||||
:param return_immediately: if True, the back-end returns even if no
|
||||
messages are available; if False, the API
|
||||
call blocks until one or more messages are
|
||||
available.
|
||||
|
||||
:type max_messages: int
|
||||
:param max_messages: the maximum number of messages to return.
|
||||
|
||||
: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 subscription's topic.
|
||||
|
||||
:rtype: list of (ack_id, message) tuples
|
||||
:returns: sequence of tuples: ``ack_id`` is the ID to be used in a
|
||||
subsequent call to :meth:`acknowledge`, and ``message``
|
||||
is an instance of :class:`gcloud.pubsub.message.Message`.
|
||||
"""
|
||||
client = self._require_client(client)
|
||||
api = client.subscriber_api
|
||||
response = api.subscription_pull(
|
||||
self.full_name, return_immediately, max_messages)
|
||||
return [(info['ackId'], Message.from_api_repr(info['message']))
|
||||
for info in response]
|
||||
|
||||
def acknowledge(self, ack_ids, client=None):
|
||||
"""API call: acknowledge retrieved messages for the subscription.
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/acknowledge
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START subscription_acknowledge]
|
||||
:end-before: [END subscription_acknowledge]
|
||||
|
||||
:type ack_ids: list of string
|
||||
:param ack_ids: ack IDs of messages being acknowledged
|
||||
|
||||
: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 subscription's topic.
|
||||
"""
|
||||
client = self._require_client(client)
|
||||
api = client.subscriber_api
|
||||
api.subscription_acknowledge(self.full_name, ack_ids)
|
||||
|
||||
def modify_ack_deadline(self, ack_ids, ack_deadline, client=None):
|
||||
"""API call: update acknowledgement deadline for a retrieved message.
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/modifyAckDeadline
|
||||
|
||||
:type ack_ids: list of string
|
||||
:param ack_ids: ack IDs of messages being updated
|
||||
|
||||
:type ack_deadline: int
|
||||
:param ack_deadline: new deadline for the message, in seconds
|
||||
|
||||
: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 subscription's topic.
|
||||
"""
|
||||
client = self._require_client(client)
|
||||
api = client.subscriber_api
|
||||
api.subscription_modify_ack_deadline(
|
||||
self.full_name, ack_ids, ack_deadline)
|
||||
|
||||
def get_iam_policy(self, client=None):
|
||||
"""Fetch the IAM policy for the subscription.
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/getIamPolicy
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START subscription_get_iam_policy]
|
||||
:end-before: [END subscription_get_iam_policy]
|
||||
|
||||
: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 subscription's topic.
|
||||
|
||||
:rtype: :class:`gcloud.pubsub.iam.Policy`
|
||||
:returns: policy created from the resource returned by the
|
||||
``getIamPolicy`` API request.
|
||||
"""
|
||||
client = self._require_client(client)
|
||||
api = client.iam_policy_api
|
||||
resp = api.get_iam_policy(self.full_name)
|
||||
return Policy.from_api_repr(resp)
|
||||
|
||||
def set_iam_policy(self, policy, client=None):
|
||||
"""Update the IAM policy for the subscription.
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/setIamPolicy
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START subscription_set_iam_policy]
|
||||
:end-before: [END subscription_set_iam_policy]
|
||||
|
||||
:type policy: :class:`gcloud.pubsub.iam.Policy`
|
||||
:param policy: the new policy, typically fetched via
|
||||
:meth:`get_iam_policy` and updated in place.
|
||||
|
||||
: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 subscription's topic.
|
||||
|
||||
:rtype: :class:`gcloud.pubsub.iam.Policy`
|
||||
:returns: updated policy created from the resource returned by the
|
||||
``setIamPolicy`` API request.
|
||||
"""
|
||||
client = self._require_client(client)
|
||||
api = client.iam_policy_api
|
||||
resource = policy.to_api_repr()
|
||||
resp = api.set_iam_policy(self.full_name, resource)
|
||||
return Policy.from_api_repr(resp)
|
||||
|
||||
def check_iam_permissions(self, permissions, client=None):
|
||||
"""Verify permissions allowed for the current user.
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.subscriptions/testIamPermissions
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START subscription_check_iam_permissions]
|
||||
:end-before: [END subscription_check_iam_permissions]
|
||||
|
||||
:type permissions: list of string
|
||||
:param permissions: list of permissions to be tested
|
||||
|
||||
: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 subscription's topic.
|
||||
|
||||
:rtype: sequence of string
|
||||
:returns: subset of ``permissions`` allowed by current IAM policy.
|
||||
"""
|
||||
client = self._require_client(client)
|
||||
api = client.iam_policy_api
|
||||
return api.test_iam_permissions(
|
||||
self.full_name, list(permissions))
|
944
venv/Lib/site-packages/gcloud/pubsub/test__gax.py
Normal file
944
venv/Lib/site-packages/gcloud/pubsub/test__gax.py
Normal file
|
@ -0,0 +1,944 @@
|
|||
# Copyright 2016 Google Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import unittest2
|
||||
|
||||
|
||||
try:
|
||||
# pylint: disable=unused-import
|
||||
import gcloud.pubsub._gax
|
||||
# pylint: enable=unused-import
|
||||
except ImportError: # pragma: NO COVER
|
||||
_HAVE_GAX = False
|
||||
else:
|
||||
_HAVE_GAX = True
|
||||
|
||||
|
||||
class _Base(object):
|
||||
PROJECT = 'PROJECT'
|
||||
PROJECT_PATH = 'projects/%s' % (PROJECT,)
|
||||
LIST_TOPICS_PATH = '%s/topics' % (PROJECT_PATH,)
|
||||
TOPIC_NAME = 'topic_name'
|
||||
TOPIC_PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME)
|
||||
LIST_TOPIC_SUBSCRIPTIONS_PATH = '%s/subscriptions' % (TOPIC_PATH,)
|
||||
SUB_NAME = 'sub_name'
|
||||
SUB_PATH = '%s/subscriptions/%s' % (TOPIC_PATH, SUB_NAME)
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
|
||||
@unittest2.skipUnless(_HAVE_GAX, 'No gax-python')
|
||||
class Test_PublisherAPI(_Base, unittest2.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from gcloud.pubsub._gax import _PublisherAPI
|
||||
return _PublisherAPI
|
||||
|
||||
def test_ctor(self):
|
||||
gax_api = _GAXPublisherAPI()
|
||||
api = self._makeOne(gax_api)
|
||||
self.assertTrue(api._gax_api is gax_api)
|
||||
|
||||
def test_list_topics_no_paging(self):
|
||||
from google.gax import INITIAL_PAGE
|
||||
from gcloud._testing import _GAXPageIterator
|
||||
TOKEN = 'TOKEN'
|
||||
response = _GAXPageIterator([_TopicPB(self.TOPIC_PATH)], TOKEN)
|
||||
gax_api = _GAXPublisherAPI(_list_topics_response=response)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
topics, next_token = api.list_topics(self.PROJECT)
|
||||
|
||||
self.assertEqual(len(topics), 1)
|
||||
topic = topics[0]
|
||||
self.assertIsInstance(topic, dict)
|
||||
self.assertEqual(topic['name'], self.TOPIC_PATH)
|
||||
self.assertEqual(next_token, TOKEN)
|
||||
|
||||
name, page_size, options = gax_api._list_topics_called_with
|
||||
self.assertEqual(name, self.PROJECT_PATH)
|
||||
self.assertEqual(page_size, 0)
|
||||
self.assertTrue(options.page_token is INITIAL_PAGE)
|
||||
|
||||
def test_list_topics_with_paging(self):
|
||||
from gcloud._testing import _GAXPageIterator
|
||||
SIZE = 23
|
||||
TOKEN = 'TOKEN'
|
||||
NEW_TOKEN = 'NEW_TOKEN'
|
||||
response = _GAXPageIterator(
|
||||
[_TopicPB(self.TOPIC_PATH)], NEW_TOKEN)
|
||||
gax_api = _GAXPublisherAPI(_list_topics_response=response)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
topics, next_token = api.list_topics(
|
||||
self.PROJECT, page_size=SIZE, page_token=TOKEN)
|
||||
|
||||
self.assertEqual(len(topics), 1)
|
||||
topic = topics[0]
|
||||
self.assertIsInstance(topic, dict)
|
||||
self.assertEqual(topic['name'], self.TOPIC_PATH)
|
||||
self.assertEqual(next_token, NEW_TOKEN)
|
||||
|
||||
name, page_size, options = gax_api._list_topics_called_with
|
||||
self.assertEqual(name, self.PROJECT_PATH)
|
||||
self.assertEqual(page_size, SIZE)
|
||||
self.assertEqual(options.page_token, TOKEN)
|
||||
|
||||
def test_topic_create(self):
|
||||
topic_pb = _TopicPB(self.TOPIC_PATH)
|
||||
gax_api = _GAXPublisherAPI(_create_topic_response=topic_pb)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
resource = api.topic_create(self.TOPIC_PATH)
|
||||
|
||||
self.assertEqual(resource, {'name': self.TOPIC_PATH})
|
||||
topic_path, options = gax_api._create_topic_called_with
|
||||
self.assertEqual(topic_path, self.TOPIC_PATH)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_topic_create_already_exists(self):
|
||||
from gcloud.exceptions import Conflict
|
||||
gax_api = _GAXPublisherAPI(_create_topic_conflict=True)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(Conflict):
|
||||
api.topic_create(self.TOPIC_PATH)
|
||||
|
||||
topic_path, options = gax_api._create_topic_called_with
|
||||
self.assertEqual(topic_path, self.TOPIC_PATH)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_topic_create_error(self):
|
||||
from google.gax.errors import GaxError
|
||||
gax_api = _GAXPublisherAPI(_random_gax_error=True)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(GaxError):
|
||||
api.topic_create(self.TOPIC_PATH)
|
||||
|
||||
topic_path, options = gax_api._create_topic_called_with
|
||||
self.assertEqual(topic_path, self.TOPIC_PATH)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_topic_get_hit(self):
|
||||
topic_pb = _TopicPB(self.TOPIC_PATH)
|
||||
gax_api = _GAXPublisherAPI(_get_topic_response=topic_pb)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
resource = api.topic_get(self.TOPIC_PATH)
|
||||
|
||||
self.assertEqual(resource, {'name': self.TOPIC_PATH})
|
||||
topic_path, options = gax_api._get_topic_called_with
|
||||
self.assertEqual(topic_path, self.TOPIC_PATH)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_topic_get_miss(self):
|
||||
from gcloud.exceptions import NotFound
|
||||
gax_api = _GAXPublisherAPI()
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(NotFound):
|
||||
api.topic_get(self.TOPIC_PATH)
|
||||
|
||||
topic_path, options = gax_api._get_topic_called_with
|
||||
self.assertEqual(topic_path, self.TOPIC_PATH)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_topic_get_error(self):
|
||||
from google.gax.errors import GaxError
|
||||
gax_api = _GAXPublisherAPI(_random_gax_error=True)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(GaxError):
|
||||
api.topic_get(self.TOPIC_PATH)
|
||||
|
||||
topic_path, options = gax_api._get_topic_called_with
|
||||
self.assertEqual(topic_path, self.TOPIC_PATH)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_topic_delete_hit(self):
|
||||
gax_api = _GAXPublisherAPI(_delete_topic_ok=True)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
api.topic_delete(self.TOPIC_PATH)
|
||||
|
||||
topic_path, options = gax_api._delete_topic_called_with
|
||||
self.assertEqual(topic_path, self.TOPIC_PATH)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_topic_delete_miss(self):
|
||||
from gcloud.exceptions import NotFound
|
||||
gax_api = _GAXPublisherAPI(_delete_topic_ok=False)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(NotFound):
|
||||
api.topic_delete(self.TOPIC_PATH)
|
||||
|
||||
topic_path, options = gax_api._delete_topic_called_with
|
||||
self.assertEqual(topic_path, self.TOPIC_PATH)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_topic_delete_error(self):
|
||||
from google.gax.errors import GaxError
|
||||
gax_api = _GAXPublisherAPI(_random_gax_error=True)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(GaxError):
|
||||
api.topic_delete(self.TOPIC_PATH)
|
||||
|
||||
topic_path, options = gax_api._delete_topic_called_with
|
||||
self.assertEqual(topic_path, self.TOPIC_PATH)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_topic_publish_hit(self):
|
||||
import base64
|
||||
PAYLOAD = b'This is the message text'
|
||||
B64 = base64.b64encode(PAYLOAD).decode('ascii')
|
||||
MSGID = 'DEADBEEF'
|
||||
MESSAGE = {'data': B64, 'attributes': {}}
|
||||
response = _PublishResponsePB([MSGID])
|
||||
gax_api = _GAXPublisherAPI(_publish_response=response)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
resource = api.topic_publish(self.TOPIC_PATH, [MESSAGE])
|
||||
|
||||
self.assertEqual(resource, [MSGID])
|
||||
topic_path, message_pbs, options = gax_api._publish_called_with
|
||||
self.assertEqual(topic_path, self.TOPIC_PATH)
|
||||
message_pb, = message_pbs
|
||||
self.assertEqual(message_pb.data, B64)
|
||||
self.assertEqual(message_pb.attributes, {})
|
||||
self.assertEqual(options.is_bundling, False)
|
||||
|
||||
def test_topic_publish_miss_w_attrs_w_bytes_payload(self):
|
||||
import base64
|
||||
from gcloud.exceptions import NotFound
|
||||
PAYLOAD = u'This is the message text'
|
||||
B64 = base64.b64encode(PAYLOAD)
|
||||
MESSAGE = {'data': B64, 'attributes': {'foo': 'bar'}}
|
||||
gax_api = _GAXPublisherAPI()
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(NotFound):
|
||||
api.topic_publish(self.TOPIC_PATH, [MESSAGE])
|
||||
|
||||
topic_path, message_pbs, options = gax_api._publish_called_with
|
||||
self.assertEqual(topic_path, self.TOPIC_PATH)
|
||||
message_pb, = message_pbs
|
||||
self.assertEqual(message_pb.data, B64)
|
||||
self.assertEqual(message_pb.attributes, {'foo': 'bar'})
|
||||
self.assertEqual(options.is_bundling, False)
|
||||
|
||||
def test_topic_publish_error(self):
|
||||
import base64
|
||||
from google.gax.errors import GaxError
|
||||
PAYLOAD = b'This is the message text'
|
||||
B64 = base64.b64encode(PAYLOAD).decode('ascii')
|
||||
MESSAGE = {'data': B64, 'attributes': {}}
|
||||
gax_api = _GAXPublisherAPI(_random_gax_error=True)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(GaxError):
|
||||
api.topic_publish(self.TOPIC_PATH, [MESSAGE])
|
||||
|
||||
topic_path, message_pbs, options = gax_api._publish_called_with
|
||||
self.assertEqual(topic_path, self.TOPIC_PATH)
|
||||
message_pb, = message_pbs
|
||||
self.assertEqual(message_pb.data, B64)
|
||||
self.assertEqual(message_pb.attributes, {})
|
||||
self.assertEqual(options.is_bundling, False)
|
||||
|
||||
def test_topic_list_subscriptions_no_paging(self):
|
||||
from google.gax import INITIAL_PAGE
|
||||
from gcloud._testing import _GAXPageIterator
|
||||
response = _GAXPageIterator([
|
||||
{'name': self.SUB_PATH, 'topic': self.TOPIC_PATH}], None)
|
||||
gax_api = _GAXPublisherAPI(_list_topic_subscriptions_response=response)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
subscriptions, next_token = api.topic_list_subscriptions(
|
||||
self.TOPIC_PATH)
|
||||
|
||||
self.assertEqual(len(subscriptions), 1)
|
||||
subscription = subscriptions[0]
|
||||
self.assertIsInstance(subscription, dict)
|
||||
self.assertEqual(subscription['name'], self.SUB_PATH)
|
||||
self.assertEqual(subscription['topic'], self.TOPIC_PATH)
|
||||
self.assertEqual(next_token, None)
|
||||
|
||||
topic_path, page_size, options = (
|
||||
gax_api._list_topic_subscriptions_called_with)
|
||||
self.assertEqual(topic_path, self.TOPIC_PATH)
|
||||
self.assertEqual(page_size, 0)
|
||||
self.assertTrue(options.page_token is INITIAL_PAGE)
|
||||
|
||||
def test_topic_list_subscriptions_with_paging(self):
|
||||
from gcloud._testing import _GAXPageIterator
|
||||
SIZE = 23
|
||||
TOKEN = 'TOKEN'
|
||||
NEW_TOKEN = 'NEW_TOKEN'
|
||||
response = _GAXPageIterator([
|
||||
{'name': self.SUB_PATH, 'topic': self.TOPIC_PATH}], NEW_TOKEN)
|
||||
gax_api = _GAXPublisherAPI(_list_topic_subscriptions_response=response)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
subscriptions, next_token = api.topic_list_subscriptions(
|
||||
self.TOPIC_PATH, page_size=SIZE, page_token=TOKEN)
|
||||
|
||||
self.assertEqual(len(subscriptions), 1)
|
||||
subscription = subscriptions[0]
|
||||
self.assertIsInstance(subscription, dict)
|
||||
self.assertEqual(subscription['name'], self.SUB_PATH)
|
||||
self.assertEqual(subscription['topic'], self.TOPIC_PATH)
|
||||
self.assertEqual(next_token, NEW_TOKEN)
|
||||
|
||||
name, page_size, options = (
|
||||
gax_api._list_topic_subscriptions_called_with)
|
||||
self.assertEqual(name, self.TOPIC_PATH)
|
||||
self.assertEqual(page_size, SIZE)
|
||||
self.assertEqual(options.page_token, TOKEN)
|
||||
|
||||
def test_topic_list_subscriptions_miss(self):
|
||||
from google.gax import INITIAL_PAGE
|
||||
from gcloud.exceptions import NotFound
|
||||
gax_api = _GAXPublisherAPI()
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(NotFound):
|
||||
api.topic_list_subscriptions(self.TOPIC_PATH)
|
||||
|
||||
topic_path, page_size, options = (
|
||||
gax_api._list_topic_subscriptions_called_with)
|
||||
self.assertEqual(topic_path, self.TOPIC_PATH)
|
||||
self.assertEqual(page_size, 0)
|
||||
self.assertTrue(options.page_token is INITIAL_PAGE)
|
||||
|
||||
def test_topic_list_subscriptions_error(self):
|
||||
from google.gax import INITIAL_PAGE
|
||||
from google.gax.errors import GaxError
|
||||
gax_api = _GAXPublisherAPI(_random_gax_error=True)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(GaxError):
|
||||
api.topic_list_subscriptions(self.TOPIC_PATH)
|
||||
|
||||
topic_path, page_size, options = (
|
||||
gax_api._list_topic_subscriptions_called_with)
|
||||
self.assertEqual(topic_path, self.TOPIC_PATH)
|
||||
self.assertEqual(page_size, 0)
|
||||
self.assertTrue(options.page_token is INITIAL_PAGE)
|
||||
|
||||
|
||||
@unittest2.skipUnless(_HAVE_GAX, 'No gax-python')
|
||||
class Test_SubscriberAPI(_Base, unittest2.TestCase):
|
||||
|
||||
PUSH_ENDPOINT = 'https://api.example.com/push'
|
||||
|
||||
def _getTargetClass(self):
|
||||
from gcloud.pubsub._gax import _SubscriberAPI
|
||||
return _SubscriberAPI
|
||||
|
||||
def test_ctor(self):
|
||||
gax_api = _GAXSubscriberAPI()
|
||||
api = self._makeOne(gax_api)
|
||||
self.assertTrue(api._gax_api is gax_api)
|
||||
|
||||
def test_list_subscriptions_no_paging(self):
|
||||
from google.gax import INITIAL_PAGE
|
||||
from gcloud._testing import _GAXPageIterator
|
||||
response = _GAXPageIterator([_SubscriptionPB(
|
||||
self.SUB_PATH, self.TOPIC_PATH, self.PUSH_ENDPOINT, 0)], None)
|
||||
gax_api = _GAXSubscriberAPI(_list_subscriptions_response=response)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
subscriptions, next_token = api.list_subscriptions(self.PROJECT)
|
||||
|
||||
self.assertEqual(len(subscriptions), 1)
|
||||
subscription = subscriptions[0]
|
||||
self.assertIsInstance(subscription, dict)
|
||||
self.assertEqual(subscription['name'], self.SUB_PATH)
|
||||
self.assertEqual(subscription['topic'], self.TOPIC_PATH)
|
||||
self.assertEqual(subscription['pushConfig'],
|
||||
{'pushEndpoint': self.PUSH_ENDPOINT})
|
||||
self.assertEqual(subscription['ackDeadlineSeconds'], 0)
|
||||
self.assertEqual(next_token, None)
|
||||
|
||||
name, page_size, options = gax_api._list_subscriptions_called_with
|
||||
self.assertEqual(name, self.PROJECT_PATH)
|
||||
self.assertEqual(page_size, 0)
|
||||
self.assertTrue(options.page_token is INITIAL_PAGE)
|
||||
|
||||
def test_list_subscriptions_with_paging(self):
|
||||
from gcloud._testing import _GAXPageIterator
|
||||
SIZE = 23
|
||||
TOKEN = 'TOKEN'
|
||||
NEW_TOKEN = 'NEW_TOKEN'
|
||||
response = _GAXPageIterator([_SubscriptionPB(
|
||||
self.SUB_PATH, self.TOPIC_PATH, self.PUSH_ENDPOINT, 0)], NEW_TOKEN)
|
||||
gax_api = _GAXSubscriberAPI(_list_subscriptions_response=response)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
subscriptions, next_token = api.list_subscriptions(
|
||||
self.PROJECT, page_size=SIZE, page_token=TOKEN)
|
||||
|
||||
self.assertEqual(len(subscriptions), 1)
|
||||
subscription = subscriptions[0]
|
||||
self.assertIsInstance(subscription, dict)
|
||||
self.assertEqual(subscription['name'], self.SUB_PATH)
|
||||
self.assertEqual(subscription['topic'], self.TOPIC_PATH)
|
||||
self.assertEqual(subscription['pushConfig'],
|
||||
{'pushEndpoint': self.PUSH_ENDPOINT})
|
||||
self.assertEqual(subscription['ackDeadlineSeconds'], 0)
|
||||
self.assertEqual(next_token, NEW_TOKEN)
|
||||
|
||||
name, page_size, options = gax_api._list_subscriptions_called_with
|
||||
self.assertEqual(name, self.PROJECT_PATH)
|
||||
self.assertEqual(page_size, 23)
|
||||
self.assertEqual(options.page_token, TOKEN)
|
||||
|
||||
def test_subscription_create(self):
|
||||
sub_pb = _SubscriptionPB(self.SUB_PATH, self.TOPIC_PATH, '', 0)
|
||||
gax_api = _GAXSubscriberAPI(_create_subscription_response=sub_pb)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
resource = api.subscription_create(self.SUB_PATH, self.TOPIC_PATH)
|
||||
|
||||
expected = {
|
||||
'name': self.SUB_PATH,
|
||||
'topic': self.TOPIC_PATH,
|
||||
'ackDeadlineSeconds': 0,
|
||||
}
|
||||
self.assertEqual(resource, expected)
|
||||
name, topic, push_config, ack_deadline, options = (
|
||||
gax_api._create_subscription_called_with)
|
||||
self.assertEqual(name, self.SUB_PATH)
|
||||
self.assertEqual(topic, self.TOPIC_PATH)
|
||||
self.assertEqual(push_config, None)
|
||||
self.assertEqual(ack_deadline, 0)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_subscription_create_already_exists(self):
|
||||
from gcloud.exceptions import Conflict
|
||||
DEADLINE = 600
|
||||
gax_api = _GAXSubscriberAPI(_create_subscription_conflict=True)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(Conflict):
|
||||
api.subscription_create(
|
||||
self.SUB_PATH, self.TOPIC_PATH, DEADLINE, self.PUSH_ENDPOINT)
|
||||
|
||||
name, topic, push_config, ack_deadline, options = (
|
||||
gax_api._create_subscription_called_with)
|
||||
self.assertEqual(name, self.SUB_PATH)
|
||||
self.assertEqual(topic, self.TOPIC_PATH)
|
||||
self.assertEqual(push_config.push_endpoint, self.PUSH_ENDPOINT)
|
||||
self.assertEqual(ack_deadline, DEADLINE)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_subscription_create_error(self):
|
||||
from google.gax.errors import GaxError
|
||||
gax_api = _GAXSubscriberAPI(_random_gax_error=True)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(GaxError):
|
||||
api.subscription_create(self.SUB_PATH, self.TOPIC_PATH)
|
||||
|
||||
name, topic, push_config, ack_deadline, options = (
|
||||
gax_api._create_subscription_called_with)
|
||||
self.assertEqual(name, self.SUB_PATH)
|
||||
self.assertEqual(topic, self.TOPIC_PATH)
|
||||
self.assertEqual(push_config, None)
|
||||
self.assertEqual(ack_deadline, 0)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_subscription_get_hit(self):
|
||||
sub_pb = _SubscriptionPB(
|
||||
self.SUB_PATH, self.TOPIC_PATH, self.PUSH_ENDPOINT, 0)
|
||||
gax_api = _GAXSubscriberAPI(_get_subscription_response=sub_pb)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
resource = api.subscription_get(self.SUB_PATH)
|
||||
|
||||
expected = {
|
||||
'name': self.SUB_PATH,
|
||||
'topic': self.TOPIC_PATH,
|
||||
'ackDeadlineSeconds': 0,
|
||||
'pushConfig': {
|
||||
'pushEndpoint': self.PUSH_ENDPOINT,
|
||||
},
|
||||
}
|
||||
self.assertEqual(resource, expected)
|
||||
sub_path, options = gax_api._get_subscription_called_with
|
||||
self.assertEqual(sub_path, self.SUB_PATH)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_subscription_get_miss(self):
|
||||
from gcloud.exceptions import NotFound
|
||||
gax_api = _GAXSubscriberAPI()
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(NotFound):
|
||||
api.subscription_get(self.SUB_PATH)
|
||||
|
||||
sub_path, options = gax_api._get_subscription_called_with
|
||||
self.assertEqual(sub_path, self.SUB_PATH)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_subscription_get_error(self):
|
||||
from google.gax.errors import GaxError
|
||||
gax_api = _GAXSubscriberAPI(_random_gax_error=True)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(GaxError):
|
||||
api.subscription_get(self.SUB_PATH)
|
||||
|
||||
sub_path, options = gax_api._get_subscription_called_with
|
||||
self.assertEqual(sub_path, self.SUB_PATH)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_subscription_delete_hit(self):
|
||||
gax_api = _GAXSubscriberAPI(_delete_subscription_ok=True)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
api.subscription_delete(self.TOPIC_PATH)
|
||||
|
||||
sub_path, options = gax_api._delete_subscription_called_with
|
||||
self.assertEqual(sub_path, self.TOPIC_PATH)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_subscription_delete_miss(self):
|
||||
from gcloud.exceptions import NotFound
|
||||
gax_api = _GAXSubscriberAPI(_delete_subscription_ok=False)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(NotFound):
|
||||
api.subscription_delete(self.TOPIC_PATH)
|
||||
|
||||
sub_path, options = gax_api._delete_subscription_called_with
|
||||
self.assertEqual(sub_path, self.TOPIC_PATH)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_subscription_delete_error(self):
|
||||
from google.gax.errors import GaxError
|
||||
gax_api = _GAXSubscriberAPI(_random_gax_error=True)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(GaxError):
|
||||
api.subscription_delete(self.TOPIC_PATH)
|
||||
|
||||
sub_path, options = gax_api._delete_subscription_called_with
|
||||
self.assertEqual(sub_path, self.TOPIC_PATH)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_subscription_modify_push_config_hit(self):
|
||||
gax_api = _GAXSubscriberAPI(_modify_push_config_ok=True)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
api.subscription_modify_push_config(self.SUB_PATH, self.PUSH_ENDPOINT)
|
||||
|
||||
sub_path, config, options = gax_api._modify_push_config_called_with
|
||||
self.assertEqual(sub_path, self.SUB_PATH)
|
||||
self.assertEqual(config.push_endpoint, self.PUSH_ENDPOINT)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_subscription_modify_push_config_miss(self):
|
||||
from gcloud.exceptions import NotFound
|
||||
gax_api = _GAXSubscriberAPI()
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(NotFound):
|
||||
api.subscription_modify_push_config(
|
||||
self.SUB_PATH, self.PUSH_ENDPOINT)
|
||||
|
||||
sub_path, config, options = gax_api._modify_push_config_called_with
|
||||
self.assertEqual(sub_path, self.SUB_PATH)
|
||||
self.assertEqual(config.push_endpoint, self.PUSH_ENDPOINT)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_subscription_modify_push_config_error(self):
|
||||
from google.gax.errors import GaxError
|
||||
gax_api = _GAXSubscriberAPI(_random_gax_error=True)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(GaxError):
|
||||
api.subscription_modify_push_config(
|
||||
self.SUB_PATH, self.PUSH_ENDPOINT)
|
||||
|
||||
sub_path, config, options = gax_api._modify_push_config_called_with
|
||||
self.assertEqual(sub_path, self.SUB_PATH)
|
||||
self.assertEqual(config.push_endpoint, self.PUSH_ENDPOINT)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_subscription_pull_explicit(self):
|
||||
import base64
|
||||
PAYLOAD = b'This is the message text'
|
||||
B64 = base64.b64encode(PAYLOAD).decode('ascii')
|
||||
ACK_ID = 'DEADBEEF'
|
||||
MSG_ID = 'BEADCAFE'
|
||||
MESSAGE = {'messageId': MSG_ID, 'data': B64, 'attributes': {'a': 'b'}}
|
||||
RECEIVED = [{'ackId': ACK_ID, 'message': MESSAGE}]
|
||||
message_pb = _PubsubMessagePB(MSG_ID, B64, {'a': 'b'})
|
||||
response_pb = _PullResponsePB([_ReceivedMessagePB(ACK_ID, message_pb)])
|
||||
gax_api = _GAXSubscriberAPI(_pull_response=response_pb)
|
||||
api = self._makeOne(gax_api)
|
||||
MAX_MESSAGES = 10
|
||||
|
||||
received = api.subscription_pull(
|
||||
self.SUB_PATH, return_immediately=True, max_messages=MAX_MESSAGES)
|
||||
|
||||
self.assertEqual(received, RECEIVED)
|
||||
sub_path, max_messages, return_immediately, options = (
|
||||
gax_api._pull_called_with)
|
||||
self.assertEqual(sub_path, self.SUB_PATH)
|
||||
self.assertEqual(max_messages, MAX_MESSAGES)
|
||||
self.assertTrue(return_immediately)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_subscription_pull_defaults_miss(self):
|
||||
from gcloud.exceptions import NotFound
|
||||
gax_api = _GAXSubscriberAPI()
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(NotFound):
|
||||
api.subscription_pull(self.SUB_PATH)
|
||||
|
||||
sub_path, max_messages, return_immediately, options = (
|
||||
gax_api._pull_called_with)
|
||||
self.assertEqual(sub_path, self.SUB_PATH)
|
||||
self.assertEqual(max_messages, 1)
|
||||
self.assertFalse(return_immediately)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_subscription_pull_defaults_error(self):
|
||||
from google.gax.errors import GaxError
|
||||
gax_api = _GAXSubscriberAPI(_random_gax_error=True)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(GaxError):
|
||||
api.subscription_pull(self.SUB_PATH)
|
||||
|
||||
sub_path, max_messages, return_immediately, options = (
|
||||
gax_api._pull_called_with)
|
||||
self.assertEqual(sub_path, self.SUB_PATH)
|
||||
self.assertEqual(max_messages, 1)
|
||||
self.assertFalse(return_immediately)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_subscription_acknowledge_hit(self):
|
||||
ACK_ID1 = 'DEADBEEF'
|
||||
ACK_ID2 = 'BEADCAFE'
|
||||
gax_api = _GAXSubscriberAPI(_acknowledge_ok=True)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
api.subscription_acknowledge(self.SUB_PATH, [ACK_ID1, ACK_ID2])
|
||||
|
||||
sub_path, ack_ids, options = gax_api._acknowledge_called_with
|
||||
self.assertEqual(sub_path, self.SUB_PATH)
|
||||
self.assertEqual(ack_ids, [ACK_ID1, ACK_ID2])
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_subscription_acknowledge_miss(self):
|
||||
from gcloud.exceptions import NotFound
|
||||
ACK_ID1 = 'DEADBEEF'
|
||||
ACK_ID2 = 'BEADCAFE'
|
||||
gax_api = _GAXSubscriberAPI()
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(NotFound):
|
||||
api.subscription_acknowledge(self.SUB_PATH, [ACK_ID1, ACK_ID2])
|
||||
|
||||
sub_path, ack_ids, options = gax_api._acknowledge_called_with
|
||||
self.assertEqual(sub_path, self.SUB_PATH)
|
||||
self.assertEqual(ack_ids, [ACK_ID1, ACK_ID2])
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_subscription_acknowledge_error(self):
|
||||
from google.gax.errors import GaxError
|
||||
ACK_ID1 = 'DEADBEEF'
|
||||
ACK_ID2 = 'BEADCAFE'
|
||||
gax_api = _GAXSubscriberAPI(_random_gax_error=True)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(GaxError):
|
||||
api.subscription_acknowledge(self.SUB_PATH, [ACK_ID1, ACK_ID2])
|
||||
|
||||
sub_path, ack_ids, options = gax_api._acknowledge_called_with
|
||||
self.assertEqual(sub_path, self.SUB_PATH)
|
||||
self.assertEqual(ack_ids, [ACK_ID1, ACK_ID2])
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_subscription_modify_ack_deadline_hit(self):
|
||||
ACK_ID1 = 'DEADBEEF'
|
||||
ACK_ID2 = 'BEADCAFE'
|
||||
NEW_DEADLINE = 90
|
||||
gax_api = _GAXSubscriberAPI(_modify_ack_deadline_ok=True)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
api.subscription_modify_ack_deadline(
|
||||
self.SUB_PATH, [ACK_ID1, ACK_ID2], NEW_DEADLINE)
|
||||
|
||||
sub_path, ack_ids, deadline, options = (
|
||||
gax_api._modify_ack_deadline_called_with)
|
||||
self.assertEqual(sub_path, self.SUB_PATH)
|
||||
self.assertEqual(ack_ids, [ACK_ID1, ACK_ID2])
|
||||
self.assertEqual(deadline, NEW_DEADLINE)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_subscription_modify_ack_deadline_miss(self):
|
||||
from gcloud.exceptions import NotFound
|
||||
ACK_ID1 = 'DEADBEEF'
|
||||
ACK_ID2 = 'BEADCAFE'
|
||||
NEW_DEADLINE = 90
|
||||
gax_api = _GAXSubscriberAPI()
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(NotFound):
|
||||
api.subscription_modify_ack_deadline(
|
||||
self.SUB_PATH, [ACK_ID1, ACK_ID2], NEW_DEADLINE)
|
||||
|
||||
sub_path, ack_ids, deadline, options = (
|
||||
gax_api._modify_ack_deadline_called_with)
|
||||
self.assertEqual(sub_path, self.SUB_PATH)
|
||||
self.assertEqual(ack_ids, [ACK_ID1, ACK_ID2])
|
||||
self.assertEqual(deadline, NEW_DEADLINE)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
def test_subscription_modify_ack_deadline_error(self):
|
||||
from google.gax.errors import GaxError
|
||||
ACK_ID1 = 'DEADBEEF'
|
||||
ACK_ID2 = 'BEADCAFE'
|
||||
NEW_DEADLINE = 90
|
||||
gax_api = _GAXSubscriberAPI(_random_gax_error=True)
|
||||
api = self._makeOne(gax_api)
|
||||
|
||||
with self.assertRaises(GaxError):
|
||||
api.subscription_modify_ack_deadline(
|
||||
self.SUB_PATH, [ACK_ID1, ACK_ID2], NEW_DEADLINE)
|
||||
|
||||
sub_path, ack_ids, deadline, options = (
|
||||
gax_api._modify_ack_deadline_called_with)
|
||||
self.assertEqual(sub_path, self.SUB_PATH)
|
||||
self.assertEqual(ack_ids, [ACK_ID1, ACK_ID2])
|
||||
self.assertEqual(deadline, NEW_DEADLINE)
|
||||
self.assertEqual(options, None)
|
||||
|
||||
|
||||
class _GaxAPIBase(object):
|
||||
|
||||
_random_gax_error = False
|
||||
|
||||
def __init__(self, **kw):
|
||||
self.__dict__.update(kw)
|
||||
|
||||
def _make_grpc_error(self, status_code):
|
||||
from grpc.framework.interfaces.face.face import AbortionError
|
||||
|
||||
class _DummyException(AbortionError):
|
||||
code = status_code
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
return _DummyException()
|
||||
|
||||
def _make_grpc_not_found(self):
|
||||
from grpc.beta.interfaces import StatusCode
|
||||
return self._make_grpc_error(StatusCode.NOT_FOUND)
|
||||
|
||||
def _make_grpc_failed_precondition(self):
|
||||
from grpc.beta.interfaces import StatusCode
|
||||
return self._make_grpc_error(StatusCode.FAILED_PRECONDITION)
|
||||
|
||||
|
||||
class _GAXPublisherAPI(_GaxAPIBase):
|
||||
|
||||
_create_topic_conflict = False
|
||||
|
||||
def list_topics(self, name, page_size, options):
|
||||
self._list_topics_called_with = name, page_size, options
|
||||
return self._list_topics_response
|
||||
|
||||
def create_topic(self, name, options=None):
|
||||
from google.gax.errors import GaxError
|
||||
self._create_topic_called_with = name, options
|
||||
if self._random_gax_error:
|
||||
raise GaxError('error')
|
||||
if self._create_topic_conflict:
|
||||
raise GaxError('conflict', self._make_grpc_failed_precondition())
|
||||
return self._create_topic_response
|
||||
|
||||
def get_topic(self, name, options=None):
|
||||
from google.gax.errors import GaxError
|
||||
self._get_topic_called_with = name, options
|
||||
if self._random_gax_error:
|
||||
raise GaxError('error')
|
||||
try:
|
||||
return self._get_topic_response
|
||||
except AttributeError:
|
||||
raise GaxError('miss', self._make_grpc_not_found())
|
||||
|
||||
def delete_topic(self, name, options=None):
|
||||
from google.gax.errors import GaxError
|
||||
self._delete_topic_called_with = name, options
|
||||
if self._random_gax_error:
|
||||
raise GaxError('error')
|
||||
if not self._delete_topic_ok:
|
||||
raise GaxError('miss', self._make_grpc_not_found())
|
||||
|
||||
def publish(self, topic, messages, options=None):
|
||||
from google.gax.errors import GaxError
|
||||
self._publish_called_with = topic, messages, options
|
||||
if self._random_gax_error:
|
||||
raise GaxError('error')
|
||||
try:
|
||||
return self._publish_response
|
||||
except AttributeError:
|
||||
raise GaxError('miss', self._make_grpc_not_found())
|
||||
|
||||
def list_topic_subscriptions(self, topic, page_size, options=None):
|
||||
from google.gax.errors import GaxError
|
||||
self._list_topic_subscriptions_called_with = topic, page_size, options
|
||||
if self._random_gax_error:
|
||||
raise GaxError('error')
|
||||
try:
|
||||
return self._list_topic_subscriptions_response
|
||||
except AttributeError:
|
||||
raise GaxError('miss', self._make_grpc_not_found())
|
||||
|
||||
|
||||
class _GAXSubscriberAPI(_GaxAPIBase):
|
||||
|
||||
_create_subscription_conflict = False
|
||||
_modify_push_config_ok = False
|
||||
_acknowledge_ok = False
|
||||
_modify_ack_deadline_ok = False
|
||||
|
||||
def list_subscriptions(self, project, page_size, options=None):
|
||||
self._list_subscriptions_called_with = (project, page_size, options)
|
||||
return self._list_subscriptions_response
|
||||
|
||||
def create_subscription(self, name, topic,
|
||||
push_config, ack_deadline_seconds,
|
||||
options=None):
|
||||
from google.gax.errors import GaxError
|
||||
self._create_subscription_called_with = (
|
||||
name, topic, push_config, ack_deadline_seconds, options)
|
||||
if self._random_gax_error:
|
||||
raise GaxError('error')
|
||||
if self._create_subscription_conflict:
|
||||
raise GaxError('conflict', self._make_grpc_failed_precondition())
|
||||
return self._create_subscription_response
|
||||
|
||||
def get_subscription(self, name, options=None):
|
||||
from google.gax.errors import GaxError
|
||||
self._get_subscription_called_with = name, options
|
||||
if self._random_gax_error:
|
||||
raise GaxError('error')
|
||||
try:
|
||||
return self._get_subscription_response
|
||||
except AttributeError:
|
||||
raise GaxError('miss', self._make_grpc_not_found())
|
||||
|
||||
def delete_subscription(self, name, options=None):
|
||||
from google.gax.errors import GaxError
|
||||
self._delete_subscription_called_with = name, options
|
||||
if self._random_gax_error:
|
||||
raise GaxError('error')
|
||||
if not self._delete_subscription_ok:
|
||||
raise GaxError('miss', self._make_grpc_not_found())
|
||||
|
||||
def modify_push_config(self, name, push_config, options=None):
|
||||
from google.gax.errors import GaxError
|
||||
self._modify_push_config_called_with = name, push_config, options
|
||||
if self._random_gax_error:
|
||||
raise GaxError('error')
|
||||
if not self._modify_push_config_ok:
|
||||
raise GaxError('miss', self._make_grpc_not_found())
|
||||
|
||||
def pull(self, name, max_messages, return_immediately, options=None):
|
||||
from google.gax.errors import GaxError
|
||||
self._pull_called_with = (
|
||||
name, max_messages, return_immediately, options)
|
||||
if self._random_gax_error:
|
||||
raise GaxError('error')
|
||||
try:
|
||||
return self._pull_response
|
||||
except AttributeError:
|
||||
raise GaxError('miss', self._make_grpc_not_found())
|
||||
|
||||
def acknowledge(self, name, ack_ids, options=None):
|
||||
from google.gax.errors import GaxError
|
||||
self._acknowledge_called_with = name, ack_ids, options
|
||||
if self._random_gax_error:
|
||||
raise GaxError('error')
|
||||
if not self._acknowledge_ok:
|
||||
raise GaxError('miss', self._make_grpc_not_found())
|
||||
|
||||
def modify_ack_deadline(self, name, ack_ids, deadline, options=None):
|
||||
from google.gax.errors import GaxError
|
||||
self._modify_ack_deadline_called_with = (
|
||||
name, ack_ids, deadline, options)
|
||||
if self._random_gax_error:
|
||||
raise GaxError('error')
|
||||
if not self._modify_ack_deadline_ok:
|
||||
raise GaxError('miss', self._make_grpc_not_found())
|
||||
|
||||
|
||||
class _TopicPB(object):
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
|
||||
class _PublishResponsePB(object):
|
||||
|
||||
def __init__(self, message_ids):
|
||||
self.message_ids = message_ids
|
||||
|
||||
|
||||
class _PushConfigPB(object):
|
||||
|
||||
def __init__(self, push_endpoint):
|
||||
self.push_endpoint = push_endpoint
|
||||
|
||||
|
||||
class _PubsubMessagePB(object):
|
||||
|
||||
def __init__(self, message_id, data, attributes):
|
||||
self.message_id = message_id
|
||||
self.data = data
|
||||
self.attributes = attributes
|
||||
|
||||
|
||||
class _ReceivedMessagePB(object):
|
||||
|
||||
def __init__(self, ack_id, message):
|
||||
self.ack_id = ack_id
|
||||
self.message = message
|
||||
|
||||
|
||||
class _PullResponsePB(object):
|
||||
|
||||
def __init__(self, received_messages):
|
||||
self.received_messages = received_messages
|
||||
|
||||
|
||||
class _SubscriptionPB(object):
|
||||
|
||||
def __init__(self, name, topic, push_endpoint, ack_deadline_seconds):
|
||||
self.name = name
|
||||
self.topic = topic
|
||||
self.push_config = _PushConfigPB(push_endpoint)
|
||||
self.ack_deadline_seconds = ack_deadline_seconds
|
57
venv/Lib/site-packages/gcloud/pubsub/test__helpers.py
Normal file
57
venv/Lib/site-packages/gcloud/pubsub/test__helpers.py
Normal file
|
@ -0,0 +1,57 @@
|
|||
# Copyright 2015 Google Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import unittest2
|
||||
|
||||
|
||||
class Test_topic_name_from_path(unittest2.TestCase):
|
||||
|
||||
def _callFUT(self, path, project):
|
||||
from gcloud.pubsub._helpers import topic_name_from_path
|
||||
return topic_name_from_path(path, project)
|
||||
|
||||
def test_w_simple_name(self):
|
||||
TOPIC_NAME = 'TOPIC_NAME'
|
||||
PROJECT = 'my-project-1234'
|
||||
PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME)
|
||||
topic_name = self._callFUT(PATH, PROJECT)
|
||||
self.assertEqual(topic_name, TOPIC_NAME)
|
||||
|
||||
def test_w_name_w_all_extras(self):
|
||||
TOPIC_NAME = 'TOPIC_NAME-part.one~part.two%part-three'
|
||||
PROJECT = 'my-project-1234'
|
||||
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_w_simple_name(self):
|
||||
SUBSCRIPTION_NAME = 'SUBSCRIPTION_NAME'
|
||||
PROJECT = 'my-project-1234'
|
||||
PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUBSCRIPTION_NAME)
|
||||
subscription_name = self._callFUT(PATH, PROJECT)
|
||||
self.assertEqual(subscription_name, SUBSCRIPTION_NAME)
|
||||
|
||||
def test_w_name_w_all_extras(self):
|
||||
SUBSCRIPTION_NAME = 'SUBSCRIPTION_NAME-part.one~part.two%part-three'
|
||||
PROJECT = 'my-project-1234'
|
||||
PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUBSCRIPTION_NAME)
|
||||
topic_name = self._callFUT(PATH, PROJECT)
|
||||
self.assertEqual(topic_name, SUBSCRIPTION_NAME)
|
299
venv/Lib/site-packages/gcloud/pubsub/test_client.py
Normal file
299
venv/Lib/site-packages/gcloud/pubsub/test_client.py
Normal file
|
@ -0,0 +1,299 @@
|
|||
# Copyright 2015 Google Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import unittest2
|
||||
|
||||
|
||||
class TestClient(unittest2.TestCase):
|
||||
PROJECT = 'PROJECT'
|
||||
TOPIC_NAME = 'topic_name'
|
||||
TOPIC_PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME)
|
||||
SUB_NAME = 'subscription_name'
|
||||
SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME)
|
||||
|
||||
def _getTargetClass(self):
|
||||
from gcloud.pubsub.client import Client
|
||||
return Client
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_publisher_api_wo_gax(self):
|
||||
from gcloud.pubsub.connection import _PublisherAPI
|
||||
from gcloud.pubsub import client as MUT
|
||||
from gcloud._testing import _Monkey
|
||||
creds = _Credentials()
|
||||
client = self._makeOne(project=self.PROJECT, credentials=creds)
|
||||
conn = client.connection = object()
|
||||
|
||||
with _Monkey(MUT, _USE_GAX=False):
|
||||
api = client.publisher_api
|
||||
|
||||
self.assertIsInstance(api, _PublisherAPI)
|
||||
self.assertTrue(api._connection is conn)
|
||||
# API instance is cached
|
||||
again = client.publisher_api
|
||||
self.assertTrue(again is api)
|
||||
|
||||
def test_publisher_api_w_gax(self):
|
||||
from gcloud.pubsub import client as MUT
|
||||
from gcloud._testing import _Monkey
|
||||
|
||||
wrapped = object()
|
||||
_called_with = []
|
||||
|
||||
def _generated_api(*args, **kw):
|
||||
_called_with.append((args, kw))
|
||||
return wrapped
|
||||
|
||||
class _GaxPublisherAPI(object):
|
||||
|
||||
def __init__(self, _wrapped):
|
||||
self._wrapped = _wrapped
|
||||
|
||||
creds = _Credentials()
|
||||
client = self._makeOne(project=self.PROJECT, credentials=creds)
|
||||
|
||||
with _Monkey(MUT,
|
||||
_USE_GAX=True,
|
||||
GeneratedPublisherAPI=_generated_api,
|
||||
GAXPublisherAPI=_GaxPublisherAPI):
|
||||
api = client.publisher_api
|
||||
|
||||
self.assertIsInstance(api, _GaxPublisherAPI)
|
||||
self.assertTrue(api._wrapped is wrapped)
|
||||
# API instance is cached
|
||||
again = client.publisher_api
|
||||
self.assertTrue(again is api)
|
||||
|
||||
def test_subscriber_api_wo_gax(self):
|
||||
from gcloud.pubsub.connection import _SubscriberAPI
|
||||
from gcloud.pubsub import client as MUT
|
||||
from gcloud._testing import _Monkey
|
||||
creds = _Credentials()
|
||||
client = self._makeOne(project=self.PROJECT, credentials=creds)
|
||||
conn = client.connection = object()
|
||||
|
||||
with _Monkey(MUT, _USE_GAX=False):
|
||||
api = client.subscriber_api
|
||||
|
||||
self.assertIsInstance(api, _SubscriberAPI)
|
||||
self.assertTrue(api._connection is conn)
|
||||
# API instance is cached
|
||||
again = client.subscriber_api
|
||||
self.assertTrue(again is api)
|
||||
|
||||
def test_subscriber_api_w_gax(self):
|
||||
from gcloud.pubsub import client as MUT
|
||||
from gcloud._testing import _Monkey
|
||||
|
||||
wrapped = object()
|
||||
_called_with = []
|
||||
|
||||
def _generated_api(*args, **kw):
|
||||
_called_with.append((args, kw))
|
||||
return wrapped
|
||||
|
||||
class _GaxSubscriberAPI(object):
|
||||
|
||||
def __init__(self, _wrapped):
|
||||
self._wrapped = _wrapped
|
||||
|
||||
creds = _Credentials()
|
||||
client = self._makeOne(project=self.PROJECT, credentials=creds)
|
||||
|
||||
with _Monkey(MUT,
|
||||
_USE_GAX=True,
|
||||
GeneratedSubscriberAPI=_generated_api,
|
||||
GAXSubscriberAPI=_GaxSubscriberAPI):
|
||||
api = client.subscriber_api
|
||||
|
||||
self.assertIsInstance(api, _GaxSubscriberAPI)
|
||||
self.assertTrue(api._wrapped is wrapped)
|
||||
# API instance is cached
|
||||
again = client.subscriber_api
|
||||
self.assertTrue(again is api)
|
||||
|
||||
def test_iam_policy_api(self):
|
||||
from gcloud.pubsub.connection import _IAMPolicyAPI
|
||||
creds = _Credentials()
|
||||
client = self._makeOne(project=self.PROJECT, credentials=creds)
|
||||
conn = client.connection = object()
|
||||
api = client.iam_policy_api
|
||||
self.assertIsInstance(api, _IAMPolicyAPI)
|
||||
self.assertTrue(api._connection is conn)
|
||||
# API instance is cached
|
||||
again = client.iam_policy_api
|
||||
self.assertTrue(again is api)
|
||||
|
||||
def test_list_topics_no_paging(self):
|
||||
from gcloud.pubsub.topic import Topic
|
||||
creds = _Credentials()
|
||||
client = self._makeOne(project=self.PROJECT, credentials=creds)
|
||||
client.connection = object()
|
||||
api = client._publisher_api = _FauxPublisherAPI()
|
||||
api._list_topics_response = [{'name': self.TOPIC_PATH}], None
|
||||
|
||||
topics, next_page_token = client.list_topics()
|
||||
|
||||
self.assertEqual(len(topics), 1)
|
||||
self.assertIsInstance(topics[0], Topic)
|
||||
self.assertEqual(topics[0].name, self.TOPIC_NAME)
|
||||
self.assertEqual(next_page_token, None)
|
||||
|
||||
self.assertEqual(api._listed_topics, (self.PROJECT, None, None))
|
||||
|
||||
def test_list_topics_with_paging(self):
|
||||
from gcloud.pubsub.topic import Topic
|
||||
TOKEN1 = 'TOKEN1'
|
||||
TOKEN2 = 'TOKEN2'
|
||||
SIZE = 1
|
||||
creds = _Credentials()
|
||||
client = self._makeOne(project=self.PROJECT, credentials=creds)
|
||||
client.connection = object()
|
||||
api = client._publisher_api = _FauxPublisherAPI()
|
||||
api._list_topics_response = [{'name': self.TOPIC_PATH}], TOKEN2
|
||||
|
||||
topics, next_page_token = client.list_topics(SIZE, TOKEN1)
|
||||
|
||||
self.assertEqual(len(topics), 1)
|
||||
self.assertIsInstance(topics[0], Topic)
|
||||
self.assertEqual(topics[0].name, self.TOPIC_NAME)
|
||||
self.assertEqual(next_page_token, TOKEN2)
|
||||
|
||||
self.assertEqual(api._listed_topics, (self.PROJECT, 1, TOKEN1))
|
||||
|
||||
def test_list_topics_missing_key(self):
|
||||
creds = _Credentials()
|
||||
client = self._makeOne(project=self.PROJECT, credentials=creds)
|
||||
client.connection = object()
|
||||
api = client._publisher_api = _FauxPublisherAPI()
|
||||
api._list_topics_response = (), None
|
||||
|
||||
topics, next_page_token = client.list_topics()
|
||||
|
||||
self.assertEqual(len(topics), 0)
|
||||
self.assertEqual(next_page_token, None)
|
||||
|
||||
self.assertEqual(api._listed_topics, (self.PROJECT, None, None))
|
||||
|
||||
def test_list_subscriptions_no_paging(self):
|
||||
from gcloud.pubsub.subscription import Subscription
|
||||
SUB_INFO = {'name': self.SUB_PATH, 'topic': self.TOPIC_PATH}
|
||||
creds = _Credentials()
|
||||
client = self._makeOne(project=self.PROJECT, credentials=creds)
|
||||
client.connection = object()
|
||||
api = client._subscriber_api = _FauxSubscriberAPI()
|
||||
api._list_subscriptions_response = [SUB_INFO], None
|
||||
|
||||
subscriptions, next_page_token = client.list_subscriptions()
|
||||
|
||||
self.assertEqual(len(subscriptions), 1)
|
||||
self.assertIsInstance(subscriptions[0], Subscription)
|
||||
self.assertEqual(subscriptions[0].name, self.SUB_NAME)
|
||||
self.assertEqual(subscriptions[0].topic.name, self.TOPIC_NAME)
|
||||
self.assertEqual(next_page_token, None)
|
||||
|
||||
self.assertEqual(api._listed_subscriptions,
|
||||
(self.PROJECT, None, None))
|
||||
|
||||
def test_list_subscriptions_with_paging(self):
|
||||
from gcloud.pubsub.subscription import Subscription
|
||||
SUB_INFO = {'name': self.SUB_PATH, 'topic': self.TOPIC_PATH}
|
||||
creds = _Credentials()
|
||||
client = self._makeOne(project=self.PROJECT, credentials=creds)
|
||||
ACK_DEADLINE = 42
|
||||
PUSH_ENDPOINT = 'https://push.example.com/endpoint'
|
||||
SUB_INFO = {'name': self.SUB_PATH,
|
||||
'topic': self.TOPIC_PATH,
|
||||
'ackDeadlineSeconds': ACK_DEADLINE,
|
||||
'pushConfig': {'pushEndpoint': PUSH_ENDPOINT}}
|
||||
TOKEN1 = 'TOKEN1'
|
||||
TOKEN2 = 'TOKEN2'
|
||||
SIZE = 1
|
||||
client.connection = object()
|
||||
api = client._subscriber_api = _FauxSubscriberAPI()
|
||||
api._list_subscriptions_response = [SUB_INFO], TOKEN2
|
||||
|
||||
subscriptions, next_page_token = client.list_subscriptions(
|
||||
SIZE, TOKEN1)
|
||||
|
||||
self.assertEqual(len(subscriptions), 1)
|
||||
self.assertIsInstance(subscriptions[0], Subscription)
|
||||
self.assertEqual(subscriptions[0].name, self.SUB_NAME)
|
||||
self.assertEqual(subscriptions[0].topic.name, self.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(api._listed_subscriptions,
|
||||
(self.PROJECT, SIZE, TOKEN1))
|
||||
|
||||
def test_list_subscriptions_w_missing_key(self):
|
||||
PROJECT = 'PROJECT'
|
||||
creds = _Credentials()
|
||||
|
||||
client = self._makeOne(project=PROJECT, credentials=creds)
|
||||
client.connection = object()
|
||||
api = client._subscriber_api = _FauxSubscriberAPI()
|
||||
api._list_subscriptions_response = (), None
|
||||
|
||||
subscriptions, next_page_token = client.list_subscriptions()
|
||||
|
||||
self.assertEqual(len(subscriptions), 0)
|
||||
self.assertEqual(next_page_token, None)
|
||||
|
||||
self.assertEqual(api._listed_subscriptions,
|
||||
(self.PROJECT, None, None))
|
||||
|
||||
def test_topic(self):
|
||||
PROJECT = 'PROJECT'
|
||||
TOPIC_NAME = 'TOPIC_NAME'
|
||||
creds = _Credentials()
|
||||
|
||||
client_obj = self._makeOne(project=PROJECT, credentials=creds)
|
||||
new_topic = client_obj.topic(TOPIC_NAME)
|
||||
self.assertEqual(new_topic.name, TOPIC_NAME)
|
||||
self.assertTrue(new_topic._client is client_obj)
|
||||
self.assertEqual(new_topic.project, PROJECT)
|
||||
self.assertEqual(new_topic.full_name,
|
||||
'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME))
|
||||
self.assertFalse(new_topic.timestamp_messages)
|
||||
|
||||
|
||||
class _Credentials(object):
|
||||
|
||||
_scopes = None
|
||||
|
||||
@staticmethod
|
||||
def create_scoped_required():
|
||||
return True
|
||||
|
||||
def create_scoped(self, scope):
|
||||
self._scopes = scope
|
||||
return self
|
||||
|
||||
|
||||
class _FauxPublisherAPI(object):
|
||||
|
||||
def list_topics(self, project, page_size, page_token):
|
||||
self._listed_topics = (project, page_size, page_token)
|
||||
return self._list_topics_response
|
||||
|
||||
|
||||
class _FauxSubscriberAPI(object):
|
||||
|
||||
def list_subscriptions(self, project, page_size, page_token):
|
||||
self._listed_subscriptions = (project, page_size, page_token)
|
||||
return self._list_subscriptions_response
|
749
venv/Lib/site-packages/gcloud/pubsub/test_connection.py
Normal file
749
venv/Lib/site-packages/gcloud/pubsub/test_connection.py
Normal file
|
@ -0,0 +1,749 @@
|
|||
# Copyright 2015 Google Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import unittest2
|
||||
|
||||
|
||||
class _Base(unittest2.TestCase):
|
||||
PROJECT = 'PROJECT'
|
||||
LIST_TOPICS_PATH = 'projects/%s/topics' % (PROJECT,)
|
||||
LIST_SUBSCRIPTIONS_PATH = 'projects/%s/subscriptions' % (PROJECT,)
|
||||
TOPIC_NAME = 'topic_name'
|
||||
TOPIC_PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME)
|
||||
LIST_TOPIC_SUBSCRIPTIONS_PATH = '%s/subscriptions' % (TOPIC_PATH,)
|
||||
SUB_NAME = 'subscription_name'
|
||||
SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME)
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
|
||||
class TestConnection(_Base):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from gcloud.pubsub.connection import Connection
|
||||
return Connection
|
||||
|
||||
def test_default_url(self):
|
||||
conn = self._makeOne()
|
||||
klass = self._getTargetClass()
|
||||
self.assertEqual(conn.api_base_url, klass.API_BASE_URL)
|
||||
|
||||
def test_custom_url_from_env(self):
|
||||
import os
|
||||
from gcloud._testing import _Monkey
|
||||
from gcloud.environment_vars import PUBSUB_EMULATOR
|
||||
|
||||
HOST = 'localhost:8187'
|
||||
fake_environ = {PUBSUB_EMULATOR: HOST}
|
||||
|
||||
with _Monkey(os, getenv=fake_environ.get):
|
||||
conn = self._makeOne()
|
||||
|
||||
klass = self._getTargetClass()
|
||||
self.assertNotEqual(conn.api_base_url, klass.API_BASE_URL)
|
||||
self.assertEqual(conn.api_base_url, 'http://' + HOST)
|
||||
|
||||
def test_custom_url_from_constructor(self):
|
||||
HOST = object()
|
||||
conn = self._makeOne(api_base_url=HOST)
|
||||
|
||||
klass = self._getTargetClass()
|
||||
self.assertNotEqual(conn.api_base_url, klass.API_BASE_URL)
|
||||
self.assertEqual(conn.api_base_url, HOST)
|
||||
|
||||
def test_custom_url_constructor_and_env(self):
|
||||
import os
|
||||
from gcloud._testing import _Monkey
|
||||
from gcloud.environment_vars import PUBSUB_EMULATOR
|
||||
|
||||
HOST1 = object()
|
||||
HOST2 = object()
|
||||
fake_environ = {PUBSUB_EMULATOR: HOST1}
|
||||
|
||||
with _Monkey(os, getenv=fake_environ.get):
|
||||
conn = self._makeOne(api_base_url=HOST2)
|
||||
|
||||
klass = self._getTargetClass()
|
||||
self.assertNotEqual(conn.api_base_url, klass.API_BASE_URL)
|
||||
self.assertNotEqual(conn.api_base_url, HOST1)
|
||||
self.assertEqual(conn.api_base_url, HOST2)
|
||||
|
||||
def test_build_api_url_no_extra_query_params(self):
|
||||
conn = self._makeOne()
|
||||
URI = '/'.join([
|
||||
conn.API_BASE_URL,
|
||||
conn.API_VERSION,
|
||||
'foo',
|
||||
])
|
||||
self.assertEqual(conn.build_api_url('/foo'), URI)
|
||||
|
||||
def test_build_api_url_w_extra_query_params(self):
|
||||
from six.moves.urllib.parse import parse_qsl
|
||||
from six.moves.urllib.parse import urlsplit
|
||||
conn = self._makeOne()
|
||||
uri = conn.build_api_url('/foo', {'bar': 'baz'})
|
||||
scheme, netloc, path, qs, _ = urlsplit(uri)
|
||||
self.assertEqual('%s://%s' % (scheme, netloc), conn.API_BASE_URL)
|
||||
self.assertEqual(path,
|
||||
'/'.join(['', conn.API_VERSION, 'foo']))
|
||||
parms = dict(parse_qsl(qs))
|
||||
self.assertEqual(parms['bar'], 'baz')
|
||||
|
||||
def test_build_api_url_w_base_url_override(self):
|
||||
base_url1 = 'api-base-url1'
|
||||
base_url2 = 'api-base-url2'
|
||||
conn = self._makeOne(api_base_url=base_url1)
|
||||
URI = '/'.join([
|
||||
base_url2,
|
||||
conn.API_VERSION,
|
||||
'foo',
|
||||
])
|
||||
self.assertEqual(conn.build_api_url('/foo', api_base_url=base_url2),
|
||||
URI)
|
||||
|
||||
|
||||
class Test_PublisherAPI(_Base):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from gcloud.pubsub.connection import _PublisherAPI
|
||||
return _PublisherAPI
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_ctor(self):
|
||||
connection = _Connection()
|
||||
api = self._makeOne(connection)
|
||||
self.assertTrue(api._connection is connection)
|
||||
|
||||
def test_list_topics_no_paging(self):
|
||||
RETURNED = {'topics': [{'name': self.TOPIC_PATH}]}
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
topics, next_token = api.list_topics(self.PROJECT)
|
||||
|
||||
self.assertEqual(len(topics), 1)
|
||||
topic = topics[0]
|
||||
self.assertIsInstance(topic, dict)
|
||||
self.assertEqual(topic['name'], self.TOPIC_PATH)
|
||||
self.assertEqual(next_token, None)
|
||||
|
||||
self.assertEqual(connection._called_with['method'], 'GET')
|
||||
path = '/%s' % (self.LIST_TOPICS_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
self.assertEqual(connection._called_with['query_params'], {})
|
||||
|
||||
def test_list_topics_with_paging(self):
|
||||
TOKEN1 = 'TOKEN1'
|
||||
TOKEN2 = 'TOKEN2'
|
||||
SIZE = 1
|
||||
RETURNED = {
|
||||
'topics': [{'name': self.TOPIC_PATH}],
|
||||
'nextPageToken': 'TOKEN2',
|
||||
}
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
topics, next_token = api.list_topics(
|
||||
self.PROJECT, page_token=TOKEN1, page_size=SIZE)
|
||||
|
||||
self.assertEqual(len(topics), 1)
|
||||
topic = topics[0]
|
||||
self.assertIsInstance(topic, dict)
|
||||
self.assertEqual(topic['name'], self.TOPIC_PATH)
|
||||
self.assertEqual(next_token, TOKEN2)
|
||||
|
||||
self.assertEqual(connection._called_with['method'], 'GET')
|
||||
path = '/%s' % (self.LIST_TOPICS_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
self.assertEqual(connection._called_with['query_params'],
|
||||
{'pageToken': TOKEN1, 'pageSize': SIZE})
|
||||
|
||||
def test_list_topics_missing_key(self):
|
||||
RETURNED = {}
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
topics, next_token = api.list_topics(self.PROJECT)
|
||||
|
||||
self.assertEqual(len(topics), 0)
|
||||
self.assertEqual(next_token, None)
|
||||
|
||||
self.assertEqual(connection._called_with['method'], 'GET')
|
||||
path = '/%s' % (self.LIST_TOPICS_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
self.assertEqual(connection._called_with['query_params'], {})
|
||||
|
||||
def test_topic_create(self):
|
||||
RETURNED = {'name': self.TOPIC_PATH}
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
resource = api.topic_create(self.TOPIC_PATH)
|
||||
|
||||
self.assertEqual(resource, RETURNED)
|
||||
self.assertEqual(connection._called_with['method'], 'PUT')
|
||||
path = '/%s' % (self.TOPIC_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
|
||||
def test_topic_create_already_exists(self):
|
||||
from gcloud.exceptions import Conflict
|
||||
connection = _Connection()
|
||||
connection._no_response_error = Conflict
|
||||
api = self._makeOne(connection)
|
||||
|
||||
with self.assertRaises(Conflict):
|
||||
api.topic_create(self.TOPIC_PATH)
|
||||
|
||||
self.assertEqual(connection._called_with['method'], 'PUT')
|
||||
path = '/%s' % (self.TOPIC_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
|
||||
def test_topic_get_hit(self):
|
||||
RETURNED = {'name': self.TOPIC_PATH}
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
resource = api.topic_get(self.TOPIC_PATH)
|
||||
|
||||
self.assertEqual(resource, RETURNED)
|
||||
self.assertEqual(connection._called_with['method'], 'GET')
|
||||
path = '/%s' % (self.TOPIC_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
|
||||
def test_topic_get_miss(self):
|
||||
from gcloud.exceptions import NotFound
|
||||
connection = _Connection()
|
||||
api = self._makeOne(connection)
|
||||
|
||||
with self.assertRaises(NotFound):
|
||||
api.topic_get(self.TOPIC_PATH)
|
||||
|
||||
self.assertEqual(connection._called_with['method'], 'GET')
|
||||
path = '/%s' % (self.TOPIC_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
|
||||
def test_topic_delete_hit(self):
|
||||
RETURNED = {}
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
api.topic_delete(self.TOPIC_PATH)
|
||||
|
||||
self.assertEqual(connection._called_with['method'], 'DELETE')
|
||||
path = '/%s' % (self.TOPIC_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
|
||||
def test_topic_delete_miss(self):
|
||||
from gcloud.exceptions import NotFound
|
||||
connection = _Connection()
|
||||
api = self._makeOne(connection)
|
||||
|
||||
with self.assertRaises(NotFound):
|
||||
api.topic_delete(self.TOPIC_PATH)
|
||||
|
||||
self.assertEqual(connection._called_with['method'], 'DELETE')
|
||||
path = '/%s' % (self.TOPIC_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
|
||||
def test_topic_publish_hit(self):
|
||||
import base64
|
||||
PAYLOAD = b'This is the message text'
|
||||
B64 = base64.b64encode(PAYLOAD).decode('ascii')
|
||||
MSGID = 'DEADBEEF'
|
||||
MESSAGE = {'data': B64, 'attributes': {}}
|
||||
RETURNED = {'messageIds': [MSGID]}
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
resource = api.topic_publish(self.TOPIC_PATH, [MESSAGE])
|
||||
|
||||
self.assertEqual(resource, [MSGID])
|
||||
self.assertEqual(connection._called_with['method'], 'POST')
|
||||
path = '/%s:publish' % (self.TOPIC_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
self.assertEqual(connection._called_with['data'],
|
||||
{'messages': [MESSAGE]})
|
||||
|
||||
def test_topic_publish_miss(self):
|
||||
import base64
|
||||
from gcloud.exceptions import NotFound
|
||||
PAYLOAD = b'This is the message text'
|
||||
B64 = base64.b64encode(PAYLOAD).decode('ascii')
|
||||
MESSAGE = {'data': B64, 'attributes': {}}
|
||||
connection = _Connection()
|
||||
api = self._makeOne(connection)
|
||||
|
||||
with self.assertRaises(NotFound):
|
||||
api.topic_publish(self.TOPIC_PATH, [MESSAGE])
|
||||
|
||||
self.assertEqual(connection._called_with['method'], 'POST')
|
||||
path = '/%s:publish' % (self.TOPIC_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
self.assertEqual(connection._called_with['data'],
|
||||
{'messages': [MESSAGE]})
|
||||
|
||||
def test_topic_list_subscriptions_no_paging(self):
|
||||
SUB_INFO = {'name': self.SUB_PATH, 'topic': self.TOPIC_PATH}
|
||||
RETURNED = {'subscriptions': [SUB_INFO]}
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
subscriptions, next_token = api.topic_list_subscriptions(
|
||||
self.TOPIC_PATH)
|
||||
|
||||
self.assertEqual(len(subscriptions), 1)
|
||||
subscription = subscriptions[0]
|
||||
self.assertIsInstance(subscription, dict)
|
||||
self.assertEqual(subscription['name'], self.SUB_PATH)
|
||||
self.assertEqual(subscription['topic'], self.TOPIC_PATH)
|
||||
self.assertEqual(next_token, None)
|
||||
|
||||
self.assertEqual(connection._called_with['method'], 'GET')
|
||||
path = '/%s' % (self.LIST_TOPIC_SUBSCRIPTIONS_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
self.assertEqual(connection._called_with['query_params'], {})
|
||||
|
||||
def test_topic_list_subscriptions_with_paging(self):
|
||||
TOKEN1 = 'TOKEN1'
|
||||
TOKEN2 = 'TOKEN2'
|
||||
SIZE = 1
|
||||
SUB_INFO = {'name': self.SUB_PATH, 'topic': self.TOPIC_PATH}
|
||||
RETURNED = {
|
||||
'subscriptions': [SUB_INFO],
|
||||
'nextPageToken': 'TOKEN2',
|
||||
}
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
subscriptions, next_token = api.topic_list_subscriptions(
|
||||
self.TOPIC_PATH, page_token=TOKEN1, page_size=SIZE)
|
||||
|
||||
self.assertEqual(len(subscriptions), 1)
|
||||
subscription = subscriptions[0]
|
||||
self.assertIsInstance(subscription, dict)
|
||||
self.assertEqual(subscription['name'], self.SUB_PATH)
|
||||
self.assertEqual(subscription['topic'], self.TOPIC_PATH)
|
||||
self.assertEqual(next_token, TOKEN2)
|
||||
|
||||
self.assertEqual(connection._called_with['method'], 'GET')
|
||||
path = '/%s' % (self.LIST_TOPIC_SUBSCRIPTIONS_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
self.assertEqual(connection._called_with['query_params'],
|
||||
{'pageToken': TOKEN1, 'pageSize': SIZE})
|
||||
|
||||
def test_topic_list_subscriptions_missing_key(self):
|
||||
RETURNED = {}
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
subscriptions, next_token = api.topic_list_subscriptions(
|
||||
self.TOPIC_PATH)
|
||||
|
||||
self.assertEqual(len(subscriptions), 0)
|
||||
self.assertEqual(next_token, None)
|
||||
|
||||
self.assertEqual(connection._called_with['method'], 'GET')
|
||||
path = '/%s' % (self.LIST_TOPIC_SUBSCRIPTIONS_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
self.assertEqual(connection._called_with['query_params'], {})
|
||||
|
||||
def test_topic_list_subscriptions_miss(self):
|
||||
from gcloud.exceptions import NotFound
|
||||
connection = _Connection()
|
||||
api = self._makeOne(connection)
|
||||
|
||||
with self.assertRaises(NotFound):
|
||||
api.topic_list_subscriptions(self.TOPIC_PATH)
|
||||
|
||||
self.assertEqual(connection._called_with['method'], 'GET')
|
||||
path = '/%s' % (self.LIST_TOPIC_SUBSCRIPTIONS_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
self.assertEqual(connection._called_with['query_params'], {})
|
||||
|
||||
|
||||
class Test_SubscriberAPI(_Base):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from gcloud.pubsub.connection import _SubscriberAPI
|
||||
return _SubscriberAPI
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_ctor(self):
|
||||
connection = _Connection()
|
||||
api = self._makeOne(connection)
|
||||
self.assertTrue(api._connection is connection)
|
||||
|
||||
def test_list_subscriptions_no_paging(self):
|
||||
SUB_INFO = {'name': self.SUB_PATH, 'topic': self.TOPIC_PATH}
|
||||
RETURNED = {'subscriptions': [SUB_INFO]}
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
subscriptions, next_token = api.list_subscriptions(self.PROJECT)
|
||||
|
||||
self.assertEqual(len(subscriptions), 1)
|
||||
subscription = subscriptions[0]
|
||||
self.assertIsInstance(subscription, dict)
|
||||
self.assertEqual(subscription['name'], self.SUB_PATH)
|
||||
self.assertEqual(subscription['topic'], self.TOPIC_PATH)
|
||||
self.assertEqual(next_token, None)
|
||||
|
||||
self.assertEqual(connection._called_with['method'], 'GET')
|
||||
path = '/%s' % (self.LIST_SUBSCRIPTIONS_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
self.assertEqual(connection._called_with['query_params'], {})
|
||||
|
||||
def test_list_subscriptions_with_paging(self):
|
||||
TOKEN1 = 'TOKEN1'
|
||||
TOKEN2 = 'TOKEN2'
|
||||
SIZE = 1
|
||||
SUB_INFO = {'name': self.SUB_PATH, 'topic': self.TOPIC_PATH}
|
||||
RETURNED = {
|
||||
'subscriptions': [SUB_INFO],
|
||||
'nextPageToken': 'TOKEN2',
|
||||
}
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
subscriptions, next_token = api.list_subscriptions(
|
||||
self.PROJECT, page_token=TOKEN1, page_size=SIZE)
|
||||
|
||||
self.assertEqual(len(subscriptions), 1)
|
||||
subscription = subscriptions[0]
|
||||
self.assertIsInstance(subscription, dict)
|
||||
self.assertEqual(subscription['name'], self.SUB_PATH)
|
||||
self.assertEqual(subscription['topic'], self.TOPIC_PATH)
|
||||
self.assertEqual(next_token, TOKEN2)
|
||||
|
||||
self.assertEqual(connection._called_with['method'], 'GET')
|
||||
path = '/%s' % (self.LIST_SUBSCRIPTIONS_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
self.assertEqual(connection._called_with['query_params'],
|
||||
{'pageToken': TOKEN1, 'pageSize': SIZE})
|
||||
|
||||
def test_list_subscriptions_missing_key(self):
|
||||
RETURNED = {}
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
subscriptions, next_token = api.list_subscriptions(self.PROJECT)
|
||||
|
||||
self.assertEqual(len(subscriptions), 0)
|
||||
self.assertEqual(next_token, None)
|
||||
|
||||
self.assertEqual(connection._called_with['method'], 'GET')
|
||||
path = '/%s' % (self.LIST_SUBSCRIPTIONS_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
self.assertEqual(connection._called_with['query_params'], {})
|
||||
|
||||
def test_subscription_create_defaults(self):
|
||||
RESOURCE = {'topic': self.TOPIC_PATH}
|
||||
RETURNED = RESOURCE.copy()
|
||||
RETURNED['name'] = self.SUB_PATH
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
resource = api.subscription_create(self.SUB_PATH, self.TOPIC_PATH)
|
||||
|
||||
self.assertEqual(resource, RETURNED)
|
||||
self.assertEqual(connection._called_with['method'], 'PUT')
|
||||
path = '/%s' % (self.SUB_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
self.assertEqual(connection._called_with['data'], RESOURCE)
|
||||
|
||||
def test_subscription_create_explicit(self):
|
||||
ACK_DEADLINE = 90
|
||||
PUSH_ENDPOINT = 'https://api.example.com/push'
|
||||
RESOURCE = {
|
||||
'topic': self.TOPIC_PATH,
|
||||
'ackDeadlineSeconds': ACK_DEADLINE,
|
||||
'pushConfig': {
|
||||
'pushEndpoint': PUSH_ENDPOINT,
|
||||
},
|
||||
}
|
||||
RETURNED = RESOURCE.copy()
|
||||
RETURNED['name'] = self.SUB_PATH
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
resource = api.subscription_create(
|
||||
self.SUB_PATH, self.TOPIC_PATH,
|
||||
ack_deadline=ACK_DEADLINE, push_endpoint=PUSH_ENDPOINT)
|
||||
|
||||
self.assertEqual(resource, RETURNED)
|
||||
self.assertEqual(connection._called_with['method'], 'PUT')
|
||||
path = '/%s' % (self.SUB_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
self.assertEqual(connection._called_with['data'], RESOURCE)
|
||||
|
||||
def test_subscription_get(self):
|
||||
ACK_DEADLINE = 90
|
||||
PUSH_ENDPOINT = 'https://api.example.com/push'
|
||||
RETURNED = {
|
||||
'topic': self.TOPIC_PATH,
|
||||
'name': self.SUB_PATH,
|
||||
'ackDeadlineSeconds': ACK_DEADLINE,
|
||||
'pushConfig': {'pushEndpoint': PUSH_ENDPOINT},
|
||||
}
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
resource = api.subscription_get(self.SUB_PATH)
|
||||
|
||||
self.assertEqual(resource, RETURNED)
|
||||
self.assertEqual(connection._called_with['method'], 'GET')
|
||||
path = '/%s' % (self.SUB_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
|
||||
def test_subscription_delete(self):
|
||||
RETURNED = {}
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
api.subscription_delete(self.SUB_PATH)
|
||||
|
||||
self.assertEqual(connection._called_with['method'], 'DELETE')
|
||||
path = '/%s' % (self.SUB_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
|
||||
def test_subscription_modify_push_config(self):
|
||||
PUSH_ENDPOINT = 'https://api.example.com/push'
|
||||
BODY = {
|
||||
'pushConfig': {'pushEndpoint': PUSH_ENDPOINT},
|
||||
}
|
||||
RETURNED = {}
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
api.subscription_modify_push_config(self.SUB_PATH, PUSH_ENDPOINT)
|
||||
|
||||
self.assertEqual(connection._called_with['method'], 'POST')
|
||||
path = '/%s:modifyPushConfig' % (self.SUB_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
self.assertEqual(connection._called_with['data'], BODY)
|
||||
|
||||
def test_subscription_pull_defaults(self):
|
||||
import base64
|
||||
PAYLOAD = b'This is the message text'
|
||||
B64 = base64.b64encode(PAYLOAD).decode('ascii')
|
||||
ACK_ID = 'DEADBEEF'
|
||||
MSG_ID = 'BEADCAFE'
|
||||
MESSAGE = {'messageId': MSG_ID, 'data': B64, 'attributes': {'a': 'b'}}
|
||||
RETURNED = {
|
||||
'receivedMessages': [{'ackId': ACK_ID, 'message': MESSAGE}],
|
||||
}
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
BODY = {
|
||||
'returnImmediately': False,
|
||||
'maxMessages': 1,
|
||||
}
|
||||
|
||||
received = api.subscription_pull(self.SUB_PATH)
|
||||
|
||||
self.assertEqual(received, RETURNED['receivedMessages'])
|
||||
self.assertEqual(connection._called_with['method'], 'POST')
|
||||
path = '/%s:pull' % (self.SUB_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
self.assertEqual(connection._called_with['data'], BODY)
|
||||
|
||||
def test_subscription_pull_explicit(self):
|
||||
import base64
|
||||
PAYLOAD = b'This is the message text'
|
||||
B64 = base64.b64encode(PAYLOAD).decode('ascii')
|
||||
ACK_ID = 'DEADBEEF'
|
||||
MSG_ID = 'BEADCAFE'
|
||||
MESSAGE = {'messageId': MSG_ID, 'data': B64, 'attributes': {'a': 'b'}}
|
||||
RETURNED = {
|
||||
'receivedMessages': [{'ackId': ACK_ID, 'message': MESSAGE}],
|
||||
}
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
MAX_MESSAGES = 10
|
||||
BODY = {
|
||||
'returnImmediately': True,
|
||||
'maxMessages': MAX_MESSAGES,
|
||||
}
|
||||
|
||||
received = api.subscription_pull(
|
||||
self.SUB_PATH, return_immediately=True, max_messages=MAX_MESSAGES)
|
||||
|
||||
self.assertEqual(received, RETURNED['receivedMessages'])
|
||||
self.assertEqual(connection._called_with['method'], 'POST')
|
||||
path = '/%s:pull' % (self.SUB_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
self.assertEqual(connection._called_with['data'], BODY)
|
||||
|
||||
def test_subscription_acknowledge(self):
|
||||
ACK_ID1 = 'DEADBEEF'
|
||||
ACK_ID2 = 'BEADCAFE'
|
||||
BODY = {
|
||||
'ackIds': [ACK_ID1, ACK_ID2],
|
||||
}
|
||||
RETURNED = {}
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
api.subscription_acknowledge(self.SUB_PATH, [ACK_ID1, ACK_ID2])
|
||||
|
||||
self.assertEqual(connection._called_with['method'], 'POST')
|
||||
path = '/%s:acknowledge' % (self.SUB_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
self.assertEqual(connection._called_with['data'], BODY)
|
||||
|
||||
def test_subscription_modify_ack_deadline(self):
|
||||
ACK_ID1 = 'DEADBEEF'
|
||||
ACK_ID2 = 'BEADCAFE'
|
||||
NEW_DEADLINE = 90
|
||||
BODY = {
|
||||
'ackIds': [ACK_ID1, ACK_ID2],
|
||||
'ackDeadlineSeconds': NEW_DEADLINE,
|
||||
}
|
||||
RETURNED = {}
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
api.subscription_modify_ack_deadline(
|
||||
self.SUB_PATH, [ACK_ID1, ACK_ID2], NEW_DEADLINE)
|
||||
|
||||
self.assertEqual(connection._called_with['method'], 'POST')
|
||||
path = '/%s:modifyAckDeadline' % (self.SUB_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
self.assertEqual(connection._called_with['data'], BODY)
|
||||
|
||||
|
||||
class Test_IAMPolicyAPI(_Base):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from gcloud.pubsub.connection import _IAMPolicyAPI
|
||||
return _IAMPolicyAPI
|
||||
|
||||
def test_ctor(self):
|
||||
connection = _Connection()
|
||||
api = self._makeOne(connection)
|
||||
self.assertTrue(api._connection is connection)
|
||||
|
||||
def test_get_iam_policy(self):
|
||||
from gcloud.pubsub.iam import OWNER_ROLE, EDITOR_ROLE, VIEWER_ROLE
|
||||
OWNER1 = 'user:phred@example.com'
|
||||
OWNER2 = 'group:cloud-logs@google.com'
|
||||
EDITOR1 = 'domain:google.com'
|
||||
EDITOR2 = 'user:phred@example.com'
|
||||
VIEWER1 = 'serviceAccount:1234-abcdef@service.example.com'
|
||||
VIEWER2 = 'user:phred@example.com'
|
||||
RETURNED = {
|
||||
'etag': 'DEADBEEF',
|
||||
'version': 17,
|
||||
'bindings': [
|
||||
{'role': OWNER_ROLE, 'members': [OWNER1, OWNER2]},
|
||||
{'role': EDITOR_ROLE, 'members': [EDITOR1, EDITOR2]},
|
||||
{'role': VIEWER_ROLE, 'members': [VIEWER1, VIEWER2]},
|
||||
],
|
||||
}
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
policy = api.get_iam_policy(self.TOPIC_PATH)
|
||||
|
||||
self.assertEqual(policy, RETURNED)
|
||||
self.assertEqual(connection._called_with['method'], 'GET')
|
||||
path = '/%s:getIamPolicy' % (self.TOPIC_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
|
||||
def test_set_iam_policy(self):
|
||||
from gcloud.pubsub.iam import OWNER_ROLE, EDITOR_ROLE, VIEWER_ROLE
|
||||
OWNER1 = 'user:phred@example.com'
|
||||
OWNER2 = 'group:cloud-logs@google.com'
|
||||
EDITOR1 = 'domain:google.com'
|
||||
EDITOR2 = 'user:phred@example.com'
|
||||
VIEWER1 = 'serviceAccount:1234-abcdef@service.example.com'
|
||||
VIEWER2 = 'user:phred@example.com'
|
||||
POLICY = {
|
||||
'etag': 'DEADBEEF',
|
||||
'version': 17,
|
||||
'bindings': [
|
||||
{'role': OWNER_ROLE, 'members': [OWNER1, OWNER2]},
|
||||
{'role': EDITOR_ROLE, 'members': [EDITOR1, EDITOR2]},
|
||||
{'role': VIEWER_ROLE, 'members': [VIEWER1, VIEWER2]},
|
||||
],
|
||||
}
|
||||
RETURNED = POLICY.copy()
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
policy = api.set_iam_policy(self.TOPIC_PATH, POLICY)
|
||||
|
||||
self.assertEqual(policy, RETURNED)
|
||||
self.assertEqual(connection._called_with['method'], 'POST')
|
||||
path = '/%s:setIamPolicy' % (self.TOPIC_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
self.assertEqual(connection._called_with['data'],
|
||||
{'policy': POLICY})
|
||||
|
||||
def test_test_iam_permissions(self):
|
||||
from gcloud.pubsub.iam import OWNER_ROLE, EDITOR_ROLE, VIEWER_ROLE
|
||||
ALL_ROLES = [OWNER_ROLE, EDITOR_ROLE, VIEWER_ROLE]
|
||||
ALLOWED = ALL_ROLES[1:]
|
||||
RETURNED = {'permissions': ALLOWED}
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
allowed = api.test_iam_permissions(self.TOPIC_PATH, ALL_ROLES)
|
||||
|
||||
self.assertEqual(allowed, ALLOWED)
|
||||
self.assertEqual(connection._called_with['method'], 'POST')
|
||||
path = '/%s:testIamPermissions' % (self.TOPIC_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
self.assertEqual(connection._called_with['data'],
|
||||
{'permissions': ALL_ROLES})
|
||||
|
||||
def test_test_iam_permissions_missing_key(self):
|
||||
from gcloud.pubsub.iam import OWNER_ROLE, EDITOR_ROLE, VIEWER_ROLE
|
||||
ALL_ROLES = [OWNER_ROLE, EDITOR_ROLE, VIEWER_ROLE]
|
||||
RETURNED = {}
|
||||
connection = _Connection(RETURNED)
|
||||
api = self._makeOne(connection)
|
||||
|
||||
allowed = api.test_iam_permissions(self.TOPIC_PATH, ALL_ROLES)
|
||||
|
||||
self.assertEqual(allowed, [])
|
||||
self.assertEqual(connection._called_with['method'], 'POST')
|
||||
path = '/%s:testIamPermissions' % (self.TOPIC_PATH,)
|
||||
self.assertEqual(connection._called_with['path'], path)
|
||||
self.assertEqual(connection._called_with['data'],
|
||||
{'permissions': ALL_ROLES})
|
||||
|
||||
|
||||
class _Connection(object):
|
||||
|
||||
_called_with = None
|
||||
_no_response_error = None
|
||||
|
||||
def __init__(self, *responses):
|
||||
self._responses = responses
|
||||
|
||||
def api_request(self, **kw):
|
||||
from gcloud.exceptions import NotFound
|
||||
self._called_with = kw
|
||||
try:
|
||||
response, self._responses = self._responses[0], self._responses[1:]
|
||||
except IndexError:
|
||||
err_class = self._no_response_error or NotFound
|
||||
raise err_class('miss')
|
||||
return response
|
188
venv/Lib/site-packages/gcloud/pubsub/test_iam.py
Normal file
188
venv/Lib/site-packages/gcloud/pubsub/test_iam.py
Normal file
|
@ -0,0 +1,188 @@
|
|||
# Copyright 2016 Google Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import unittest2
|
||||
|
||||
|
||||
class TestPolicy(unittest2.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from gcloud.pubsub.iam import Policy
|
||||
return Policy
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_ctor_defaults(self):
|
||||
policy = self._makeOne()
|
||||
self.assertEqual(policy.etag, None)
|
||||
self.assertEqual(policy.version, None)
|
||||
self.assertEqual(list(policy.owners), [])
|
||||
self.assertEqual(list(policy.editors), [])
|
||||
self.assertEqual(list(policy.viewers), [])
|
||||
self.assertEqual(list(policy.publishers), [])
|
||||
self.assertEqual(list(policy.subscribers), [])
|
||||
|
||||
def test_ctor_explicit(self):
|
||||
VERSION = 17
|
||||
ETAG = 'ETAG'
|
||||
policy = self._makeOne(ETAG, VERSION)
|
||||
self.assertEqual(policy.etag, ETAG)
|
||||
self.assertEqual(policy.version, VERSION)
|
||||
self.assertEqual(list(policy.owners), [])
|
||||
self.assertEqual(list(policy.editors), [])
|
||||
self.assertEqual(list(policy.viewers), [])
|
||||
self.assertEqual(list(policy.publishers), [])
|
||||
self.assertEqual(list(policy.subscribers), [])
|
||||
|
||||
def test_user(self):
|
||||
EMAIL = 'phred@example.com'
|
||||
MEMBER = 'user:%s' % (EMAIL,)
|
||||
policy = self._makeOne()
|
||||
self.assertEqual(policy.user(EMAIL), MEMBER)
|
||||
|
||||
def test_service_account(self):
|
||||
EMAIL = 'phred@example.com'
|
||||
MEMBER = 'serviceAccount:%s' % (EMAIL,)
|
||||
policy = self._makeOne()
|
||||
self.assertEqual(policy.service_account(EMAIL), MEMBER)
|
||||
|
||||
def test_group(self):
|
||||
EMAIL = 'phred@example.com'
|
||||
MEMBER = 'group:%s' % (EMAIL,)
|
||||
policy = self._makeOne()
|
||||
self.assertEqual(policy.group(EMAIL), MEMBER)
|
||||
|
||||
def test_domain(self):
|
||||
DOMAIN = 'example.com'
|
||||
MEMBER = 'domain:%s' % (DOMAIN,)
|
||||
policy = self._makeOne()
|
||||
self.assertEqual(policy.domain(DOMAIN), MEMBER)
|
||||
|
||||
def test_all_users(self):
|
||||
policy = self._makeOne()
|
||||
self.assertEqual(policy.all_users(), 'allUsers')
|
||||
|
||||
def test_authenticated_users(self):
|
||||
policy = self._makeOne()
|
||||
self.assertEqual(policy.authenticated_users(), 'allAuthenticatedUsers')
|
||||
|
||||
def test_from_api_repr_only_etag(self):
|
||||
RESOURCE = {
|
||||
'etag': 'ACAB',
|
||||
}
|
||||
klass = self._getTargetClass()
|
||||
policy = klass.from_api_repr(RESOURCE)
|
||||
self.assertEqual(policy.etag, 'ACAB')
|
||||
self.assertEqual(policy.version, None)
|
||||
self.assertEqual(list(policy.owners), [])
|
||||
self.assertEqual(list(policy.editors), [])
|
||||
self.assertEqual(list(policy.viewers), [])
|
||||
|
||||
def test_from_api_repr_complete(self):
|
||||
from gcloud.pubsub.iam import (
|
||||
OWNER_ROLE,
|
||||
EDITOR_ROLE,
|
||||
VIEWER_ROLE,
|
||||
PUBSUB_PUBLISHER_ROLE,
|
||||
PUBSUB_SUBSCRIBER_ROLE,
|
||||
)
|
||||
OWNER1 = 'user:phred@example.com'
|
||||
OWNER2 = 'group:cloud-logs@google.com'
|
||||
EDITOR1 = 'domain:google.com'
|
||||
EDITOR2 = 'user:phred@example.com'
|
||||
VIEWER1 = 'serviceAccount:1234-abcdef@service.example.com'
|
||||
VIEWER2 = 'user:phred@example.com'
|
||||
PUBLISHER = 'user:phred@example.com'
|
||||
SUBSCRIBER = 'serviceAccount:1234-abcdef@service.example.com'
|
||||
RESOURCE = {
|
||||
'etag': 'DEADBEEF',
|
||||
'version': 17,
|
||||
'bindings': [
|
||||
{'role': OWNER_ROLE, 'members': [OWNER1, OWNER2]},
|
||||
{'role': EDITOR_ROLE, 'members': [EDITOR1, EDITOR2]},
|
||||
{'role': VIEWER_ROLE, 'members': [VIEWER1, VIEWER2]},
|
||||
{'role': PUBSUB_PUBLISHER_ROLE, 'members': [PUBLISHER]},
|
||||
{'role': PUBSUB_SUBSCRIBER_ROLE, 'members': [SUBSCRIBER]},
|
||||
],
|
||||
}
|
||||
klass = self._getTargetClass()
|
||||
policy = klass.from_api_repr(RESOURCE)
|
||||
self.assertEqual(policy.etag, 'DEADBEEF')
|
||||
self.assertEqual(policy.version, 17)
|
||||
self.assertEqual(sorted(policy.owners), [OWNER2, OWNER1])
|
||||
self.assertEqual(sorted(policy.editors), [EDITOR1, EDITOR2])
|
||||
self.assertEqual(sorted(policy.viewers), [VIEWER1, VIEWER2])
|
||||
self.assertEqual(sorted(policy.publishers), [PUBLISHER])
|
||||
self.assertEqual(sorted(policy.subscribers), [SUBSCRIBER])
|
||||
|
||||
def test_from_api_repr_bad_role(self):
|
||||
BOGUS1 = 'user:phred@example.com'
|
||||
BOGUS2 = 'group:cloud-logs@google.com'
|
||||
RESOURCE = {
|
||||
'etag': 'DEADBEEF',
|
||||
'version': 17,
|
||||
'bindings': [
|
||||
{'role': 'nonesuch', 'members': [BOGUS1, BOGUS2]},
|
||||
],
|
||||
}
|
||||
klass = self._getTargetClass()
|
||||
with self.assertRaises(ValueError):
|
||||
klass.from_api_repr(RESOURCE)
|
||||
|
||||
def test_to_api_repr_defaults(self):
|
||||
policy = self._makeOne()
|
||||
self.assertEqual(policy.to_api_repr(), {})
|
||||
|
||||
def test_to_api_repr_only_etag(self):
|
||||
policy = self._makeOne('DEADBEEF')
|
||||
self.assertEqual(policy.to_api_repr(), {'etag': 'DEADBEEF'})
|
||||
|
||||
def test_to_api_repr_full(self):
|
||||
from gcloud.pubsub.iam import (
|
||||
PUBSUB_ADMIN_ROLE,
|
||||
PUBSUB_EDITOR_ROLE,
|
||||
PUBSUB_VIEWER_ROLE,
|
||||
PUBSUB_PUBLISHER_ROLE,
|
||||
PUBSUB_SUBSCRIBER_ROLE,
|
||||
)
|
||||
OWNER1 = 'group:cloud-logs@google.com'
|
||||
OWNER2 = 'user:phred@example.com'
|
||||
EDITOR1 = 'domain:google.com'
|
||||
EDITOR2 = 'user:phred@example.com'
|
||||
VIEWER1 = 'serviceAccount:1234-abcdef@service.example.com'
|
||||
VIEWER2 = 'user:phred@example.com'
|
||||
PUBLISHER = 'user:phred@example.com'
|
||||
SUBSCRIBER = 'serviceAccount:1234-abcdef@service.example.com'
|
||||
EXPECTED = {
|
||||
'etag': 'DEADBEEF',
|
||||
'version': 17,
|
||||
'bindings': [
|
||||
{'role': PUBSUB_ADMIN_ROLE, 'members': [OWNER1, OWNER2]},
|
||||
{'role': PUBSUB_EDITOR_ROLE, 'members': [EDITOR1, EDITOR2]},
|
||||
{'role': PUBSUB_VIEWER_ROLE, 'members': [VIEWER1, VIEWER2]},
|
||||
{'role': PUBSUB_PUBLISHER_ROLE, 'members': [PUBLISHER]},
|
||||
{'role': PUBSUB_SUBSCRIBER_ROLE, 'members': [SUBSCRIBER]},
|
||||
],
|
||||
}
|
||||
policy = self._makeOne('DEADBEEF', 17)
|
||||
policy.owners.add(OWNER1)
|
||||
policy.owners.add(OWNER2)
|
||||
policy.editors.add(EDITOR1)
|
||||
policy.editors.add(EDITOR2)
|
||||
policy.viewers.add(VIEWER1)
|
||||
policy.viewers.add(VIEWER2)
|
||||
policy.publishers.add(PUBLISHER)
|
||||
policy.subscribers.add(SUBSCRIBER)
|
||||
self.assertEqual(policy.to_api_repr(), EXPECTED)
|
126
venv/Lib/site-packages/gcloud/pubsub/test_message.py
Normal file
126
venv/Lib/site-packages/gcloud/pubsub/test_message.py
Normal file
|
@ -0,0 +1,126 @@
|
|||
# Copyright 2015 Google Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import unittest2
|
||||
|
||||
|
||||
class TestMessage(unittest2.TestCase):
|
||||
|
||||
def _getTargetClass(self):
|
||||
from gcloud.pubsub.message import Message
|
||||
return Message
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_ctor_no_attributes(self):
|
||||
DATA = b'DEADBEEF'
|
||||
MESSAGE_ID = b'12345'
|
||||
message = self._makeOne(data=DATA, message_id=MESSAGE_ID)
|
||||
self.assertEqual(message.data, DATA)
|
||||
self.assertEqual(message.message_id, MESSAGE_ID)
|
||||
self.assertEqual(message.attributes, {})
|
||||
self.assertEqual(message.service_timestamp, None)
|
||||
|
||||
def test_ctor_w_attributes(self):
|
||||
DATA = b'DEADBEEF'
|
||||
MESSAGE_ID = b'12345'
|
||||
ATTRS = {'a': 'b'}
|
||||
message = self._makeOne(data=DATA, message_id=MESSAGE_ID,
|
||||
attributes=ATTRS)
|
||||
self.assertEqual(message.data, DATA)
|
||||
self.assertEqual(message.message_id, MESSAGE_ID)
|
||||
self.assertEqual(message.attributes, ATTRS)
|
||||
self.assertEqual(message.service_timestamp, None)
|
||||
|
||||
def test_timestamp_no_attributes(self):
|
||||
DATA = b'DEADBEEF'
|
||||
MESSAGE_ID = b'12345'
|
||||
message = self._makeOne(data=DATA, message_id=MESSAGE_ID)
|
||||
|
||||
def _to_fail():
|
||||
return message.timestamp
|
||||
|
||||
self.assertRaises(ValueError, _to_fail)
|
||||
|
||||
def test_timestamp_wo_timestamp_in_attributes(self):
|
||||
DATA = b'DEADBEEF'
|
||||
MESSAGE_ID = b'12345'
|
||||
ATTRS = {'a': 'b'}
|
||||
message = self._makeOne(data=DATA, message_id=MESSAGE_ID,
|
||||
attributes=ATTRS)
|
||||
|
||||
def _to_fail():
|
||||
return message.timestamp
|
||||
|
||||
self.assertRaises(ValueError, _to_fail)
|
||||
|
||||
def test_timestamp_w_timestamp_in_attributes(self):
|
||||
from datetime import datetime
|
||||
from gcloud._helpers import _RFC3339_MICROS
|
||||
from gcloud._helpers import UTC
|
||||
DATA = b'DEADBEEF'
|
||||
MESSAGE_ID = b'12345'
|
||||
TIMESTAMP = '2015-04-10T18:42:27.131956Z'
|
||||
naive = datetime.strptime(TIMESTAMP, _RFC3339_MICROS)
|
||||
timestamp = naive.replace(tzinfo=UTC)
|
||||
ATTRS = {'timestamp': TIMESTAMP}
|
||||
message = self._makeOne(data=DATA, message_id=MESSAGE_ID,
|
||||
attributes=ATTRS)
|
||||
self.assertEqual(message.timestamp, timestamp)
|
||||
|
||||
def test_from_api_repr_missing_data(self):
|
||||
MESSAGE_ID = '12345'
|
||||
api_repr = {'messageId': MESSAGE_ID}
|
||||
message = self._getTargetClass().from_api_repr(api_repr)
|
||||
self.assertEqual(message.data, b'')
|
||||
self.assertEqual(message.message_id, MESSAGE_ID)
|
||||
self.assertEqual(message.attributes, {})
|
||||
self.assertEqual(message.service_timestamp, None)
|
||||
|
||||
def test_from_api_repr_no_attributes(self):
|
||||
from base64 import b64encode as b64
|
||||
DATA = b'DEADBEEF'
|
||||
B64_DATA = b64(DATA)
|
||||
MESSAGE_ID = '12345'
|
||||
TIMESTAMP = '2016-03-18-19:38:22.001393427Z'
|
||||
api_repr = {
|
||||
'data': B64_DATA,
|
||||
'messageId': MESSAGE_ID,
|
||||
'publishTimestamp': TIMESTAMP,
|
||||
}
|
||||
message = self._getTargetClass().from_api_repr(api_repr)
|
||||
self.assertEqual(message.data, DATA)
|
||||
self.assertEqual(message.message_id, MESSAGE_ID)
|
||||
self.assertEqual(message.attributes, {})
|
||||
self.assertEqual(message.service_timestamp, TIMESTAMP)
|
||||
|
||||
def test_from_api_repr_w_attributes(self):
|
||||
from base64 import b64encode as b64
|
||||
DATA = b'DEADBEEF'
|
||||
B64_DATA = b64(DATA)
|
||||
MESSAGE_ID = '12345'
|
||||
ATTRS = {'a': 'b'}
|
||||
TIMESTAMP = '2016-03-18-19:38:22.001393427Z'
|
||||
api_repr = {
|
||||
'data': B64_DATA,
|
||||
'messageId': MESSAGE_ID,
|
||||
'publishTimestamp': TIMESTAMP,
|
||||
'attributes': ATTRS,
|
||||
}
|
||||
message = self._getTargetClass().from_api_repr(api_repr)
|
||||
self.assertEqual(message.data, DATA)
|
||||
self.assertEqual(message.message_id, MESSAGE_ID)
|
||||
self.assertEqual(message.service_timestamp, TIMESTAMP)
|
||||
self.assertEqual(message.attributes, ATTRS)
|
679
venv/Lib/site-packages/gcloud/pubsub/test_subscription.py
Normal file
679
venv/Lib/site-packages/gcloud/pubsub/test_subscription.py
Normal file
|
@ -0,0 +1,679 @@
|
|||
# Copyright 2015 Google Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import unittest2
|
||||
|
||||
|
||||
class TestSubscription(unittest2.TestCase):
|
||||
PROJECT = 'PROJECT'
|
||||
TOPIC_NAME = 'topic_name'
|
||||
TOPIC_PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME)
|
||||
SUB_NAME = 'sub_name'
|
||||
SUB_PATH = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME)
|
||||
DEADLINE = 42
|
||||
ENDPOINT = 'https://api.example.com/push'
|
||||
|
||||
def _getTargetClass(self):
|
||||
from gcloud.pubsub.subscription import Subscription
|
||||
return Subscription
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_ctor_defaults(self):
|
||||
client = _Client(project=self.PROJECT)
|
||||
topic = _Topic(self.TOPIC_NAME, client=client)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic)
|
||||
self.assertEqual(subscription.name, self.SUB_NAME)
|
||||
self.assertTrue(subscription.topic is topic)
|
||||
self.assertEqual(subscription.ack_deadline, None)
|
||||
self.assertEqual(subscription.push_endpoint, None)
|
||||
|
||||
def test_ctor_explicit(self):
|
||||
client = _Client(project=self.PROJECT)
|
||||
topic = _Topic(self.TOPIC_NAME, client=client)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic,
|
||||
self.DEADLINE, self.ENDPOINT)
|
||||
self.assertEqual(subscription.name, self.SUB_NAME)
|
||||
self.assertTrue(subscription.topic is topic)
|
||||
self.assertEqual(subscription.ack_deadline, self.DEADLINE)
|
||||
self.assertEqual(subscription.push_endpoint, self.ENDPOINT)
|
||||
|
||||
def test_ctor_w_client_wo_topic(self):
|
||||
client = _Client(project=self.PROJECT)
|
||||
subscription = self._makeOne(self.SUB_NAME, client=client)
|
||||
self.assertEqual(subscription.name, self.SUB_NAME)
|
||||
self.assertTrue(subscription.topic is None)
|
||||
|
||||
def test_ctor_w_both_topic_and_client(self):
|
||||
client1 = _Client(project=self.PROJECT)
|
||||
client2 = _Client(project=self.PROJECT)
|
||||
topic = _Topic(self.TOPIC_NAME, client=client1)
|
||||
with self.assertRaises(TypeError):
|
||||
self._makeOne(self.SUB_NAME, topic, client=client2)
|
||||
|
||||
def test_ctor_w_neither_topic_nor_client(self):
|
||||
with self.assertRaises(TypeError):
|
||||
self._makeOne(self.SUB_NAME)
|
||||
|
||||
def test_from_api_repr_no_topics(self):
|
||||
from gcloud.pubsub.topic import Topic
|
||||
resource = {'topic': self.TOPIC_PATH,
|
||||
'name': self.SUB_PATH,
|
||||
'ackDeadlineSeconds': self.DEADLINE,
|
||||
'pushConfig': {'pushEndpoint': self.ENDPOINT}}
|
||||
klass = self._getTargetClass()
|
||||
client = _Client(project=self.PROJECT)
|
||||
subscription = klass.from_api_repr(resource, client)
|
||||
self.assertEqual(subscription.name, self.SUB_NAME)
|
||||
topic = subscription.topic
|
||||
self.assertIsInstance(topic, Topic)
|
||||
self.assertEqual(topic.name, self.TOPIC_NAME)
|
||||
self.assertEqual(topic.project, self.PROJECT)
|
||||
self.assertEqual(subscription.ack_deadline, self.DEADLINE)
|
||||
self.assertEqual(subscription.push_endpoint, self.ENDPOINT)
|
||||
|
||||
def test_from_api_repr_w_deleted_topic(self):
|
||||
klass = self._getTargetClass()
|
||||
resource = {'topic': klass._DELETED_TOPIC_PATH,
|
||||
'name': self.SUB_PATH,
|
||||
'ackDeadlineSeconds': self.DEADLINE,
|
||||
'pushConfig': {'pushEndpoint': self.ENDPOINT}}
|
||||
klass = self._getTargetClass()
|
||||
client = _Client(project=self.PROJECT)
|
||||
subscription = klass.from_api_repr(resource, client)
|
||||
self.assertEqual(subscription.name, self.SUB_NAME)
|
||||
self.assertTrue(subscription.topic is None)
|
||||
self.assertEqual(subscription.ack_deadline, self.DEADLINE)
|
||||
self.assertEqual(subscription.push_endpoint, self.ENDPOINT)
|
||||
|
||||
def test_from_api_repr_w_topics_no_topic_match(self):
|
||||
from gcloud.pubsub.topic import Topic
|
||||
resource = {'topic': self.TOPIC_PATH,
|
||||
'name': self.SUB_PATH,
|
||||
'ackDeadlineSeconds': self.DEADLINE,
|
||||
'pushConfig': {'pushEndpoint': self.ENDPOINT}}
|
||||
topics = {}
|
||||
klass = self._getTargetClass()
|
||||
client = _Client(project=self.PROJECT)
|
||||
subscription = klass.from_api_repr(resource, client, topics=topics)
|
||||
self.assertEqual(subscription.name, self.SUB_NAME)
|
||||
topic = subscription.topic
|
||||
self.assertIsInstance(topic, Topic)
|
||||
self.assertTrue(topic is topics[self.TOPIC_PATH])
|
||||
self.assertEqual(topic.name, self.TOPIC_NAME)
|
||||
self.assertEqual(topic.project, self.PROJECT)
|
||||
self.assertEqual(subscription.ack_deadline, self.DEADLINE)
|
||||
self.assertEqual(subscription.push_endpoint, self.ENDPOINT)
|
||||
|
||||
def test_from_api_repr_w_topics_w_topic_match(self):
|
||||
resource = {'topic': self.TOPIC_PATH,
|
||||
'name': self.SUB_PATH,
|
||||
'ackDeadlineSeconds': self.DEADLINE,
|
||||
'pushConfig': {'pushEndpoint': self.ENDPOINT}}
|
||||
client = _Client(project=self.PROJECT)
|
||||
topic = _Topic(self.TOPIC_NAME, client=client)
|
||||
topics = {self.TOPIC_PATH: topic}
|
||||
klass = self._getTargetClass()
|
||||
subscription = klass.from_api_repr(resource, client, topics=topics)
|
||||
self.assertEqual(subscription.name, self.SUB_NAME)
|
||||
self.assertTrue(subscription.topic is topic)
|
||||
self.assertEqual(subscription.ack_deadline, self.DEADLINE)
|
||||
self.assertEqual(subscription.push_endpoint, self.ENDPOINT)
|
||||
|
||||
def test_full_name_and_path(self):
|
||||
PROJECT = 'PROJECT'
|
||||
SUB_NAME = 'sub_name'
|
||||
SUB_FULL = 'projects/%s/subscriptions/%s' % (PROJECT, SUB_NAME)
|
||||
SUB_PATH = '/%s' % (SUB_FULL,)
|
||||
TOPIC_NAME = 'topic_name'
|
||||
CLIENT = _Client(project=PROJECT)
|
||||
topic = _Topic(TOPIC_NAME, client=CLIENT)
|
||||
subscription = self._makeOne(SUB_NAME, topic)
|
||||
self.assertEqual(subscription.full_name, SUB_FULL)
|
||||
self.assertEqual(subscription.path, SUB_PATH)
|
||||
|
||||
def test_create_pull_wo_ack_deadline_w_bound_client(self):
|
||||
RESPONSE = {
|
||||
'topic': self.TOPIC_PATH,
|
||||
'name': self.SUB_PATH,
|
||||
}
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.subscriber_api = _FauxSubscribererAPI()
|
||||
api._subscription_create_response = RESPONSE
|
||||
topic = _Topic(self.TOPIC_NAME, client=client)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic)
|
||||
|
||||
subscription.create()
|
||||
|
||||
self.assertEqual(api._subscription_created,
|
||||
(self.SUB_PATH, self.TOPIC_PATH, None, None))
|
||||
|
||||
def test_create_push_w_ack_deadline_w_alternate_client(self):
|
||||
RESPONSE = {
|
||||
'topic': self.TOPIC_PATH,
|
||||
'name': self.SUB_PATH,
|
||||
'ackDeadlineSeconds': self.DEADLINE,
|
||||
'pushConfig': {'pushEndpoint': self.ENDPOINT}
|
||||
}
|
||||
client1 = _Client(project=self.PROJECT)
|
||||
client2 = _Client(project=self.PROJECT)
|
||||
api = client2.subscriber_api = _FauxSubscribererAPI()
|
||||
api._subscription_create_response = RESPONSE
|
||||
topic = _Topic(self.TOPIC_NAME, client=client1)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic,
|
||||
self.DEADLINE, self.ENDPOINT)
|
||||
|
||||
subscription.create(client=client2)
|
||||
|
||||
self.assertEqual(
|
||||
api._subscription_created,
|
||||
(self.SUB_PATH, self.TOPIC_PATH, self.DEADLINE, self.ENDPOINT))
|
||||
|
||||
def test_exists_miss_w_bound_client(self):
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.subscriber_api = _FauxSubscribererAPI()
|
||||
topic = _Topic(self.TOPIC_NAME, client=client)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic)
|
||||
|
||||
self.assertFalse(subscription.exists())
|
||||
|
||||
self.assertEqual(api._subscription_got, self.SUB_PATH)
|
||||
|
||||
def test_exists_hit_w_alternate_client(self):
|
||||
RESPONSE = {'name': self.SUB_PATH, 'topic': self.TOPIC_PATH}
|
||||
client1 = _Client(project=self.PROJECT)
|
||||
client2 = _Client(project=self.PROJECT)
|
||||
api = client2.subscriber_api = _FauxSubscribererAPI()
|
||||
api._subscription_get_response = RESPONSE
|
||||
topic = _Topic(self.TOPIC_NAME, client=client1)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic)
|
||||
|
||||
self.assertTrue(subscription.exists(client=client2))
|
||||
|
||||
self.assertEqual(api._subscription_got, self.SUB_PATH)
|
||||
|
||||
def test_reload_w_bound_client(self):
|
||||
RESPONSE = {
|
||||
'name': self.SUB_PATH,
|
||||
'topic': self.TOPIC_PATH,
|
||||
'ackDeadlineSeconds': self.DEADLINE,
|
||||
'pushConfig': {'pushEndpoint': self.ENDPOINT},
|
||||
}
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.subscriber_api = _FauxSubscribererAPI()
|
||||
api._subscription_get_response = RESPONSE
|
||||
topic = _Topic(self.TOPIC_NAME, client=client)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic)
|
||||
|
||||
subscription.reload()
|
||||
|
||||
self.assertEqual(subscription.ack_deadline, self.DEADLINE)
|
||||
self.assertEqual(subscription.push_endpoint, self.ENDPOINT)
|
||||
self.assertEqual(api._subscription_got, self.SUB_PATH)
|
||||
|
||||
def test_reload_w_alternate_client(self):
|
||||
RESPONSE = {
|
||||
'name': self.SUB_PATH,
|
||||
'topic': self.TOPIC_PATH,
|
||||
}
|
||||
client1 = _Client(project=self.PROJECT)
|
||||
client2 = _Client(project=self.PROJECT)
|
||||
api = client2.subscriber_api = _FauxSubscribererAPI()
|
||||
api._subscription_get_response = RESPONSE
|
||||
topic = _Topic(self.TOPIC_NAME, client=client1)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic,
|
||||
self.DEADLINE, self.ENDPOINT)
|
||||
|
||||
subscription.reload(client=client2)
|
||||
|
||||
self.assertEqual(subscription.ack_deadline, None)
|
||||
self.assertEqual(subscription.push_endpoint, None)
|
||||
self.assertEqual(api._subscription_got, self.SUB_PATH)
|
||||
|
||||
def test_delete_w_bound_client(self):
|
||||
RESPONSE = {}
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.subscriber_api = _FauxSubscribererAPI()
|
||||
api._subscription_delete_response = RESPONSE
|
||||
topic = _Topic(self.TOPIC_NAME, client=client)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic)
|
||||
|
||||
subscription.delete()
|
||||
|
||||
self.assertEqual(api._subscription_deleted, self.SUB_PATH)
|
||||
|
||||
def test_delete_w_alternate_client(self):
|
||||
RESPONSE = {}
|
||||
client1 = _Client(project=self.PROJECT)
|
||||
client2 = _Client(project=self.PROJECT)
|
||||
api = client2.subscriber_api = _FauxSubscribererAPI()
|
||||
api._subscription_delete_response = RESPONSE
|
||||
topic = _Topic(self.TOPIC_NAME, client=client1)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic,
|
||||
self.DEADLINE, self.ENDPOINT)
|
||||
|
||||
subscription.delete(client=client2)
|
||||
|
||||
self.assertEqual(api._subscription_deleted, self.SUB_PATH)
|
||||
|
||||
def test_modify_push_config_w_endpoint_w_bound_client(self):
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.subscriber_api = _FauxSubscribererAPI()
|
||||
api._subscription_modify_push_config_response = {}
|
||||
topic = _Topic(self.TOPIC_NAME, client=client)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic)
|
||||
|
||||
subscription.modify_push_configuration(push_endpoint=self.ENDPOINT)
|
||||
|
||||
self.assertEqual(subscription.push_endpoint, self.ENDPOINT)
|
||||
self.assertEqual(api._subscription_modified_push_config,
|
||||
(self.SUB_PATH, self.ENDPOINT))
|
||||
|
||||
def test_modify_push_config_wo_endpoint_w_alternate_client(self):
|
||||
client1 = _Client(project=self.PROJECT)
|
||||
client2 = _Client(project=self.PROJECT)
|
||||
api = client2.subscriber_api = _FauxSubscribererAPI()
|
||||
api._subscription_modify_push_config_response = {}
|
||||
topic = _Topic(self.TOPIC_NAME, client=client1)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic,
|
||||
push_endpoint=self.ENDPOINT)
|
||||
|
||||
subscription.modify_push_configuration(push_endpoint=None,
|
||||
client=client2)
|
||||
|
||||
self.assertEqual(subscription.push_endpoint, None)
|
||||
self.assertEqual(api._subscription_modified_push_config,
|
||||
(self.SUB_PATH, None))
|
||||
|
||||
def test_pull_wo_return_immediately_max_messages_w_bound_client(self):
|
||||
import base64
|
||||
from gcloud.pubsub.message import Message
|
||||
ACK_ID = 'DEADBEEF'
|
||||
MSG_ID = 'BEADCAFE'
|
||||
PAYLOAD = b'This is the message text'
|
||||
B64 = base64.b64encode(PAYLOAD)
|
||||
MESSAGE = {'messageId': MSG_ID, 'data': B64}
|
||||
REC_MESSAGE = {'ackId': ACK_ID, 'message': MESSAGE}
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.subscriber_api = _FauxSubscribererAPI()
|
||||
api._subscription_pull_response = [REC_MESSAGE]
|
||||
topic = _Topic(self.TOPIC_NAME, client=client)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic)
|
||||
|
||||
pulled = subscription.pull()
|
||||
|
||||
self.assertEqual(len(pulled), 1)
|
||||
ack_id, message = pulled[0]
|
||||
self.assertEqual(ack_id, ACK_ID)
|
||||
self.assertIsInstance(message, Message)
|
||||
self.assertEqual(message.data, PAYLOAD)
|
||||
self.assertEqual(message.message_id, MSG_ID)
|
||||
self.assertEqual(message.attributes, {})
|
||||
self.assertEqual(api._subscription_pulled,
|
||||
(self.SUB_PATH, False, 1))
|
||||
|
||||
def test_pull_w_return_immediately_w_max_messages_w_alt_client(self):
|
||||
import base64
|
||||
from gcloud.pubsub.message import Message
|
||||
ACK_ID = 'DEADBEEF'
|
||||
MSG_ID = 'BEADCAFE'
|
||||
PAYLOAD = b'This is the message text'
|
||||
B64 = base64.b64encode(PAYLOAD)
|
||||
MESSAGE = {'messageId': MSG_ID, 'data': B64, 'attributes': {'a': 'b'}}
|
||||
REC_MESSAGE = {'ackId': ACK_ID, 'message': MESSAGE}
|
||||
client1 = _Client(project=self.PROJECT)
|
||||
client2 = _Client(project=self.PROJECT)
|
||||
api = client2.subscriber_api = _FauxSubscribererAPI()
|
||||
api._subscription_pull_response = [REC_MESSAGE]
|
||||
topic = _Topic(self.TOPIC_NAME, client=client1)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic)
|
||||
|
||||
pulled = subscription.pull(return_immediately=True, max_messages=3,
|
||||
client=client2)
|
||||
|
||||
self.assertEqual(len(pulled), 1)
|
||||
ack_id, message = pulled[0]
|
||||
self.assertEqual(ack_id, ACK_ID)
|
||||
self.assertIsInstance(message, Message)
|
||||
self.assertEqual(message.data, PAYLOAD)
|
||||
self.assertEqual(message.message_id, MSG_ID)
|
||||
self.assertEqual(message.attributes, {'a': 'b'})
|
||||
self.assertEqual(api._subscription_pulled,
|
||||
(self.SUB_PATH, True, 3))
|
||||
|
||||
def test_pull_wo_receivedMessages(self):
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.subscriber_api = _FauxSubscribererAPI()
|
||||
api._subscription_pull_response = {}
|
||||
topic = _Topic(self.TOPIC_NAME, client=client)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic)
|
||||
|
||||
pulled = subscription.pull(return_immediately=False)
|
||||
|
||||
self.assertEqual(len(pulled), 0)
|
||||
self.assertEqual(api._subscription_pulled,
|
||||
(self.SUB_PATH, False, 1))
|
||||
|
||||
def test_acknowledge_w_bound_client(self):
|
||||
ACK_ID1 = 'DEADBEEF'
|
||||
ACK_ID2 = 'BEADCAFE'
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.subscriber_api = _FauxSubscribererAPI()
|
||||
api._subscription_acknowlege_response = {}
|
||||
topic = _Topic(self.TOPIC_NAME, client=client)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic)
|
||||
|
||||
subscription.acknowledge([ACK_ID1, ACK_ID2])
|
||||
|
||||
self.assertEqual(api._subscription_acked,
|
||||
(self.SUB_PATH, [ACK_ID1, ACK_ID2]))
|
||||
|
||||
def test_acknowledge_w_alternate_client(self):
|
||||
ACK_ID1 = 'DEADBEEF'
|
||||
ACK_ID2 = 'BEADCAFE'
|
||||
client1 = _Client(project=self.PROJECT)
|
||||
client2 = _Client(project=self.PROJECT)
|
||||
api = client2.subscriber_api = _FauxSubscribererAPI()
|
||||
api._subscription_acknowlege_response = {}
|
||||
topic = _Topic(self.TOPIC_NAME, client=client1)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic)
|
||||
|
||||
subscription.acknowledge([ACK_ID1, ACK_ID2], client=client2)
|
||||
|
||||
self.assertEqual(api._subscription_acked,
|
||||
(self.SUB_PATH, [ACK_ID1, ACK_ID2]))
|
||||
|
||||
def test_modify_ack_deadline_w_bound_client(self):
|
||||
ACK_ID1 = 'DEADBEEF'
|
||||
ACK_ID2 = 'BEADCAFE'
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.subscriber_api = _FauxSubscribererAPI()
|
||||
api._subscription_modify_ack_deadline_response = {}
|
||||
topic = _Topic(self.TOPIC_NAME, client=client)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic)
|
||||
|
||||
subscription.modify_ack_deadline([ACK_ID1, ACK_ID2], self.DEADLINE)
|
||||
|
||||
self.assertEqual(api._subscription_modified_ack_deadline,
|
||||
(self.SUB_PATH, [ACK_ID1, ACK_ID2], self.DEADLINE))
|
||||
|
||||
def test_modify_ack_deadline_w_alternate_client(self):
|
||||
ACK_ID1 = 'DEADBEEF'
|
||||
ACK_ID2 = 'BEADCAFE'
|
||||
client1 = _Client(project=self.PROJECT)
|
||||
client2 = _Client(project=self.PROJECT)
|
||||
api = client2.subscriber_api = _FauxSubscribererAPI()
|
||||
api._subscription_modify_ack_deadline_response = {}
|
||||
topic = _Topic(self.TOPIC_NAME, client=client1)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic)
|
||||
|
||||
subscription.modify_ack_deadline(
|
||||
[ACK_ID1, ACK_ID2], self.DEADLINE, client=client2)
|
||||
|
||||
self.assertEqual(api._subscription_modified_ack_deadline,
|
||||
(self.SUB_PATH, [ACK_ID1, ACK_ID2], self.DEADLINE))
|
||||
|
||||
def test_get_iam_policy_w_bound_client(self):
|
||||
from gcloud.pubsub.iam import (
|
||||
PUBSUB_ADMIN_ROLE,
|
||||
PUBSUB_EDITOR_ROLE,
|
||||
PUBSUB_VIEWER_ROLE,
|
||||
PUBSUB_PUBLISHER_ROLE,
|
||||
PUBSUB_SUBSCRIBER_ROLE,
|
||||
)
|
||||
OWNER1 = 'user:phred@example.com'
|
||||
OWNER2 = 'group:cloud-logs@google.com'
|
||||
EDITOR1 = 'domain:google.com'
|
||||
EDITOR2 = 'user:phred@example.com'
|
||||
VIEWER1 = 'serviceAccount:1234-abcdef@service.example.com'
|
||||
VIEWER2 = 'user:phred@example.com'
|
||||
PUBLISHER = 'user:phred@example.com'
|
||||
SUBSCRIBER = 'serviceAccount:1234-abcdef@service.example.com'
|
||||
POLICY = {
|
||||
'etag': 'DEADBEEF',
|
||||
'version': 17,
|
||||
'bindings': [
|
||||
{'role': PUBSUB_ADMIN_ROLE, 'members': [OWNER1, OWNER2]},
|
||||
{'role': PUBSUB_EDITOR_ROLE, 'members': [EDITOR1, EDITOR2]},
|
||||
{'role': PUBSUB_VIEWER_ROLE, 'members': [VIEWER1, VIEWER2]},
|
||||
{'role': PUBSUB_PUBLISHER_ROLE, 'members': [PUBLISHER]},
|
||||
{'role': PUBSUB_SUBSCRIBER_ROLE, 'members': [SUBSCRIBER]},
|
||||
],
|
||||
}
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.iam_policy_api = _FauxIAMPolicy()
|
||||
api._get_iam_policy_response = POLICY
|
||||
topic = _Topic(self.TOPIC_NAME, client=client)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic)
|
||||
|
||||
policy = subscription.get_iam_policy()
|
||||
|
||||
self.assertEqual(policy.etag, 'DEADBEEF')
|
||||
self.assertEqual(policy.version, 17)
|
||||
self.assertEqual(sorted(policy.owners), [OWNER2, OWNER1])
|
||||
self.assertEqual(sorted(policy.editors), [EDITOR1, EDITOR2])
|
||||
self.assertEqual(sorted(policy.viewers), [VIEWER1, VIEWER2])
|
||||
self.assertEqual(sorted(policy.publishers), [PUBLISHER])
|
||||
self.assertEqual(sorted(policy.subscribers), [SUBSCRIBER])
|
||||
self.assertEqual(api._got_iam_policy, self.SUB_PATH)
|
||||
|
||||
def test_get_iam_policy_w_alternate_client(self):
|
||||
POLICY = {
|
||||
'etag': 'ACAB',
|
||||
}
|
||||
client1 = _Client(project=self.PROJECT)
|
||||
client2 = _Client(project=self.PROJECT)
|
||||
api = client2.iam_policy_api = _FauxIAMPolicy()
|
||||
api._get_iam_policy_response = POLICY
|
||||
topic = _Topic(self.TOPIC_NAME, client=client1)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic)
|
||||
|
||||
policy = subscription.get_iam_policy(client=client2)
|
||||
|
||||
self.assertEqual(policy.etag, 'ACAB')
|
||||
self.assertEqual(policy.version, None)
|
||||
self.assertEqual(sorted(policy.owners), [])
|
||||
self.assertEqual(sorted(policy.editors), [])
|
||||
self.assertEqual(sorted(policy.viewers), [])
|
||||
|
||||
self.assertEqual(api._got_iam_policy, self.SUB_PATH)
|
||||
|
||||
def test_set_iam_policy_w_bound_client(self):
|
||||
from gcloud.pubsub.iam import Policy
|
||||
from gcloud.pubsub.iam import (
|
||||
PUBSUB_ADMIN_ROLE,
|
||||
PUBSUB_EDITOR_ROLE,
|
||||
PUBSUB_VIEWER_ROLE,
|
||||
PUBSUB_PUBLISHER_ROLE,
|
||||
PUBSUB_SUBSCRIBER_ROLE,
|
||||
)
|
||||
OWNER1 = 'group:cloud-logs@google.com'
|
||||
OWNER2 = 'user:phred@example.com'
|
||||
EDITOR1 = 'domain:google.com'
|
||||
EDITOR2 = 'user:phred@example.com'
|
||||
VIEWER1 = 'serviceAccount:1234-abcdef@service.example.com'
|
||||
VIEWER2 = 'user:phred@example.com'
|
||||
PUBLISHER = 'user:phred@example.com'
|
||||
SUBSCRIBER = 'serviceAccount:1234-abcdef@service.example.com'
|
||||
POLICY = {
|
||||
'etag': 'DEADBEEF',
|
||||
'version': 17,
|
||||
'bindings': [
|
||||
{'role': PUBSUB_ADMIN_ROLE, 'members': [OWNER1, OWNER2]},
|
||||
{'role': PUBSUB_EDITOR_ROLE, 'members': [EDITOR1, EDITOR2]},
|
||||
{'role': PUBSUB_VIEWER_ROLE, 'members': [VIEWER1, VIEWER2]},
|
||||
{'role': PUBSUB_PUBLISHER_ROLE, 'members': [PUBLISHER]},
|
||||
{'role': PUBSUB_SUBSCRIBER_ROLE, 'members': [SUBSCRIBER]},
|
||||
],
|
||||
}
|
||||
RESPONSE = POLICY.copy()
|
||||
RESPONSE['etag'] = 'ABACABAF'
|
||||
RESPONSE['version'] = 18
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.iam_policy_api = _FauxIAMPolicy()
|
||||
api._set_iam_policy_response = RESPONSE
|
||||
topic = _Topic(self.TOPIC_NAME, client=client)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic)
|
||||
policy = Policy('DEADBEEF', 17)
|
||||
policy.owners.add(OWNER1)
|
||||
policy.owners.add(OWNER2)
|
||||
policy.editors.add(EDITOR1)
|
||||
policy.editors.add(EDITOR2)
|
||||
policy.viewers.add(VIEWER1)
|
||||
policy.viewers.add(VIEWER2)
|
||||
policy.publishers.add(PUBLISHER)
|
||||
policy.subscribers.add(SUBSCRIBER)
|
||||
|
||||
new_policy = subscription.set_iam_policy(policy)
|
||||
|
||||
self.assertEqual(new_policy.etag, 'ABACABAF')
|
||||
self.assertEqual(new_policy.version, 18)
|
||||
self.assertEqual(sorted(new_policy.owners), [OWNER1, OWNER2])
|
||||
self.assertEqual(sorted(new_policy.editors), [EDITOR1, EDITOR2])
|
||||
self.assertEqual(sorted(new_policy.viewers), [VIEWER1, VIEWER2])
|
||||
self.assertEqual(sorted(new_policy.publishers), [PUBLISHER])
|
||||
self.assertEqual(sorted(new_policy.subscribers), [SUBSCRIBER])
|
||||
self.assertEqual(api._set_iam_policy, (self.SUB_PATH, POLICY))
|
||||
|
||||
def test_set_iam_policy_w_alternate_client(self):
|
||||
from gcloud.pubsub.iam import Policy
|
||||
RESPONSE = {'etag': 'ACAB'}
|
||||
client1 = _Client(project=self.PROJECT)
|
||||
client2 = _Client(project=self.PROJECT)
|
||||
api = client2.iam_policy_api = _FauxIAMPolicy()
|
||||
api._set_iam_policy_response = RESPONSE
|
||||
topic = _Topic(self.TOPIC_NAME, client=client1)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic)
|
||||
|
||||
policy = Policy()
|
||||
new_policy = subscription.set_iam_policy(policy, client=client2)
|
||||
|
||||
self.assertEqual(new_policy.etag, 'ACAB')
|
||||
self.assertEqual(new_policy.version, None)
|
||||
self.assertEqual(sorted(new_policy.owners), [])
|
||||
self.assertEqual(sorted(new_policy.editors), [])
|
||||
self.assertEqual(sorted(new_policy.viewers), [])
|
||||
self.assertEqual(api._set_iam_policy, (self.SUB_PATH, {}))
|
||||
|
||||
def test_check_iam_permissions_w_bound_client(self):
|
||||
from gcloud.pubsub.iam import OWNER_ROLE, EDITOR_ROLE, VIEWER_ROLE
|
||||
ROLES = [VIEWER_ROLE, EDITOR_ROLE, OWNER_ROLE]
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.iam_policy_api = _FauxIAMPolicy()
|
||||
api._test_iam_permissions_response = ROLES[:-1]
|
||||
topic = _Topic(self.TOPIC_NAME, client=client)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic)
|
||||
|
||||
allowed = subscription.check_iam_permissions(ROLES)
|
||||
|
||||
self.assertEqual(allowed, ROLES[:-1])
|
||||
self.assertEqual(api._tested_iam_permissions,
|
||||
(self.SUB_PATH, ROLES))
|
||||
|
||||
def test_check_iam_permissions_w_alternate_client(self):
|
||||
from gcloud.pubsub.iam import OWNER_ROLE, EDITOR_ROLE, VIEWER_ROLE
|
||||
ROLES = [VIEWER_ROLE, EDITOR_ROLE, OWNER_ROLE]
|
||||
client1 = _Client(project=self.PROJECT)
|
||||
client2 = _Client(project=self.PROJECT)
|
||||
api = client2.iam_policy_api = _FauxIAMPolicy()
|
||||
api._test_iam_permissions_response = []
|
||||
topic = _Topic(self.TOPIC_NAME, client=client1)
|
||||
subscription = self._makeOne(self.SUB_NAME, topic)
|
||||
|
||||
allowed = subscription.check_iam_permissions(ROLES, client=client2)
|
||||
|
||||
self.assertEqual(len(allowed), 0)
|
||||
self.assertEqual(api._tested_iam_permissions,
|
||||
(self.SUB_PATH, ROLES))
|
||||
|
||||
|
||||
class _FauxSubscribererAPI(object):
|
||||
|
||||
def subscription_create(self, subscription_path, topic_path,
|
||||
ack_deadline=None, push_endpoint=None):
|
||||
self._subscription_created = (
|
||||
subscription_path, topic_path, ack_deadline, push_endpoint)
|
||||
return self._subscription_create_response
|
||||
|
||||
def subscription_get(self, subscription_path):
|
||||
from gcloud.exceptions import NotFound
|
||||
self._subscription_got = subscription_path
|
||||
try:
|
||||
return self._subscription_get_response
|
||||
except AttributeError:
|
||||
raise NotFound(subscription_path)
|
||||
|
||||
def subscription_delete(self, subscription_path):
|
||||
self._subscription_deleted = subscription_path
|
||||
return self._subscription_delete_response
|
||||
|
||||
def subscription_modify_push_config(
|
||||
self, subscription_path, push_endpoint):
|
||||
self._subscription_modified_push_config = (
|
||||
subscription_path, push_endpoint)
|
||||
return self._subscription_modify_push_config_response
|
||||
|
||||
def subscription_pull(self, subscription_path, return_immediately,
|
||||
max_messages):
|
||||
self._subscription_pulled = (
|
||||
subscription_path, return_immediately, max_messages)
|
||||
return self._subscription_pull_response
|
||||
|
||||
def subscription_acknowledge(self, subscription_path, ack_ids):
|
||||
self._subscription_acked = (subscription_path, ack_ids)
|
||||
return self._subscription_acknowlege_response
|
||||
|
||||
def subscription_modify_ack_deadline(self, subscription_path, ack_ids,
|
||||
ack_deadline):
|
||||
self._subscription_modified_ack_deadline = (
|
||||
subscription_path, ack_ids, ack_deadline)
|
||||
return self._subscription_modify_ack_deadline_response
|
||||
|
||||
|
||||
class _FauxIAMPolicy(object):
|
||||
|
||||
def get_iam_policy(self, target_path):
|
||||
self._got_iam_policy = target_path
|
||||
return self._get_iam_policy_response
|
||||
|
||||
def set_iam_policy(self, target_path, policy):
|
||||
self._set_iam_policy = target_path, policy
|
||||
return self._set_iam_policy_response
|
||||
|
||||
def test_iam_permissions(self, target_path, permissions):
|
||||
self._tested_iam_permissions = target_path, permissions
|
||||
return self._test_iam_permissions_response
|
||||
|
||||
|
||||
class _Topic(object):
|
||||
|
||||
def __init__(self, name, client):
|
||||
self.name = name
|
||||
self._client = client
|
||||
self.project = client.project
|
||||
self.full_name = 'projects/%s/topics/%s' % (client.project, name)
|
||||
self.path = '/projects/%s/topics/%s' % (client.project, name)
|
||||
|
||||
|
||||
class _Client(object):
|
||||
|
||||
connection = None
|
||||
|
||||
def __init__(self, project):
|
||||
self.project = project
|
||||
|
||||
def topic(self, name, timestamp_messages=False):
|
||||
from gcloud.pubsub.topic import Topic
|
||||
return Topic(name, client=self, timestamp_messages=timestamp_messages)
|
783
venv/Lib/site-packages/gcloud/pubsub/test_topic.py
Normal file
783
venv/Lib/site-packages/gcloud/pubsub/test_topic.py
Normal file
|
@ -0,0 +1,783 @@
|
|||
# Copyright 2015 Google Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import unittest2
|
||||
|
||||
|
||||
class TestTopic(unittest2.TestCase):
|
||||
PROJECT = 'PROJECT'
|
||||
TOPIC_NAME = 'topic_name'
|
||||
TOPIC_PATH = 'projects/%s/topics/%s' % (PROJECT, TOPIC_NAME)
|
||||
|
||||
def _getTargetClass(self):
|
||||
from gcloud.pubsub.topic import Topic
|
||||
return Topic
|
||||
|
||||
def _makeOne(self, *args, **kw):
|
||||
return self._getTargetClass()(*args, **kw)
|
||||
|
||||
def test_ctor_w_explicit_timestamp(self):
|
||||
client = _Client(project=self.PROJECT)
|
||||
topic = self._makeOne(self.TOPIC_NAME,
|
||||
client=client,
|
||||
timestamp_messages=True)
|
||||
self.assertEqual(topic.name, self.TOPIC_NAME)
|
||||
self.assertEqual(topic.project, self.PROJECT)
|
||||
self.assertEqual(topic.full_name, self.TOPIC_PATH)
|
||||
self.assertTrue(topic.timestamp_messages)
|
||||
|
||||
def test_from_api_repr(self):
|
||||
client = _Client(project=self.PROJECT)
|
||||
resource = {'name': self.TOPIC_PATH}
|
||||
klass = self._getTargetClass()
|
||||
topic = klass.from_api_repr(resource, client=client)
|
||||
self.assertEqual(topic.name, self.TOPIC_NAME)
|
||||
self.assertTrue(topic._client is client)
|
||||
self.assertEqual(topic.project, self.PROJECT)
|
||||
self.assertEqual(topic.full_name, self.TOPIC_PATH)
|
||||
|
||||
def test_from_api_repr_with_bad_client(self):
|
||||
PROJECT1 = 'PROJECT1'
|
||||
PROJECT2 = 'PROJECT2'
|
||||
client = _Client(project=PROJECT1)
|
||||
PATH = 'projects/%s/topics/%s' % (PROJECT2, self.TOPIC_NAME)
|
||||
resource = {'name': PATH}
|
||||
klass = self._getTargetClass()
|
||||
self.assertRaises(ValueError, klass.from_api_repr,
|
||||
resource, client=client)
|
||||
|
||||
def test_create_w_bound_client(self):
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.publisher_api = _FauxPublisherAPI()
|
||||
api._topic_create_response = {'name': self.TOPIC_PATH}
|
||||
topic = self._makeOne(self.TOPIC_NAME, client=client)
|
||||
|
||||
topic.create()
|
||||
|
||||
self.assertEqual(api._topic_created, self.TOPIC_PATH)
|
||||
|
||||
def test_create_w_alternate_client(self):
|
||||
client1 = _Client(project=self.PROJECT)
|
||||
client2 = _Client(project=self.PROJECT)
|
||||
api = client2.publisher_api = _FauxPublisherAPI()
|
||||
api._topic_create_response = {'name': self.TOPIC_PATH}
|
||||
topic = self._makeOne(self.TOPIC_NAME, client=client1)
|
||||
|
||||
topic.create(client=client2)
|
||||
|
||||
self.assertEqual(api._topic_created, self.TOPIC_PATH)
|
||||
|
||||
def test_exists_miss_w_bound_client(self):
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.publisher_api = _FauxPublisherAPI()
|
||||
topic = self._makeOne(self.TOPIC_NAME, client=client)
|
||||
|
||||
self.assertFalse(topic.exists())
|
||||
|
||||
self.assertEqual(api._topic_got, self.TOPIC_PATH)
|
||||
|
||||
def test_exists_hit_w_alternate_client(self):
|
||||
client1 = _Client(project=self.PROJECT)
|
||||
client2 = _Client(project=self.PROJECT)
|
||||
api = client2.publisher_api = _FauxPublisherAPI()
|
||||
api._topic_get_response = {'name': self.TOPIC_PATH}
|
||||
topic = self._makeOne(self.TOPIC_NAME, client=client1)
|
||||
|
||||
self.assertTrue(topic.exists(client=client2))
|
||||
|
||||
self.assertEqual(api._topic_got, self.TOPIC_PATH)
|
||||
|
||||
def test_delete_w_bound_client(self):
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.publisher_api = _FauxPublisherAPI()
|
||||
api._topic_delete_response = {}
|
||||
topic = self._makeOne(self.TOPIC_NAME, client=client)
|
||||
|
||||
topic.delete()
|
||||
|
||||
self.assertEqual(api._topic_deleted, self.TOPIC_PATH)
|
||||
|
||||
def test_delete_w_alternate_client(self):
|
||||
client1 = _Client(project=self.PROJECT)
|
||||
client2 = _Client(project=self.PROJECT)
|
||||
api = client2.publisher_api = _FauxPublisherAPI()
|
||||
api._topic_delete_response = {}
|
||||
topic = self._makeOne(self.TOPIC_NAME, client=client1)
|
||||
|
||||
topic.delete(client=client2)
|
||||
|
||||
self.assertEqual(api._topic_deleted, self.TOPIC_PATH)
|
||||
|
||||
def test_publish_single_bytes_wo_attrs_w_bound_client(self):
|
||||
import base64
|
||||
PAYLOAD = b'This is the message text'
|
||||
B64 = base64.b64encode(PAYLOAD).decode('ascii')
|
||||
MSGID = 'DEADBEEF'
|
||||
MESSAGE = {'data': B64, 'attributes': {}}
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.publisher_api = _FauxPublisherAPI()
|
||||
api._topic_publish_response = [MSGID]
|
||||
topic = self._makeOne(self.TOPIC_NAME, client=client)
|
||||
|
||||
msgid = topic.publish(PAYLOAD)
|
||||
|
||||
self.assertEqual(msgid, MSGID)
|
||||
self.assertEqual(api._topic_published, (self.TOPIC_PATH, [MESSAGE]))
|
||||
|
||||
def test_publish_single_bytes_wo_attrs_w_add_timestamp_alt_client(self):
|
||||
import base64
|
||||
import datetime
|
||||
from gcloud.pubsub import topic as MUT
|
||||
from gcloud._helpers import _RFC3339_MICROS
|
||||
from gcloud._testing import _Monkey
|
||||
NOW = datetime.datetime.utcnow()
|
||||
|
||||
def _utcnow():
|
||||
return NOW
|
||||
|
||||
PAYLOAD = b'This is the message text'
|
||||
B64 = base64.b64encode(PAYLOAD).decode('ascii')
|
||||
MSGID = 'DEADBEEF'
|
||||
MESSAGE = {
|
||||
'data': B64,
|
||||
'attributes': {'timestamp': NOW.strftime(_RFC3339_MICROS)},
|
||||
}
|
||||
client1 = _Client(project=self.PROJECT)
|
||||
client2 = _Client(project=self.PROJECT)
|
||||
api = client2.publisher_api = _FauxPublisherAPI()
|
||||
api._topic_publish_response = [MSGID]
|
||||
|
||||
topic = self._makeOne(self.TOPIC_NAME, client=client1,
|
||||
timestamp_messages=True)
|
||||
with _Monkey(MUT, _NOW=_utcnow):
|
||||
msgid = topic.publish(PAYLOAD, client=client2)
|
||||
|
||||
self.assertEqual(msgid, MSGID)
|
||||
self.assertEqual(api._topic_published, (self.TOPIC_PATH, [MESSAGE]))
|
||||
|
||||
def test_publish_single_bytes_w_add_timestamp_w_ts_in_attrs(self):
|
||||
import base64
|
||||
PAYLOAD = b'This is the message text'
|
||||
B64 = base64.b64encode(PAYLOAD).decode('ascii')
|
||||
MSGID = 'DEADBEEF'
|
||||
OVERRIDE = '2015-04-10T16:46:22.868399Z'
|
||||
MESSAGE = {'data': B64,
|
||||
'attributes': {'timestamp': OVERRIDE}}
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.publisher_api = _FauxPublisherAPI()
|
||||
api._topic_publish_response = [MSGID]
|
||||
topic = self._makeOne(self.TOPIC_NAME, client=client,
|
||||
timestamp_messages=True)
|
||||
|
||||
msgid = topic.publish(PAYLOAD, timestamp=OVERRIDE)
|
||||
|
||||
self.assertEqual(msgid, MSGID)
|
||||
self.assertEqual(api._topic_published, (self.TOPIC_PATH, [MESSAGE]))
|
||||
|
||||
def test_publish_single_w_attrs(self):
|
||||
import base64
|
||||
PAYLOAD = b'This is the message text'
|
||||
B64 = base64.b64encode(PAYLOAD).decode('ascii')
|
||||
MSGID = 'DEADBEEF'
|
||||
MESSAGE = {'data': B64,
|
||||
'attributes': {'attr1': 'value1', 'attr2': 'value2'}}
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.publisher_api = _FauxPublisherAPI()
|
||||
api._topic_publish_response = [MSGID]
|
||||
topic = self._makeOne(self.TOPIC_NAME, client=client)
|
||||
|
||||
msgid = topic.publish(PAYLOAD, attr1='value1', attr2='value2')
|
||||
|
||||
self.assertEqual(msgid, MSGID)
|
||||
self.assertEqual(api._topic_published, (self.TOPIC_PATH, [MESSAGE]))
|
||||
|
||||
def test_publish_multiple_w_bound_client(self):
|
||||
import base64
|
||||
PAYLOAD1 = b'This is the first message text'
|
||||
PAYLOAD2 = b'This is the second message text'
|
||||
B64_1 = base64.b64encode(PAYLOAD1)
|
||||
B64_2 = base64.b64encode(PAYLOAD2)
|
||||
MSGID1 = 'DEADBEEF'
|
||||
MSGID2 = 'BEADCAFE'
|
||||
MESSAGE1 = {'data': B64_1.decode('ascii'),
|
||||
'attributes': {}}
|
||||
MESSAGE2 = {'data': B64_2.decode('ascii'),
|
||||
'attributes': {'attr1': 'value1', 'attr2': 'value2'}}
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.publisher_api = _FauxPublisherAPI()
|
||||
api._topic_publish_response = [MSGID1, MSGID2]
|
||||
topic = self._makeOne(self.TOPIC_NAME, client=client)
|
||||
|
||||
with topic.batch() as batch:
|
||||
batch.publish(PAYLOAD1)
|
||||
batch.publish(PAYLOAD2, attr1='value1', attr2='value2')
|
||||
|
||||
self.assertEqual(list(batch), [MSGID1, MSGID2])
|
||||
self.assertEqual(list(batch.messages), [])
|
||||
self.assertEqual(api._topic_published,
|
||||
(self.TOPIC_PATH, [MESSAGE1, MESSAGE2]))
|
||||
|
||||
def test_publish_multiple_w_alternate_client(self):
|
||||
import base64
|
||||
PAYLOAD1 = b'This is the first message text'
|
||||
PAYLOAD2 = b'This is the second message text'
|
||||
B64_1 = base64.b64encode(PAYLOAD1)
|
||||
B64_2 = base64.b64encode(PAYLOAD2)
|
||||
MSGID1 = 'DEADBEEF'
|
||||
MSGID2 = 'BEADCAFE'
|
||||
MESSAGE1 = {'data': B64_1.decode('ascii'), 'attributes': {}}
|
||||
MESSAGE2 = {
|
||||
'data': B64_2.decode('ascii'),
|
||||
'attributes': {'attr1': 'value1', 'attr2': 'value2'},
|
||||
}
|
||||
client1 = _Client(project=self.PROJECT)
|
||||
client2 = _Client(project=self.PROJECT)
|
||||
api = client2.publisher_api = _FauxPublisherAPI()
|
||||
api._topic_publish_response = [MSGID1, MSGID2]
|
||||
topic = self._makeOne(self.TOPIC_NAME, client=client1)
|
||||
|
||||
with topic.batch(client=client2) as batch:
|
||||
batch.publish(PAYLOAD1)
|
||||
batch.publish(PAYLOAD2, attr1='value1', attr2='value2')
|
||||
|
||||
self.assertEqual(list(batch), [MSGID1, MSGID2])
|
||||
self.assertEqual(list(batch.messages), [])
|
||||
self.assertEqual(api._topic_published,
|
||||
(self.TOPIC_PATH, [MESSAGE1, MESSAGE2]))
|
||||
|
||||
def test_publish_multiple_error(self):
|
||||
PAYLOAD1 = b'This is the first message text'
|
||||
PAYLOAD2 = b'This is the second message text'
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.publisher_api = _FauxPublisherAPI()
|
||||
topic = self._makeOne(self.TOPIC_NAME, client=client)
|
||||
|
||||
try:
|
||||
with topic.batch() as batch:
|
||||
batch.publish(PAYLOAD1)
|
||||
batch.publish(PAYLOAD2, attr1='value1', attr2='value2')
|
||||
raise _Bugout()
|
||||
except _Bugout:
|
||||
pass
|
||||
|
||||
self.assertEqual(list(batch), [])
|
||||
self.assertEqual(getattr(api, '_topic_published', self), self)
|
||||
|
||||
def test_subscription(self):
|
||||
from gcloud.pubsub.subscription import Subscription
|
||||
client = _Client(project=self.PROJECT)
|
||||
topic = self._makeOne(self.TOPIC_NAME, client=client)
|
||||
|
||||
SUBSCRIPTION_NAME = 'subscription_name'
|
||||
subscription = topic.subscription(SUBSCRIPTION_NAME)
|
||||
self.assertIsInstance(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
|
||||
SUB_NAME_1 = 'subscription_1'
|
||||
SUB_PATH_1 = 'projects/%s/subscriptions/%s' % (
|
||||
self.PROJECT, SUB_NAME_1)
|
||||
SUB_NAME_2 = 'subscription_2'
|
||||
SUB_PATH_2 = 'projects/%s/subscriptions/%s' % (
|
||||
self.PROJECT, SUB_NAME_2)
|
||||
SUBS_LIST = [SUB_PATH_1, SUB_PATH_2]
|
||||
TOKEN = 'TOKEN'
|
||||
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.publisher_api = _FauxPublisherAPI()
|
||||
api._topic_list_subscriptions_response = SUBS_LIST, TOKEN
|
||||
topic = self._makeOne(self.TOPIC_NAME, client=client)
|
||||
|
||||
subscriptions, next_page_token = topic.list_subscriptions()
|
||||
|
||||
self.assertEqual(len(subscriptions), 2)
|
||||
|
||||
subscription = subscriptions[0]
|
||||
self.assertIsInstance(subscription, Subscription)
|
||||
self.assertEqual(subscriptions[0].name, SUB_NAME_1)
|
||||
self.assertTrue(subscription.topic is topic)
|
||||
|
||||
subscription = subscriptions[1]
|
||||
self.assertIsInstance(subscription, Subscription)
|
||||
self.assertEqual(subscriptions[1].name, SUB_NAME_2)
|
||||
self.assertTrue(subscription.topic is topic)
|
||||
|
||||
self.assertEqual(next_page_token, TOKEN)
|
||||
self.assertEqual(api._topic_listed,
|
||||
(self.TOPIC_PATH, None, None))
|
||||
|
||||
def test_list_subscriptions_with_paging(self):
|
||||
from gcloud.pubsub.subscription import Subscription
|
||||
SUB_NAME_1 = 'subscription_1'
|
||||
SUB_PATH_1 = 'projects/%s/subscriptions/%s' % (
|
||||
self.PROJECT, SUB_NAME_1)
|
||||
SUB_NAME_2 = 'subscription_2'
|
||||
SUB_PATH_2 = 'projects/%s/subscriptions/%s' % (
|
||||
self.PROJECT, SUB_NAME_2)
|
||||
SUBS_LIST = [SUB_PATH_1, SUB_PATH_2]
|
||||
PAGE_SIZE = 10
|
||||
TOKEN = 'TOKEN'
|
||||
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.publisher_api = _FauxPublisherAPI()
|
||||
api._topic_list_subscriptions_response = SUBS_LIST, None
|
||||
topic = self._makeOne(self.TOPIC_NAME, client=client)
|
||||
|
||||
subscriptions, next_page_token = topic.list_subscriptions(
|
||||
page_size=PAGE_SIZE, page_token=TOKEN)
|
||||
|
||||
self.assertEqual(len(subscriptions), 2)
|
||||
|
||||
subscription = subscriptions[0]
|
||||
self.assertIsInstance(subscription, Subscription)
|
||||
self.assertEqual(subscriptions[0].name, SUB_NAME_1)
|
||||
self.assertTrue(subscription.topic is topic)
|
||||
|
||||
subscription = subscriptions[1]
|
||||
self.assertIsInstance(subscription, Subscription)
|
||||
self.assertEqual(subscriptions[1].name, SUB_NAME_2)
|
||||
self.assertTrue(subscription.topic is topic)
|
||||
|
||||
self.assertEqual(next_page_token, None)
|
||||
self.assertEqual(api._topic_listed,
|
||||
(self.TOPIC_PATH, PAGE_SIZE, TOKEN))
|
||||
|
||||
def test_list_subscriptions_missing_key(self):
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.publisher_api = _FauxPublisherAPI()
|
||||
api._topic_list_subscriptions_response = (), None
|
||||
topic = self._makeOne(self.TOPIC_NAME, client=client)
|
||||
|
||||
subscriptions, next_page_token = topic.list_subscriptions()
|
||||
|
||||
self.assertEqual(len(subscriptions), 0)
|
||||
self.assertEqual(next_page_token, None)
|
||||
|
||||
self.assertEqual(api._topic_listed,
|
||||
(self.TOPIC_PATH, None, None))
|
||||
|
||||
def test_get_iam_policy_w_bound_client(self):
|
||||
from gcloud.pubsub.iam import (
|
||||
PUBSUB_ADMIN_ROLE,
|
||||
PUBSUB_EDITOR_ROLE,
|
||||
PUBSUB_VIEWER_ROLE,
|
||||
PUBSUB_PUBLISHER_ROLE,
|
||||
PUBSUB_SUBSCRIBER_ROLE,
|
||||
)
|
||||
OWNER1 = 'user:phred@example.com'
|
||||
OWNER2 = 'group:cloud-logs@google.com'
|
||||
EDITOR1 = 'domain:google.com'
|
||||
EDITOR2 = 'user:phred@example.com'
|
||||
VIEWER1 = 'serviceAccount:1234-abcdef@service.example.com'
|
||||
VIEWER2 = 'user:phred@example.com'
|
||||
PUBLISHER = 'user:phred@example.com'
|
||||
SUBSCRIBER = 'serviceAccount:1234-abcdef@service.example.com'
|
||||
POLICY = {
|
||||
'etag': 'DEADBEEF',
|
||||
'version': 17,
|
||||
'bindings': [
|
||||
{'role': PUBSUB_ADMIN_ROLE, 'members': [OWNER1, OWNER2]},
|
||||
{'role': PUBSUB_EDITOR_ROLE, 'members': [EDITOR1, EDITOR2]},
|
||||
{'role': PUBSUB_VIEWER_ROLE, 'members': [VIEWER1, VIEWER2]},
|
||||
{'role': PUBSUB_PUBLISHER_ROLE, 'members': [PUBLISHER]},
|
||||
{'role': PUBSUB_SUBSCRIBER_ROLE, 'members': [SUBSCRIBER]},
|
||||
],
|
||||
}
|
||||
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.iam_policy_api = _FauxIAMPolicy()
|
||||
api._get_iam_policy_response = POLICY
|
||||
topic = self._makeOne(self.TOPIC_NAME, client=client)
|
||||
|
||||
policy = topic.get_iam_policy()
|
||||
|
||||
self.assertEqual(policy.etag, 'DEADBEEF')
|
||||
self.assertEqual(policy.version, 17)
|
||||
self.assertEqual(sorted(policy.owners), [OWNER2, OWNER1])
|
||||
self.assertEqual(sorted(policy.editors), [EDITOR1, EDITOR2])
|
||||
self.assertEqual(sorted(policy.viewers), [VIEWER1, VIEWER2])
|
||||
self.assertEqual(sorted(policy.publishers), [PUBLISHER])
|
||||
self.assertEqual(sorted(policy.subscribers), [SUBSCRIBER])
|
||||
self.assertEqual(api._got_iam_policy, self.TOPIC_PATH)
|
||||
|
||||
def test_get_iam_policy_w_alternate_client(self):
|
||||
POLICY = {
|
||||
'etag': 'ACAB',
|
||||
}
|
||||
|
||||
client1 = _Client(project=self.PROJECT)
|
||||
client2 = _Client(project=self.PROJECT)
|
||||
api = client2.iam_policy_api = _FauxIAMPolicy()
|
||||
api._get_iam_policy_response = POLICY
|
||||
topic = self._makeOne(self.TOPIC_NAME, client=client1)
|
||||
|
||||
policy = topic.get_iam_policy(client=client2)
|
||||
|
||||
self.assertEqual(policy.etag, 'ACAB')
|
||||
self.assertEqual(policy.version, None)
|
||||
self.assertEqual(sorted(policy.owners), [])
|
||||
self.assertEqual(sorted(policy.editors), [])
|
||||
self.assertEqual(sorted(policy.viewers), [])
|
||||
|
||||
self.assertEqual(api._got_iam_policy, self.TOPIC_PATH)
|
||||
|
||||
def test_set_iam_policy_w_bound_client(self):
|
||||
from gcloud.pubsub.iam import Policy
|
||||
from gcloud.pubsub.iam import (
|
||||
PUBSUB_ADMIN_ROLE,
|
||||
PUBSUB_EDITOR_ROLE,
|
||||
PUBSUB_VIEWER_ROLE,
|
||||
PUBSUB_PUBLISHER_ROLE,
|
||||
PUBSUB_SUBSCRIBER_ROLE,
|
||||
)
|
||||
OWNER1 = 'group:cloud-logs@google.com'
|
||||
OWNER2 = 'user:phred@example.com'
|
||||
EDITOR1 = 'domain:google.com'
|
||||
EDITOR2 = 'user:phred@example.com'
|
||||
VIEWER1 = 'serviceAccount:1234-abcdef@service.example.com'
|
||||
VIEWER2 = 'user:phred@example.com'
|
||||
PUBLISHER = 'user:phred@example.com'
|
||||
SUBSCRIBER = 'serviceAccount:1234-abcdef@service.example.com'
|
||||
POLICY = {
|
||||
'etag': 'DEADBEEF',
|
||||
'version': 17,
|
||||
'bindings': [
|
||||
{'role': PUBSUB_ADMIN_ROLE,
|
||||
'members': [OWNER1, OWNER2]},
|
||||
{'role': PUBSUB_EDITOR_ROLE,
|
||||
'members': [EDITOR1, EDITOR2]},
|
||||
{'role': PUBSUB_VIEWER_ROLE,
|
||||
'members': [VIEWER1, VIEWER2]},
|
||||
{'role': PUBSUB_PUBLISHER_ROLE,
|
||||
'members': [PUBLISHER]},
|
||||
{'role': PUBSUB_SUBSCRIBER_ROLE,
|
||||
'members': [SUBSCRIBER]},
|
||||
],
|
||||
}
|
||||
RESPONSE = POLICY.copy()
|
||||
RESPONSE['etag'] = 'ABACABAF'
|
||||
RESPONSE['version'] = 18
|
||||
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.iam_policy_api = _FauxIAMPolicy()
|
||||
api._set_iam_policy_response = RESPONSE
|
||||
topic = self._makeOne(self.TOPIC_NAME, client=client)
|
||||
policy = Policy('DEADBEEF', 17)
|
||||
policy.owners.add(OWNER1)
|
||||
policy.owners.add(OWNER2)
|
||||
policy.editors.add(EDITOR1)
|
||||
policy.editors.add(EDITOR2)
|
||||
policy.viewers.add(VIEWER1)
|
||||
policy.viewers.add(VIEWER2)
|
||||
policy.publishers.add(PUBLISHER)
|
||||
policy.subscribers.add(SUBSCRIBER)
|
||||
|
||||
new_policy = topic.set_iam_policy(policy)
|
||||
|
||||
self.assertEqual(new_policy.etag, 'ABACABAF')
|
||||
self.assertEqual(new_policy.version, 18)
|
||||
self.assertEqual(sorted(new_policy.owners), [OWNER1, OWNER2])
|
||||
self.assertEqual(sorted(new_policy.editors), [EDITOR1, EDITOR2])
|
||||
self.assertEqual(sorted(new_policy.viewers), [VIEWER1, VIEWER2])
|
||||
self.assertEqual(sorted(new_policy.publishers), [PUBLISHER])
|
||||
self.assertEqual(sorted(new_policy.subscribers), [SUBSCRIBER])
|
||||
self.assertEqual(api._set_iam_policy, (self.TOPIC_PATH, POLICY))
|
||||
|
||||
def test_set_iam_policy_w_alternate_client(self):
|
||||
from gcloud.pubsub.iam import Policy
|
||||
RESPONSE = {'etag': 'ACAB'}
|
||||
|
||||
client1 = _Client(project=self.PROJECT)
|
||||
client2 = _Client(project=self.PROJECT)
|
||||
api = client2.iam_policy_api = _FauxIAMPolicy()
|
||||
api._set_iam_policy_response = RESPONSE
|
||||
topic = self._makeOne(self.TOPIC_NAME, client=client1)
|
||||
|
||||
policy = Policy()
|
||||
new_policy = topic.set_iam_policy(policy, client=client2)
|
||||
|
||||
self.assertEqual(new_policy.etag, 'ACAB')
|
||||
self.assertEqual(new_policy.version, None)
|
||||
self.assertEqual(sorted(new_policy.owners), [])
|
||||
self.assertEqual(sorted(new_policy.editors), [])
|
||||
self.assertEqual(sorted(new_policy.viewers), [])
|
||||
|
||||
self.assertEqual(api._set_iam_policy, (self.TOPIC_PATH, {}))
|
||||
|
||||
def test_check_iam_permissions_w_bound_client(self):
|
||||
from gcloud.pubsub.iam import OWNER_ROLE, EDITOR_ROLE, VIEWER_ROLE
|
||||
ROLES = [VIEWER_ROLE, EDITOR_ROLE, OWNER_ROLE]
|
||||
client = _Client(project=self.PROJECT)
|
||||
api = client.iam_policy_api = _FauxIAMPolicy()
|
||||
api._test_iam_permissions_response = ROLES[:-1]
|
||||
topic = self._makeOne(self.TOPIC_NAME, client=client)
|
||||
|
||||
allowed = topic.check_iam_permissions(ROLES)
|
||||
|
||||
self.assertEqual(allowed, ROLES[:-1])
|
||||
self.assertEqual(api._tested_iam_permissions,
|
||||
(self.TOPIC_PATH, ROLES))
|
||||
|
||||
def test_check_iam_permissions_w_alternate_client(self):
|
||||
from gcloud.pubsub.iam import OWNER_ROLE, EDITOR_ROLE, VIEWER_ROLE
|
||||
ROLES = [VIEWER_ROLE, EDITOR_ROLE, OWNER_ROLE]
|
||||
client1 = _Client(project=self.PROJECT)
|
||||
client2 = _Client(project=self.PROJECT)
|
||||
api = client2.iam_policy_api = _FauxIAMPolicy()
|
||||
api._test_iam_permissions_response = []
|
||||
topic = self._makeOne(self.TOPIC_NAME, client=client1)
|
||||
|
||||
allowed = topic.check_iam_permissions(ROLES, client=client2)
|
||||
|
||||
self.assertEqual(len(allowed), 0)
|
||||
self.assertEqual(api._tested_iam_permissions,
|
||||
(self.TOPIC_PATH, ROLES))
|
||||
|
||||
|
||||
class TestBatch(unittest2.TestCase):
|
||||
PROJECT = 'PROJECT'
|
||||
|
||||
def _getTargetClass(self):
|
||||
from gcloud.pubsub.topic import Batch
|
||||
return Batch
|
||||
|
||||
def _makeOne(self, *args, **kwargs):
|
||||
return self._getTargetClass()(*args, **kwargs)
|
||||
|
||||
def test_ctor_defaults(self):
|
||||
topic = _Topic()
|
||||
client = _Client(project=self.PROJECT)
|
||||
batch = self._makeOne(topic, client)
|
||||
self.assertTrue(batch.topic is topic)
|
||||
self.assertTrue(batch.client is client)
|
||||
self.assertEqual(len(batch.messages), 0)
|
||||
self.assertEqual(len(batch.message_ids), 0)
|
||||
|
||||
def test___iter___empty(self):
|
||||
topic = _Topic()
|
||||
client = object()
|
||||
batch = self._makeOne(topic, client)
|
||||
self.assertEqual(list(batch), [])
|
||||
|
||||
def test___iter___non_empty(self):
|
||||
topic = _Topic()
|
||||
client = object()
|
||||
batch = self._makeOne(topic, client)
|
||||
batch.message_ids[:] = ['ONE', 'TWO', 'THREE']
|
||||
self.assertEqual(list(batch), ['ONE', 'TWO', 'THREE'])
|
||||
|
||||
def test_publish_bytes_wo_attrs(self):
|
||||
import base64
|
||||
PAYLOAD = b'This is the message text'
|
||||
B64 = base64.b64encode(PAYLOAD).decode('ascii')
|
||||
MESSAGE = {'data': B64,
|
||||
'attributes': {}}
|
||||
client = _Client(project=self.PROJECT)
|
||||
topic = _Topic()
|
||||
batch = self._makeOne(topic, client=client)
|
||||
batch.publish(PAYLOAD)
|
||||
self.assertEqual(batch.messages, [MESSAGE])
|
||||
|
||||
def test_publish_bytes_w_add_timestamp(self):
|
||||
import base64
|
||||
PAYLOAD = b'This is the message text'
|
||||
B64 = base64.b64encode(PAYLOAD).decode('ascii')
|
||||
MESSAGE = {'data': B64,
|
||||
'attributes': {'timestamp': 'TIMESTAMP'}}
|
||||
client = _Client(project=self.PROJECT)
|
||||
topic = _Topic(timestamp_messages=True)
|
||||
batch = self._makeOne(topic, client=client)
|
||||
batch.publish(PAYLOAD)
|
||||
self.assertEqual(batch.messages, [MESSAGE])
|
||||
|
||||
def test_commit_w_bound_client(self):
|
||||
import base64
|
||||
PAYLOAD1 = b'This is the first message text'
|
||||
PAYLOAD2 = b'This is the second message text'
|
||||
B64_1 = base64.b64encode(PAYLOAD1)
|
||||
B64_2 = base64.b64encode(PAYLOAD2)
|
||||
MSGID1 = 'DEADBEEF'
|
||||
MSGID2 = 'BEADCAFE'
|
||||
MESSAGE1 = {'data': B64_1.decode('ascii'),
|
||||
'attributes': {}}
|
||||
MESSAGE2 = {'data': B64_2.decode('ascii'),
|
||||
'attributes': {'attr1': 'value1', 'attr2': 'value2'}}
|
||||
client = _Client(project='PROJECT')
|
||||
api = client.publisher_api = _FauxPublisherAPI()
|
||||
api._topic_publish_response = [MSGID1, MSGID2]
|
||||
topic = _Topic()
|
||||
batch = self._makeOne(topic, client=client)
|
||||
|
||||
batch.publish(PAYLOAD1)
|
||||
batch.publish(PAYLOAD2, attr1='value1', attr2='value2')
|
||||
batch.commit()
|
||||
|
||||
self.assertEqual(list(batch), [MSGID1, MSGID2])
|
||||
self.assertEqual(list(batch.messages), [])
|
||||
self.assertEqual(api._topic_published,
|
||||
(topic.full_name, [MESSAGE1, MESSAGE2]))
|
||||
|
||||
def test_commit_w_alternate_client(self):
|
||||
import base64
|
||||
PAYLOAD1 = b'This is the first message text'
|
||||
PAYLOAD2 = b'This is the second message text'
|
||||
B64_1 = base64.b64encode(PAYLOAD1)
|
||||
B64_2 = base64.b64encode(PAYLOAD2)
|
||||
MSGID1 = 'DEADBEEF'
|
||||
MSGID2 = 'BEADCAFE'
|
||||
MESSAGE1 = {'data': B64_1.decode('ascii'),
|
||||
'attributes': {}}
|
||||
MESSAGE2 = {'data': B64_2.decode('ascii'),
|
||||
'attributes': {'attr1': 'value1', 'attr2': 'value2'}}
|
||||
client1 = _Client(project='PROJECT')
|
||||
client2 = _Client(project='PROJECT')
|
||||
api = client2.publisher_api = _FauxPublisherAPI()
|
||||
api._topic_publish_response = [MSGID1, MSGID2]
|
||||
topic = _Topic()
|
||||
batch = self._makeOne(topic, client=client1)
|
||||
|
||||
batch.publish(PAYLOAD1)
|
||||
batch.publish(PAYLOAD2, attr1='value1', attr2='value2')
|
||||
batch.commit(client=client2)
|
||||
|
||||
self.assertEqual(list(batch), [MSGID1, MSGID2])
|
||||
self.assertEqual(list(batch.messages), [])
|
||||
self.assertEqual(api._topic_published,
|
||||
(topic.full_name, [MESSAGE1, MESSAGE2]))
|
||||
|
||||
def test_context_mgr_success(self):
|
||||
import base64
|
||||
PAYLOAD1 = b'This is the first message text'
|
||||
PAYLOAD2 = b'This is the second message text'
|
||||
B64_1 = base64.b64encode(PAYLOAD1)
|
||||
B64_2 = base64.b64encode(PAYLOAD2)
|
||||
MSGID1 = 'DEADBEEF'
|
||||
MSGID2 = 'BEADCAFE'
|
||||
MESSAGE1 = {'data': B64_1.decode('ascii'),
|
||||
'attributes': {}}
|
||||
MESSAGE2 = {'data': B64_2.decode('ascii'),
|
||||
'attributes': {'attr1': 'value1', 'attr2': 'value2'}}
|
||||
client = _Client(project='PROJECT')
|
||||
api = client.publisher_api = _FauxPublisherAPI()
|
||||
api._topic_publish_response = [MSGID1, MSGID2]
|
||||
topic = _Topic()
|
||||
batch = self._makeOne(topic, client=client)
|
||||
|
||||
with batch as other:
|
||||
batch.publish(PAYLOAD1)
|
||||
batch.publish(PAYLOAD2, attr1='value1', attr2='value2')
|
||||
|
||||
self.assertTrue(other is batch)
|
||||
self.assertEqual(list(batch), [MSGID1, MSGID2])
|
||||
self.assertEqual(list(batch.messages), [])
|
||||
self.assertEqual(api._topic_published,
|
||||
(topic.full_name, [MESSAGE1, MESSAGE2]))
|
||||
|
||||
def test_context_mgr_failure(self):
|
||||
import base64
|
||||
PAYLOAD1 = b'This is the first message text'
|
||||
PAYLOAD2 = b'This is the second message text'
|
||||
B64_1 = base64.b64encode(PAYLOAD1)
|
||||
B64_2 = base64.b64encode(PAYLOAD2)
|
||||
MESSAGE1 = {'data': B64_1.decode('ascii'),
|
||||
'attributes': {}}
|
||||
MESSAGE2 = {'data': B64_2.decode('ascii'),
|
||||
'attributes': {'attr1': 'value1', 'attr2': 'value2'}}
|
||||
client = _Client(project='PROJECT')
|
||||
api = client.publisher_api = _FauxPublisherAPI()
|
||||
topic = _Topic()
|
||||
batch = self._makeOne(topic, client=client)
|
||||
|
||||
try:
|
||||
with batch as other:
|
||||
batch.publish(PAYLOAD1)
|
||||
batch.publish(PAYLOAD2, attr1='value1', attr2='value2')
|
||||
raise _Bugout()
|
||||
except _Bugout:
|
||||
pass
|
||||
|
||||
self.assertTrue(other is batch)
|
||||
self.assertEqual(list(batch), [])
|
||||
self.assertEqual(list(batch.messages), [MESSAGE1, MESSAGE2])
|
||||
self.assertEqual(getattr(api, '_topic_published', self), self)
|
||||
|
||||
|
||||
class _FauxPublisherAPI(object):
|
||||
|
||||
def topic_create(self, topic_path):
|
||||
self._topic_created = topic_path
|
||||
return self._topic_create_response
|
||||
|
||||
def topic_get(self, topic_path):
|
||||
from gcloud.exceptions import NotFound
|
||||
self._topic_got = topic_path
|
||||
try:
|
||||
return self._topic_get_response
|
||||
except AttributeError:
|
||||
raise NotFound(topic_path)
|
||||
|
||||
def topic_delete(self, topic_path):
|
||||
self._topic_deleted = topic_path
|
||||
return self._topic_delete_response
|
||||
|
||||
def topic_publish(self, topic_path, messages):
|
||||
self._topic_published = topic_path, messages
|
||||
return self._topic_publish_response
|
||||
|
||||
def topic_list_subscriptions(self, topic_path, page_size=None,
|
||||
page_token=None):
|
||||
self._topic_listed = topic_path, page_size, page_token
|
||||
return self._topic_list_subscriptions_response
|
||||
|
||||
|
||||
class _FauxIAMPolicy(object):
|
||||
|
||||
def get_iam_policy(self, target_path):
|
||||
self._got_iam_policy = target_path
|
||||
return self._get_iam_policy_response
|
||||
|
||||
def set_iam_policy(self, target_path, policy):
|
||||
self._set_iam_policy = target_path, policy
|
||||
return self._set_iam_policy_response
|
||||
|
||||
def test_iam_permissions(self, target_path, permissions):
|
||||
self._tested_iam_permissions = target_path, permissions
|
||||
return self._test_iam_permissions_response
|
||||
|
||||
|
||||
class _Topic(object):
|
||||
|
||||
def __init__(self, name="NAME", project="PROJECT",
|
||||
timestamp_messages=False):
|
||||
self.full_name = 'projects/%s/topics/%s' % (project, name)
|
||||
self.path = '/%s' % (self.full_name,)
|
||||
self.timestamp_messages = timestamp_messages
|
||||
|
||||
def _timestamp_message(self, attrs):
|
||||
if self.timestamp_messages:
|
||||
attrs['timestamp'] = 'TIMESTAMP'
|
||||
|
||||
|
||||
class _Client(object):
|
||||
|
||||
connection = None
|
||||
|
||||
def __init__(self, project):
|
||||
self.project = project
|
||||
|
||||
|
||||
class _Bugout(Exception):
|
||||
pass
|
451
venv/Lib/site-packages/gcloud/pubsub/topic.py
Normal file
451
venv/Lib/site-packages/gcloud/pubsub/topic.py
Normal file
|
@ -0,0 +1,451 @@
|
|||
# Copyright 2015 Google Inc. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Define API Topics."""
|
||||
|
||||
import base64
|
||||
|
||||
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.iam import Policy
|
||||
from gcloud.pubsub.subscription import Subscription
|
||||
|
||||
|
||||
class Topic(object):
|
||||
"""Topics are targets to which messages can be published.
|
||||
|
||||
Subscribers then receive those messages.
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics
|
||||
|
||||
:type name: string
|
||||
:param name: the name of the topic
|
||||
|
||||
:type client: :class:`gcloud.pubsub.client.Client`
|
||||
:param client: A client which holds credentials and project configuration
|
||||
for the topic (which requires a project).
|
||||
|
||||
:type timestamp_messages: boolean
|
||||
:param timestamp_messages: If true, the topic will add a ``timestamp`` key
|
||||
to the attributes of each published message:
|
||||
the value will be an RFC 3339 timestamp.
|
||||
"""
|
||||
def __init__(self, name, client, timestamp_messages=False):
|
||||
self.name = name
|
||||
self._client = client
|
||||
self.timestamp_messages = timestamp_messages
|
||||
|
||||
def subscription(self, name, ack_deadline=None, push_endpoint=None):
|
||||
"""Creates a subscription bound to the current topic.
|
||||
|
||||
Example: pull-mode subcription, default paramter values
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START topic_subscription_defaults]
|
||||
:end-before: [END topic_subscription_defaults]
|
||||
|
||||
Example: pull-mode subcription, override ``ack_deadline`` default
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START topic_subscription_ack90]
|
||||
:end-before: [END topic_subscription_ack90]
|
||||
|
||||
Example: push-mode subcription
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START topic_subscription_push]
|
||||
:end-before: [END topic_subscription_push]
|
||||
|
||||
:type name: string
|
||||
:param name: the name of the subscription
|
||||
|
||||
:type ack_deadline: int
|
||||
:param ack_deadline: the deadline (in seconds) by which messages pulled
|
||||
from the back-end must be acknowledged.
|
||||
|
||||
:type push_endpoint: string
|
||||
:param push_endpoint: URL to which messages will be pushed by the
|
||||
back-end. If not set, the application must pull
|
||||
messages.
|
||||
"""
|
||||
return Subscription(name, self, ack_deadline=ack_deadline,
|
||||
push_endpoint=push_endpoint)
|
||||
|
||||
@classmethod
|
||||
def from_api_repr(cls, resource, client):
|
||||
"""Factory: construct a topic given its API representation
|
||||
|
||||
:type resource: dict
|
||||
:param resource: topic resource representation returned from the API
|
||||
|
||||
:type client: :class:`gcloud.pubsub.client.Client`
|
||||
:param client: Client which holds credentials and project
|
||||
configuration for the topic.
|
||||
|
||||
:rtype: :class:`gcloud.pubsub.topic.Topic`
|
||||
:returns: Topic parsed from ``resource``.
|
||||
:raises: :class:`ValueError` if ``client`` is not ``None`` and the
|
||||
project from the resource does not agree with the project
|
||||
from the client.
|
||||
"""
|
||||
topic_name = topic_name_from_path(resource['name'], client.project)
|
||||
return cls(topic_name, client=client)
|
||||
|
||||
@property
|
||||
def project(self):
|
||||
"""Project bound to the topic."""
|
||||
return self._client.project
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
"""Fully-qualified name used in topic / subscription APIs"""
|
||||
return 'projects/%s/topics/%s' % (self.project, self.name)
|
||||
|
||||
def _require_client(self, client):
|
||||
"""Check client or verify over-ride.
|
||||
|
||||
: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: :class:`gcloud.pubsub.client.Client`
|
||||
:returns: The client passed in or the currently bound client.
|
||||
"""
|
||||
if client is None:
|
||||
client = self._client
|
||||
return client
|
||||
|
||||
def create(self, client=None):
|
||||
"""API call: create the topic via a PUT request
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/create
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START topic_create]
|
||||
:end-before: [END topic_create]
|
||||
|
||||
: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.
|
||||
"""
|
||||
client = self._require_client(client)
|
||||
api = client.publisher_api
|
||||
api.topic_create(topic_path=self.full_name)
|
||||
|
||||
def exists(self, client=None):
|
||||
"""API call: test for the existence of the topic via a GET request
|
||||
|
||||
See
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/get
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START topic_exists]
|
||||
:end-before: [END topic_exists]
|
||||
|
||||
: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.
|
||||
"""
|
||||
client = self._require_client(client)
|
||||
api = client.publisher_api
|
||||
|
||||
try:
|
||||
api.topic_get(topic_path=self.full_name)
|
||||
except NotFound:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def delete(self, client=None):
|
||||
"""API call: delete the topic via a DELETE request
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/delete
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START topic_delete]
|
||||
:end-before: [END topic_delete]
|
||||
|
||||
: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.
|
||||
"""
|
||||
client = self._require_client(client)
|
||||
api = client.publisher_api
|
||||
api.topic_delete(topic_path=self.full_name)
|
||||
|
||||
def _timestamp_message(self, attrs):
|
||||
"""Add a timestamp to ``attrs``, if the topic is so configured.
|
||||
|
||||
If ``attrs`` already has the key, do nothing.
|
||||
|
||||
Helper method for ``publish``/``Batch.publish``.
|
||||
"""
|
||||
if self.timestamp_messages and 'timestamp' not in attrs:
|
||||
attrs['timestamp'] = _datetime_to_rfc3339(_NOW())
|
||||
|
||||
def publish(self, message, client=None, **attrs):
|
||||
"""API call: publish a message to a topic via a POST request
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/publish
|
||||
|
||||
Example without message attributes:
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START topic_publish_simple_message]
|
||||
:end-before: [END topic_publish_simple_message]
|
||||
|
||||
With message attributes:
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START topic_publish_message_with_attrs]
|
||||
:end-before: [END topic_publish_message_with_attrs]
|
||||
|
||||
:type message: bytes
|
||||
:param message: the message payload
|
||||
|
||||
: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.
|
||||
|
||||
:type attrs: dict (string -> string)
|
||||
:param attrs: key-value pairs to send as message attributes
|
||||
|
||||
:rtype: str
|
||||
:returns: message ID assigned by the server to the published message
|
||||
"""
|
||||
client = self._require_client(client)
|
||||
api = client.publisher_api
|
||||
|
||||
self._timestamp_message(attrs)
|
||||
message_b = base64.b64encode(message).decode('ascii')
|
||||
message_data = {'data': message_b, 'attributes': attrs}
|
||||
message_ids = api.topic_publish(self.full_name, [message_data])
|
||||
return message_ids[0]
|
||||
|
||||
def batch(self, client=None):
|
||||
"""Return a batch to use as a context manager.
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START topic_batch]
|
||||
:end-before: [END topic_batch]
|
||||
|
||||
.. note::
|
||||
|
||||
The only API request happens during the ``__exit__()`` of the topic
|
||||
used as a context manager, and only if the block exits without
|
||||
raising an exception.
|
||||
|
||||
: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: :class:`Batch`
|
||||
:returns: A batch to use as a context manager.
|
||||
"""
|
||||
client = self._require_client(client)
|
||||
return Batch(self, client)
|
||||
|
||||
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
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START topic_list_subscriptions]
|
||||
:end-before: [END topic_list_subscriptions]
|
||||
|
||||
: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)
|
||||
api = client.publisher_api
|
||||
sub_paths, next_token = api.topic_list_subscriptions(
|
||||
self.full_name, page_size, page_token)
|
||||
subscriptions = []
|
||||
for sub_path in sub_paths:
|
||||
sub_name = subscription_name_from_path(sub_path, self.project)
|
||||
subscriptions.append(Subscription(sub_name, self))
|
||||
return subscriptions, next_token
|
||||
|
||||
def get_iam_policy(self, client=None):
|
||||
"""Fetch the IAM policy for the topic.
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/getIamPolicy
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START topic_get_iam_policy]
|
||||
:end-before: [END topic_get_iam_policy]
|
||||
|
||||
: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 batch.
|
||||
|
||||
:rtype: :class:`gcloud.pubsub.iam.Policy`
|
||||
:returns: policy created from the resource returned by the
|
||||
``getIamPolicy`` API request.
|
||||
"""
|
||||
client = self._require_client(client)
|
||||
api = client.iam_policy_api
|
||||
resp = api.get_iam_policy(self.full_name)
|
||||
return Policy.from_api_repr(resp)
|
||||
|
||||
def set_iam_policy(self, policy, client=None):
|
||||
"""Update the IAM policy for the topic.
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/setIamPolicy
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START topic_set_iam_policy]
|
||||
:end-before: [END topic_set_iam_policy]
|
||||
|
||||
:type policy: :class:`gcloud.pubsub.iam.Policy`
|
||||
:param policy: the new policy, typically fetched via
|
||||
:meth:`get_iam_policy` and updated in place.
|
||||
|
||||
: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 batch.
|
||||
|
||||
:rtype: :class:`gcloud.pubsub.iam.Policy`
|
||||
:returns: updated policy created from the resource returned by the
|
||||
``setIamPolicy`` API request.
|
||||
"""
|
||||
client = self._require_client(client)
|
||||
api = client.iam_policy_api
|
||||
resource = policy.to_api_repr()
|
||||
resp = api.set_iam_policy(self.full_name, resource)
|
||||
return Policy.from_api_repr(resp)
|
||||
|
||||
def check_iam_permissions(self, permissions, client=None):
|
||||
"""Verify permissions allowed for the current user.
|
||||
|
||||
See:
|
||||
https://cloud.google.com/pubsub/reference/rest/v1/projects.topics/testIamPermissions
|
||||
|
||||
Example:
|
||||
|
||||
.. literalinclude:: pubsub_snippets.py
|
||||
:start-after: [START topic_check_iam_permissions]
|
||||
:end-before: [END topic_check_iam_permissions]
|
||||
|
||||
:type permissions: list of string
|
||||
:param permissions: list of permissions to be tested
|
||||
|
||||
: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 batch.
|
||||
|
||||
:rtype: sequence of string
|
||||
:returns: subset of ``permissions`` allowed by current IAM policy.
|
||||
"""
|
||||
client = self._require_client(client)
|
||||
api = client.iam_policy_api
|
||||
return api.test_iam_permissions(
|
||||
self.full_name, list(permissions))
|
||||
|
||||
|
||||
class Batch(object):
|
||||
"""Context manager: collect messages to publish via a single API call.
|
||||
|
||||
Helper returned by :meth:Topic.batch
|
||||
|
||||
:type topic: :class:`gcloud.pubsub.topic.Topic`
|
||||
:param topic: the topic being published
|
||||
|
||||
:type client: :class:`gcloud.pubsub.client.Client`
|
||||
:param client: The client to use.
|
||||
"""
|
||||
def __init__(self, topic, client):
|
||||
self.topic = topic
|
||||
self.messages = []
|
||||
self.message_ids = []
|
||||
self.client = client
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if exc_type is None:
|
||||
self.commit()
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.message_ids)
|
||||
|
||||
def publish(self, message, **attrs):
|
||||
"""Emulate publishing a message, but save it.
|
||||
|
||||
:type message: bytes
|
||||
:param message: the message payload
|
||||
|
||||
:type attrs: dict (string -> string)
|
||||
:param attrs: key-value pairs to send as message attributes
|
||||
"""
|
||||
self.topic._timestamp_message(attrs)
|
||||
self.messages.append(
|
||||
{'data': base64.b64encode(message).decode('ascii'),
|
||||
'attributes': attrs})
|
||||
|
||||
def commit(self, client=None):
|
||||
"""Send saved messages as a single API call.
|
||||
|
||||
: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 batch.
|
||||
"""
|
||||
if client is None:
|
||||
client = self.client
|
||||
api = client.publisher_api
|
||||
message_ids = api.topic_publish(self.topic.full_name, self.messages[:])
|
||||
self.message_ids.extend(message_ids)
|
||||
del self.messages[:]
|
Loading…
Add table
Add a link
Reference in a new issue