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