-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path__init__.py
More file actions
executable file
·565 lines (440 loc) · 22.7 KB
/
Copy path__init__.py
File metadata and controls
executable file
·565 lines (440 loc) · 22.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
import activities
import activity
import filter
import publisher
import datetime
import iso8601
import time
import gzip
import StringIO
import logging
import httplib2
from elementtree.ElementTree import *
from pyjavaproperties import Properties
from xml_objects import *
from response import *
class Gnip:
"""Provides the primary interface to the Gnip service.
Provides an authenticated connection between your code, and the Gnip servers.
Gnip's primary functionality is provided through this class.
"""
def __init__(self, username, password, gnip_server=None, properties_file=None):
"""Initialize the class.
@type username string
@param username Your Gnip account username
@type password string
@param password Your Gnip account password
@type gnip_server string
@param gnip_server The Gnip server to connect to
Initializes a Gnip class by setting up authentication
information, used to log into the Gnip service.
"""
p = Properties()
if properties_file is not None:
p.load(open(properties_file))
else:
index = int(__file__.rfind("/"))
basedir = __file__[0:index]
p.load(open(basedir + '/gnip.properties'))
# Determine base Gnip URL
if (gnip_server is None):
self.base_url = p['gnip.server']
else:
self.base_url = gnip_server
self.tunnel_over_post = bool(p['gnip.tunnel.over.post=false'])
# Configure authentication
self.client = httplib2.Http(timeout = int(p['gnip.http.timeout']))
self.client.add_credentials(username, password)
self.headers = {}
self.headers['Accept'] = 'gzip, application/xml'
self.headers['User-Agent'] = 'Gnip-Client-Python/2.1.0'
self.headers['Content-Encoding'] = 'gzip'
self.headers['Content-Type'] = 'application/xml'
def sync_clock(self, the_time):
"""Adjust a time so that it corresponds with Gnip time
@type datetime datetime
@param datetime The datetime object to adjust
@return datetime object representing the corrected time
This method gets the current time from the Gnip server,
gets the current local time and determines the difference
between the two. It then adjusts the passed in time to
account for the difference. This method can be used to ensure
your application's time is in sync with Gnip server time in
order to prevent clock drift between the two.
"""
# Do HTTP HEAD request
resp, content = self.__do_http_head()
# Get local time, before we do any other processing
# so that we can get the two times as close as possible
local_time = datetime.datetime.utcnow()
# Get time from headers and parse into python format
gnip_time = datetime.datetime.strptime(resp["date"], "%a, %d %b %Y %H:%M:%S %Z")
# Determine the time difference
time_delta = gnip_time - local_time
# Return the corrected time
return the_time + time_delta
def time_to_string(self, time):
"""Convert the time to a Gnip bucket formatted string.
@type time time
@param time The time object to convert to a string
@return string representing time
Converts the time passed in to a string of the
form YYYYMMDDHHMM. Gnip uses this string format for
notification/activity bucket specification.
"""
return str(time.strftime("%Y%m%d%H%M"))
def publish_activities(self, publisher_name, activities):
"""Publish the provided activities to Gnip.
@type publisher_name string
@param publisher_name string The name of the publisher to
receive the activities. You must be the owner of the publisher.
@type activities list of Activity objects
@param activities The activities to be published
@return string containing response from the server
This method allows a publisher to publish activities to the Gnip
service. You can only publish activities to a publisher that you own.
"""
url_path = "/my/publishers/" + publisher_name + "/activity.xml"
return self.publish_activities_to_path(url_path, activities)
def publish_activities_to_path(self, url_path, activities):
"""Publish the provided activities to Gnip.
@type url_path string
@param url_path string The url path to publish activities to.
@type activities list of Activity objects
@param activities The activities to be published
@return string containing response from the server
This method allows a publisher to publish activities to the Gnip
service. You can only publish activities to a publisher that you own.
"""
return self.__parse_response(self.__do_http_post(url_path, activities.to_xml()))
def publish_xml_to_path(self, url_path, xml):
"""Publish the provided xml to Gnip.
@type url_path string
@param url_path string The url path to publish activities to.
@type xml string representing activities in xml
@param xml The xml to be published
@return string containing response from the server
This method allows a publisher to publish activities to the Gnip
service. You can only publish activities to a publisher that you own.
"""
return self.__parse_response(self.__do_http_post(url_path, xml))
def create_filter(self, publisher_scope, publisher_name, filter):
"""Create a Filter on the Gnip service.
@type publisher_scope string
@param publisher_scope The scope of the publisher ("my," "public" or "gnip")
@type publisher_name string
@param publisher_name The publisher that the Filter will be applied to.
@type filter Filter
@param filter A populated Filter object
@return string containing response from the server
Creates a new filter, specific to your account, on the Gnip service.
"""
url_path = "/" + publisher_scope + "/publishers/" + publisher_name + "/filters.xml"
return self.__parse_response(self.__do_http_post(url_path, filter.to_xml()))
def add_rule_to_filter(self, publisher_scope, publisher_name, filter_name, rule):
"""Add a rule to a pre-existing Gnip filter.
@type publisher_scope string
@param publisher_scope The scope of the publisher ("my," "public" or "gnip")
@type publisher_name string
@param publisher_name The publisher of the filter to update
@type filter_name string
@param filter_name The filter to update
@type rule string
@param rule a Rule object to add
@return string containing response from the server
"""
url_path = "/" + publisher_scope + "/publishers/" + publisher_name + "/filters/" + filter_name + "/rules.xml"
return self.__parse_response(self.__do_http_post(url_path, rule.to_xml()))
def add_rules_to_filter(self, publisher_scope, publisher_name, filter_name, rules):
"""Add rules to a Gnip filter.
@type publisher_scope string
@param publisher_scope The scope of the publisher ("my," "public" or "gnip")
@type publisher_name string
@param publisher_name The publisher of the filter to update
@type filter_name string
@param filter_name The filter to update
@type rules List of Rule objects
@param rules List of Rule objects
@return string containing response from the server
"""
url_path = "/" + publisher_scope + "/publishers/" + publisher_name + "/filters/" + filter_name + "/rules.xml"
rules_xml = "<rules>"
for rule in rules:
rules_xml+=rule.to_xml()
rules_xml+="</rules>"
return self.__parse_response(self.__do_http_post(url_path, rules_xml))
def remove_rule_from_filter(self, publisher_scope, publisher_name, filter_name, rule):
"""Remove a rule from a Gnip filter.
@type publisher_scope string
@param publisher_scope The scope of the publisher ("my," "public" or "gnip")
@type publisher_name string
@param publisher_name The publisher of the filter to update
@type filter_name string
@param filter_name The filter to update
@type rule string
@param rule a Rule to remove
@return string containing response from the server
"""
url_path = "/" + publisher_scope + "/publishers/" + publisher_name + "/filters/" + filter_name + "/rules"
return self.__parse_response(self.__do_http_delete(url_path, rule.to_delete_query_string()))
def rule_exists_in_filter(self, publisher_scope, publisher_name, filter_name, rule):
"""Determine whether or not a given rule exists in an existing Filter.
@type publisher_scope string
@param publisher_scope The scope of the publisher ("my," "public" or "gnip")
@type publisher_name string
@param publisher_name The publisher of the filter to update
@type filter_name string
@param filter_name The filter to check
@type rule string
@param rule a Rule to check
@return boolean as to the existance of the rule, None if existance of the rule can't be determined
"""
url_path = "/" + publisher_scope + "/publishers/" + publisher_name + "/filters/" + filter_name + "/rules?" + rule.to_delete_query_string()
response, body = self.__do_http_get(url_path)
if (response.status == 200):
return True
elif (response.status == 404):
return False
else:
return None
def delete_filter(self, publisher_scope, publisher_name, name):
"""Delete a Gnip filter.
@type publisher_scope string
@param publisher_scope The scope of the publisher ("my," "public" or "gnip")
@type publisher_name string
@param publisher_name The publisher to create filter for
@type name string
@param name The name of the filter to delete
@return string containing response from the server
"""
url_path = "/" + publisher_scope + "/publishers/" + publisher_name + "/filters/" + name + ".xml"
return self.__parse_response(self.__do_http_delete(url_path))
def get_filter(self, publisher_scope, publisher_name, name):
"""Find an account specific Filter in Gnip.
@type publisher_scope string
@param publisher_scope The scope of the publisher ("my," "public" or "gnip")
@type publisher_name string
@param publisher_name The publisher to create filter for
@type name string
@param name The name of the filter to find
@return Response which contains the Filter object
"""
url_path = "/" + publisher_scope + "/publishers/" + publisher_name + "/filters/" + name + ".xml"
return self.__parse_response(self.__do_http_get(url_path), filter.Filter())
def get_publisher_activities(self, publisher_scope, publisher_name, date_time=None):
"""Get a Publisher's Activities (as opposed to Notifications).
@type publisher_scope string
@param publisher_scope The scope of the publisher ("my," "public" or "gnip")
@type publisher_name string
@param publisher_name The publisher you want Activities for.
@type date_time datetime
@param date_time The datetime for which data should be retrieved
@return List of Activity objects, one for each activity retrieved
Gets all of the Activities for a specific publisher. You can specify a time
(in UTC) from which you would like specific Activities, otherwise the current
time will be used.
See Also: get_publisher_notifications()
"""
if None == date_time:
url_path = "/" + publisher_scope + "/publishers/" + publisher_name + "/activity/current.xml"
else:
corrected_time = self.sync_clock(date_time)
time_string = self.time_to_string(corrected_time)
url_path = "/" + publisher_scope + "/publishers/" + publisher_name + \
"/activity/" + time_string + ".xml"
return self.__parse_response(self.__do_http_get(url_path), activities.Activities())
def get_filter_activities(self, publisher_scope, publisher_name, name, date_time=None):
"""Get Activites (as opposed to Notifications) from a Filter.
@type publisher_scope string
@param publisher_scope The scope of the publisher (my, public or gnip)
@type name string
@param name The name of the filter you want activities for
@type publisher_name string
@param publisher_name The publisher associated with the filter.
@type date_time datetime
@param date_time The time for which data should be retrieved
@return string containing response from the server
Gets all of the Activities for a specific Filter. You can specify a time
(in UTC) from which you would like specific Activities, otherwise the current
time will be used.
See Also: get_filter_notifications()
"""
if None == date_time:
url_path = "/" + publisher_scope + "/publishers/" + publisher_name + "/filters/" + name + "/activity/current.xml"
else:
corrected_time = self.sync_clock(date_time)
time_string = self.time_to_string(corrected_time)
url_path = "/" + publisher_scope + "/publishers/" + publisher_name + "/filters/" + name + "/activity/" + \
time_string + ".xml"
return self.__parse_response(self.__do_http_get(url_path), activities.Activities())
def get_publisher_notifications(self, publisher_scope, publisher_name, date_time=None):
"""Get a Publisher's Notifications (as opposed to Activities).
@type publisher_scope string
@param publisher_scope The scope of the publisher ("my," "public" or "gnip")
@type publisher_name string
@param publisher_name The publisher you want Notifications for.
@type date_time datetime
@param date_time The datetime for which data should be retrieved
@return List of Activity objects, one for each activity retrieved
Gets all of the Notifications for a specific publisher. You can specify a time
(in UTC) from which you would like specific Notifications, otherwise the current
time will be used.
Gnip currently represents both "Activities" and "Notifications" in the same
object: Activity. "Notification" and "Activity" Activity objects are identical, with the
exception of the "Activity" Activity object containing a payload.
See Also: get_publisher_activities()
"""
if None == date_time:
url_path = "/" + publisher_scope + "/publishers/" + publisher_name + "/notification/current.xml"
else:
corrected_time = self.sync_clock(date_time)
time_string = self.time_to_string(corrected_time)
url_path = "/" + publisher_scope + "/publishers/" + publisher_name + "/notification/" + time_string + ".xml"
return self.__parse_response(self.__do_http_get(url_path), activities.Activities())
def get_filter_notifications(self, publisher_scope, publisher_name, name, date_time=None):
"""Get Notifications (as opposed to Activities) from a Filter.
@type publisher_scope string
@param publisher_scope The scope of the publisher (my, public or gnip)
@type name string
@param name The name of the filter you want Notifications for
@type publisher_name string
@param publisher_name The publisher associated with the filter.
@type date_time datetime
@param date_time The time for which data should be retrieved
@return string containing response from the server
Gets all of the Notifications for a specific Filter. You can specify a time
(in UTC) from which you would like specific Notifications, otherwise the current
time will be used.
Gnip currently represents both "Activities" and "Notifications" in the same
object: Activity. "Notification" and "Activity" Activity objects are identical, with the
exception of the "Activity" Activity object containing a payload.
See Also: get_filter_activities()
"""
if None == date_time:
url_path = "/" + publisher_scope + "/publishers/" + publisher_name + "/filters/" + name + "/notification/current.xml"
else:
corrected_time = self.sync_clock(date_time)
time_string = self.time_to_string(corrected_time)
url_path = "/" + publisher_scope + "/publishers/" + publisher_name + "/filters/" + name + "/notification/" + time_string + ".xml"
return self.__parse_response(self.__do_http_get(url_path), activities.Activities())
def update_filter(self, publisher_scope, publisher_name, filter):
"""Update a Gnip filter.
@type publisher_scope string
@param publisher_scope The scope of the publisher (my, public or gnip)
@type publisher_name string
@param publisher_name The publisher of the filter
@type filter Filter
@param filte A populated Filter object
@return string containing response from the server
Updates the Filter on the Gnip service with the Filter provided.
The Filter must already exist on the service.
"""
url_path = "/" + publisher_scope + "/publishers/" + publisher_name + "/filters/" + filter.name + ".xml"
return self.__parse_response(self.__do_http_put(url_path, filter.to_xml()))
def create_publisher(self, publisher):
"""Create a Gnip publisher in the "my" scope.
@type publisher Publisher
@param publisher A populated Publisher object
@return string containing response from the server
Creates a new publisher on the Gnip server within the "my" publisher scope.
The publisher will be visible only to your account, and only you, can
publish to it.
"""
url_path = "/my/publishers"
return self.__parse_response(self.__do_http_post(url_path, publisher.to_xml()))
def delete_publisher(self, publisher):
"""Delete a Gnip publisher from the "my" scope.
@type publisher Publisher
@param publisher A populated Publisher object
@return a response object that holds the HTTP response code and an unmarshalled response object
Deletes an existing publisher from the Gnip server within the "my" publisher scope. This operation
also deletes all Filters associated with the Publisher.
"""
url_path = "/my/publishers/" + publisher.name + ".xml"
return self.__parse_response(self.__do_http_delete(url_path))
def get_publisher(self, scope, name):
"""Get a Gnip publisher.
@type scope string
@param scope The scope of the publisher ("my," "public" or "gnip")
@type name string
@param name The name of the publisher to get
@return Publisher object based on response from the server
Gets a Publisher from the Gnip server. The Publisher object allows
you to determine what capabilities a Publisher supports. These
capabilities determine what kind of rules you can use when creating
a Filter.
"""
url_path = "/" + scope + "/publishers/" + name + ".xml"
return self.__parse_response(self.__do_http_get(url_path),publisher.Publisher())
def update_publisher(self, publisher):
"""Update a Gnip filter.
@type publisher Publisher
@param publisher The publisher object to update
@return string containing response from the server
Updates a pre-existing Publisher with the Publisher provided.
"""
url_path = "/my/publishers/" + publisher.name + ".xml"
return self.__parse_response(self.__do_http_put(url_path, publisher.to_xml()))
def __compress_with_gzip(self, string):
if (string is None or len(string) is 0):
return ""
zbuf = StringIO.StringIO()
zfile = gzip.GzipFile(mode='wb', fileobj=zbuf, compresslevel=9)
zfile.write(string)
zfile.close()
return zbuf.getvalue()
def __do_http_head(self):
return self.client.request(self.base_url, "HEAD", headers=self.headers)
def __do_http_get(self, url_path, query_string = None):
url = self.base_url + url_path
if query_string is not None:
url+="?" + query_string
return self.client.request(url, "GET", headers=self.headers)
def __do_http_post(self, url_path, data, query_string = None):
url = self.base_url + url_path
if query_string is not None:
url+="?" + query_string
return self.client.request(url, "POST", headers=self.headers, body=self.__compress_with_gzip(data))
def __do_http_put(self, url_path, data, query_string = None):
url = self.base_url + url_path
if (self.tunnel_over_post):
url += ';edit'
verb = "POST"
else:
verb = "PUT"
if query_string is not None:
url+="?" + query_string
return self.client.request(url, verb, headers=self.headers, body=self.__compress_with_gzip(data))
def __do_http_delete(self, url_path, query_string = None):
url = self.base_url + url_path
if (self.tunnel_over_post):
url += ';delete'
verb = "POST"
else:
verb = "DELETE"
if query_string is not None:
url+="?" + query_string
return self.client.request(url, verb, headers=self.headers, body=self.__compress_with_gzip(" "))
def __parse_response(self, response, data_object=None):
if (response[0].status == 200):
if data_object is None:
return Response(response[0].status, self.__parse_result(response[1]))
else:
data_object.from_xml(response[1])
return Response(response[0].status, data_object)
else:
return Response(response[0].status, self.__parse_error(response[1]))
def __parse_error(self, error_xml):
logging.info("Parsing error from XML: " + error_xml)
error = Error()
error.from_xml(error_xml)
return error
def __parse_result(self, result_xml):
result = Result()
result.from_xml(result_xml)
return result
if __name__=="__main__":
print "This module was not designed to be called directly."
print
print "Try 'from gnip import Gnip'"
print "or 'from gnip import *'"