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