]>
jfr.im git - z_archive/twitter.git/blob - twitter/api.py
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
8 from twitter
.twitter_globals
import POST_ACTIONS
9 from twitter
.auth
import NoAuth
16 import simplejson
as json
18 class _DEFAULT(object):
21 class TwitterError(Exception):
23 Base Exception thrown by the Twitter object when there is a
24 general error interacting with the API.
28 class TwitterHTTPError(TwitterError
):
30 Exception thrown by the Twitter object when there is an
31 HTTP error interacting with twitter.com.
33 def __init__(self
, e
, uri
, format
, uriparts
):
37 self
.uriparts
= uriparts
38 self
.response_data
= self
.e
.fp
.read()
41 fmt
= ("." + self
.format
) if self
.format
else ""
43 "Twitter sent status %i for URL: %s%s using parameters: "
44 "(%s)\ndetails: %s" %(
45 self
.e
.code
, self
.uri
, fmt
, self
.uriparts
,
48 class TwitterResponse(object):
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
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.
58 def __init__(self
, headers
):
59 self
.headers
= headers
62 def rate_limit_remaining(self
):
64 Remaining requests in the current rate-limit.
66 return int(self
.headers
.getheader('X-RateLimit-Remaining'))
69 def rate_limit_reset(self
):
71 Time in UTC epoch seconds when the rate limit will reset.
73 return int(self
.headers
.getheader('X-RateLimit-Reset'))
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.
82 class WrappedTwitterResponse(response_typ
, TwitterResponse
):
83 __doc__
= TwitterResponse
.__doc
__
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
)
92 return WrappedTwitterResponse(response
, headers
)
96 class TwitterCall(object):
99 self
, auth
, format
, domain
, callable_cls
, uri
="",
100 uriparts
=None, secure
=True):
104 self
.callable_cls
= callable_cls
106 self
.uriparts
= uriparts
109 def __getattr__(self
, k
):
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 \
122 return extend_call(k
)
124 def __call__(self
, **kwargs
):
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
)
133 method
= kwargs
.pop('_method', None)
136 for action
in POST_ACTIONS
:
137 if re
.search("%s(/\d+)?$" % action
, uri
):
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)
153 uriBase
= "http%s://%s/%s%s%s" %(
154 secure_str
, self
.domain
, uri
, dot
, self
.format
)
158 headers
.update(self
.auth
.generate_headers())
159 arg_data
= self
.auth
.encode_params(uriBase
, method
, kwargs
)
161 uriBase
+= '?' + arg_data
164 body
= arg_data
.encode('utf8')
166 req
= urllib_request
.Request(uriBase
, body
, headers
)
167 return self
._handle
_response
(req
, uri
, arg_data
)
169 def _handle_response(self
, req
, uri
, arg_data
):
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
)
176 return wrap_response(
177 handle
.read().decode('utf8'), handle
.headers
)
178 except urllib_error
.HTTPError
as e
:
182 raise TwitterHTTPError(e
, uri
, self
.format
, arg_data
)
184 class Twitter(TwitterCall
):
186 The minimalist yet fully featured Twitter API class.
188 Get RESTful data by accessing members of this class. The result
189 is decoded python objects (lists and dicts).
191 The Twitter API is documented here:
193 http://dev.twitter.com/doc
199 auth=OAuth(token, token_key, con_secret, con_secret_key)))
201 # Get the public timeline
202 twitter.statuses.public_timeline()
204 # Get a particular friend's timeline
205 twitter.statuses.friends_timeline(id="billybob")
207 # Also supported (but totally weird)
208 twitter.statuses.friends_timeline.billybob()
210 # Send a direct message
211 twitter.direct_messages.new(
213 text="I think yer swell!")
215 # Get the members of a particular list of a particular friend
216 twitter.user.listname.members(user="billybob", listname="billysbuds")
221 twitter_search = Twitter(domain="search.twitter.com")
223 # Find the latest search trends
224 twitter_search.trends()
226 # Search for the latest News on #gaza
227 twitter_search.search(q="#gaza")
230 Using the data returned
231 -----------------------
233 Twitter API calls return decoded JSON. This is converted into
234 a bunch of Python lists, dicts, ints, and strings. For example::
236 x = twitter.statuses.public_timeline()
238 # The first 'tweet' in the timeline
241 # The screen name of the user who wrote the first 'tweet'
242 x[0]['user']['screen_name']
248 If you prefer to get your Twitter data in XML format, pass
249 format="xml" to the Twitter object when you instantiate it::
251 twitter = Twitter(format="xml")
253 The output will not be parsed in any way. It will be a raw string
259 domain
="api.twitter.com", secure
=True, auth
=None,
260 api_version
=_DEFAULT
):
262 Create a new twitter API connector.
264 Pass an `auth` parameter to use the credentials of a specific
265 user. Generally you'll want to pass an `OAuth`
268 twitter = Twitter(auth=OAuth(
269 token, token_secret, consumer_key, consumer_secret))
272 `domain` lets you change the domain you are connecting. By
273 default it's `api.twitter.com` but `search.twitter.com` may be
276 If `secure` is False you will connect with HTTP instead of
279 `api_version` is used to set the base uri. By default it's
280 '1'. If you are using "search.twitter.com" set this to None.
285 if (format
not in ("json", "xml", "")):
286 raise ValueError("Unknown data format '%s'" %(format))
288 if api_version
is _DEFAULT
:
289 if domain
== 'api.twitter.com':
296 uriparts
+= (str(api_version
),)
298 TwitterCall
.__init
__(
299 self
, auth
=auth
, format
=format
, domain
=domain
,
300 callable_cls
=TwitterCall
,
301 secure
=secure
, uriparts
=uriparts
)
304 __all__
= ["Twitter", "TwitterError", "TwitterHTTPError", "TwitterResponse"]