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