]> jfr.im git - z_archive/twitter.git/blame - twitter/api.py
Use a generic wrap_response with dynamic class instead. This is kind of scary but...
[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
abddd419
MV
70def wrap_response(response, headers):
71 response_typ = type(response)
72 class WrappedTwitterResponse(TwitterResponse, response_typ):
73 __doc__ = TwitterResponse.__doc__
74
75 def __init__(self, response, headers):
76 response_typ.__init__(self, response)
77 TwitterResponse.__init__(self, headers)
78
79 return WrappedTwitterResponse(response, headers)
80
0d6c0646
MV
81
82
7364ea65 83class TwitterCall(object):
c8d451e8 84 def __init__(
568331a9 85 self, auth, format, domain, uri="", agent=None,
7e43e2ed 86 uriparts=None, secure=True):
568331a9 87 self.auth = auth
a55e6a11 88 self.format = format
153dee29 89 self.domain = domain
7364ea65 90 self.uri = uri
4a6070c8 91 self.agent = agent
b0dedfc0 92 self.uriparts = uriparts
9a148ed1 93 self.secure = secure
fd2bc885 94
7364ea65 95 def __getattr__(self, k):
96 try:
97 return object.__getattr__(self, k)
98 except AttributeError:
99 return TwitterCall(
1be4ce71
MV
100 auth=self.auth, format=self.format, domain=self.domain,
101 agent=self.agent, uriparts=self.uriparts + (k,),
102 secure=self.secure)
fd2bc885 103
7364ea65 104 def __call__(self, **kwargs):
aec68959 105 # Build the uri.
1be4ce71 106 uriparts = []
b0dedfc0 107 for uripart in self.uriparts:
aec68959
MV
108 # If this part matches a keyword argument, use the
109 # supplied value otherwise, just use the part.
aef72b31
MV
110 uriparts.append(unicode(kwargs.pop(uripart, uripart)))
111 uri = u'/'.join(uriparts)
1be4ce71 112
7364ea65 113 method = "GET"
612ececa 114 for action in POST_ACTIONS:
b0dedfc0 115 if uri.endswith(action):
2dab41b1
MV
116 method = "POST"
117 break
612ececa 118
aec68959
MV
119 # If an id kwarg is present and there is no id to fill in in
120 # the list of uriparts, assume the id goes at the end.
da45d039
MV
121 id = kwargs.pop('id', None)
122 if id:
123 uri += "/%s" %(id)
4e9d6343 124
568331a9
MH
125 secure_str = ''
126 if self.secure:
127 secure_str = 's'
6c527e72 128 dot = ""
1be4ce71 129 if self.format:
6c527e72
MV
130 dot = "."
131 uriBase = "http%s://%s/%s%s%s" %(
132 secure_str, self.domain, uri, dot, self.format)
568331a9 133
5b8b1ead 134 headers = {}
1be4ce71 135 if self.auth:
568331a9 136 headers.update(self.auth.generate_headers())
1be4ce71
MV
137 arg_data = self.auth.encode_params(uriBase, method, kwargs)
138 if method == 'GET':
139 uriBase += '?' + arg_data
140 body = None
141 else:
142 body = arg_data
143
144 req = urllib2.Request(uriBase, body, headers)
102acdb1 145
7364ea65 146 try:
102acdb1 147 handle = urllib2.urlopen(req)
de072195 148 if "json" == self.format:
452d1d12 149 res = json.loads(handle.read())
abddd419 150 return wrap_response(res, handle.headers)
de072195 151 else:
abddd419 152 return wrap_response(handle.read(), handle.headers)
de072195
HN
153 except urllib2.HTTPError, e:
154 if (e.code == 304):
7364ea65 155 return []
de072195 156 else:
aec68959 157 raise TwitterHTTPError(e, uri, self.format, arg_data)
102acdb1 158
7364ea65 159class Twitter(TwitterCall):
160 """
161 The minimalist yet fully featured Twitter API class.
4e9d6343 162
7364ea65 163 Get RESTful data by accessing members of this class. The result
164 is decoded python objects (lists and dicts).
165
166 The Twitter API is documented here:
153dee29 167
aec68959
MV
168 http://dev.twitter.com/doc
169
4e9d6343 170
7364ea65 171 Examples::
4e9d6343 172
69e1f98e
MV
173 twitter = Twitter(
174 auth=OAuth(token, token_key, con_secret, con_secret_key)))
4e9d6343 175
7364ea65 176 # Get the public timeline
177 twitter.statuses.public_timeline()
4e9d6343 178
7364ea65 179 # Get a particular friend's timeline
180 twitter.statuses.friends_timeline(id="billybob")
4e9d6343 181
7364ea65 182 # Also supported (but totally weird)
183 twitter.statuses.friends_timeline.billybob()
4e9d6343 184
7364ea65 185 # Send a direct message
186 twitter.direct_messages.new(
187 user="billybob",
188 text="I think yer swell!")
189
b0dedfc0
MV
190 # Get the members of a particular list of a particular friend
191 twitter.user.listname.members(user="billybob", listname="billysbuds")
192
69e1f98e 193
153dee29 194 Searching Twitter::
4e9d6343 195
0b486eda 196 twitter_search = Twitter(domain="search.twitter.com")
153dee29 197
0b486eda
HN
198 # Find the latest search trends
199 twitter_search.trends()
153dee29 200
0b486eda
HN
201 # Search for the latest News on #gaza
202 twitter_search.search(q="#gaza")
153dee29 203
7364ea65 204
68b3e2ee
MV
205 Using the data returned
206 -----------------------
207
208 Twitter API calls return decoded JSON. This is converted into
209 a bunch of Python lists, dicts, ints, and strings. For example::
7364ea65 210
211 x = twitter.statuses.public_timeline()
212
213 # The first 'tweet' in the timeline
214 x[0]
215
216 # The screen name of the user who wrote the first 'tweet'
217 x[0]['user']['screen_name']
4e9d6343 218
4e9d6343 219
68b3e2ee
MV
220 Getting raw XML data
221 --------------------
222
223 If you prefer to get your Twitter data in XML format, pass
224 format="xml" to the Twitter object when you instantiate it::
4e9d6343 225
a55e6a11 226 twitter = Twitter(format="xml")
4e9d6343 227
a55e6a11 228 The output will not be parsed in any way. It will be a raw string
229 of XML.
68b3e2ee 230
7364ea65 231 """
45688301 232 def __init__(
aec68959
MV
233 self, format="json",
234 domain="twitter.com", secure=True, auth=None,
1cc9ab0b 235 api_version=''):
7364ea65 236 """
68b3e2ee
MV
237 Create a new twitter API connector.
238
239 Pass an `auth` parameter to use the credentials of a specific
240 user. Generally you'll want to pass an `OAuth`
69e1f98e
MV
241 instance::
242
243 twitter = Twitter(auth=OAuth(
244 token, token_secret, consumer_key, consumer_secret))
245
246
68b3e2ee
MV
247 `domain` lets you change the domain you are connecting. By
248 default it's twitter.com but `search.twitter.com` may be
249 useful too.
250
251 If `secure` is False you will connect with HTTP instead of
252 HTTPS.
253
254 The value of `agent` is sent in the `X-Twitter-Client`
255 header. This is deprecated. Instead Twitter determines the
256 application using the OAuth Client Key and Client Key Secret
257 parameters.
1cc9ab0b
MV
258
259 `api_version` is used to set the base uri. By default it's
260 nothing, but if you set it to '1' your URI will start with
261 '1/'.
7364ea65 262 """
d20da7f3
MV
263 if not auth:
264 auth = NoAuth()
265
6c527e72 266 if (format not in ("json", "xml", "")):
68b3e2ee
MV
267 raise ValueError("Unknown data format '%s'" %(format))
268
1be4ce71 269 uriparts = ()
68b3e2ee 270 if api_version:
1be4ce71 271 uriparts += (str(api_version),)
68b3e2ee 272
9a148ed1 273 TwitterCall.__init__(
aec68959 274 self, auth=auth, format=format, domain=domain,
1be4ce71 275 secure=secure, uriparts=uriparts)
7e43e2ed 276
7364ea65 277
abddd419 278__all__ = ["Twitter", "TwitterError", "TwitterHTTPError", "TwitterResponse"]