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 again

xitiomet / python-twitter (fork of saiyr / python-twitter)

Mostly the same, but i added support for Mentions http://apiwiki.twitter.com/Twitter-REST-API-Met...

Clone this repository (size: 393.3 KB): HTTPS / SSH
hg clone https://bitbucket.org/xitiomet/python-twitter
hg clone ssh://hg@bitbucket.org/xitiomet/python-twitter

python-twitter / twitter.py

commit
c05534ecbc50
parent
fa53aa14dce8
branch
dclinton

Implemented the VerifyCredentials api method

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