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
22 import simplejson
as json
24 class _DEFAULT(object):
27 class TwitterError(Exception):
29 Base Exception thrown by the Twitter object when there is a
30 general error interacting with the API.
34 class TwitterHTTPError(TwitterError
):
36 Exception thrown by the Twitter object when there is an
37 HTTP error interacting with twitter.com.
39 def __init__(self
, e
, uri
, format
, uriparts
):
43 self
.uriparts
= uriparts
44 self
.response_data
= self
.e
.fp
.read()
47 fmt
= ("." + self
.format
) if self
.format
else ""
49 "Twitter sent status %i for URL: %s%s using parameters: "
50 "(%s)\ndetails: %s" %(
51 self
.e
.code
, self
.uri
, fmt
, self
.uriparts
,
54 class TwitterResponse(object):
56 Response from a twitter request. Behaves like a list or a string
57 (depending on requested format) but it has a few other interesting
60 `headers` gives you access to the response headers as an
61 httplib.HTTPHeaders instance. You can do
62 `response.headers.getheader('h')` to retrieve a header.
64 def __init__(self
, headers
):
65 self
.headers
= headers
68 def rate_limit_remaining(self
):
70 Remaining requests in the current rate-limit.
72 return int(self
.headers
.getheader('X-RateLimit-Remaining'))
75 def rate_limit_reset(self
):
77 Time in UTC epoch seconds when the rate limit will reset.
79 return int(self
.headers
.getheader('X-RateLimit-Reset'))
82 def wrap_response(response
, headers
):
83 response_typ
= type(response
)
84 if response_typ
is bool:
85 # HURF DURF MY NAME IS PYTHON AND I CAN'T SUBCLASS bool.
88 class WrappedTwitterResponse(response_typ
, TwitterResponse
):
89 __doc__
= TwitterResponse
.__doc
__
91 def __init__(self
, response
, headers
):
92 response_typ
.__init
__(self
, response
)
93 TwitterResponse
.__init
__(self
, headers
)
94 def __new__(cls
, response
, headers
):
95 return response_typ
.__new
__(cls
, response
)
98 return WrappedTwitterResponse(response
, headers
)
102 class TwitterCall(object):
105 self
, auth
, format
, domain
, callable_cls
, uri
="",
106 uriparts
=None, secure
=True):
110 self
.callable_cls
= callable_cls
112 self
.uriparts
= uriparts
115 def __getattr__(self
, k
):
117 return object.__getattr
__(self
, k
)
118 except AttributeError:
119 def extend_call(arg
):
120 return self
.callable_cls(
121 auth
=self
.auth
, format
=self
.format
, domain
=self
.domain
,
122 callable_cls
=self
.callable_cls
, uriparts
=self
.uriparts \
128 return extend_call(k
)
130 def __call__(self
, **kwargs
):
133 for uripart
in self
.uriparts
:
134 # If this part matches a keyword argument, use the
135 # supplied value otherwise, just use the part.
136 uriparts
.append(str(kwargs
.pop(uripart
, uripart
)))
137 uri
= '/'.join(uriparts
)
139 method
= kwargs
.pop('_method', None)
142 for action
in POST_ACTIONS
:
143 if re
.search("%s(/\d+)?$" % action
, uri
):
147 # If an id kwarg is present and there is no id to fill in in
148 # the list of uriparts, assume the id goes at the end.
149 id = kwargs
.pop('id', None)
159 uriBase
= "http%s://%s/%s%s%s" %(
160 secure_str
, self
.domain
, uri
, dot
, self
.format
)
162 headers
= {'Accept-Encoding': 'gzip'}
164 headers
.update(self
.auth
.generate_headers())
165 arg_data
= self
.auth
.encode_params(uriBase
, method
, kwargs
)
167 uriBase
+= '?' + arg_data
170 body
= arg_data
.encode('utf8')
172 req
= urllib_request
.Request(uriBase
, body
, headers
)
173 return self
._handle
_response
(req
, uri
, arg_data
)
175 def _handle_response(self
, req
, uri
, arg_data
):
177 handle
= urllib_request
.urlopen(req
)
178 if handle
.headers
['Content-Type'] in ['image/jpeg', 'image/png']:
180 elif handle
.info().get('Content-Encoding') == 'gzip':
181 # Handle gzip decompression
182 buf
= StringIO(handle
.read())
183 f
= gzip
.GzipFile(fileobj
=buf
)
188 if "json" == self
.format
:
189 res
= json
.loads(data
.decode('utf8'))
190 return wrap_response(res
, handle
.headers
)
192 return wrap_response(
193 data
.decode('utf8'), handle
.headers
)
194 except urllib_error
.HTTPError
as e
:
198 raise TwitterHTTPError(e
, uri
, self
.format
, arg_data
)
200 class Twitter(TwitterCall
):
202 The minimalist yet fully featured Twitter API class.
204 Get RESTful data by accessing members of this class. The result
205 is decoded python objects (lists and dicts).
207 The Twitter API is documented at:
209 http://dev.twitter.com/doc
215 auth=OAuth(token, token_key, con_secret, con_secret_key)))
217 # Get the public timeline
218 t.statuses.public_timeline()
220 # Get a particular friend's timeline
221 t.statuses.friends_timeline(id="billybob")
223 # Also supported (but totally weird)
224 t.statuses.friends_timeline.billybob()
228 status="Using @sixohsix's sweet Python Twitter Tools.")
230 # Send a direct message
231 t.direct_messages.new(
233 text="I think yer swell!")
235 # Get the members of tamtar's list "Things That Are Rad"
236 t._("tamtar")._("things-that-are-rad").members()
238 # Note how the magic `_` method can be used to insert data
239 # into the middle of a call. You can also use replacement:
240 t.user.list.members(user="tamtar", list="things-that-are-rad")
245 twitter_search = Twitter(domain="search.twitter.com")
247 # Find the latest search trends
248 twitter_search.trends()
250 # Search for the latest News on #gaza
251 twitter_search.search(q="#gaza")
254 Using the data returned
255 -----------------------
257 Twitter API calls return decoded JSON. This is converted into
258 a bunch of Python lists, dicts, ints, and strings. For example::
260 x = twitter.statuses.public_timeline()
262 # The first 'tweet' in the timeline
265 # The screen name of the user who wrote the first 'tweet'
266 x[0]['user']['screen_name']
272 If you prefer to get your Twitter data in XML format, pass
273 format="xml" to the Twitter object when you instantiate it::
275 twitter = Twitter(format="xml")
277 The output will not be parsed in any way. It will be a raw string
283 domain
="api.twitter.com", secure
=True, auth
=None,
284 api_version
=_DEFAULT
):
286 Create a new twitter API connector.
288 Pass an `auth` parameter to use the credentials of a specific
289 user. Generally you'll want to pass an `OAuth`
292 twitter = Twitter(auth=OAuth(
293 token, token_secret, consumer_key, consumer_secret))
296 `domain` lets you change the domain you are connecting. By
297 default it's `api.twitter.com` but `search.twitter.com` may be
300 If `secure` is False you will connect with HTTP instead of
303 `api_version` is used to set the base uri. By default it's
304 '1'. If you are using "search.twitter.com" set this to None.
309 if (format
not in ("json", "xml", "")):
310 raise ValueError("Unknown data format '%s'" %(format))
312 if api_version
is _DEFAULT
:
313 if domain
== 'api.twitter.com':
320 uriparts
+= (str(api_version
),)
322 TwitterCall
.__init
__(
323 self
, auth
=auth
, format
=format
, domain
=domain
,
324 callable_cls
=TwitterCall
,
325 secure
=secure
, uriparts
=uriparts
)
328 __all__
= ["Twitter", "TwitterError", "TwitterHTTPError", "TwitterResponse"]