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 if self
.e
.headers
['Content-Encoding'] == 'gzip':
45 buf
= StringIO(self
.e
.fp
.read())
46 f
= gzip
.GzipFile(fileobj
=buf
)
47 self
.response_data
= f
.read()
49 self
.response_data
= self
.e
.fp
.read()
52 fmt
= ("." + self
.format
) if self
.format
else ""
54 "Twitter sent status %i for URL: %s%s using parameters: "
55 "(%s)\ndetails: %s" %(
56 self
.e
.code
, self
.uri
, fmt
, self
.uriparts
,
59 class TwitterResponse(object):
61 Response from a twitter request. Behaves like a list or a string
62 (depending on requested format) but it has a few other interesting
65 `headers` gives you access to the response headers as an
66 httplib.HTTPHeaders instance. You can do
67 `response.headers.get('h')` to retrieve a header.
69 def __init__(self
, headers
):
70 self
.headers
= headers
73 def rate_limit_remaining(self
):
75 Remaining requests in the current rate-limit.
77 return int(self
.headers
.get('X-RateLimit-Remaining', "0"))
80 def rate_limit_reset(self
):
82 Time in UTC epoch seconds when the rate limit will reset.
84 return int(self
.headers
.get('X-RateLimit-Reset', "0"))
87 def wrap_response(response
, headers
):
88 response_typ
= type(response
)
89 if response_typ
is bool:
90 # HURF DURF MY NAME IS PYTHON AND I CAN'T SUBCLASS bool.
93 class WrappedTwitterResponse(response_typ
, TwitterResponse
):
94 __doc__
= TwitterResponse
.__doc
__
96 def __init__(self
, response
, headers
):
97 response_typ
.__init
__(self
, response
)
98 TwitterResponse
.__init
__(self
, headers
)
99 def __new__(cls
, response
, headers
):
100 return response_typ
.__new
__(cls
, response
)
103 return WrappedTwitterResponse(response
, headers
)
107 class TwitterCall(object):
110 self
, auth
, format
, domain
, callable_cls
, uri
="",
111 uriparts
=None, secure
=True):
115 self
.callable_cls
= callable_cls
117 self
.uriparts
= uriparts
120 def __getattr__(self
, k
):
122 return object.__getattr
__(self
, k
)
123 except AttributeError:
124 def extend_call(arg
):
125 return self
.callable_cls(
126 auth
=self
.auth
, format
=self
.format
, domain
=self
.domain
,
127 callable_cls
=self
.callable_cls
, uriparts
=self
.uriparts \
133 return extend_call(k
)
135 def __call__(self
, **kwargs
):
138 for uripart
in self
.uriparts
:
139 # If this part matches a keyword argument, use the
140 # supplied value otherwise, just use the part.
141 uriparts
.append(str(kwargs
.pop(uripart
, uripart
)))
142 uri
= '/'.join(uriparts
)
144 method
= kwargs
.pop('_method', None)
147 for action
in POST_ACTIONS
:
148 if re
.search("%s(/\d+)?$" % action
, uri
):
152 # If an id kwarg is present and there is no id to fill in in
153 # the list of uriparts, assume the id goes at the end.
154 id = kwargs
.pop('id', None)
164 uriBase
= "http%s://%s/%s%s%s" %(
165 secure_str
, self
.domain
, uri
, dot
, self
.format
)
167 headers
= {'Accept-Encoding': 'gzip'}
169 headers
.update(self
.auth
.generate_headers())
170 arg_data
= self
.auth
.encode_params(uriBase
, method
, kwargs
)
172 uriBase
+= '?' + arg_data
175 body
= arg_data
.encode('utf8')
177 req
= urllib_request
.Request(uriBase
, body
, headers
)
178 return self
._handle
_response
(req
, uri
, arg_data
)
180 def _handle_response(self
, req
, uri
, arg_data
):
182 handle
= urllib_request
.urlopen(req
)
183 if handle
.headers
['Content-Type'] in ['image/jpeg', 'image/png']:
185 elif handle
.info().get('Content-Encoding') == 'gzip':
186 # Handle gzip decompression
187 buf
= StringIO(handle
.read())
188 f
= gzip
.GzipFile(fileobj
=buf
)
193 if "json" == self
.format
:
194 res
= json
.loads(data
.decode('utf8'))
195 return wrap_response(res
, handle
.headers
)
197 return wrap_response(
198 data
.decode('utf8'), handle
.headers
)
199 except urllib_error
.HTTPError
as e
:
203 raise TwitterHTTPError(e
, uri
, self
.format
, arg_data
)
205 class Twitter(TwitterCall
):
207 The minimalist yet fully featured Twitter API class.
209 Get RESTful data by accessing members of this class. The result
210 is decoded python objects (lists and dicts).
212 The Twitter API is documented at:
214 http://dev.twitter.com/doc
220 auth=OAuth(token, token_key, con_secret, con_secret_key)))
222 # Get the public timeline
223 t.statuses.public_timeline()
225 # Get a particular friend's timeline
226 t.statuses.friends_timeline(id="billybob")
228 # Also supported (but totally weird)
229 t.statuses.friends_timeline.billybob()
233 status="Using @sixohsix's sweet Python Twitter Tools.")
235 # Send a direct message
236 t.direct_messages.new(
238 text="I think yer swell!")
240 # Get the members of tamtar's list "Things That Are Rad"
241 t._("tamtar")._("things-that-are-rad").members()
243 # Note how the magic `_` method can be used to insert data
244 # into the middle of a call. You can also use replacement:
245 t.user.list.members(user="tamtar", list="things-that-are-rad")
250 twitter_search = Twitter(domain="search.twitter.com")
252 # Find the latest search trends
253 twitter_search.trends()
255 # Search for the latest News on #gaza
256 twitter_search.search(q="#gaza")
259 Using the data returned
260 -----------------------
262 Twitter API calls return decoded JSON. This is converted into
263 a bunch of Python lists, dicts, ints, and strings. For example::
265 x = twitter.statuses.public_timeline()
267 # The first 'tweet' in the timeline
270 # The screen name of the user who wrote the first 'tweet'
271 x[0]['user']['screen_name']
277 If you prefer to get your Twitter data in XML format, pass
278 format="xml" to the Twitter object when you instantiate it::
280 twitter = Twitter(format="xml")
282 The output will not be parsed in any way. It will be a raw string
288 domain
="api.twitter.com", secure
=True, auth
=None,
289 api_version
=_DEFAULT
):
291 Create a new twitter API connector.
293 Pass an `auth` parameter to use the credentials of a specific
294 user. Generally you'll want to pass an `OAuth`
297 twitter = Twitter(auth=OAuth(
298 token, token_secret, consumer_key, consumer_secret))
301 `domain` lets you change the domain you are connecting. By
302 default it's `api.twitter.com` but `search.twitter.com` may be
305 If `secure` is False you will connect with HTTP instead of
308 `api_version` is used to set the base uri. By default it's
309 '1'. If you are using "search.twitter.com" set this to None.
314 if (format
not in ("json", "xml", "")):
315 raise ValueError("Unknown data format '%s'" %(format))
317 if api_version
is _DEFAULT
:
318 if domain
== 'api.twitter.com':
325 uriparts
+= (str(api_version
),)
327 TwitterCall
.__init
__(
328 self
, auth
=auth
, format
=format
, domain
=domain
,
329 callable_cls
=TwitterCall
,
330 secure
=secure
, uriparts
=uriparts
)
333 __all__
= ["Twitter", "TwitterError", "TwitterHTTPError", "TwitterResponse"]