Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(284)

Side by Side Diff: reviewbot/third_party/google-api-python-client/apiclient/push.py

Issue 20515002: Add google-api-python-client in third_party/ (Closed) Base URL: https://src.chromium.org/chrome/trunk/tools/
Patch Set: Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 # Licensed under the Apache License, Version 2.0 (the "License");
2 # you may not use this file except in compliance with the License.
3 # You may obtain a copy of the License at
4 #
5 # http://www.apache.org/licenses/LICENSE-2.0
6 #
7 # Unless required by applicable law or agreed to in writing, software
8 # distributed under the License is distributed on an "AS IS" BASIS,
9 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 # See the License for the specific language governing permissions and
11 # limitations under the License.
12
13 """Push notifications support.
14
15 This code is based on experimental APIs and is subject to change.
16 """
17
18 __author__ = 'afshar@google.com (Ali Afshar)'
19
20 import binascii
21 import collections
22 import os
23 import urllib
24
25 SUBSCRIBE = 'X-GOOG-SUBSCRIBE'
26 SUBSCRIPTION_ID = 'X-GOOG-SUBSCRIPTION-ID'
27 TOPIC_ID = 'X-GOOG-TOPIC-ID'
28 TOPIC_URI = 'X-GOOG-TOPIC-URI'
29 CLIENT_TOKEN = 'X-GOOG-CLIENT-TOKEN'
30 EVENT_TYPE = 'X-GOOG-EVENT-TYPE'
31 UNSUBSCRIBE = 'X-GOOG-UNSUBSCRIBE'
32
33
34 class InvalidSubscriptionRequestError(ValueError):
35 """The request cannot be subscribed."""
36
37
38 def new_token():
39 """Gets a random token for use as a client_token in push notifications.
40
41 Returns:
42 str, a new random token.
43 """
44 return binascii.hexlify(os.urandom(32))
45
46
47 class Channel(object):
48 """Base class for channel types."""
49
50 def __init__(self, channel_type, channel_args):
51 """Create a new Channel.
52
53 You probably won't need to create this channel manually, since there are
54 subclassed Channel for each specific type with a more customized set of
55 arguments to pass. However, you may wish to just create it manually here.
56
57 Args:
58 channel_type: str, the type of channel.
59 channel_args: dict, arguments to pass to the channel.
60 """
61 self.channel_type = channel_type
62 self.channel_args = channel_args
63
64 def as_header_value(self):
65 """Create the appropriate header for this channel.
66
67 Returns:
68 str encoded channel description suitable for use as a header.
69 """
70 return '%s?%s' % (self.channel_type, urllib.urlencode(self.channel_args))
71
72 def write_header(self, headers):
73 """Write the appropriate subscribe header to a headers dict.
74
75 Args:
76 headers: dict, headers to add subscribe header to.
77 """
78 headers[SUBSCRIBE] = self.as_header_value()
79
80
81 class WebhookChannel(Channel):
82 """Channel for registering web hook notifications."""
83
84 def __init__(self, url, app_engine=False):
85 """Create a new WebhookChannel
86
87 Args:
88 url: str, URL to post notifications to.
89 app_engine: bool, default=False, whether the destination for the
90 notifications is an App Engine application.
91 """
92 super(WebhookChannel, self).__init__(
93 channel_type='web_hook',
94 channel_args={
95 'url': url,
96 'app_engine': app_engine and 'true' or 'false',
97 }
98 )
99
100
101 class Headers(collections.defaultdict):
102 """Headers for managing subscriptions."""
103
104
105 ALL_HEADERS = set([SUBSCRIBE, SUBSCRIPTION_ID, TOPIC_ID, TOPIC_URI,
106 CLIENT_TOKEN, EVENT_TYPE, UNSUBSCRIBE])
107
108 def __init__(self):
109 """Create a new subscription configuration instance."""
110 collections.defaultdict.__init__(self, str)
111
112 def __setitem__(self, key, value):
113 """Set a header value, ensuring the key is an allowed value.
114
115 Args:
116 key: str, the header key.
117 value: str, the header value.
118 Raises:
119 ValueError if key is not one of the accepted headers.
120 """
121 normal_key = self._normalize_key(key)
122 if normal_key not in self.ALL_HEADERS:
123 raise ValueError('Header name must be one of %s.' % self.ALL_HEADERS)
124 else:
125 return collections.defaultdict.__setitem__(self, normal_key, value)
126
127 def __getitem__(self, key):
128 """Get a header value, normalizing the key case.
129
130 Args:
131 key: str, the header key.
132 Returns:
133 String header value.
134 Raises:
135 KeyError if the key is not one of the accepted headers.
136 """
137 normal_key = self._normalize_key(key)
138 if normal_key not in self.ALL_HEADERS:
139 raise ValueError('Header name must be one of %s.' % self.ALL_HEADERS)
140 else:
141 return collections.defaultdict.__getitem__(self, normal_key)
142
143 def _normalize_key(self, key):
144 """Normalize a header name for use as a key."""
145 return key.upper()
146
147 def items(self):
148 """Generator for each header."""
149 for header in self.ALL_HEADERS:
150 value = self[header]
151 if value:
152 yield header, value
153
154 def write(self, headers):
155 """Applies the subscription headers.
156
157 Args:
158 headers: dict of headers to insert values into.
159 """
160 for header, value in self.items():
161 headers[header.lower()] = value
162
163 def read(self, headers):
164 """Read from headers.
165
166 Args:
167 headers: dict of headers to read from.
168 """
169 for header in self.ALL_HEADERS:
170 if header.lower() in headers:
171 self[header] = headers[header.lower()]
172
173
174 class Subscription(object):
175 """Information about a subscription."""
176
177 def __init__(self):
178 """Create a new Subscription."""
179 self.headers = Headers()
180
181 @classmethod
182 def for_request(cls, request, channel, client_token=None):
183 """Creates a subscription and attaches it to a request.
184
185 Args:
186 request: An http.HttpRequest to modify for making a subscription.
187 channel: A apiclient.push.Channel describing the subscription to
188 create.
189 client_token: (optional) client token to verify the notification.
190
191 Returns:
192 New subscription object.
193 """
194 subscription = cls.for_channel(channel=channel, client_token=client_token)
195 subscription.headers.write(request.headers)
196 if request.method != 'GET':
197 raise InvalidSubscriptionRequestError(
198 'Can only subscribe to requests which are GET.')
199 request.method = 'POST'
200
201 def _on_response(response, subscription=subscription):
202 """Called with the response headers. Reads the subscription headers."""
203 subscription.headers.read(response)
204
205 request.add_response_callback(_on_response)
206 return subscription
207
208 @classmethod
209 def for_channel(cls, channel, client_token=None):
210 """Alternate constructor to create a subscription from a channel.
211
212 Args:
213 channel: A apiclient.push.Channel describing the subscription to
214 create.
215 client_token: (optional) client token to verify the notification.
216
217 Returns:
218 New subscription object.
219 """
220 subscription = cls()
221 channel.write_header(subscription.headers)
222 if client_token is None:
223 client_token = new_token()
224 subscription.headers[SUBSCRIPTION_ID] = new_token()
225 subscription.headers[CLIENT_TOKEN] = client_token
226 return subscription
227
228 def verify(self, headers):
229 """Verifies that a webhook notification has the correct client_token.
230
231 Args:
232 headers: dict of request headers for a push notification.
233
234 Returns:
235 Boolean value indicating whether the notification is verified.
236 """
237 new_subscription = Subscription()
238 new_subscription.headers.read(headers)
239 return new_subscription.client_token == self.client_token
240
241 @property
242 def subscribe(self):
243 """Subscribe header value."""
244 return self.headers[SUBSCRIBE]
245
246 @property
247 def subscription_id(self):
248 """Subscription ID header value."""
249 return self.headers[SUBSCRIPTION_ID]
250
251 @property
252 def topic_id(self):
253 """Topic ID header value."""
254 return self.headers[TOPIC_ID]
255
256 @property
257 def topic_uri(self):
258 """Topic URI header value."""
259 return self.headers[TOPIC_URI]
260
261 @property
262 def client_token(self):
263 """Client Token header value."""
264 return self.headers[CLIENT_TOKEN]
265
266 @property
267 def event_type(self):
268 """Event Type header value."""
269 return self.headers[EVENT_TYPE]
270
271 @property
272 def unsubscribe(self):
273 """Unsuscribe header value."""
274 return self.headers[UNSUBSCRIBE]
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698