]> jfr.im git - z_archive/twitter.git/blame_incremental - 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
1import urllib2
2
3from exceptions import Exception
4
5from twitter.twitter_globals import POST_ACTIONS
6from twitter.auth import NoAuth
7
8def _py26OrGreater():
9 import sys
10 return sys.hexversion > 0x20600f0
11
12if _py26OrGreater():
13 import json
14else:
15 import simplejson as json
16
17class 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
24class 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
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 """
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
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
81
82
83class TwitterCall(object):
84 def __init__(
85 self, auth, format, domain, uri="", agent=None,
86 uriparts=None, secure=True):
87 self.auth = auth
88 self.format = format
89 self.domain = domain
90 self.uri = uri
91 self.agent = agent
92 self.uriparts = uriparts
93 self.secure = secure
94
95 def __getattr__(self, k):
96 try:
97 return object.__getattr__(self, k)
98 except AttributeError:
99 return TwitterCall(
100 auth=self.auth, format=self.format, domain=self.domain,
101 agent=self.agent, uriparts=self.uriparts + (k,),
102 secure=self.secure)
103
104 def __call__(self, **kwargs):
105 # Build the uri.
106 uriparts = []
107 for uripart in self.uriparts:
108 # If this part matches a keyword argument, use the
109 # supplied value otherwise, just use the part.
110 uriparts.append(unicode(kwargs.pop(uripart, uripart)))
111 uri = u'/'.join(uriparts)
112
113 method = "GET"
114 for action in POST_ACTIONS:
115 if uri.endswith(action):
116 method = "POST"
117 break
118
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.
121 id = kwargs.pop('id', None)
122 if id:
123 uri += "/%s" %(id)
124
125 secure_str = ''
126 if self.secure:
127 secure_str = 's'
128 dot = ""
129 if self.format:
130 dot = "."
131 uriBase = "http%s://%s/%s%s%s" %(
132 secure_str, self.domain, uri, dot, self.format)
133
134 headers = {}
135 if self.auth:
136 headers.update(self.auth.generate_headers())
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)
145
146 try:
147 handle = urllib2.urlopen(req)
148 if "json" == self.format:
149 res = json.loads(handle.read())
150 return wrap_response(res, handle.headers)
151 else:
152 return wrap_response(handle.read(), handle.headers)
153 except urllib2.HTTPError, e:
154 if (e.code == 304):
155 return []
156 else:
157 raise TwitterHTTPError(e, uri, self.format, arg_data)
158
159class Twitter(TwitterCall):
160 """
161 The minimalist yet fully featured Twitter API class.
162
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:
167
168 http://dev.twitter.com/doc
169
170
171 Examples::
172
173 twitter = Twitter(
174 auth=OAuth(token, token_key, con_secret, con_secret_key)))
175
176 # Get the public timeline
177 twitter.statuses.public_timeline()
178
179 # Get a particular friend's timeline
180 twitter.statuses.friends_timeline(id="billybob")
181
182 # Also supported (but totally weird)
183 twitter.statuses.friends_timeline.billybob()
184
185 # Send a direct message
186 twitter.direct_messages.new(
187 user="billybob",
188 text="I think yer swell!")
189
190 # Get the members of a particular list of a particular friend
191 twitter.user.listname.members(user="billybob", listname="billysbuds")
192
193
194 Searching Twitter::
195
196 twitter_search = Twitter(domain="search.twitter.com")
197
198 # Find the latest search trends
199 twitter_search.trends()
200
201 # Search for the latest News on #gaza
202 twitter_search.search(q="#gaza")
203
204
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::
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']
218
219
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::
225
226 twitter = Twitter(format="xml")
227
228 The output will not be parsed in any way. It will be a raw string
229 of XML.
230
231 """
232 def __init__(
233 self, format="json",
234 domain="twitter.com", secure=True, auth=None,
235 api_version=''):
236 """
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`
241 instance::
242
243 twitter = Twitter(auth=OAuth(
244 token, token_secret, consumer_key, consumer_secret))
245
246
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.
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/'.
262 """
263 if not auth:
264 auth = NoAuth()
265
266 if (format not in ("json", "xml", "")):
267 raise ValueError("Unknown data format '%s'" %(format))
268
269 uriparts = ()
270 if api_version:
271 uriparts += (str(api_version),)
272
273 TwitterCall.__init__(
274 self, auth=auth, format=format, domain=domain,
275 secure=secure, uriparts=uriparts)
276
277
278__all__ = ["Twitter", "TwitterError", "TwitterHTTPError", "TwitterResponse"]