| Index: reviewbot/third_party/google-api-python-client/apiclient/push.py
|
| ===================================================================
|
| --- reviewbot/third_party/google-api-python-client/apiclient/push.py (revision 0)
|
| +++ reviewbot/third_party/google-api-python-client/apiclient/push.py (revision 0)
|
| @@ -0,0 +1,274 @@
|
| +# 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.
|
| +
|
| +"""Push notifications support.
|
| +
|
| +This code is based on experimental APIs and is subject to change.
|
| +"""
|
| +
|
| +__author__ = 'afshar@google.com (Ali Afshar)'
|
| +
|
| +import binascii
|
| +import collections
|
| +import os
|
| +import urllib
|
| +
|
| +SUBSCRIBE = 'X-GOOG-SUBSCRIBE'
|
| +SUBSCRIPTION_ID = 'X-GOOG-SUBSCRIPTION-ID'
|
| +TOPIC_ID = 'X-GOOG-TOPIC-ID'
|
| +TOPIC_URI = 'X-GOOG-TOPIC-URI'
|
| +CLIENT_TOKEN = 'X-GOOG-CLIENT-TOKEN'
|
| +EVENT_TYPE = 'X-GOOG-EVENT-TYPE'
|
| +UNSUBSCRIBE = 'X-GOOG-UNSUBSCRIBE'
|
| +
|
| +
|
| +class InvalidSubscriptionRequestError(ValueError):
|
| + """The request cannot be subscribed."""
|
| +
|
| +
|
| +def new_token():
|
| + """Gets a random token for use as a client_token in push notifications.
|
| +
|
| + Returns:
|
| + str, a new random token.
|
| + """
|
| + return binascii.hexlify(os.urandom(32))
|
| +
|
| +
|
| +class Channel(object):
|
| + """Base class for channel types."""
|
| +
|
| + def __init__(self, channel_type, channel_args):
|
| + """Create a new Channel.
|
| +
|
| + You probably won't need to create this channel manually, since there are
|
| + subclassed Channel for each specific type with a more customized set of
|
| + arguments to pass. However, you may wish to just create it manually here.
|
| +
|
| + Args:
|
| + channel_type: str, the type of channel.
|
| + channel_args: dict, arguments to pass to the channel.
|
| + """
|
| + self.channel_type = channel_type
|
| + self.channel_args = channel_args
|
| +
|
| + def as_header_value(self):
|
| + """Create the appropriate header for this channel.
|
| +
|
| + Returns:
|
| + str encoded channel description suitable for use as a header.
|
| + """
|
| + return '%s?%s' % (self.channel_type, urllib.urlencode(self.channel_args))
|
| +
|
| + def write_header(self, headers):
|
| + """Write the appropriate subscribe header to a headers dict.
|
| +
|
| + Args:
|
| + headers: dict, headers to add subscribe header to.
|
| + """
|
| + headers[SUBSCRIBE] = self.as_header_value()
|
| +
|
| +
|
| +class WebhookChannel(Channel):
|
| + """Channel for registering web hook notifications."""
|
| +
|
| + def __init__(self, url, app_engine=False):
|
| + """Create a new WebhookChannel
|
| +
|
| + Args:
|
| + url: str, URL to post notifications to.
|
| + app_engine: bool, default=False, whether the destination for the
|
| + notifications is an App Engine application.
|
| + """
|
| + super(WebhookChannel, self).__init__(
|
| + channel_type='web_hook',
|
| + channel_args={
|
| + 'url': url,
|
| + 'app_engine': app_engine and 'true' or 'false',
|
| + }
|
| + )
|
| +
|
| +
|
| +class Headers(collections.defaultdict):
|
| + """Headers for managing subscriptions."""
|
| +
|
| +
|
| + ALL_HEADERS = set([SUBSCRIBE, SUBSCRIPTION_ID, TOPIC_ID, TOPIC_URI,
|
| + CLIENT_TOKEN, EVENT_TYPE, UNSUBSCRIBE])
|
| +
|
| + def __init__(self):
|
| + """Create a new subscription configuration instance."""
|
| + collections.defaultdict.__init__(self, str)
|
| +
|
| + def __setitem__(self, key, value):
|
| + """Set a header value, ensuring the key is an allowed value.
|
| +
|
| + Args:
|
| + key: str, the header key.
|
| + value: str, the header value.
|
| + Raises:
|
| + ValueError if key is not one of the accepted headers.
|
| + """
|
| + normal_key = self._normalize_key(key)
|
| + if normal_key not in self.ALL_HEADERS:
|
| + raise ValueError('Header name must be one of %s.' % self.ALL_HEADERS)
|
| + else:
|
| + return collections.defaultdict.__setitem__(self, normal_key, value)
|
| +
|
| + def __getitem__(self, key):
|
| + """Get a header value, normalizing the key case.
|
| +
|
| + Args:
|
| + key: str, the header key.
|
| + Returns:
|
| + String header value.
|
| + Raises:
|
| + KeyError if the key is not one of the accepted headers.
|
| + """
|
| + normal_key = self._normalize_key(key)
|
| + if normal_key not in self.ALL_HEADERS:
|
| + raise ValueError('Header name must be one of %s.' % self.ALL_HEADERS)
|
| + else:
|
| + return collections.defaultdict.__getitem__(self, normal_key)
|
| +
|
| + def _normalize_key(self, key):
|
| + """Normalize a header name for use as a key."""
|
| + return key.upper()
|
| +
|
| + def items(self):
|
| + """Generator for each header."""
|
| + for header in self.ALL_HEADERS:
|
| + value = self[header]
|
| + if value:
|
| + yield header, value
|
| +
|
| + def write(self, headers):
|
| + """Applies the subscription headers.
|
| +
|
| + Args:
|
| + headers: dict of headers to insert values into.
|
| + """
|
| + for header, value in self.items():
|
| + headers[header.lower()] = value
|
| +
|
| + def read(self, headers):
|
| + """Read from headers.
|
| +
|
| + Args:
|
| + headers: dict of headers to read from.
|
| + """
|
| + for header in self.ALL_HEADERS:
|
| + if header.lower() in headers:
|
| + self[header] = headers[header.lower()]
|
| +
|
| +
|
| +class Subscription(object):
|
| + """Information about a subscription."""
|
| +
|
| + def __init__(self):
|
| + """Create a new Subscription."""
|
| + self.headers = Headers()
|
| +
|
| + @classmethod
|
| + def for_request(cls, request, channel, client_token=None):
|
| + """Creates a subscription and attaches it to a request.
|
| +
|
| + Args:
|
| + request: An http.HttpRequest to modify for making a subscription.
|
| + channel: A apiclient.push.Channel describing the subscription to
|
| + create.
|
| + client_token: (optional) client token to verify the notification.
|
| +
|
| + Returns:
|
| + New subscription object.
|
| + """
|
| + subscription = cls.for_channel(channel=channel, client_token=client_token)
|
| + subscription.headers.write(request.headers)
|
| + if request.method != 'GET':
|
| + raise InvalidSubscriptionRequestError(
|
| + 'Can only subscribe to requests which are GET.')
|
| + request.method = 'POST'
|
| +
|
| + def _on_response(response, subscription=subscription):
|
| + """Called with the response headers. Reads the subscription headers."""
|
| + subscription.headers.read(response)
|
| +
|
| + request.add_response_callback(_on_response)
|
| + return subscription
|
| +
|
| + @classmethod
|
| + def for_channel(cls, channel, client_token=None):
|
| + """Alternate constructor to create a subscription from a channel.
|
| +
|
| + Args:
|
| + channel: A apiclient.push.Channel describing the subscription to
|
| + create.
|
| + client_token: (optional) client token to verify the notification.
|
| +
|
| + Returns:
|
| + New subscription object.
|
| + """
|
| + subscription = cls()
|
| + channel.write_header(subscription.headers)
|
| + if client_token is None:
|
| + client_token = new_token()
|
| + subscription.headers[SUBSCRIPTION_ID] = new_token()
|
| + subscription.headers[CLIENT_TOKEN] = client_token
|
| + return subscription
|
| +
|
| + def verify(self, headers):
|
| + """Verifies that a webhook notification has the correct client_token.
|
| +
|
| + Args:
|
| + headers: dict of request headers for a push notification.
|
| +
|
| + Returns:
|
| + Boolean value indicating whether the notification is verified.
|
| + """
|
| + new_subscription = Subscription()
|
| + new_subscription.headers.read(headers)
|
| + return new_subscription.client_token == self.client_token
|
| +
|
| + @property
|
| + def subscribe(self):
|
| + """Subscribe header value."""
|
| + return self.headers[SUBSCRIBE]
|
| +
|
| + @property
|
| + def subscription_id(self):
|
| + """Subscription ID header value."""
|
| + return self.headers[SUBSCRIPTION_ID]
|
| +
|
| + @property
|
| + def topic_id(self):
|
| + """Topic ID header value."""
|
| + return self.headers[TOPIC_ID]
|
| +
|
| + @property
|
| + def topic_uri(self):
|
| + """Topic URI header value."""
|
| + return self.headers[TOPIC_URI]
|
| +
|
| + @property
|
| + def client_token(self):
|
| + """Client Token header value."""
|
| + return self.headers[CLIENT_TOKEN]
|
| +
|
| + @property
|
| + def event_type(self):
|
| + """Event Type header value."""
|
| + return self.headers[EVENT_TYPE]
|
| +
|
| + @property
|
| + def unsubscribe(self):
|
| + """Unsuscribe header value."""
|
| + return self.headers[UNSUBSCRIBE]
|
|
|
| Property changes on: reviewbot/third_party/google-api-python-client/apiclient/push.py
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|