2 import urllib
.request
as urllib_request
3 import urllib
.error
as urllib_error
5 import urllib2
as urllib_request
6 import urllib2
as urllib_error
9 from cStringIO
import StringIO
11 from io
import BytesIO
as StringIO
13 from twitter
.twitter_globals
import POST_ACTIONS
14 from twitter
.auth
import NoAuth
20 import http
.client
as http_client
22 import httplib
as http_client
27 class _DEFAULT(object):
30 class TwitterError(Exception):
32 Base Exception thrown by the Twitter object when there is a
33 general error interacting with the API.
37 class TwitterHTTPError(TwitterError
):
39 Exception thrown by the Twitter object when there is an
40 HTTP error interacting with twitter.com.
42 def __init__(self
, e
, uri
, format
, uriparts
):
46 self
.uriparts
= uriparts
48 data
= self
.e
.fp
.read()
49 except http_client
.IncompleteRead
as e
:
50 # can't read the error text
51 # let's try some of it
53 if self
.e
.headers
.get('Content-Encoding') == 'gzip':
55 f
= gzip
.GzipFile(fileobj
=buf
)
56 self
.response_data
= f
.read()
58 self
.response_data
= data
61 fmt
= ("." + self
.format
) if self
.format
else ""
63 "Twitter sent status %i for URL: %s%s using parameters: "
64 "(%s)\ndetails: %s" %(
65 self
.e
.code
, self
.uri
, fmt
, self
.uriparts
,
68 class TwitterResponse(object):
70 Response from a twitter request. Behaves like a list or a string
71 (depending on requested format) but it has a few other interesting
74 `headers` gives you access to the response headers as an
75 httplib.HTTPHeaders instance. You can do
76 `response.headers.get('h')` to retrieve a header.
78 def __init__(self
, headers
):
79 self
.headers
= headers
82 def rate_limit_remaining(self
):
84 Remaining requests in the current rate-limit.
86 return int(self
.headers
.get('X-Rate-Limit-Remaining', "0"))
89 def rate_limit_limit(self
):
91 The rate limit ceiling for that given request.
93 return int(self
.headers
.get('X-Rate-Limit-Limit', "0"))
96 def rate_limit_reset(self
):
98 Time in UTC epoch seconds when the rate limit will reset.
100 return int(self
.headers
.get('X-Rate-Limit-Reset', "0"))
103 def wrap_response(response
, headers
):
104 response_typ
= type(response
)
105 if response_typ
is bool:
106 # HURF DURF MY NAME IS PYTHON AND I CAN'T SUBCLASS bool.
109 class WrappedTwitterResponse(response_typ
, TwitterResponse
):
110 __doc__
= TwitterResponse
.__doc
__
112 def __init__(self
, response
, headers
):
113 response_typ
.__init
__(self
, response
)
114 TwitterResponse
.__init
__(self
, headers
)
115 def __new__(cls
, response
, headers
):
116 return response_typ
.__new
__(cls
, response
)
119 return WrappedTwitterResponse(response
, headers
)
123 class TwitterCall(object):
126 self
, auth
, format
, domain
, callable_cls
, uri
="",
127 uriparts
=None, secure
=True):
131 self
.callable_cls
= callable_cls
133 self
.uriparts
= uriparts
136 def __getattr__(self
, k
):
138 return object.__getattr
__(self
, k
)
139 except AttributeError:
140 def extend_call(arg
):
141 return self
.callable_cls(
142 auth
=self
.auth
, format
=self
.format
, domain
=self
.domain
,
143 callable_cls
=self
.callable_cls
, uriparts
=self
.uriparts \
149 return extend_call(k
)
151 def __call__(self
, **kwargs
):
154 for uripart
in self
.uriparts
:
155 # If this part matches a keyword argument, use the
156 # supplied value otherwise, just use the part.
157 uriparts
.append(str(kwargs
.pop(uripart
, uripart
)))
158 uri
= '/'.join(uriparts
)
160 method
= kwargs
.pop('_method', None)
163 for action
in POST_ACTIONS
:
164 if re
.search("%s(/\d+)?$" % action
, uri
):
168 # If an id kwarg is present and there is no id to fill in in
169 # the list of uriparts, assume the id goes at the end.
170 id = kwargs
.pop('id', None)
174 # If an _id kwarg is present, this is treated as id as a CGI
176 _id
= kwargs
.pop('_id', None)
180 # If an _timeout is specified in kwargs, use it
181 _timeout
= kwargs
.pop('_timeout', None)
189 uriBase
= "http%s://%s/%s%s%s" %(
190 secure_str
, self
.domain
, uri
, dot
, self
.format
)
192 headers
= {'Accept-Encoding': 'gzip'}
194 headers
.update(self
.auth
.generate_headers())
195 arg_data
= self
.auth
.encode_params(uriBase
, method
, kwargs
)
197 uriBase
+= '?' + arg_data
200 body
= arg_data
.encode('utf8')
202 req
= urllib_request
.Request(uriBase
, body
, headers
)
203 return self
._handle
_response
(req
, uri
, arg_data
, _timeout
)
205 def _handle_response(self
, req
, uri
, arg_data
, _timeout
=None):
208 kwargs
['timeout'] = _timeout
210 handle
= urllib_request
.urlopen(req
, **kwargs
)
211 if handle
.headers
['Content-Type'] in ['image/jpeg', 'image/png']:
215 except http_client
.IncompleteRead
as e
:
216 # Even if we don't get all the bytes we should have there
217 # may be a complete response in e.partial
219 if handle
.info().get('Content-Encoding') == 'gzip':
220 # Handle gzip decompression
222 f
= gzip
.GzipFile(fileobj
=buf
)
224 if "json" == self
.format
:
225 res
= json
.loads(data
.decode('utf8'))
226 return wrap_response(res
, handle
.headers
)
228 return wrap_response(
229 data
.decode('utf8'), handle
.headers
)
230 except urllib_error
.HTTPError
as e
:
234 raise TwitterHTTPError(e
, uri
, self
.format
, arg_data
)
236 class Twitter(TwitterCall
):
238 The minimalist yet fully featured Twitter API class.
240 Get RESTful data by accessing members of this class. The result
241 is decoded python objects (lists and dicts).
243 The Twitter API is documented at:
245 http://dev.twitter.com/doc
251 auth=OAuth(token, token_key, con_secret, con_secret_key)))
253 # Get your "home" timeline
254 t.statuses.home_timeline()
256 # Get a particular friend's timeline
257 t.statuses.friends_timeline(id="billybob")
259 # Also supported (but totally weird)
260 t.statuses.friends_timeline.billybob()
264 status="Using @sixohsix's sweet Python Twitter Tools.")
266 # Send a direct message
267 t.direct_messages.new(
269 text="I think yer swell!")
271 # Get the members of tamtar's list "Things That Are Rad"
272 t._("tamtar")._("things-that-are-rad").members()
274 # Note how the magic `_` method can be used to insert data
275 # into the middle of a call. You can also use replacement:
276 t.user.list.members(user="tamtar", list="things-that-are-rad")
278 # An *optional* `_timeout` parameter can also be used for API
279 # calls which take much more time than normal or twitter stops
280 # responding for some reasone
282 screen_name=','.join(A_LIST_OF_100_SCREEN_NAMES), \
289 # Search for the latest tweets about #pycon
290 t.search.tweets(q="#pycon")
293 Using the data returned
294 -----------------------
296 Twitter API calls return decoded JSON. This is converted into
297 a bunch of Python lists, dicts, ints, and strings. For example::
299 x = twitter.statuses.home_timeline()
301 # The first 'tweet' in the timeline
304 # The screen name of the user who wrote the first 'tweet'
305 x[0]['user']['screen_name']
311 If you prefer to get your Twitter data in XML format, pass
312 format="xml" to the Twitter object when you instantiate it::
314 twitter = Twitter(format="xml")
316 The output will not be parsed in any way. It will be a raw string
322 domain
="api.twitter.com", secure
=True, auth
=None,
323 api_version
=_DEFAULT
):
325 Create a new twitter API connector.
327 Pass an `auth` parameter to use the credentials of a specific
328 user. Generally you'll want to pass an `OAuth`
331 twitter = Twitter(auth=OAuth(
332 token, token_secret, consumer_key, consumer_secret))
335 `domain` lets you change the domain you are connecting. By
336 default it's `api.twitter.com` but `search.twitter.com` may be
339 If `secure` is False you will connect with HTTP instead of
342 `api_version` is used to set the base uri. By default it's
343 '1'. If you are using "search.twitter.com" set this to None.
348 if (format
not in ("json", "xml", "")):
349 raise ValueError("Unknown data format '%s'" %(format))
351 if api_version
is _DEFAULT
:
356 uriparts
+= (str(api_version
),)
358 TwitterCall
.__init
__(
359 self
, auth
=auth
, format
=format
, domain
=domain
,
360 callable_cls
=TwitterCall
,
361 secure
=secure
, uriparts
=uriparts
)
364 __all__
= ["Twitter", "TwitterError", "TwitterHTTPError", "TwitterResponse"]