]> jfr.im git - z_archive/twitter.git/blob - twitter/api.py
Add a new response class that handles json dicts.
[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 TwitterJsonListResponse(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 class 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)
85
86 class TwitterXmlResponse(TwitterResponse, str):
87 __doc__ = """Twitter XML Response
88 """ + TwitterResponse.__doc__
89
90
91 class TwitterCall(object):
92 def __init__(
93 self, auth, format, domain, uri="", agent=None,
94 uriparts=None, secure=True):
95 self.auth = auth
96 self.format = format
97 self.domain = domain
98 self.uri = uri
99 self.agent = agent
100 self.uriparts = uriparts
101 self.secure = secure
102
103 def __getattr__(self, k):
104 try:
105 return object.__getattr__(self, k)
106 except AttributeError:
107 return TwitterCall(
108 auth=self.auth, format=self.format, domain=self.domain,
109 agent=self.agent, uriparts=self.uriparts + (k,),
110 secure=self.secure)
111
112 def __call__(self, **kwargs):
113 # Build the uri.
114 uriparts = []
115 for uripart in self.uriparts:
116 # If this part matches a keyword argument, use the
117 # supplied value otherwise, just use the part.
118 uriparts.append(unicode(kwargs.pop(uripart, uripart)))
119 uri = u'/'.join(uriparts)
120
121 method = "GET"
122 for action in POST_ACTIONS:
123 if uri.endswith(action):
124 method = "POST"
125 break
126
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.
129 id = kwargs.pop('id', None)
130 if id:
131 uri += "/%s" %(id)
132
133 secure_str = ''
134 if self.secure:
135 secure_str = 's'
136 dot = ""
137 if self.format:
138 dot = "."
139 uriBase = "http%s://%s/%s%s%s" %(
140 secure_str, self.domain, uri, dot, self.format)
141
142 headers = {}
143 if self.auth:
144 headers.update(self.auth.generate_headers())
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)
153
154 try:
155 handle = urllib2.urlopen(req)
156 if "json" == self.format:
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)
162 else:
163 r = TwitterXmlResponse(handle.read())
164 r.headers = handle.headers
165 return r
166 except urllib2.HTTPError, e:
167 if (e.code == 304):
168 return []
169 else:
170 raise TwitterHTTPError(e, uri, self.format, arg_data)
171
172 class Twitter(TwitterCall):
173 """
174 The minimalist yet fully featured Twitter API class.
175
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:
180
181 http://dev.twitter.com/doc
182
183
184 Examples::
185
186 twitter = Twitter(
187 auth=OAuth(token, token_key, con_secret, con_secret_key)))
188
189 # Get the public timeline
190 twitter.statuses.public_timeline()
191
192 # Get a particular friend's timeline
193 twitter.statuses.friends_timeline(id="billybob")
194
195 # Also supported (but totally weird)
196 twitter.statuses.friends_timeline.billybob()
197
198 # Send a direct message
199 twitter.direct_messages.new(
200 user="billybob",
201 text="I think yer swell!")
202
203 # Get the members of a particular list of a particular friend
204 twitter.user.listname.members(user="billybob", listname="billysbuds")
205
206
207 Searching Twitter::
208
209 twitter_search = Twitter(domain="search.twitter.com")
210
211 # Find the latest search trends
212 twitter_search.trends()
213
214 # Search for the latest News on #gaza
215 twitter_search.search(q="#gaza")
216
217
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::
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']
231
232
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::
238
239 twitter = Twitter(format="xml")
240
241 The output will not be parsed in any way. It will be a raw string
242 of XML.
243
244 """
245 def __init__(
246 self, format="json",
247 domain="twitter.com", secure=True, auth=None,
248 api_version=''):
249 """
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`
254 instance::
255
256 twitter = Twitter(auth=OAuth(
257 token, token_secret, consumer_key, consumer_secret))
258
259
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.
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/'.
275 """
276 if not auth:
277 auth = NoAuth()
278
279 if (format not in ("json", "xml", "")):
280 raise ValueError("Unknown data format '%s'" %(format))
281
282 uriparts = ()
283 if api_version:
284 uriparts += (str(api_version),)
285
286 TwitterCall.__init__(
287 self, auth=auth, format=format, domain=domain,
288 secure=secure, uriparts=uriparts)
289
290
291 __all__ = ["Twitter", "TwitterError", "TwitterHTTPError",
292 "TwitterJsonListResponse", "TwitterJsonDictResponse",
293 "TwitterXmlResponse"]