]> jfr.im git - z_archive/twitter.git/blob - twitter/api.py
__builtins__["raw_input"] throws a KeyError on Python 3.2.3, so adding it to the...
[z_archive/twitter.git] / twitter / api.py
1 try:
2 import urllib.request as urllib_request
3 import urllib.error as urllib_error
4 except ImportError:
5 import urllib2 as urllib_request
6 import urllib2 as urllib_error
7
8 from twitter.twitter_globals import POST_ACTIONS
9 from twitter.auth import NoAuth
10
11 import re
12
13 try:
14 import json
15 except ImportError:
16 import simplejson as json
17
18 class _DEFAULT(object):
19 pass
20
21 class TwitterError(Exception):
22 """
23 Base Exception thrown by the Twitter object when there is a
24 general error interacting with the API.
25 """
26 pass
27
28 class TwitterHTTPError(TwitterError):
29 """
30 Exception thrown by the Twitter object when there is an
31 HTTP error interacting with twitter.com.
32 """
33 def __init__(self, e, uri, format, uriparts):
34 self.e = e
35 self.uri = uri
36 self.format = format
37 self.uriparts = uriparts
38 self.response_data = self.e.fp.read()
39
40 def __str__(self):
41 fmt = ("." + self.format) if self.format else ""
42 return (
43 "Twitter sent status %i for URL: %s%s using parameters: "
44 "(%s)\ndetails: %s" %(
45 self.e.code, self.uri, fmt, self.uriparts,
46 self.response_data))
47
48 class TwitterResponse(object):
49 """
50 Response from a twitter request. Behaves like a list or a string
51 (depending on requested format) but it has a few other interesting
52 attributes.
53
54 `headers` gives you access to the response headers as an
55 httplib.HTTPHeaders instance. You can do
56 `response.headers.getheader('h')` to retrieve a header.
57 """
58 def __init__(self, headers):
59 self.headers = headers
60
61 @property
62 def rate_limit_remaining(self):
63 """
64 Remaining requests in the current rate-limit.
65 """
66 return int(self.headers.getheader('X-RateLimit-Remaining'))
67
68 @property
69 def rate_limit_reset(self):
70 """
71 Time in UTC epoch seconds when the rate limit will reset.
72 """
73 return int(self.headers.getheader('X-RateLimit-Reset'))
74
75
76 def wrap_response(response, headers):
77 response_typ = type(response)
78 if response_typ is bool:
79 # HURF DURF MY NAME IS PYTHON AND I CAN'T SUBCLASS bool.
80 response_typ = int
81
82 class WrappedTwitterResponse(response_typ, TwitterResponse):
83 __doc__ = TwitterResponse.__doc__
84
85 def __init__(self, response, headers):
86 response_typ.__init__(self, response)
87 TwitterResponse.__init__(self, headers)
88 def __new__(cls, response, headers):
89 return response_typ.__new__(cls, response)
90
91
92 return WrappedTwitterResponse(response, headers)
93
94
95
96 class TwitterCall(object):
97
98 def __init__(
99 self, auth, format, domain, callable_cls, uri="",
100 uriparts=None, secure=True):
101 self.auth = auth
102 self.format = format
103 self.domain = domain
104 self.callable_cls = callable_cls
105 self.uri = uri
106 self.uriparts = uriparts
107 self.secure = secure
108
109 def __getattr__(self, k):
110 try:
111 return object.__getattr__(self, k)
112 except AttributeError:
113 def extend_call(arg):
114 return self.callable_cls(
115 auth=self.auth, format=self.format, domain=self.domain,
116 callable_cls=self.callable_cls, uriparts=self.uriparts \
117 + (arg,),
118 secure=self.secure)
119 if k == "_":
120 return extend_call
121 else:
122 return extend_call(k)
123
124 def __call__(self, **kwargs):
125 # Build the uri.
126 uriparts = []
127 for uripart in self.uriparts:
128 # If this part matches a keyword argument, use the
129 # supplied value otherwise, just use the part.
130 uriparts.append(str(kwargs.pop(uripart, uripart)))
131 uri = '/'.join(uriparts)
132
133 method = kwargs.pop('_method', None)
134 if not method:
135 method = "GET"
136 for action in POST_ACTIONS:
137 if re.search("%s(/\d+)?$" % action, uri):
138 method = "POST"
139 break
140
141 # If an id kwarg is present and there is no id to fill in in
142 # the list of uriparts, assume the id goes at the end.
143 id = kwargs.pop('id', None)
144 if id:
145 uri += "/%s" %(id)
146
147 secure_str = ''
148 if self.secure:
149 secure_str = 's'
150 dot = ""
151 if self.format:
152 dot = "."
153 uriBase = "http%s://%s/%s%s%s" %(
154 secure_str, self.domain, uri, dot, self.format)
155
156 headers = {}
157 if self.auth:
158 headers.update(self.auth.generate_headers())
159 arg_data = self.auth.encode_params(uriBase, method, kwargs)
160 if method == 'GET':
161 uriBase += '?' + arg_data
162 body = None
163 else:
164 body = arg_data.encode('utf8')
165
166 req = urllib_request.Request(uriBase, body, headers)
167 return self._handle_response(req, uri, arg_data)
168
169 def _handle_response(self, req, uri, arg_data):
170 try:
171 handle = urllib_request.urlopen(req)
172 if "json" == self.format:
173 res = json.loads(handle.read().decode('utf8'))
174 return wrap_response(res, handle.headers)
175 else:
176 return wrap_response(
177 handle.read().decode('utf8'), handle.headers)
178 except urllib_error.HTTPError as e:
179 if (e.code == 304):
180 return []
181 else:
182 raise TwitterHTTPError(e, uri, self.format, arg_data)
183
184 class Twitter(TwitterCall):
185 """
186 The minimalist yet fully featured Twitter API class.
187
188 Get RESTful data by accessing members of this class. The result
189 is decoded python objects (lists and dicts).
190
191 The Twitter API is documented at:
192
193 http://dev.twitter.com/doc
194
195
196 Examples::
197
198 t = Twitter(
199 auth=OAuth(token, token_key, con_secret, con_secret_key)))
200
201 # Get the public timeline
202 t.statuses.public_timeline()
203
204 # Get a particular friend's timeline
205 t.statuses.friends_timeline(id="billybob")
206
207 # Also supported (but totally weird)
208 t.statuses.friends_timeline.billybob()
209
210 # Update your status
211 t.statuses.update(
212 status="Using @sixohsix's sweet Python Twitter Tools.")
213
214 # Send a direct message
215 t.direct_messages.new(
216 user="billybob",
217 text="I think yer swell!")
218
219 # Get the members of tamtar's list "Things That Are Rad"
220 t._("tamtar")._("things-that-are-rad").members()
221
222 # Note how the magic `_` method can be used to insert data
223 # into the middle of a call. You can also use replacement:
224 t.user.list.members(user="tamtar", list="things-that-are-rad")
225
226
227 Searching Twitter::
228
229 twitter_search = Twitter(domain="search.twitter.com")
230
231 # Find the latest search trends
232 twitter_search.trends()
233
234 # Search for the latest News on #gaza
235 twitter_search.search(q="#gaza")
236
237
238 Using the data returned
239 -----------------------
240
241 Twitter API calls return decoded JSON. This is converted into
242 a bunch of Python lists, dicts, ints, and strings. For example::
243
244 x = twitter.statuses.public_timeline()
245
246 # The first 'tweet' in the timeline
247 x[0]
248
249 # The screen name of the user who wrote the first 'tweet'
250 x[0]['user']['screen_name']
251
252
253 Getting raw XML data
254 --------------------
255
256 If you prefer to get your Twitter data in XML format, pass
257 format="xml" to the Twitter object when you instantiate it::
258
259 twitter = Twitter(format="xml")
260
261 The output will not be parsed in any way. It will be a raw string
262 of XML.
263
264 """
265 def __init__(
266 self, format="json",
267 domain="api.twitter.com", secure=True, auth=None,
268 api_version=_DEFAULT):
269 """
270 Create a new twitter API connector.
271
272 Pass an `auth` parameter to use the credentials of a specific
273 user. Generally you'll want to pass an `OAuth`
274 instance::
275
276 twitter = Twitter(auth=OAuth(
277 token, token_secret, consumer_key, consumer_secret))
278
279
280 `domain` lets you change the domain you are connecting. By
281 default it's `api.twitter.com` but `search.twitter.com` may be
282 useful too.
283
284 If `secure` is False you will connect with HTTP instead of
285 HTTPS.
286
287 `api_version` is used to set the base uri. By default it's
288 '1'. If you are using "search.twitter.com" set this to None.
289 """
290 if not auth:
291 auth = NoAuth()
292
293 if (format not in ("json", "xml", "")):
294 raise ValueError("Unknown data format '%s'" %(format))
295
296 if api_version is _DEFAULT:
297 if domain == 'api.twitter.com':
298 api_version = '1'
299 else:
300 api_version = None
301
302 uriparts = ()
303 if api_version:
304 uriparts += (str(api_version),)
305
306 TwitterCall.__init__(
307 self, auth=auth, format=format, domain=domain,
308 callable_cls=TwitterCall,
309 secure=secure, uriparts=uriparts)
310
311
312 __all__ = ["Twitter", "TwitterError", "TwitterHTTPError", "TwitterResponse"]