]> jfr.im git - z_archive/twitter.git/blame - twitter/api.py
Always show the URL during the oauth dance.
[z_archive/twitter.git] / twitter / api.py
CommitLineData
de072195 1import urllib2
7364ea65 2
5251ea48 3from exceptions import Exception
4
612ececa 5from twitter.twitter_globals import POST_ACTIONS
aec68959 6from twitter.auth import NoAuth
4e9d6343 7
f1a8ed67 8def _py26OrGreater():
9 import sys
10 return sys.hexversion > 0x20600f0
11
12if _py26OrGreater():
13 import json
14else:
15 import simplejson as json
16
5251ea48 17class TwitterError(Exception):
21e3bd23 18 """
64a8d213
B
19 Base Exception thrown by the Twitter object when there is a
20 general error interacting with the API.
21e3bd23 21 """
5251ea48 22 pass
23
64a8d213
B
24class TwitterHTTPError(TwitterError):
25 """
26 Exception thrown by the Twitter object when there is an
27 HTTP error interacting with twitter.com.
28 """
1be4ce71 29 def __init__(self, e, uri, format, uriparts):
64a8d213
B
30 self.e = e
31 self.uri = uri
32 self.format = format
1be4ce71 33 self.uriparts = uriparts
64a8d213
B
34
35 def __str__(self):
68b3e2ee
MV
36 return (
37 "Twitter sent status %i for URL: %s.%s using parameters: "
38 "(%s)\ndetails: %s" %(
1be4ce71 39 self.e.code, self.uri, self.format, self.uriparts,
68b3e2ee 40 self.e.fp.read()))
64a8d213 41
84d0a294
MV
42class TwitterResponse(object):
43 """
44 Response from a twitter request. Behaves like a list or a string
45 (depending on requested format) but it has a few other interesting
46 attributes.
47
48 `headers` gives you access to the response headers as an
49 httplib.HTTPHeaders instance. You can do
50 `response.headers.getheader('h')` to retrieve a header.
51 """
aef72b31 52 def __init__(self, headers):
84d0a294
MV
53 self.headers = headers
54
84d0a294
MV
55 @property
56 def rate_limit_remaining(self):
57 """
58 Remaining requests in the current rate-limit.
59 """
60 return int(self.headers.getheader('X-RateLimit-Remaining'))
61
62 @property
63 def rate_limit_reset(self):
64 """
65 Time in UTC epoch seconds when the rate limit will reset.
66 """
67 return int(self.headers.getheader('X-RateLimit-Reset'))
68
69
0d6c0646
MV
70# Multiple inheritance makes my inner Java nerd cry. Why can't I just
71# add arbitrary attributes to list or str objects?! Guido, we need to
72# talk.
452d1d12 73class TwitterJsonListResponse(TwitterResponse, list):
0d6c0646
MV
74 __doc__ = """Twitter JSON Response
75 """ + TwitterResponse.__doc__
aef72b31
MV
76 def __init__(self, lst, headers):
77 TwitterResponse.__init__(self, headers)
78 list.__init__(self, lst)
452d1d12
MV
79class TwitterJsonDictResponse(TwitterResponse, dict):
80 __doc__ = """Twitter JSON Response
81 """ + TwitterResponse.__doc__
82 def __init__(self, d, headers):
83 TwitterResponse.__init__(self, headers)
84 dict.__init__(self, d)
aef72b31
MV
85
86class TwitterXmlResponse(TwitterResponse, str):
0d6c0646
MV
87 __doc__ = """Twitter XML Response
88 """ + TwitterResponse.__doc__
89
90
7364ea65 91class TwitterCall(object):
c8d451e8 92 def __init__(
568331a9 93 self, auth, format, domain, uri="", agent=None,
7e43e2ed 94 uriparts=None, secure=True):
568331a9 95 self.auth = auth
a55e6a11 96 self.format = format
153dee29 97 self.domain = domain
7364ea65 98 self.uri = uri
4a6070c8 99 self.agent = agent
b0dedfc0 100 self.uriparts = uriparts
9a148ed1 101 self.secure = secure
fd2bc885 102
7364ea65 103 def __getattr__(self, k):
104 try:
105 return object.__getattr__(self, k)
106 except AttributeError:
107 return TwitterCall(
1be4ce71
MV
108 auth=self.auth, format=self.format, domain=self.domain,
109 agent=self.agent, uriparts=self.uriparts + (k,),
110 secure=self.secure)
fd2bc885 111
7364ea65 112 def __call__(self, **kwargs):
aec68959 113 # Build the uri.
1be4ce71 114 uriparts = []
b0dedfc0 115 for uripart in self.uriparts:
aec68959
MV
116 # If this part matches a keyword argument, use the
117 # supplied value otherwise, just use the part.
aef72b31
MV
118 uriparts.append(unicode(kwargs.pop(uripart, uripart)))
119 uri = u'/'.join(uriparts)
1be4ce71 120
7364ea65 121 method = "GET"
612ececa 122 for action in POST_ACTIONS:
b0dedfc0 123 if uri.endswith(action):
2dab41b1
MV
124 method = "POST"
125 break
612ececa 126
aec68959
MV
127 # If an id kwarg is present and there is no id to fill in in
128 # the list of uriparts, assume the id goes at the end.
da45d039
MV
129 id = kwargs.pop('id', None)
130 if id:
131 uri += "/%s" %(id)
4e9d6343 132
568331a9
MH
133 secure_str = ''
134 if self.secure:
135 secure_str = 's'
6c527e72 136 dot = ""
1be4ce71 137 if self.format:
6c527e72
MV
138 dot = "."
139 uriBase = "http%s://%s/%s%s%s" %(
140 secure_str, self.domain, uri, dot, self.format)
568331a9 141
5b8b1ead 142 headers = {}
1be4ce71 143 if self.auth:
568331a9 144 headers.update(self.auth.generate_headers())
1be4ce71
MV
145 arg_data = self.auth.encode_params(uriBase, method, kwargs)
146 if method == 'GET':
147 uriBase += '?' + arg_data
148 body = None
149 else:
150 body = arg_data
151
152 req = urllib2.Request(uriBase, body, headers)
102acdb1 153
7364ea65 154 try:
102acdb1 155 handle = urllib2.urlopen(req)
de072195 156 if "json" == self.format:
452d1d12
MV
157 res = json.loads(handle.read())
158 response_cls = (
159 TwitterJsonListResponse if type(res) is list
160 else TwitterJsonDictResponse)
161 return response_cls(res, handle.headers)
de072195 162 else:
aef72b31
MV
163 r = TwitterXmlResponse(handle.read())
164 r.headers = handle.headers
165 return r
de072195
HN
166 except urllib2.HTTPError, e:
167 if (e.code == 304):
7364ea65 168 return []
de072195 169 else:
aec68959 170 raise TwitterHTTPError(e, uri, self.format, arg_data)
102acdb1 171
7364ea65 172class Twitter(TwitterCall):
173 """
174 The minimalist yet fully featured Twitter API class.
4e9d6343 175
7364ea65 176 Get RESTful data by accessing members of this class. The result
177 is decoded python objects (lists and dicts).
178
179 The Twitter API is documented here:
153dee29 180
aec68959
MV
181 http://dev.twitter.com/doc
182
4e9d6343 183
7364ea65 184 Examples::
4e9d6343 185
69e1f98e
MV
186 twitter = Twitter(
187 auth=OAuth(token, token_key, con_secret, con_secret_key)))
4e9d6343 188
7364ea65 189 # Get the public timeline
190 twitter.statuses.public_timeline()
4e9d6343 191
7364ea65 192 # Get a particular friend's timeline
193 twitter.statuses.friends_timeline(id="billybob")
4e9d6343 194
7364ea65 195 # Also supported (but totally weird)
196 twitter.statuses.friends_timeline.billybob()
4e9d6343 197
7364ea65 198 # Send a direct message
199 twitter.direct_messages.new(
200 user="billybob",
201 text="I think yer swell!")
202
b0dedfc0
MV
203 # Get the members of a particular list of a particular friend
204 twitter.user.listname.members(user="billybob", listname="billysbuds")
205
69e1f98e 206
153dee29 207 Searching Twitter::
4e9d6343 208
0b486eda 209 twitter_search = Twitter(domain="search.twitter.com")
153dee29 210
0b486eda
HN
211 # Find the latest search trends
212 twitter_search.trends()
153dee29 213
0b486eda
HN
214 # Search for the latest News on #gaza
215 twitter_search.search(q="#gaza")
153dee29 216
7364ea65 217
68b3e2ee
MV
218 Using the data returned
219 -----------------------
220
221 Twitter API calls return decoded JSON. This is converted into
222 a bunch of Python lists, dicts, ints, and strings. For example::
7364ea65 223
224 x = twitter.statuses.public_timeline()
225
226 # The first 'tweet' in the timeline
227 x[0]
228
229 # The screen name of the user who wrote the first 'tweet'
230 x[0]['user']['screen_name']
4e9d6343 231
4e9d6343 232
68b3e2ee
MV
233 Getting raw XML data
234 --------------------
235
236 If you prefer to get your Twitter data in XML format, pass
237 format="xml" to the Twitter object when you instantiate it::
4e9d6343 238
a55e6a11 239 twitter = Twitter(format="xml")
4e9d6343 240
a55e6a11 241 The output will not be parsed in any way. It will be a raw string
242 of XML.
68b3e2ee 243
7364ea65 244 """
45688301 245 def __init__(
aec68959
MV
246 self, format="json",
247 domain="twitter.com", secure=True, auth=None,
1cc9ab0b 248 api_version=''):
7364ea65 249 """
68b3e2ee
MV
250 Create a new twitter API connector.
251
252 Pass an `auth` parameter to use the credentials of a specific
253 user. Generally you'll want to pass an `OAuth`
69e1f98e
MV
254 instance::
255
256 twitter = Twitter(auth=OAuth(
257 token, token_secret, consumer_key, consumer_secret))
258
259
68b3e2ee
MV
260 `domain` lets you change the domain you are connecting. By
261 default it's twitter.com but `search.twitter.com` may be
262 useful too.
263
264 If `secure` is False you will connect with HTTP instead of
265 HTTPS.
266
267 The value of `agent` is sent in the `X-Twitter-Client`
268 header. This is deprecated. Instead Twitter determines the
269 application using the OAuth Client Key and Client Key Secret
270 parameters.
1cc9ab0b
MV
271
272 `api_version` is used to set the base uri. By default it's
273 nothing, but if you set it to '1' your URI will start with
274 '1/'.
7364ea65 275 """
d20da7f3
MV
276 if not auth:
277 auth = NoAuth()
278
6c527e72 279 if (format not in ("json", "xml", "")):
68b3e2ee
MV
280 raise ValueError("Unknown data format '%s'" %(format))
281
1be4ce71 282 uriparts = ()
68b3e2ee 283 if api_version:
1be4ce71 284 uriparts += (str(api_version),)
68b3e2ee 285
9a148ed1 286 TwitterCall.__init__(
aec68959 287 self, auth=auth, format=format, domain=domain,
1be4ce71 288 secure=secure, uriparts=uriparts)
7e43e2ed 289
7364ea65 290
452d1d12
MV
291__all__ = ["Twitter", "TwitterError", "TwitterHTTPError",
292 "TwitterJsonListResponse", "TwitterJsonDictResponse",
0d6c0646 293 "TwitterXmlResponse"]