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