xitiomet is sharing code with you
Bitbucket is a code hosting site. Unlimited public and private repositories. Free for small teams.
Don't show this againpython-twitter / twitter.py
- commit
- 42a0465d458f
- parent
- 19260f87c32a
- branch
- dclinton
Cleanup whitespace
1 |
fc159c49c080
|
#!/usr/bin/python2.4 |
2 |
4805aee792f0
|
# |
3 |
4805aee792f0
|
# Copyright 2007 Google Inc. All Rights Reserved. |
4 |
fceb5d2374d2
|
# |
5 |
fceb5d2374d2
|
# Licensed under the Apache License, Version 2.0 (the "License"); |
6 |
fceb5d2374d2
|
# you may not use this file except in compliance with the License. |
7 |
fceb5d2374d2
|
# You may obtain a copy of the License at |
8 |
fceb5d2374d2
|
# |
9 |
fceb5d2374d2
|
# http://www.apache.org/licenses/LICENSE-2.0 |
10 |
fceb5d2374d2
|
# |
11 |
fceb5d2374d2
|
# Unless required by applicable law or agreed to in writing, software |
12 |
fceb5d2374d2
|
# distributed under the License is distributed on an "AS IS" BASIS, |
13 |
fceb5d2374d2
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
14 |
fceb5d2374d2
|
# See the License for the specific language governing permissions and |
15 |
fceb5d2374d2
|
# limitations under the License. |
16 |
4805aee792f0
|
|
17 |
4805aee792f0
|
'''A library that provides a python interface to the Twitter API''' |
18 |
4805aee792f0
|
|
19 |
4805aee792f0
|
__author__ = 'dewitt@google.com' |
20 |
8aaa14d8c799
|
__version__ = '0.6-devel' |
21 |
4805aee792f0
|
|
22 |
4805aee792f0
|
|
23 |
f73c99cf9b51
|
import base64 |
24 |
3a4752d10bb7
|
import calendar |
25 |
4805aee792f0
|
import os |
26 |
3a4752d10bb7
|
import rfc822 |
27 |
4805aee792f0
|
import simplejson |
28 |
4dfd36cb761d
|
import sys |
29 |
4805aee792f0
|
import tempfile |
30 |
81cef10d2991
|
import textwrap |
31 |
4805aee792f0
|
import time |
32 |
4805aee792f0
|
import urllib |
33 |
4805aee792f0
|
import urllib2 |
34 |
4805aee792f0
|
import urlparse |
35 |
0ef37898cd2b
|
|
36 |
c05ccbbb1d5c
|
import twitter_pb2 |
37 |
c05ccbbb1d5c
|
|
38 |
a58f4a7ad991
|
try: |
39 |
c05ccbbb1d5c
|
from hashlib import md5 |
40 |
a58f4a7ad991
|
except ImportError: |
41 |
c05ccbbb1d5c
|
from md5 import md5 |
42 |
a58f4a7ad991
|
|
43 |
4805aee792f0
|
|
44 |
81cef10d2991
|
CHARACTER_LIMIT = 140 |
45 |
81cef10d2991
|
|
46 |
81cef10d2991
|
|
47 |
4805aee792f0
|
class TwitterError(Exception): |
48 |
4805aee792f0
|
'''Base class for Twitter errors''' |
49 |
42a0465d458f
|
|
50 |
2367f0c7822c
|
@property |
51 |
2367f0c7822c
|
def message(self): |
52 |
2367f0c7822c
|
'''Returns the first argument used to construct this error.''' |
53 |
2367f0c7822c
|
return self.args[0] |
54 |
4805aee792f0
|
|
55 |
4805aee792f0
|
|
56 |
c05ccbbb1d5c
|
def _CopyProperty(source, destination, name, destination_name=None): |
57 |
c05ccbbb1d5c
|
'''Optionally copies a property from source to destination. |
58 |
4805aee792f0
|
|
59 |
c05ccbbb1d5c
|
Properties will be copied if and only if they appear in the dict |
60 |
c05ccbbb1d5c
|
and are not None. |
61 |
4805aee792f0
|
|
62 |
c05ccbbb1d5c
|
Args: |
63 |
c05ccbbb1d5c
|
source: A python dict |
64 |
c05ccbbb1d5c
|
destination: A python object with a property accessor |
65 |
c05ccbbb1d5c
|
name: The name of the source property |
66 |
c05ccbbb1d5c
|
destination_name: The name of the destination property, if different from name |
67 |
4805aee792f0
|
''' |
68 |
c05ccbbb1d5c
|
if destination_name is None: |
69 |
c05ccbbb1d5c
|
destination_name = name |
70 |
c05ccbbb1d5c
|
try: |
71 |
c05ccbbb1d5c
|
value = source[name] |
72 |
c05ccbbb1d5c
|
if value is not None: |
73 |
c05ccbbb1d5c
|
# If the destination_name has one or more '.' in it |
74 |
c05ccbbb1d5c
|
# traverse the destination object downward to find |
75 |
c05ccbbb1d5c
|
# the actual destination object and property name |
76 |
c05ccbbb1d5c
|
parts = destination_name.split('.') |
77 |
c05ccbbb1d5c
|
for part in parts[0:-1]: |
78 |
c05ccbbb1d5c
|
destination = getattr(destination, part) |
79 |
c05ccbbb1d5c
|
destination_name = parts[-1] |
80 |
c05ccbbb1d5c
|
setattr(destination, destination_name, value) |
81 |
c05ccbbb1d5c
|
except KeyError: |
82 |
c05ccbbb1d5c
|
# source property not found |
83 |
c05ccbbb1d5c
|
pass |
84 |
4805aee792f0
|
|
85 |
4805aee792f0
|
|
86 |
c05ccbbb1d5c
|
def NewStatusFromJsonDict(data): |
87 |
c05ccbbb1d5c
|
'''Create a new Status instance based on a JSON dict. |
88 |
17f566981b41
|
|
89 |
c05ccbbb1d5c
|
Args: |
90 |
c05ccbbb1d5c
|
data: A JSON dict, as parsed from a twitter API response |
91 |
c05ccbbb1d5c
|
Returns: |
92 |
c05ccbbb1d5c
|
A Status instance |
93 |
c05ccbbb1d5c
|
''' |
94 |
c05ccbbb1d5c
|
status = twitter_pb2.Status() |
95 |
c05ccbbb1d5c
|
_CopyProperty(data, status, 'created_at') |
96 |
c05ccbbb1d5c
|
_CopyProperty(data, status, 'favorited') |
97 |
c05ccbbb1d5c
|
_CopyProperty(data, status, 'id') |
98 |
c05ccbbb1d5c
|
_CopyProperty(data, status, 'text') |
99 |
c05ccbbb1d5c
|
_CopyProperty(data, status, 'in_reply_to_screen_name') |
100 |
c05ccbbb1d5c
|
_CopyProperty(data, status, 'in_reply_to_user_id') |
101 |
c05ccbbb1d5c
|
_CopyProperty(data, status, 'in_reply_to_status_id') |
102 |
c05ccbbb1d5c
|
_CopyProperty(data, status, 'truncated') |
103 |
c05ccbbb1d5c
|
_CopyProperty(data, status, 'source') |
104 |
c05ccbbb1d5c
|
if 'user' in data: |
105 |
c05ccbbb1d5c
|
status.user.CopyFrom(NewUserFromJsonDict(data['user'])) |
106 |
c05ccbbb1d5c
|
return status |
107 |
c05ccbbb1d5c
|
|
108 |
c05ccbbb1d5c
|
|
109 |
c05ccbbb1d5c
|
def ComputeCreatedAtInSeconds(status): |
110 |
c05ccbbb1d5c
|
'''Returns the number of seconds past the epoch in the current timezone. |
111 |
c05ccbbb1d5c
|
|
112 |
c05ccbbb1d5c
|
Args: |
113 |
c05ccbbb1d5c
|
status: A status instance |
114 |
c05ccbbb1d5c
|
Returns: |
115 |
c05ccbbb1d5c
|
The number of seconds since the status was created. |
116 |
c05ccbbb1d5c
|
''' |
117 |
c05ccbbb1d5c
|
return calendar.timegm(rfc822.parsedate(status.created_at)) |
118 |
c05ccbbb1d5c
|
|
119 |
c05ccbbb1d5c
|
|
120 |
c05ccbbb1d5c
|
def ComputeRelativeCreatedAt(status, now=None): |
121 |
c05ccbbb1d5c
|
'''Get a human redable string representing the posting time |
122 |
c05ccbbb1d5c
|
|
123 |
c05ccbbb1d5c
|
Args: |
124 |
c05ccbbb1d5c
|
status: A status instance |
125 |
42a0465d458f
|
now: |
126 |
42a0465d458f
|
The current time, if the client choses to set it. Defaults |
127 |
c05ccbbb1d5c
|
to the wall clock time. |
128 |
c05ccbbb1d5c
|
Returns: |
129 |
c05ccbbb1d5c
|
A human readable string representing the posting time |
130 |
4805aee792f0
|
''' |
131 |
c05ccbbb1d5c
|
if now is None: |
132 |
c05ccbbb1d5c
|
now = time.time() |
133 |
c05ccbbb1d5c
|
fudge = 1.25 |
134 |
c05ccbbb1d5c
|
created_at_in_seconds = ComputeCreatedAtInSeconds(status) |
135 |
c05ccbbb1d5c
|
delta = int(now) - int(created_at_in_seconds) |
136 |
c05ccbbb1d5c
|
if delta < (1 * fudge): |
137 |
c05ccbbb1d5c
|
return 'about a second ago' |
138 |
c05ccbbb1d5c
|
elif delta < (60 * (1/fudge)): |
139 |
c05ccbbb1d5c
|
return 'about %d seconds ago' % (delta) |
140 |
c05ccbbb1d5c
|
elif delta < (60 * fudge): |
141 |
c05ccbbb1d5c
|
return 'about a minute ago' |
142 |
c05ccbbb1d5c
|
elif delta < (60 * 60 * (1/fudge)): |
143 |
c05ccbbb1d5c
|
return 'about %d minutes ago' % (delta / 60) |
144 |
c05ccbbb1d5c
|
elif delta < (60 * 60 * fudge): |
145 |
c05ccbbb1d5c
|
return 'about an hour ago' |
146 |
c05ccbbb1d5c
|
elif delta < (60 * 60 * 24 * (1/fudge)): |
147 |
c05ccbbb1d5c
|
return 'about %d hours ago' % (delta / (60 * 60)) |
148 |
c05ccbbb1d5c
|
elif delta < (60 * 60 * 24 * fudge): |
149 |
c05ccbbb1d5c
|
return 'about a day ago' |
150 |
c05ccbbb1d5c
|
else: |
151 |
c05ccbbb1d5c
|
return 'about %d days ago' % (delta / (60 * 60 * 24)) |
152 |
4805aee792f0
|
|
153 |
4805aee792f0
|
|
154 |
c05ccbbb1d5c
|
def NewUserFromJsonDict(data): |
155 |
c05ccbbb1d5c
|
'''Create a new User instance based on a JSON dict. |
156 |
4805aee792f0
|
|
157 |
c05ccbbb1d5c
|
Args: |
158 |
c05ccbbb1d5c
|
data: A JSON dict, as parsed from a twitter API response |
159 |
c05ccbbb1d5c
|
Returns: |
160 |
c05ccbbb1d5c
|
A User instance |
161 |
c05ccbbb1d5c
|
''' |
162 |
c05ccbbb1d5c
|
user = twitter_pb2.User() |
163 |
c05ccbbb1d5c
|
_CopyProperty(data, user, 'id') |
164 |
c05ccbbb1d5c
|
_CopyProperty(data, user, 'name') |
165 |
c05ccbbb1d5c
|
_CopyProperty(data, user, 'screen_name') |
166 |
c05ccbbb1d5c
|
_CopyProperty(data, user, 'location') |
167 |
c05ccbbb1d5c
|
_CopyProperty(data, user, 'description') |
168 |
c05ccbbb1d5c
|
_CopyProperty(data, user, 'statuses_count') |
169 |
c05ccbbb1d5c
|
_CopyProperty(data, user, 'followers_count') |
170 |
c05ccbbb1d5c
|
_CopyProperty(data, user, 'favourites_count', 'favorites_count') |
171 |
c05ccbbb1d5c
|
_CopyProperty(data, user, 'friends_count') |
172 |
c05ccbbb1d5c
|
_CopyProperty(data, user, 'profile_image_url', 'profile.image_url') |
173 |
c05ccbbb1d5c
|
_CopyProperty(data, user, 'profile_background_tile', 'profile.background_tile') |
174 |
c05ccbbb1d5c
|
_CopyProperty(data, user, 'profile_background_image_url', 'profile.background_image_url') |
175 |
c05ccbbb1d5c
|
_CopyProperty(data, user, 'profile_sidebar_fill_color', 'profile.sidebar_fill_color') |
176 |
c05ccbbb1d5c
|
_CopyProperty(data, user, 'profile_background_color', 'profile.background_color') |
177 |
c05ccbbb1d5c
|
_CopyProperty(data, user, 'profile_link_color', 'profile.link_color') |
178 |
c05ccbbb1d5c
|
_CopyProperty(data, user, 'profile_text_color', 'profile.text_color') |
179 |
c05ccbbb1d5c
|
_CopyProperty(data, user, 'protected') |
180 |
c05ccbbb1d5c
|
_CopyProperty(data, user, 'utc_offset') |
181 |
c05ccbbb1d5c
|
_CopyProperty(data, user, 'time_zone') |
182 |
c05ccbbb1d5c
|
_CopyProperty(data, user, 'url') |
183 |
c05ccbbb1d5c
|
if 'status' in data: |
184 |
c05ccbbb1d5c
|
user.status.CopyFrom(NewStatusFromJsonDict(data['status'])) |
185 |
c05ccbbb1d5c
|
return user |
186 |
4805aee792f0
|
|
187 |
4805aee792f0
|
|
188 |
c05ccbbb1d5c
|
def NewDirectMessageFromJsonDict(data): |
189 |
c05ccbbb1d5c
|
'''Create a new DirectMessage instance based on a JSON dict. |
190 |
4805aee792f0
|
|
191 |
c05ccbbb1d5c
|
Args: |
192 |
c05ccbbb1d5c
|
data: A JSON dict, as parsed from a twitter API response |
193 |
c05ccbbb1d5c
|
Returns: |
194 |
c05ccbbb1d5c
|
A DirectMessage instance |
195 |
c05ccbbb1d5c
|
''' |
196 |
c05ccbbb1d5c
|
direct_message = twitter_pb2.DirectMessage() |
197 |
c05ccbbb1d5c
|
_CopyProperty(data, direct_message, 'created_at') |
198 |
c05ccbbb1d5c
|
_CopyProperty(data, direct_message, 'recipient_id') |
199 |
c05ccbbb1d5c
|
_CopyProperty(data, direct_message, 'sender_id') |
200 |
c05ccbbb1d5c
|
_CopyProperty(data, direct_message, 'text') |
201 |
c05ccbbb1d5c
|
_CopyProperty(data, direct_message, 'sender_screen_name') |
202 |
c05ccbbb1d5c
|
_CopyProperty(data, direct_message, 'id') |
203 |
c05ccbbb1d5c
|
_CopyProperty(data, direct_message, 'recipient_screen_name') |
204 |
c05ccbbb1d5c
|
return direct_message |
205 |
17f566981b41
|
|
206 |
4805aee792f0
|
|
207 |
737a5a105dcf
|
def NewResultsFromJsonDict(data): |
208 |
737a5a105dcf
|
'''Create a new Results instance based on a JSON dict. |
209 |
737a5a105dcf
|
|
210 |
737a5a105dcf
|
Args: |
211 |
737a5a105dcf
|
data: A JSON dict, as parsed from a twitter API response |
212 |
737a5a105dcf
|
Returns: |
213 |
737a5a105dcf
|
A Results instance |
214 |
737a5a105dcf
|
''' |
215 |
737a5a105dcf
|
results = twitter_pb2.Results() |
216 |
737a5a105dcf
|
_CopyProperty(data, results, 'completed_in') |
217 |
737a5a105dcf
|
_CopyProperty(data, results, 'max_id') |
218 |
737a5a105dcf
|
_CopyProperty(data, results, 'next_page') |
219 |
737a5a105dcf
|
_CopyProperty(data, results, 'page') |
220 |
737a5a105dcf
|
_CopyProperty(data, results, 'query') |
221 |
737a5a105dcf
|
_CopyProperty(data, results, 'refresh_url') |
222 |
737a5a105dcf
|
_CopyProperty(data, results, 'since_id') |
223 |
737a5a105dcf
|
_CopyProperty(data, results, 'results_per_page') |
224 |
737a5a105dcf
|
if 'results' in data: |
225 |
737a5a105dcf
|
for result_data in data['results']: |
226 |
737a5a105dcf
|
result = results.results.add() |
227 |
737a5a105dcf
|
result.CopyFrom(NewResultFromJsonDict(result_data)) |
228 |
737a5a105dcf
|
return results |
229 |
737a5a105dcf
|
|
230 |
737a5a105dcf
|
|
231 |
737a5a105dcf
|
def NewResultFromJsonDict(data): |
232 |
737a5a105dcf
|
'''Create a new Result instance based on a JSON dict. |
233 |
737a5a105dcf
|
|
234 |
737a5a105dcf
|
Args: |
235 |
737a5a105dcf
|
data: A JSON dict, as parsed from a twitter API response |
236 |
737a5a105dcf
|
Returns: |
237 |
737a5a105dcf
|
A Result instance |
238 |
737a5a105dcf
|
''' |
239 |
737a5a105dcf
|
result = twitter_pb2.Results.Result() |
240 |
737a5a105dcf
|
_CopyProperty(data, result, 'created_at') |
241 |
737a5a105dcf
|
_CopyProperty(data, result, 'from_user') |
242 |
737a5a105dcf
|
_CopyProperty(data, result, 'from_user_id') |
243 |
737a5a105dcf
|
_CopyProperty(data, result, 'id') |
244 |
737a5a105dcf
|
_CopyProperty(data, result, 'iso_language_code') |
245 |
737a5a105dcf
|
_CopyProperty(data, result, 'profile_image_url') |
246 |
737a5a105dcf
|
_CopyProperty(data, result, 'source') |
247 |
737a5a105dcf
|
_CopyProperty(data, result, 'text') |
248 |
737a5a105dcf
|
_CopyProperty(data, result, 'to_user') |
249 |
737a5a105dcf
|
_CopyProperty(data, result, 'to_user_id') |
250 |
737a5a105dcf
|
return result |
251 |
737a5a105dcf
|
|
252 |
737a5a105dcf
|
|
253 |
4805aee792f0
|
class Api(object): |
254 |
4805aee792f0
|
'''A python interface into the Twitter API |
255 |
4805aee792f0
|
|
256 |
4805aee792f0
|
By default, the Api caches results for 1 minute. |
257 |
17f566981b41
|
|
258 |
4805aee792f0
|
Example usage: |
259 |
4805aee792f0
|
|
260 |
ee84714ea665
|
To create an instance of the twitter.Api class, with no authentication: |
261 |
4805aee792f0
|
|
262 |
e67a0a1b407c
|
>>> import twitter |
263 |
e67a0a1b407c
|
>>> api = twitter.Api() |
264 |
4805aee792f0
|
|
265 |
e67a0a1b407c
|
To fetch the most recently posted public twitter status messages: |
266 |
4805aee792f0
|
|
267 |
e67a0a1b407c
|
>>> statuses = api.GetPublicTimeline() |
268 |
e67a0a1b407c
|
>>> print [s.user.name for s in statuses] |
269 |
e67a0a1b407c
|
[u'DeWitt', u'Kesuke Miyagi', u'ev', u'Buzz Andersen', u'Biz Stone'] #... |
270 |
4805aee792f0
|
|
271 |
3e87a5de4fc0
|
To fetch a single user's public status messages, where "user" is either |
272 |
3e87a5de4fc0
|
a Twitter "short name" or their user id. |
273 |
4805aee792f0
|
|
274 |
3e87a5de4fc0
|
>>> statuses = api.GetUserTimeline(user) |
275 |
e67a0a1b407c
|
>>> print [s.text for s in statuses] |
276 |
4805aee792f0
|
|
277 |
ee84714ea665
|
To use authentication, instantiate the twitter.Api class with a |
278 |
ee84714ea665
|
username and password: |
279 |
e67a0a1b407c
|
|
280 |
ee84714ea665
|
>>> api = twitter.Api(username='twitter user', password='twitter pass') |
281 |
32a0266e88a8
|
|
282 |
ee84714ea665
|
To fetch your friends (after being authenticated): |
283 |
ee84714ea665
|
|
284 |
ee84714ea665
|
>>> users = api.GetFriends() |
285 |
e67a0a1b407c
|
>>> print [u.name for u in users] |
286 |
e67a0a1b407c
|
|
287 |
ee84714ea665
|
To post a twitter status message (after being authenticated): |
288 |
17f566981b41
|
|
289 |
ee84714ea665
|
>>> status = api.PostUpdate('I love python-twitter!') |
290 |
e67a0a1b407c
|
>>> print status.text |
291 |
e67a0a1b407c
|
I love python-twitter! |
292 |
b9effe73b630
|
|
293 |
b9effe73b630
|
There are many other methods, including: |
294 |
b9effe73b630
|
|
295 |
81cef10d2991
|
>>> api.PostUpdates(status) |
296 |
b9effe73b630
|
>>> api.PostDirectMessage(user, text) |
297 |
b9effe73b630
|
>>> api.GetUser(user) |
298 |
b9effe73b630
|
>>> api.GetReplies() |
299 |
b9effe73b630
|
>>> api.GetUserTimeline(user) |
300 |
b9effe73b630
|
>>> api.GetStatus(id) |
301 |
6c1724c3654d
|
>>> api.DestroyStatus(id) |
302 |
b9effe73b630
|
>>> api.GetFriendsTimeline(user) |
303 |
b9effe73b630
|
>>> api.GetFriends(user) |
304 |
b9effe73b630
|
>>> api.GetFollowers() |
305 |
b9effe73b630
|
>>> api.GetFeatured() |
306 |
b9effe73b630
|
>>> api.GetDirectMessages() |
307 |
b9effe73b630
|
>>> api.PostDirectMessage(user, text) |
308 |
b9effe73b630
|
>>> api.DestroyDirectMessage(id) |
309 |
b9effe73b630
|
>>> api.DestroyFriendship(user) |
310 |
b9effe73b630
|
>>> api.CreateFriendship(user) |
311 |
e2f8ed587060
|
>>> api.GetUserByEmail(email) |
312 |
4805aee792f0
|
''' |
313 |
4805aee792f0
|
|
314 |
17f566981b41
|
DEFAULT_CACHE_TIMEOUT = 60 # cache for 1 minute |
315 |
4805aee792f0
|
|
316 |
4805aee792f0
|
_API_REALM = 'Twitter API' |
317 |
17f566981b41
|
|
318 |
90cd841771cf
|
def __init__(self, |
319 |
90cd841771cf
|
username=None, |
320 |
90cd841771cf
|
password=None, |
321 |
90cd841771cf
|
input_encoding=None, |
322 |
90cd841771cf
|
request_headers=None): |
323 |
ee84714ea665
|
'''Instantiate a new twitter.Api object. |
324 |
ee84714ea665
|
|
325 |
ee84714ea665
|
Args: |
326 |
ee84714ea665
|
username: The username of the twitter account. [optional] |
327 |
ee84714ea665
|
password: The password for the twitter account. [optional] |
328 |
90cd841771cf
|
input_encoding: The encoding used to encode input strings. [optional] |
329 |
90cd841771cf
|
request_header: A dictionary of additional HTTP request headers. [optional] |
330 |
ee84714ea665
|
''' |
331 |
4805aee792f0
|
self._cache = _FileCache() |
332 |
4805aee792f0
|
self._urllib = urllib2 |
333 |
4805aee792f0
|
self._cache_timeout = Api.DEFAULT_CACHE_TIMEOUT |
334 |
90cd841771cf
|
self._InitializeRequestHeaders(request_headers) |
335 |
90cd841771cf
|
self._InitializeUserAgent() |
336 |
cb10f131e83f
|
self._InitializeDefaultParameters() |
337 |
4dfd36cb761d
|
self._input_encoding = input_encoding |
338 |
ee84714ea665
|
self.SetCredentials(username, password) |
339 |
4805aee792f0
|
|
340 |
ee84714ea665
|
def GetPublicTimeline(self, since_id=None): |
341 |
c05ccbbb1d5c
|
'''Fetch the sequnce of public Status message for all users. |
342 |
4805aee792f0
|
|
343 |
ee84714ea665
|
Args: |
344 |
32a0266e88a8
|
since_id: |
345 |
ee84714ea665
|
Returns only public statuses with an ID greater than (that is, |
346 |
ee84714ea665
|
more recent than) the specified ID. [Optional] |
347 |
32a0266e88a8
|
|
348 |
4805aee792f0
|
Returns: |
349 |
c05ccbbb1d5c
|
An sequence of Status instances, one for each message |
350 |
4805aee792f0
|
''' |
351 |
ee84714ea665
|
parameters = {} |
352 |
ee84714ea665
|
if since_id: |
353 |
ee84714ea665
|
parameters['since_id'] = since_id |
354 |
4805aee792f0
|
url = 'http://twitter.com/statuses/public_timeline.json' |
355 |
ee84714ea665
|
json = self._FetchUrl(url, parameters=parameters) |
356 |
4805aee792f0
|
data = simplejson.loads(json) |
357 |
265fcf2a1e01
|
self._CheckForTwitterError(data) |
358 |
c05ccbbb1d5c
|
return [NewStatusFromJsonDict(x) for x in data] |
359 |
4805aee792f0
|
|
360 |
11710035d0b9
|
def GetFriendsTimeline(self, |
361 |
11710035d0b9
|
user=None, |
362 |
11710035d0b9
|
count=None, |
363 |
42a0465d458f
|
since=None, |
364 |
11710035d0b9
|
since_id=None): |
365 |
c05ccbbb1d5c
|
'''Fetch the sequence of Status messages for a user's friends |
366 |
ee84714ea665
|
|
367 |
ee84714ea665
|
The twitter.Api instance must be authenticated if the user is private. |
368 |
ee84714ea665
|
|
369 |
ee84714ea665
|
Args: |
370 |
32a0266e88a8
|
user: |
371 |
ee84714ea665
|
Specifies the ID or screen name of the user for whom to return |
372 |
32a0266e88a8
|
the friends_timeline. If unspecified, the username and password |
373 |
11710035d0b9
|
must be set in the twitter.Api instance. [Optional] |
374 |
42a0465d458f
|
count: |
375 |
11710035d0b9
|
Specifies the number of statuses to retrieve. May not be |
376 |
11710035d0b9
|
greater than 200. [Optional] |
377 |
ee84714ea665
|
since: |
378 |
ee84714ea665
|
Narrows the returned results to just those statuses created |
379 |
11710035d0b9
|
after the specified HTTP-formatted date. [Optional] |
380 |
806829c7dc10
|
since_id: |
381 |
806829c7dc10
|
Returns only public statuses with an ID greater than (that is, |
382 |
806829c7dc10
|
more recent than) the specified ID. [Optional] |
383 |
ee84714ea665
|
|
384 |
ee84714ea665
|
Returns: |
385 |
c05ccbbb1d5c
|
A sequence of Status instances, one for each message |
386 |
ee84714ea665
|
''' |
387 |
ee84714ea665
|
if user: |
388 |
ee84714ea665
|
url = 'http://twitter.com/statuses/friends_timeline/%s.json' % user |
389 |
ee84714ea665
|
elif not user and not self._username: |
390 |
ee84714ea665
|
raise TwitterError("User must be specified if API is not authenticated.") |
391 |
32a0266e88a8
|
else: |
392 |
ee84714ea665
|
url = 'http://twitter.com/statuses/friends_timeline.json' |
393 |
ee84714ea665
|
parameters = {} |
394 |
11710035d0b9
|
if count is not None: |
395 |
11710035d0b9
|
try: |
396 |
11710035d0b9
|
if int(count) > 200: |
397 |
11710035d0b9
|
raise TwitterError("'count' may not be greater than 200") |
398 |
11710035d0b9
|
except ValueError: |
399 |
11710035d0b9
|
raise TwitterError("'count' must be an integer") |
400 |
11710035d0b9
|
parameters['count'] = count |
401 |
ee84714ea665
|
if since: |
402 |
ee84714ea665
|
parameters['since'] = since |
403 |
806829c7dc10
|
if since_id: |
404 |
806829c7dc10
|
parameters['since_id'] = since_id |
405 |
ee84714ea665
|
json = self._FetchUrl(url, parameters=parameters) |
406 |
ee84714ea665
|
data = simplejson.loads(json) |
407 |
265fcf2a1e01
|
self._CheckForTwitterError(data) |
408 |
c05ccbbb1d5c
|
return [NewStatusFromJsonDict(x) for x in data] |
409 |
ee84714ea665
|
|
410 |
806829c7dc10
|
def GetUserTimeline(self, user=None, count=None, since=None, since_id=None): |
411 |
c05ccbbb1d5c
|
'''Fetch the sequence of public Status messages for a single user. |
412 |
4805aee792f0
|
|
413 |
ee84714ea665
|
The twitter.Api instance must be authenticated if the user is private. |
414 |
ee84714ea665
|
|
415 |
4805aee792f0
|
Args: |
416 |
e565d3a31699
|
user: |
417 |
32a0266e88a8
|
either the username (short_name) or id of the user to retrieve. If |
418 |
ee84714ea665
|
not specified, then the current authenticated user is used. [optional] |
419 |
ee84714ea665
|
count: the number of status messages to retrieve [optional] |
420 |
ee84714ea665
|
since: |
421 |
ee84714ea665
|
Narrows the returned results to just those statuses created |
422 |
ee84714ea665
|
after the specified HTTP-formatted date. [optional] |
423 |
806829c7dc10
|
since_id: |
424 |
806829c7dc10
|
Returns only public statuses with an ID greater than (that is, |
425 |
806829c7dc10
|
more recent than) the specified ID. [Optional] |
426 |
4805aee792f0
|
|
427 |
4805aee792f0
|
Returns: |
428 |
c05ccbbb1d5c
|
A sequence of Status instances, one for each message up to count |
429 |
4805aee792f0
|
''' |
430 |
4805aee792f0
|
try: |
431 |
4805aee792f0
|
if count: |
432 |
4805aee792f0
|
int(count) |
433 |
4805aee792f0
|
except: |
434 |
4805aee792f0
|
raise TwitterError("Count must be an integer") |
435 |
ee84714ea665
|
parameters = {} |
436 |
4805aee792f0
|
if count: |
437 |
ee84714ea665
|
parameters['count'] = count |
438 |
ee84714ea665
|
if since: |
439 |
ee84714ea665
|
parameters['since'] = since |
440 |
806829c7dc10
|
if since_id: |
441 |
806829c7dc10
|
parameters['since_id'] = since_id |
442 |
ee84714ea665
|
if user: |
443 |
ee84714ea665
|
url = 'http://twitter.com/statuses/user_timeline/%s.json' % user |
444 |
ee84714ea665
|
elif not user and not self._username: |
445 |
ee84714ea665
|
raise TwitterError("User must be specified if API is not authenticated.") |
446 |
4805aee792f0
|
else: |
447 |
ee84714ea665
|
url = 'http://twitter.com/statuses/user_timeline.json' |
448 |
4805aee792f0
|
json = self._FetchUrl(url, parameters=parameters) |
449 |
4805aee792f0
|
data = simplejson.loads(json) |
450 |
265fcf2a1e01
|
self._CheckForTwitterError(data) |
451 |
c05ccbbb1d5c
|
return [NewStatusFromJsonDict(x) for x in data] |
452 |
32a0266e88a8
|
|
453 |
ee84714ea665
|
def GetStatus(self, id): |
454 |
ee84714ea665
|
'''Returns a single status message. |
455 |
4805aee792f0
|
|
456 |
ee84714ea665
|
The twitter.Api instance must be authenticated if the status message is private. |
457 |
4805aee792f0
|
|
458 |
4805aee792f0
|
Args: |
459 |
32a0266e88a8
|
id: The numerical ID of the status you're trying to retrieve. |
460 |
4805aee792f0
|
|
461 |
4805aee792f0
|
Returns: |
462 |
c05ccbbb1d5c
|
A Status instance representing that status message |
463 |
4805aee792f0
|
''' |
464 |
ee84714ea665
|
try: |
465 |
ee84714ea665
|
if id: |
466 |
ee84714ea665
|
int(id) |
467 |
ee84714ea665
|
except: |
468 |
ee84714ea665
|
raise TwitterError("id must be an integer") |
469 |
ee84714ea665
|
url = 'http://twitter.com/statuses/show/%s.json' % id |
470 |
ee84714ea665
|
json = self._FetchUrl(url) |
471 |
4805aee792f0
|
data = simplejson.loads(json) |
472 |
265fcf2a1e01
|
self._CheckForTwitterError(data) |
473 |
c05ccbbb1d5c
|
return NewStatusFromJsonDict(data) |
474 |
4805aee792f0
|
|
475 |
4da5ec4d173a
|
def DestroyStatus(self, id): |
476 |
4da5ec4d173a
|
'''Destroys the status specified by the required ID parameter. |
477 |
4da5ec4d173a
|
|
478 |
4da5ec4d173a
|
The twitter.Api instance must be authenticated and thee |
479 |
4da5ec4d173a
|
authenticating user must be the author of the specified status. |
480 |
4da5ec4d173a
|
|
481 |
4da5ec4d173a
|
Args: |
482 |
4da5ec4d173a
|
id: The numerical ID of the status you're trying to destroy. |
483 |
4da5ec4d173a
|
|
484 |
4da5ec4d173a
|
Returns: |
485 |
c05ccbbb1d5c
|
A Status instance representing the destroyed status message |
486 |
4da5ec4d173a
|
''' |
487 |
4da5ec4d173a
|
try: |
488 |
4da5ec4d173a
|
if id: |
489 |
4da5ec4d173a
|
int(id) |
490 |
4da5ec4d173a
|
except: |
491 |
4da5ec4d173a
|
raise TwitterError("id must be an integer") |
492 |
4da5ec4d173a
|
url = 'http://twitter.com/statuses/destroy/%s.json' % id |
493 |
4da5ec4d173a
|
json = self._FetchUrl(url, post_data={}) |
494 |
4da5ec4d173a
|
data = simplejson.loads(json) |
495 |
265fcf2a1e01
|
self._CheckForTwitterError(data) |
496 |
c05ccbbb1d5c
|
return NewStatusFromJsonDict(data) |
497 |
4da5ec4d173a
|
|
498 |
81cef10d2991
|
def PostUpdate(self, status, in_reply_to_status_id=None): |
499 |
c42880b017af
|
'''Post a twitter status message from the authenticated user. |
500 |
ee84714ea665
|
|
501 |
ee84714ea665
|
The twitter.Api instance must be authenticated. |
502 |
4805aee792f0
|
|
503 |
4805aee792f0
|
Args: |
504 |
81cef10d2991
|
status: |
505 |
81cef10d2991
|
The message text to be posted. Must be less than or equal to |
506 |
81cef10d2991
|
140 characters. |
507 |
71b5f9cae083
|
in_reply_to_status_id: |
508 |
71b5f9cae083
|
The ID of an existing status that the status to be posted is |
509 |
71b5f9cae083
|
in reply to. This implicitly sets the in_reply_to_user_id |
510 |
71b5f9cae083
|
attribute of the resulting status to the user ID of the |
511 |
71b5f9cae083
|
message being replied to. Invalid/missing status IDs will be |
512 |
71b5f9cae083
|
ignored. [Optional] |
513 |
4805aee792f0
|
Returns: |
514 |
c05ccbbb1d5c
|
A Status instance representing the message posted. |
515 |
4805aee792f0
|
''' |
516 |
ee84714ea665
|
if not self._username: |
517 |
ee84714ea665
|
raise TwitterError("The twitter.Api instance must be authenticated.") |
518 |
81cef10d2991
|
|
519 |
c42880b017af
|
url = 'http://twitter.com/statuses/update.json' |
520 |
81cef10d2991
|
|
521 |
81cef10d2991
|
if len(status) > CHARACTER_LIMIT: |
522 |
81cef10d2991
|
raise TwitterError("Text must be less than or equal to %d characters. " |
523 |
81cef10d2991
|
"Consider using PostUpdates." % CHARACTER_LIMIT) |
524 |
81cef10d2991
|
|
525 |
81cef10d2991
|
data = {'status': status} |
526 |
71b5f9cae083
|
if in_reply_to_status_id: |
527 |
71b5f9cae083
|
data['in_reply_to_status_id'] = in_reply_to_status_id |
528 |
79429eb1b54d
|
json = self._FetchUrl(url, post_data=data) |
529 |
4805aee792f0
|
data = simplejson.loads(json) |
530 |
265fcf2a1e01
|
self._CheckForTwitterError(data) |
531 |
c05ccbbb1d5c
|
return NewStatusFromJsonDict(data) |
532 |
4805aee792f0
|
|
533 |
8bdda4c8187b
|
def PostUpdates(self, status, continuation=None, **kwargs): |
534 |
81cef10d2991
|
'''Post one or more twitter status messages from the authenticated user. |
535 |
81cef10d2991
|
|
536 |
81cef10d2991
|
Unlike api.PostUpdate, this method will post multiple status updates |
537 |
81cef10d2991
|
if the message is longer than 140 characters. |
538 |
81cef10d2991
|
|
539 |
81cef10d2991
|
The twitter.Api instance must be authenticated. |
540 |
81cef10d2991
|
|
541 |
81cef10d2991
|
Args: |
542 |
81cef10d2991
|
status: |
543 |
81cef10d2991
|
The message text to be posted. May be longer than 140 characters. |
544 |
8bdda4c8187b
|
continuation: |
545 |
8bdda4c8187b
|
The character string, if any, to be appended to all but the |
546 |
8bdda4c8187b
|
last message. Note that Twitter strips trailing '...' strings |
547 |
8bdda4c8187b
|
from messages. Consider using the unicode \u2026 character |
548 |
8bdda4c8187b
|
(horizontal ellipsis) instead. [Defaults to None] |
549 |
81cef10d2991
|
**kwargs: |
550 |
81cef10d2991
|
See api.PostUpdate for a list of accepted parameters. |
551 |
81cef10d2991
|
Returns: |
552 |
c05ccbbb1d5c
|
A list of Status instances representing the messages posted. |
553 |
81cef10d2991
|
''' |
554 |
81cef10d2991
|
results = list() |
555 |
8bdda4c8187b
|
if continuation is None: |
556 |
8bdda4c8187b
|
continuation = '' |
557 |
8bdda4c8187b
|
line_length = CHARACTER_LIMIT - len(continuation) |
558 |
8bdda4c8187b
|
lines = textwrap.wrap(status, line_length) |
559 |
8bdda4c8187b
|
for line in lines[0:-1]: |
560 |
8bdda4c8187b
|
results.append(self.PostUpdate(line + continuation, **kwargs)) |
561 |
8bdda4c8187b
|
results.append(self.PostUpdate(lines[-1], **kwargs)) |
562 |
81cef10d2991
|
return results |
563 |
81cef10d2991
|
|
564 |
42a0465d458f
|
def GetReplies(self, since=None, since_id=None, page=None): |
565 |
ee84714ea665
|
'''Get a sequence of status messages representing the 20 most recent |
566 |
ee84714ea665
|
replies (status updates prefixed with @username) to the authenticating |
567 |
ee84714ea665
|
user. |
568 |
71b5f9cae083
|
|
569 |
a10f9a46dc3d
|
Args: |
570 |
42a0465d458f
|
page: |
571 |
a10f9a46dc3d
|
since: |
572 |
a10f9a46dc3d
|
Narrows the returned results to just those statuses created |
573 |
a10f9a46dc3d
|
after the specified HTTP-formatted date. [optional] |
574 |
a10f9a46dc3d
|
since_id: |
575 |
a10f9a46dc3d
|
Returns only public statuses with an ID greater than (that is, |
576 |
a10f9a46dc3d
|
more recent than) the specified ID. [Optional] |
577 |
ee84714ea665
|
|
578 |
ee84714ea665
|
Returns: |
579 |
c05ccbbb1d5c
|
A sequence of Status instances, one for each reply to the user. |
580 |
32a0266e88a8
|
''' |
581 |
ee84714ea665
|
url = 'http://twitter.com/statuses/replies.json' |
582 |
ee84714ea665
|
if not self._username: |
583 |
ee84714ea665
|
raise TwitterError("The twitter.Api instance must be authenticated.") |
584 |
a10f9a46dc3d
|
parameters = {} |
585 |
a10f9a46dc3d
|
if since: |
586 |
a10f9a46dc3d
|
parameters['since'] = since |
587 |
a10f9a46dc3d
|
if since_id: |
588 |
a10f9a46dc3d
|
parameters['since_id'] = since_id |
589 |
935cf080b638
|
if page: |
590 |
935cf080b638
|
parameters['page'] = page |
591 |
a10f9a46dc3d
|
json = self._FetchUrl(url, parameters=parameters) |
592 |
ee84714ea665
|
data = simplejson.loads(json) |
593 |
265fcf2a1e01
|
self._CheckForTwitterError(data) |
594 |
c05ccbbb1d5c
|
return [NewStatusFromJsonDict(x) for x in data] |
595 |
ee84714ea665
|
|
596 |
935cf080b638
|
def GetFriends(self, user=None, page=None): |
597 |
ee84714ea665
|
'''Fetch the sequence of twitter.User instances, one for each friend. |
598 |
ee84714ea665
|
|
599 |
ee84714ea665
|
Args: |
600 |
ee84714ea665
|
user: the username or id of the user whose friends you are fetching. If |
601 |
ee84714ea665
|
not specified, defaults to the authenticated user. [optional] |
602 |
ee84714ea665
|
|
603 |
ee84714ea665
|
The twitter.Api instance must be authenticated. |
604 |
ee84714ea665
|
|
605 |
ee84714ea665
|
Returns: |
606 |
ee84714ea665
|
A sequence of twitter.User instances, one for each friend |
607 |
ee84714ea665
|
''' |
608 |
ee84714ea665
|
if not self._username: |
609 |
ee84714ea665
|
raise TwitterError("twitter.Api instance must be authenticated") |
610 |
ee84714ea665
|
if user: |
611 |
42a0465d458f
|
url = 'http://twitter.com/statuses/friends/%s.json' % user |
612 |
32a0266e88a8
|
else: |
613 |
ee84714ea665
|
url = 'http://twitter.com/statuses/friends.json' |
614 |
935cf080b638
|
parameters = {} |
615 |
935cf080b638
|
if page: |
616 |
935cf080b638
|
parameters['page'] = page |
617 |
935cf080b638
|
json = self._FetchUrl(url, parameters=parameters) |
618 |
ee84714ea665
|
data = simplejson.loads(json) |
619 |
265fcf2a1e01
|
self._CheckForTwitterError(data) |
620 |
c05ccbbb1d5c
|
return [NewUserFromJsonDict(x) for x in data] |
621 |
ee84714ea665
|
|
622 |
935cf080b638
|
def GetFollowers(self, page=None): |
623 |
ee84714ea665
|
'''Fetch the sequence of twitter.User instances, one for each follower |
624 |
ee84714ea665
|
|
625 |
ee84714ea665
|
The twitter.Api instance must be authenticated. |
626 |
ee84714ea665
|
|
627 |
ee84714ea665
|
Returns: |
628 |
ee84714ea665
|
A sequence of twitter.User instances, one for each follower |
629 |
ee84714ea665
|
''' |
630 |
ee84714ea665
|
if not self._username: |
631 |
ee84714ea665
|
raise TwitterError("twitter.Api instance must be authenticated") |
632 |
ee84714ea665
|
url = 'http://twitter.com/statuses/followers.json' |
633 |
935cf080b638
|
parameters = {} |
634 |
935cf080b638
|
if page: |
635 |
935cf080b638
|
parameters['page'] = page |
636 |
935cf080b638
|
json = self._FetchUrl(url, parameters=parameters) |
637 |
ee84714ea665
|
data = simplejson.loads(json) |
638 |
265fcf2a1e01
|
self._CheckForTwitterError(data) |
639 |
c05ccbbb1d5c
|
return [NewUserFromJsonDict(x) for x in data] |
640 |
ee84714ea665
|
|
641 |
e3638dafb13c
|
def GetFeatured(self): |
642 |
e3638dafb13c
|
'''Fetch the sequence of twitter.User instances featured on twitter.com |
643 |
e3638dafb13c
|
|
644 |
e3638dafb13c
|
The twitter.Api instance must be authenticated. |
645 |
e3638dafb13c
|
|
646 |
e3638dafb13c
|
Returns: |
647 |
e3638dafb13c
|
A sequence of twitter.User instances |
648 |
e3638dafb13c
|
''' |
649 |
e3638dafb13c
|
url = 'http://twitter.com/statuses/featured.json' |
650 |
e3638dafb13c
|
json = self._FetchUrl(url) |
651 |
e3638dafb13c
|
data = simplejson.loads(json) |
652 |
265fcf2a1e01
|
self._CheckForTwitterError(data) |
653 |
c05ccbbb1d5c
|
return [NewUserFromJsonDict(x) for x in data] |
654 |
e3638dafb13c
|
|
655 |
ee84714ea665
|
def GetUser(self, user): |
656 |
ee84714ea665
|
'''Returns a single user. |
657 |
ee84714ea665
|
|
658 |
ee84714ea665
|
The twitter.Api instance must be authenticated. |
659 |
ee84714ea665
|
|
660 |
ee84714ea665
|
Args: |
661 |
ee84714ea665
|
user: The username or id of the user to retrieve. |
662 |
ee84714ea665
|
|
663 |
ee84714ea665
|
Returns: |
664 |
ee84714ea665
|
A twitter.User instance representing that user |
665 |
ee84714ea665
|
''' |
666 |
ee84714ea665
|
url = 'http://twitter.com/users/show/%s.json' % user |
667 |
ee84714ea665
|
json = self._FetchUrl(url) |
668 |
ee84714ea665
|
data = simplejson.loads(json) |
669 |
265fcf2a1e01
|
self._CheckForTwitterError(data) |
670 |
c05ccbbb1d5c
|
return NewUserFromJsonDict(data) |
671 |
ee84714ea665
|
|
672 |
935cf080b638
|
def GetDirectMessages(self, since=None, since_id=None, page=None): |
673 |
90377483d9fa
|
'''Returns a list of the direct messages sent to the authenticating user. |
674 |
90377483d9fa
|
|
675 |
90377483d9fa
|
The twitter.Api instance must be authenticated. |
676 |
90377483d9fa
|
|
677 |
32a0266e88a8
|
Args: |
678 |
90377483d9fa
|
since: |
679 |
90377483d9fa
|
Narrows the returned results to just those statuses created |
680 |
90377483d9fa
|
after the specified HTTP-formatted date. [optional] |
681 |
472ef0f2ea07
|
since_id: |
682 |
472ef0f2ea07
|
Returns only public statuses with an ID greater than (that is, |
683 |
472ef0f2ea07
|
more recent than) the specified ID. [Optional] |
684 |
90377483d9fa
|
|
685 |
90377483d9fa
|
Returns: |
686 |
32a0266e88a8
|
A sequence of twitter.DirectMessage instances |
687 |
90377483d9fa
|
''' |
688 |
90377483d9fa
|
url = 'http://twitter.com/direct_messages.json' |
689 |
90377483d9fa
|
if not self._username: |
690 |
90377483d9fa
|
raise TwitterError("The twitter.Api instance must be authenticated.") |
691 |
90377483d9fa
|
parameters = {} |
692 |
90377483d9fa
|
if since: |
693 |
90377483d9fa
|
parameters['since'] = since |
694 |
472ef0f2ea07
|
if since_id: |
695 |
472ef0f2ea07
|
parameters['since_id'] = since_id |
696 |
935cf080b638
|
if page: |
697 |
42a0465d458f
|
parameters['page'] = page |
698 |
90377483d9fa
|
json = self._FetchUrl(url, parameters=parameters) |
699 |
90377483d9fa
|
data = simplejson.loads(json) |
700 |
265fcf2a1e01
|
self._CheckForTwitterError(data) |
701 |
c05ccbbb1d5c
|
return [NewDirectMessageFromJsonDict(x) for x in data] |
702 |
32a0266e88a8
|
|
703 |
c42880b017af
|
def PostDirectMessage(self, user, text): |
704 |
c42880b017af
|
'''Post a twitter direct message from the authenticated user |
705 |
c42880b017af
|
|
706 |
c42880b017af
|
The twitter.Api instance must be authenticated. |
707 |
c42880b017af
|
|
708 |
c42880b017af
|
Args: |
709 |
c42880b017af
|
user: The ID or screen name of the recipient user. |
710 |
c42880b017af
|
text: The message text to be posted. Must be less than 140 characters. |
711 |
c42880b017af
|
|
712 |
c42880b017af
|
Returns: |
713 |
32a0266e88a8
|
A twitter.DirectMessage instance representing the message posted |
714 |
c42880b017af
|
''' |
715 |
c42880b017af
|
if not self._username: |
716 |
c42880b017af
|
raise TwitterError("The twitter.Api instance must be authenticated.") |
717 |
c42880b017af
|
url = 'http://twitter.com/direct_messages/new.json' |
718 |
c42880b017af
|
data = {'text': text, 'user': user} |
719 |
79429eb1b54d
|
json = self._FetchUrl(url, post_data=data) |
720 |
32a0266e88a8
|
data = simplejson.loads(json) |
721 |
265fcf2a1e01
|
self._CheckForTwitterError(data) |
722 |
c05ccbbb1d5c
|
return NewDirectMessageFromJsonDict(data) |
723 |
32a0266e88a8
|
|
724 |
32a0266e88a8
|
def DestroyDirectMessage(self, id): |
725 |
32a0266e88a8
|
'''Destroys the direct message specified in the required ID parameter. |
726 |
32a0266e88a8
|
|
727 |
32a0266e88a8
|
The twitter.Api instance must be authenticated, and the |
728 |
32a0266e88a8
|
authenticating user must be the recipient of the specified direct |
729 |
32a0266e88a8
|
message. |
730 |
32a0266e88a8
|
|
731 |
32a0266e88a8
|
Args: |
732 |
32a0266e88a8
|
id: The id of the direct message to be destroyed |
733 |
32a0266e88a8
|
|
734 |
32a0266e88a8
|
Returns: |
735 |
32a0266e88a8
|
A twitter.DirectMessage instance representing the message destroyed |
736 |
32a0266e88a8
|
''' |
737 |
32a0266e88a8
|
url = 'http://twitter.com/direct_messages/destroy/%s.json' % id |
738 |
79429eb1b54d
|
json = self._FetchUrl(url, post_data={}) |
739 |
c42880b017af
|
data = simplejson.loads(json) |
740 |
265fcf2a1e01
|
self._CheckForTwitterError(data) |
741 |
c05ccbbb1d5c
|
return NewDirectMessageFromJsonDict(data) |
742 |
90377483d9fa
|
|
743 |
79429eb1b54d
|
def CreateFriendship(self, user): |
744 |
79429eb1b54d
|
'''Befriends the user specified in the user parameter as the authenticating user. |
745 |
79429eb1b54d
|
|
746 |
79429eb1b54d
|
The twitter.Api instance must be authenticated. |
747 |
79429eb1b54d
|
|
748 |
79429eb1b54d
|
Args: |
749 |
79429eb1b54d
|
The ID or screen name of the user to befriend. |
750 |
79429eb1b54d
|
Returns: |
751 |
79429eb1b54d
|
A twitter.User instance representing the befriended user. |
752 |
79429eb1b54d
|
''' |
753 |
79429eb1b54d
|
url = 'http://twitter.com/friendships/create/%s.json' % user |
754 |
79429eb1b54d
|
json = self._FetchUrl(url, post_data={}) |
755 |
79429eb1b54d
|
data = simplejson.loads(json) |
756 |
265fcf2a1e01
|
self._CheckForTwitterError(data) |
757 |
c05ccbbb1d5c
|
return NewUserFromJsonDict(data) |
758 |
79429eb1b54d
|
|
759 |
79429eb1b54d
|
def DestroyFriendship(self, user): |
760 |
79429eb1b54d
|
'''Discontinues friendship with the user specified in the user parameter. |
761 |
79429eb1b54d
|
|
762 |
79429eb1b54d
|
The twitter.Api instance must be authenticated. |
763 |
79429eb1b54d
|
|
764 |
79429eb1b54d
|
Args: |
765 |
79429eb1b54d
|
The ID or screen name of the user with whom to discontinue friendship. |
766 |
79429eb1b54d
|
Returns: |
767 |
79429eb1b54d
|
A twitter.User instance representing the discontinued friend. |
768 |
79429eb1b54d
|
''' |
769 |
79429eb1b54d
|
url = 'http://twitter.com/friendships/destroy/%s.json' % user |
770 |
79429eb1b54d
|
json = self._FetchUrl(url, post_data={}) |
771 |
79429eb1b54d
|
data = simplejson.loads(json) |
772 |
265fcf2a1e01
|
self._CheckForTwitterError(data) |
773 |
c05ccbbb1d5c
|
return NewUserFromJsonDict(data) |
774 |
79429eb1b54d
|
|
775 |
a1e65b8c9435
|
def CreateFavorite(self, status): |
776 |
a1e65b8c9435
|
'''Favorites the status specified in the status parameter as the authenticating user. |
777 |
a1e65b8c9435
|
Returns the favorite status when successful. |
778 |
a1e65b8c9435
|
|
779 |
a1e65b8c9435
|
The twitter.Api instance must be authenticated. |
780 |
a1e65b8c9435
|
|
781 |
a1e65b8c9435
|
Args: |
782 |
c05ccbbb1d5c
|
The Status instance to mark as a favorite. |
783 |
a1e65b8c9435
|
Returns: |
784 |
c05ccbbb1d5c
|
A Status instance representing the newly-marked favorite. |
785 |
a1e65b8c9435
|
''' |
786 |
a1e65b8c9435
|
url = 'http://twitter.com/favorites/create/%s.json' % status.id |
787 |
a1e65b8c9435
|
json = self._FetchUrl(url, post_data={}) |
788 |
a1e65b8c9435
|
data = simplejson.loads(json) |
789 |
265fcf2a1e01
|
self._CheckForTwitterError(data) |
790 |
c05ccbbb1d5c
|
return NewStatusFromJsonDict(data) |
791 |
a1e65b8c9435
|
|
792 |
a1e65b8c9435
|
def DestroyFavorite(self, status): |
793 |
a1e65b8c9435
|
'''Un-favorites the status specified in the ID parameter as the authenticating user. |
794 |
a1e65b8c9435
|
Returns the un-favorited status in the requested format when successful. |
795 |
a1e65b8c9435
|
|
796 |
a1e65b8c9435
|
The twitter.Api instance must be authenticated. |
797 |
a1e65b8c9435
|
|
798 |
a1e65b8c9435
|
Args: |
799 |
c05ccbbb1d5c
|
The Status to unmark as a favorite. |
800 |
a1e65b8c9435
|
Returns: |
801 |
c05ccbbb1d5c
|
A Status instance representing the newly-unmarked favorite. |
802 |
a1e65b8c9435
|
''' |
803 |
a1e65b8c9435
|
url = 'http://twitter.com/favorites/destroy/%s.json' % status.id |
804 |
a1e65b8c9435
|
json = self._FetchUrl(url, post_data={}) |
805 |
a1e65b8c9435
|
data = simplejson.loads(json) |
806 |
265fcf2a1e01
|
self._CheckForTwitterError(data) |
807 |
c05ccbbb1d5c
|
return NewStatusFromJsonDict(data) |
808 |
a1e65b8c9435
|
|
809 |
e2f8ed587060
|
def GetUserByEmail(self, email): |
810 |
e2f8ed587060
|
'''Returns a single user by email address. |
811 |
e2f8ed587060
|
|
812 |
e2f8ed587060
|
Args: |
813 |
e2f8ed587060
|
email: The email of the user to retrieve. |
814 |
e2f8ed587060
|
Returns: |
815 |
e2f8ed587060
|
A twitter.User instance representing that user |
816 |
e2f8ed587060
|
''' |
817 |
e2f8ed587060
|
url = 'http://twitter.com/users/show.json?email=%s' % email |
818 |
e2f8ed587060
|
json = self._FetchUrl(url) |
819 |
e2f8ed587060
|
data = simplejson.loads(json) |
820 |
265fcf2a1e01
|
self._CheckForTwitterError(data) |
821 |
c05ccbbb1d5c
|
return NewUserFromJsonDict(data) |
822 |
e2f8ed587060
|
|
823 |
42a0465d458f
|
def Search(self, |
824 |
42a0465d458f
|
query, |
825 |
42a0465d458f
|
lang=None, |
826 |
42a0465d458f
|
rpp=None, |
827 |
42a0465d458f
|
page=None, |
828 |
42a0465d458f
|
since_id=None, |
829 |
42a0465d458f
|
geocode=None, |
830 |
19260f87c32a
|
show_user=None): |
831 |
19260f87c32a
|
'''Returns tweets that match a specified query. |
832 |
19260f87c32a
|
|
833 |
19260f87c32a
|
Args: |
834 |
19260f87c32a
|
query: The search query string, must be less than 140 characters |
835 |
42a0465d458f
|
lang: |
836 |
42a0465d458f
|
Restricts tweets to the given language, given by an ISO 639-1 |
837 |
19260f87c32a
|
code. [Optional] |
838 |
19260f87c32a
|
rpp: |
839 |
19260f87c32a
|
The number of tweets to return per page, up to a max of 100. [Optional] |
840 |
42a0465d458f
|
page: |
841 |
19260f87c32a
|
The page number (starting at 1) to return, up to a max of |
842 |
19260f87c32a
|
roughly 1500 results (based on rpp * page. Note: there are |
843 |
19260f87c32a
|
pagination limits. [Optional] |
844 |
42a0465d458f
|
since_id: |
845 |
19260f87c32a
|
Returns tweets with status ids greater than the given id. [Optional] |
846 |
42a0465d458f
|
geocode: |
847 |
19260f87c32a
|
Returns tweets by users located within a given radius of the |
848 |
19260f87c32a
|
given latitude/longitude, where the user's location is taken |
849 |
19260f87c32a
|
from their Twitter profile. The parameter value is specified |
850 |
19260f87c32a
|
by "latitide,longitude,radius", where radius units must be |
851 |
19260f87c32a
|
specified as either "mi" (miles) or "km" (kilometers). Note |
852 |
19260f87c32a
|
that you cannot use the near operator via the API to geocode |
853 |
19260f87c32a
|
arbitrary locations; however you can use this geocode |
854 |
19260f87c32a
|
parameter to search near geocodes directly. [Optional] |
855 |
19260f87c32a
|
show_user: |
856 |
19260f87c32a
|
When true, prepends "<user>:" to the beginning of the |
857 |
19260f87c32a
|
tweet. This is useful for readers that do not display Atom's |
858 |
19260f87c32a
|
author field. The default is false. [Optional] |
859 |
19260f87c32a
|
Returns: |
860 |
19260f87c32a
|
A Results instance representing the search results |
861 |
19260f87c32a
|
''' |
862 |
19260f87c32a
|
url = 'http://search.twitter.com/search.json' |
863 |
42a0465d458f
|
|
864 |
19260f87c32a
|
parameters = {'q': query} |
865 |
19260f87c32a
|
if len(query) > 140: |
866 |
19260f87c32a
|
raise TwitterError('query must be <= 140 characters') |
867 |
19260f87c32a
|
if lang: |
868 |
19260f87c32a
|
parameters['lang'] = lang |
869 |
19260f87c32a
|
if rpp is not None: |
870 |
19260f87c32a
|
try: |
871 |
19260f87c32a
|
if int(rpp) > 100: |
872 |
19260f87c32a
|
raise TwitterError("'rpp' may not be greater than 100") |
873 |
19260f87c32a
|
except ValueError: |
874 |
19260f87c32a
|
raise TwitterError("'rpp' must be an integer") |
875 |
19260f87c32a
|
parameters['rpp'] = rpp |
876 |
19260f87c32a
|
if page: |
877 |
19260f87c32a
|
parameters['page'] = page |
878 |
19260f87c32a
|
if since_id: |
879 |
19260f87c32a
|
parameters['since_id'] = since_id |
880 |
19260f87c32a
|
if geocode: |
881 |
19260f87c32a
|
parameters['geocode'] = geocode |
882 |
19260f87c32a
|
if show_user: |
883 |
19260f87c32a
|
parameters['show_user'] = show_user |
884 |
19260f87c32a
|
json = self._FetchUrl(url, parameters=parameters) |
885 |
19260f87c32a
|
data = simplejson.loads(json) |
886 |
19260f87c32a
|
self._CheckForTwitterError(data) |
887 |
19260f87c32a
|
return NewResultsFromJsonDict(data) |
888 |
19260f87c32a
|
|
889 |
ee84714ea665
|
def SetCredentials(self, username, password): |
890 |
ee84714ea665
|
'''Set the username and password for this instance |
891 |
ee84714ea665
|
|
892 |
ee84714ea665
|
Args: |
893 |
ee84714ea665
|
username: The twitter username. |
894 |
ee84714ea665
|
password: The twitter password. |
895 |
ee84714ea665
|
''' |
896 |
ee84714ea665
|
self._username = username |
897 |
ee84714ea665
|
self._password = password |
898 |
ee84714ea665
|
|
899 |
ee84714ea665
|
def ClearCredentials(self): |
900 |
ee84714ea665
|
'''Clear the username and password for this instance |
901 |
ee84714ea665
|
''' |
902 |
ee84714ea665
|
self._username = None |
903 |
ee84714ea665
|
self._password = None |
904 |
4805aee792f0
|
|
905 |
4805aee792f0
|
def SetCache(self, cache): |
906 |
4805aee792f0
|
'''Override the default cache. Set to None to prevent caching. |
907 |
4805aee792f0
|
|
908 |
4805aee792f0
|
Args: |
909 |
4805aee792f0
|
cache: an instance that supports the same API as the twitter._FileCache |
910 |
4805aee792f0
|
''' |
911 |
4805aee792f0
|
self._cache = cache |
912 |
4805aee792f0
|
|
913 |
4805aee792f0
|
def SetUrllib(self, urllib): |
914 |
4805aee792f0
|
'''Override the default urllib implementation. |
915 |
4805aee792f0
|
|
916 |
4805aee792f0
|
Args: |
917 |
4805aee792f0
|
urllib: an instance that supports the same API as the urllib2 module |
918 |
4805aee792f0
|
''' |
919 |
4805aee792f0
|
self._urllib = urllib |
920 |
4805aee792f0
|
|
921 |
4805aee792f0
|
def SetCacheTimeout(self, cache_timeout): |
922 |
4805aee792f0
|
'''Override the default cache timeout. |
923 |
4805aee792f0
|
|
924 |
4805aee792f0
|
Args: |
925 |
4805aee792f0
|
cache_timeout: time, in seconds, that responses should be reused. |
926 |
4805aee792f0
|
''' |
927 |
4805aee792f0
|
self._cache_timeout = cache_timeout |
928 |
4805aee792f0
|
|
929 |
4805aee792f0
|
def SetUserAgent(self, user_agent): |
930 |
4805aee792f0
|
'''Override the default user agent |
931 |
4805aee792f0
|
|
932 |
4805aee792f0
|
Args: |
933 |
4805aee792f0
|
user_agent: a string that should be send to the server as the User-agent |
934 |
4805aee792f0
|
''' |
935 |
90cd841771cf
|
self._request_headers['User-Agent'] = user_agent |
936 |
90cd841771cf
|
|
937 |
90cd841771cf
|
def SetXTwitterHeaders(self, client, url, version): |
938 |
90cd841771cf
|
'''Set the X-Twitter HTTP headers that will be sent to the server. |
939 |
90cd841771cf
|
|
940 |
90cd841771cf
|
Args: |
941 |
90cd841771cf
|
client: |
942 |
90cd841771cf
|
The client name as a string. Will be sent to the server as |
943 |
90cd841771cf
|
the 'X-Twitter-Client' header. |
944 |
90cd841771cf
|
url: |
945 |
90cd841771cf
|
The URL of the meta.xml as a string. Will be sent to the server |
946 |
90cd841771cf
|
as the 'X-Twitter-Client-URL' header. |
947 |
90cd841771cf
|
version: |
948 |
90cd841771cf
|
The client version as a string. Will be sent to the server |
949 |
90cd841771cf
|
as the 'X-Twitter-Client-Version' header. |
950 |
90cd841771cf
|
''' |
951 |
90cd841771cf
|
self._request_headers['X-Twitter-Client'] = client |
952 |
90cd841771cf
|
self._request_headers['X-Twitter-Client-URL'] = url |
953 |
90cd841771cf
|
self._request_headers['X-Twitter-Client-Version'] = version |
954 |
4805aee792f0
|
|
955 |
cb10f131e83f
|
def SetSource(self, source): |
956 |
cb10f131e83f
|
'''Suggest the "from source" value to be displayed on the Twitter web site. |
957 |
cb10f131e83f
|
|
958 |
cb10f131e83f
|
The value of the 'source' parameter must be first recognized by |
959 |
cb10f131e83f
|
the Twitter server. New source values are authorized on a case by |
960 |
cb10f131e83f
|
case basis by the Twitter development team. |
961 |
cb10f131e83f
|
|
962 |
cb10f131e83f
|
Args: |
963 |
cb10f131e83f
|
source: |
964 |
cb10f131e83f
|
The source name as a string. Will be sent to the server as |
965 |
cb10f131e83f
|
the 'source' parameter. |
966 |
cb10f131e83f
|
''' |
967 |
cb10f131e83f
|
self._default_params['source'] = source |
968 |
cb10f131e83f
|
|
969 |
4805aee792f0
|
def _BuildUrl(self, url, path_elements=None, extra_params=None): |
970 |
4805aee792f0
|
# Break url into consituent parts |
971 |
4805aee792f0
|
(scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url) |
972 |
17f566981b41
|
|
973 |
4805aee792f0
|
# Add any additional path elements to the path |
974 |
4805aee792f0
|
if path_elements: |
975 |
4805aee792f0
|
# Filter out the path elements that have a value of None |
976 |
4805aee792f0
|
p = [i for i in path_elements if i] |
977 |
4805aee792f0
|
if not path.endswith('/'): |
978 |
4805aee792f0
|
path += '/' |
979 |
4805aee792f0
|
path += '/'.join(p) |
980 |
17f566981b41
|
|
981 |
4805aee792f0
|
# Add any additional query parameters to the query string |
982 |
4805aee792f0
|
if extra_params and len(extra_params) > 0: |
983 |
32a0266e88a8
|
extra_query = self._EncodeParameters(extra_params) |
984 |
b0bbd9fa96ce
|
# Add it to the existing query |
985 |
b0bbd9fa96ce
|
if query: |
986 |
b0bbd9fa96ce
|
query += '&' + extra_query |
987 |
b0bbd9fa96ce
|
else: |
988 |
b0bbd9fa96ce
|
query = extra_query |
989 |
17f566981b41
|
|
990 |
4805aee792f0
|
# Return the rebuilt URL |
991 |
4805aee792f0
|
return urlparse.urlunparse((scheme, netloc, path, params, query, fragment)) |
992 |
4805aee792f0
|
|
993 |
90cd841771cf
|
def _InitializeRequestHeaders(self, request_headers): |
994 |
90cd841771cf
|
if request_headers: |
995 |
90cd841771cf
|
self._request_headers = request_headers |
996 |
90cd841771cf
|
else: |
997 |
90cd841771cf
|
self._request_headers = {} |
998 |
90cd841771cf
|
|
999 |
90cd841771cf
|
def _InitializeUserAgent(self): |
1000 |
90cd841771cf
|
user_agent = 'Python-urllib/%s (python-twitter/%s)' % \ |
1001 |
0ef37898cd2b
|
(self._urllib.__version__, __version__) |
1002 |
90cd841771cf
|
self.SetUserAgent(user_agent) |
1003 |
90cd841771cf
|
|
1004 |
cb10f131e83f
|
def _InitializeDefaultParameters(self): |
1005 |
cb10f131e83f
|
self._default_params = {} |
1006 |
cb10f131e83f
|
|
1007 |
f73c99cf9b51
|
def _AddAuthorizationHeader(self, username, password): |
1008 |
f73c99cf9b51
|
if username and password: |
1009 |
f73c99cf9b51
|
basic_auth = base64.encodestring('%s:%s' % (username, password))[:-1] |
1010 |
f73c99cf9b51
|
self._request_headers['Authorization'] = 'Basic %s' % basic_auth |
1011 |
f73c99cf9b51
|
|
1012 |
f73c99cf9b51
|
def _RemoveAuthorizationHeader(self): |
1013 |
f73c99cf9b51
|
if self._request_headers and 'Authorization' in self._request_headers: |
1014 |
f73c99cf9b51
|
del self._request_headers['Authorization'] |
1015 |
f73c99cf9b51
|
|
1016 |
4805aee792f0
|
def _GetOpener(self, url, username=None, password=None): |
1017 |
4805aee792f0
|
if username and password: |
1018 |
f73c99cf9b51
|
self._AddAuthorizationHeader(username, password) |
1019 |
4805aee792f0
|
handler = self._urllib.HTTPBasicAuthHandler() |
1020 |
4805aee792f0
|
(scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url) |
1021 |
4805aee792f0
|
handler.add_password(Api._API_REALM, netloc, username, password) |
1022 |
4805aee792f0
|
opener = self._urllib.build_opener(handler) |
1023 |
4805aee792f0
|
else: |
1024 |
4805aee792f0
|
opener = self._urllib.build_opener() |
1025 |
90cd841771cf
|
opener.addheaders = self._request_headers.items() |
1026 |
4805aee792f0
|
return opener |
1027 |
4805aee792f0
|
|
1028 |
4dfd36cb761d
|
def _Encode(self, s): |
1029 |
4dfd36cb761d
|
if self._input_encoding: |
1030 |
4dfd36cb761d
|
return unicode(s, self._input_encoding).encode('utf-8') |
1031 |
4dfd36cb761d
|
else: |
1032 |
4dfd36cb761d
|
return unicode(s).encode('utf-8') |
1033 |
4dfd36cb761d
|
|
1034 |
32a0266e88a8
|
def _EncodeParameters(self, parameters): |
1035 |
32a0266e88a8
|
'''Return a string in key=value&key=value form |
1036 |
32a0266e88a8
|
|
1037 |
32a0266e88a8
|
Values of None are not included in the output string. |
1038 |
32a0266e88a8
|
|
1039 |
32a0266e88a8
|
Args: |
1040 |
32a0266e88a8
|
parameters: |
1041 |
32a0266e88a8
|
A dict of (key, value) tuples, where value is encoded as |
1042 |
32a0266e88a8
|
specified by self._encoding |
1043 |
32a0266e88a8
|
Returns: |
1044 |
32a0266e88a8
|
A URL-encoded string in "key=value&key=value" form |
1045 |
32a0266e88a8
|
''' |
1046 |
32a0266e88a8
|
if parameters is None: |
1047 |
32a0266e88a8
|
return None |
1048 |
32a0266e88a8
|
else: |
1049 |
4dfd36cb761d
|
return urllib.urlencode(dict([(k, self._Encode(v)) for k, v in parameters.items() if v is not None])) |
1050 |
32a0266e88a8
|
|
1051 |
32a0266e88a8
|
def _EncodePostData(self, post_data): |
1052 |
32a0266e88a8
|
'''Return a string in key=value&key=value form |
1053 |
32a0266e88a8
|
|
1054 |
32a0266e88a8
|
Values are assumed to be encoded in the format specified by self._encoding, |
1055 |
32a0266e88a8
|
and are subsequently URL encoded. |
1056 |
32a0266e88a8
|
|
1057 |
32a0266e88a8
|
Args: |
1058 |
32a0266e88a8
|
post_data: |
1059 |
32a0266e88a8
|
A dict of (key, value) tuples, where value is encoded as |
1060 |
32a0266e88a8
|
specified by self._encoding |
1061 |
32a0266e88a8
|
Returns: |
1062 |
32a0266e88a8
|
A URL-encoded string in "key=value&key=value" form |
1063 |
32a0266e88a8
|
''' |
1064 |
32a0266e88a8
|
if post_data is None: |
1065 |
32a0266e88a8
|
return None |
1066 |
32a0266e88a8
|
else: |
1067 |
4dfd36cb761d
|
return urllib.urlencode(dict([(k, self._Encode(v)) for k, v in post_data.items()])) |
1068 |
32a0266e88a8
|
|
1069 |
265fcf2a1e01
|
def _CheckForTwitterError(self, data): |
1070 |
265fcf2a1e01
|
"""Raises a TwitterError if twitter returns an error message. |
1071 |
265fcf2a1e01
|
|
1072 |
265fcf2a1e01
|
Args: |
1073 |
265fcf2a1e01
|
data: A python dict created from the Twitter json response |
1074 |
265fcf2a1e01
|
Raises: |
1075 |
265fcf2a1e01
|
TwitterError wrapping the twitter error message if one exists. |
1076 |
265fcf2a1e01
|
""" |
1077 |
265fcf2a1e01
|
# Twitter errors are relatively unlikely, so it is faster |
1078 |
265fcf2a1e01
|
# to check first, rather than try and catch the exception |
1079 |
265fcf2a1e01
|
if 'error' in data: |
1080 |
265fcf2a1e01
|
raise TwitterError(data['error']) |
1081 |
265fcf2a1e01
|
|
1082 |
4805aee792f0
|
def _FetchUrl(self, |
1083 |
4805aee792f0
|
url, |
1084 |
32a0266e88a8
|
post_data=None, |
1085 |
4805aee792f0
|
parameters=None, |
1086 |
4805aee792f0
|
no_cache=None): |
1087 |
ed04d2ff26fc
|
'''Fetch a URL, optionally caching for a specified time. |
1088 |
4805aee792f0
|
|
1089 |
4805aee792f0
|
Args: |
1090 |
4805aee792f0
|
url: The URL to retrieve |
1091 |
42a0465d458f
|
post_data: |
1092 |
b02aa8558d4f
|
A dict of (str, unicode) key/value pairs. If set, POST will be used. |
1093 |
b02aa8558d4f
|
parameters: |
1094 |
42a0465d458f
|
A dict whose key/value pairs should encoded and added |
1095 |
b02aa8558d4f
|
to the query string. [OPTIONAL] |
1096 |
4805aee792f0
|
no_cache: If true, overrides the cache on the current request |
1097 |
4805aee792f0
|
|
1098 |
4805aee792f0
|
Returns: |
1099 |
4805aee792f0
|
A string containing the body of the response. |
1100 |
ed04d2ff26fc
|
''' |
1101 |
cb10f131e83f
|
# Build the extra parameters dict |
1102 |
cb10f131e83f
|
extra_params = {} |
1103 |
cb10f131e83f
|
if self._default_params: |
1104 |
cb10f131e83f
|
extra_params.update(self._default_params) |
1105 |
cb10f131e83f
|
if parameters: |
1106 |
cb10f131e83f
|
extra_params.update(parameters) |
1107 |
cb10f131e83f
|
|
1108 |
4805aee792f0
|
# Add key/value parameters to the query string of the url |
1109 |
cb10f131e83f
|
url = self._BuildUrl(url, extra_params=extra_params) |
1110 |
4805aee792f0
|
|
1111 |
4805aee792f0
|
# Get a url opener that can handle basic auth |
1112 |
ee84714ea665
|
opener = self._GetOpener(url, username=self._username, password=self._password) |
1113 |
4805aee792f0
|
|
1114 |
32a0266e88a8
|
encoded_post_data = self._EncodePostData(post_data) |
1115 |
c42880b017af
|
|
1116 |
4805aee792f0
|
# Open and return the URL immediately if we're not going to cache |
1117 |
32a0266e88a8
|
if encoded_post_data or no_cache or not self._cache or not self._cache_timeout: |
1118 |
32a0266e88a8
|
url_data = opener.open(url, encoded_post_data).read() |
1119 |
c1d12896a51f
|
opener.close() |
1120 |
4805aee792f0
|
else: |
1121 |
4805aee792f0
|
# Unique keys are a combination of the url and the username |
1122 |
ee84714ea665
|
if self._username: |
1123 |
ee84714ea665
|
key = self._username + ':' + url |
1124 |
4805aee792f0
|
else: |
1125 |
4805aee792f0
|
key = url |
1126 |
4805aee792f0
|
|
1127 |
4805aee792f0
|
# See if it has been cached before |
1128 |
4805aee792f0
|
last_cached = self._cache.GetCachedTime(key) |
1129 |
4805aee792f0
|
|
1130 |
4805aee792f0
|
# If the cached version is outdated then fetch another and store it |
1131 |
4805aee792f0
|
if not last_cached or time.time() >= last_cached + self._cache_timeout: |
1132 |
32a0266e88a8
|
url_data = opener.open(url, encoded_post_data).read() |
1133 |
c1d12896a51f
|
opener.close() |
1134 |
4805aee792f0
|
self._cache.Set(key, url_data) |
1135 |
4805aee792f0
|
else: |
1136 |
4805aee792f0
|
url_data = self._cache.Get(key) |
1137 |
17f566981b41
|
|
1138 |
4805aee792f0
|
# Always return the latest version |
1139 |
4805aee792f0
|
return url_data |
1140 |
4805aee792f0
|
|
1141 |
81cef10d2991
|
|
1142 |
4805aee792f0
|
class _FileCacheError(Exception): |
1143 |
4805aee792f0
|
'''Base exception class for FileCache related errors''' |
1144 |
4805aee792f0
|
|
1145 |
4805aee792f0
|
class _FileCache(object): |
1146 |
17f566981b41
|
|
1147 |
4805aee792f0
|
DEPTH = 3 |
1148 |
17f566981b41
|
|
1149 |
4805aee792f0
|
def __init__(self,root_directory=None): |
1150 |
4805aee792f0
|
self._InitializeRootDirectory(root_directory) |
1151 |
17f566981b41
|
|
1152 |
4805aee792f0
|
def Get(self,key): |
1153 |
4805aee792f0
|
path = self._GetPath(key) |
1154 |
4805aee792f0
|
if os.path.exists(path): |
1155 |
4805aee792f0
|
return open(path).read() |
1156 |
4805aee792f0
|
else: |
1157 |
4805aee792f0
|
return None |
1158 |
17f566981b41
|
|
1159 |
4805aee792f0
|
def Set(self,key,data): |
1160 |
4805aee792f0
|
path = self._GetPath(key) |
1161 |
4805aee792f0
|
directory = os.path.dirname(path) |
1162 |
4805aee792f0
|
if not os.path.exists(directory): |
1163 |
4805aee792f0
|
os.makedirs(directory) |
1164 |
4805aee792f0
|
if not os.path.isdir(directory): |
1165 |
4805aee792f0
|
raise _FileCacheError('%s exists but is not a directory' % directory) |
1166 |
4805aee792f0
|
temp_fd, temp_path = tempfile.mkstemp() |
1167 |
4805aee792f0
|
temp_fp = os.fdopen(temp_fd, 'w') |
1168 |
4805aee792f0
|
temp_fp.write(data) |
1169 |
4805aee792f0
|
temp_fp.close() |
1170 |
4805aee792f0
|
if not path.startswith(self._root_directory): |
1171 |
4805aee792f0
|
raise _FileCacheError('%s does not appear to live under %s' % |
1172 |
4805aee792f0
|
(path, self._root_directory)) |
1173 |
744e374963dc
|
if os.path.exists(path): |
1174 |
744e374963dc
|
os.remove(path) |
1175 |
4805aee792f0
|
os.rename(temp_path, path) |
1176 |
17f566981b41
|
|
1177 |
4805aee792f0
|
def Remove(self,key): |
1178 |
4805aee792f0
|
path = self._GetPath(key) |
1179 |
4805aee792f0
|
if not path.startswith(self._root_directory): |
1180 |
4805aee792f0
|
raise _FileCacheError('%s does not appear to live under %s' % |
1181 |
4805aee792f0
|
(path, self._root_directory )) |
1182 |
4805aee792f0
|
if os.path.exists(path): |
1183 |
17f566981b41
|
os.remove(path) |
1184 |
17f566981b41
|
|
1185 |
4805aee792f0
|
def GetCachedTime(self,key): |
1186 |
4805aee792f0
|
path = self._GetPath(key) |
1187 |
4805aee792f0
|
if os.path.exists(path): |
1188 |
4805aee792f0
|
return os.path.getmtime(path) |
1189 |
4805aee792f0
|
else: |
1190 |
4805aee792f0
|
return None |
1191 |
17f566981b41
|
|
1192 |
795f1c9d49c9
|
def _GetUsername(self): |
1193 |
3bef9042269d
|
'''Attempt to find the username in a cross-platform fashion.''' |
1194 |
d981d739f261
|
try: |
1195 |
d981d739f261
|
return os.getenv('USER') or \ |
1196 |
d981d739f261
|
os.getenv('LOGNAME') or \ |
1197 |
d981d739f261
|
os.getenv('USERNAME') or \ |
1198 |
d981d739f261
|
os.getlogin() or \ |
1199 |
d981d739f261
|
'nobody' |
1200 |
fc7763723e15
|
except (IOError, OSError), e: |
1201 |
d981d739f261
|
return 'nobody' |
1202 |
795f1c9d49c9
|
|
1203 |
795f1c9d49c9
|
def _GetTmpCachePath(self): |
1204 |
795f1c9d49c9
|
username = self._GetUsername() |
1205 |
795f1c9d49c9
|
cache_directory = 'python.cache_' + username |
1206 |
795f1c9d49c9
|
return os.path.join(tempfile.gettempdir(), cache_directory) |
1207 |
795f1c9d49c9
|
|
1208 |
4805aee792f0
|
def _InitializeRootDirectory(self, root_directory): |
1209 |
4805aee792f0
|
if not root_directory: |
1210 |
795f1c9d49c9
|
root_directory = self._GetTmpCachePath() |
1211 |
4805aee792f0
|
root_directory = os.path.abspath(root_directory) |
1212 |
4805aee792f0
|
if not os.path.exists(root_directory): |
1213 |
4805aee792f0
|
os.mkdir(root_directory) |
1214 |
4805aee792f0
|
if not os.path.isdir(root_directory): |
1215 |
4805aee792f0
|
raise _FileCacheError('%s exists but is not a directory' % |
1216 |
4805aee792f0
|
root_directory) |
1217 |
4805aee792f0
|
self._root_directory = root_directory |
1218 |
17f566981b41
|
|
1219 |
4805aee792f0
|
def _GetPath(self,key): |
1220 |
a58f4a7ad991
|
try: |
1221 |
a58f4a7ad991
|
hashed_key = md5(key).hexdigest() |
1222 |
a58f4a7ad991
|
except TypeError: |
1223 |
a58f4a7ad991
|
hashed_key = md5.new(key).hexdigest() |
1224 |
42a0465d458f
|
|
1225 |
4805aee792f0
|
return os.path.join(self._root_directory, |
1226 |
4805aee792f0
|
self._GetPrefix(hashed_key), |
1227 |
4805aee792f0
|
hashed_key) |
1228 |
17f566981b41
|
|
1229 |
4805aee792f0
|
def _GetPrefix(self,hashed_key): |
1230 |
4805aee792f0
|
return os.path.sep.join(hashed_key[0:_FileCache.DEPTH]) |