]> jfr.im git - z_archive/twitter.git/blob - twitter/api.py
Stupid bug when inheriting from str.
[z_archive/twitter.git] / twitter / api.py
1 import urllib2
2
3 from exceptions import Exception
4
5 from twitter.twitter_globals import POST_ACTIONS
6 from twitter.auth import NoAuth
7
8 def _py26OrGreater():
9 import sys
10 return sys.hexversion > 0x20600f0
11
12 if _py26OrGreater():
13 import json
14 else:
15 import simplejson as json
16
17 class TwitterError(Exception):
18 """
19 Base Exception thrown by the Twitter object when there is a
20 general error interacting with the API.
21 """
22 pass
23
24 class TwitterHTTPError(TwitterError):
25 """
26 Exception thrown by the Twitter object when there is an
27 HTTP error interacting with twitter.com.
28 """
29 def __init__(self, e, uri, format, uriparts):
30 self.e = e
31 self.uri = uri
32 self.format = format
33 self.uriparts = uriparts
34
35 def __str__(self):
36 return (
37 "Twitter sent status %i for URL: %s.%s using parameters: "
38 "(%s)\ndetails: %s" %(
39 self.e.code, self.uri, self.format, self.uriparts,
40 self.e.fp.read()))
41
42 class 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 """
52 def __init__(self, headers):
53 self.headers = headers
54
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
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.
73 class TwitterJsonResponse(TwitterResponse, list):
74 __doc__ = """Twitter JSON Response
75 """ + TwitterResponse.__doc__
76 def __init__(self, lst, headers):
77 TwitterResponse.__init__(self, headers)
78 list.__init__(self, lst)
79
80 class TwitterXmlResponse(TwitterResponse, str):
81 __doc__ = """Twitter XML Response
82 """ + TwitterResponse.__doc__
83
84
85 class TwitterCall(object):
86 def __init__(
87 self, auth, format, domain, uri="", agent=None,
88 uriparts=None, secure=True):
89 self.auth = auth
90 self.format = format
91 self.domain = domain
92 self.uri = uri
93 self.agent = agent
94 self.uriparts = uriparts
95 self.secure = secure
96
97 def __getattr__(self, k):
98 try:
99 return object.__getattr__(self, k)
100 except AttributeError:
101 return TwitterCall(
102 auth=self.auth, format=self.format, domain=self.domain,
103 agent=self.agent, uriparts=self.uriparts + (k,),
104 secure=self.secure)
105
106 def __call__(self, **kwargs):
107 # Build the uri.
108 uriparts = []
109 for uripart in self.uriparts:
110 # If this part matches a keyword argument, use the
111 # supplied value otherwise, just use the part.
112 uriparts.append(unicode(kwargs.pop(uripart, uripart)))
113 uri = u'/'.join(uriparts)
114
115 method = "GET"
116 for action in POST_ACTIONS:
117 if uri.endswith(action):
118 method = "POST"
119 break
120
121 # If an id kwarg is present and there is no id to fill in in
122 # the list of uriparts, assume the id goes at the end.
123 id = kwargs.pop('id', None)
124 if id:
125 uri += "/%s" %(id)
126
127 secure_str = ''
128 if self.secure:
129 secure_str = 's'
130 dot = ""
131 if self.format:
132 dot = "."
133 uriBase = "http%s://%s/%s%s%s" %(
134 secure_str, self.domain, uri, dot, self.format)
135
136 headers = {}
137 if self.auth:
138 headers.update(self.auth.generate_headers())
139 arg_data = self.auth.encode_params(uriBase, method, kwargs)
140 if method == 'GET':
141 uriBase += '?' + arg_data
142 body = None
143 else:
144 body = arg_data
145
146 req = urllib2.Request(uriBase, body, headers)
147
148 try:
149 handle = urllib2.urlopen(req)
150 if "json" == self.format:
151 return TwitterJsonResponse(json.loads(handle.read()),
152 handle.headers)
153 else:
154 r = TwitterXmlResponse(handle.read())
155 r.headers = handle.headers
156 return r
157 except urllib2.HTTPError, e:
158 if (e.code == 304):
159 return []
160 else:
161 raise TwitterHTTPError(e, uri, self.format, arg_data)
162
163 class Twitter(TwitterCall):
164 """
165 The minimalist yet fully featured Twitter API class.
166
167 Get RESTful data by accessing members of this class. The result
168 is decoded python objects (lists and dicts).
169
170 The Twitter API is documented here:
171
172 http://dev.twitter.com/doc
173
174
175 Examples::
176
177 twitter = Twitter(
178 auth=OAuth(token, token_key, con_secret, con_secret_key)))
179
180 # Get the public timeline
181 twitter.statuses.public_timeline()
182
183 # Get a particular friend's timeline
184 twitter.statuses.friends_timeline(id="billybob")
185
186 # Also supported (but totally weird)
187 twitter.statuses.friends_timeline.billybob()
188
189 # Send a direct message
190 twitter.direct_messages.new(
191 user="billybob",
192 text="I think yer swell!")
193
194 # Get the members of a particular list of a particular friend
195 twitter.user.listname.members(user="billybob", listname="billysbuds")
196
197
198 Searching Twitter::
199
200 twitter_search = Twitter(domain="search.twitter.com")
201
202 # Find the latest search trends
203 twitter_search.trends()
204
205 # Search for the latest News on #gaza
206 twitter_search.search(q="#gaza")
207
208
209 Using the data returned
210 -----------------------
211
212 Twitter API calls return decoded JSON. This is converted into
213 a bunch of Python lists, dicts, ints, and strings. For example::
214
215 x = twitter.statuses.public_timeline()
216
217 # The first 'tweet' in the timeline
218 x[0]
219
220 # The screen name of the user who wrote the first 'tweet'
221 x[0]['user']['screen_name']
222
223
224 Getting raw XML data
225 --------------------
226
227 If you prefer to get your Twitter data in XML format, pass
228 format="xml" to the Twitter object when you instantiate it::
229
230 twitter = Twitter(format="xml")
231
232 The output will not be parsed in any way. It will be a raw string
233 of XML.
234
235 """
236 def __init__(
237 self, format="json",
238 domain="twitter.com", secure=True, auth=None,
239 api_version=''):
240 """
241 Create a new twitter API connector.
242
243 Pass an `auth` parameter to use the credentials of a specific
244 user. Generally you'll want to pass an `OAuth`
245 instance::
246
247 twitter = Twitter(auth=OAuth(
248 token, token_secret, consumer_key, consumer_secret))
249
250
251 `domain` lets you change the domain you are connecting. By
252 default it's twitter.com but `search.twitter.com` may be
253 useful too.
254
255 If `secure` is False you will connect with HTTP instead of
256 HTTPS.
257
258 The value of `agent` is sent in the `X-Twitter-Client`
259 header. This is deprecated. Instead Twitter determines the
260 application using the OAuth Client Key and Client Key Secret
261 parameters.
262
263 `api_version` is used to set the base uri. By default it's
264 nothing, but if you set it to '1' your URI will start with
265 '1/'.
266 """
267 if not auth:
268 auth = NoAuth()
269
270 if (format not in ("json", "xml", "")):
271 raise ValueError("Unknown data format '%s'" %(format))
272
273 uriparts = ()
274 if api_version:
275 uriparts += (str(api_version),)
276
277 TwitterCall.__init__(
278 self, auth=auth, format=format, domain=domain,
279 secure=secure, uriparts=uriparts)
280
281
282 __all__ = ["Twitter", "TwitterError", "TwitterHTTPError", "TwitterJsonResponse",
283 "TwitterXmlResponse"]