]> jfr.im git - z_archive/twitter.git/blob - twitter/api.py
Make the code python 2 and 3 compatible.
[z_archive/twitter.git] / twitter / api.py
1 try:
2 import urllib.request as urllib_request
3 import urllib.error as urllib_error
4 except ImportError:
5 import urllib2 as urllib_request
6 import urllib2 as urllib_error
7
8 from twitter.twitter_globals import POST_ACTIONS
9 from twitter.auth import NoAuth
10
11 def _py26OrGreater():
12 import sys
13 return sys.hexversion > 0x20600f0
14
15 if _py26OrGreater():
16 import json
17 else:
18 import simplejson as json
19
20 class TwitterError(Exception):
21 """
22 Base Exception thrown by the Twitter object when there is a
23 general error interacting with the API.
24 """
25 pass
26
27 class TwitterHTTPError(TwitterError):
28 """
29 Exception thrown by the Twitter object when there is an
30 HTTP error interacting with twitter.com.
31 """
32 def __init__(self, e, uri, format, uriparts):
33 self.e = e
34 self.uri = uri
35 self.format = format
36 self.uriparts = uriparts
37
38 def __str__(self):
39 return (
40 "Twitter sent status %i for URL: %s.%s using parameters: "
41 "(%s)\ndetails: %s" %(
42 self.e.code, self.uri, self.format, self.uriparts,
43 self.e.fp.read()))
44
45 class 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 """
55 def __init__(self, headers):
56 self.headers = headers
57
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
73 def wrap_response(response, headers):
74 response_typ = type(response)
75 if response_typ is bool:
76 # HURF DURF MY NAME IS PYTHON AND I CAN'T SUBCLASS bool.
77 response_typ = int
78
79 class WrappedTwitterResponse(response_typ, TwitterResponse):
80 __doc__ = TwitterResponse.__doc__
81
82 def __init__(self, response):
83 if response_typ is not int:
84 response_typ.__init__(self, response)
85 TwitterResponse.__init__(self, headers)
86
87 return WrappedTwitterResponse(response)
88
89
90
91 class TwitterCall(object):
92 def __init__(
93 self, auth, format, domain, uri="", agent=None,
94 uriparts=None, secure=True):
95 self.auth = auth
96 self.format = format
97 self.domain = domain
98 self.uri = uri
99 self.agent = agent
100 self.uriparts = uriparts
101 self.secure = secure
102
103 def __getattr__(self, k):
104 try:
105 return object.__getattr__(self, k)
106 except AttributeError:
107 return TwitterCall(
108 auth=self.auth, format=self.format, domain=self.domain,
109 agent=self.agent, uriparts=self.uriparts + (k,),
110 secure=self.secure)
111
112 def __call__(self, **kwargs):
113 # Build the uri.
114 uriparts = []
115 for uripart in self.uriparts:
116 # If this part matches a keyword argument, use the
117 # supplied value otherwise, just use the part.
118 uriparts.append(str(kwargs.pop(uripart, uripart)))
119 uri = '/'.join(uriparts)
120
121 method = "GET"
122 for action in POST_ACTIONS:
123 if uri.endswith(action):
124 method = "POST"
125 break
126
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.
129 id = kwargs.pop('id', None)
130 if id:
131 uri += "/%s" %(id)
132
133 secure_str = ''
134 if self.secure:
135 secure_str = 's'
136 dot = ""
137 if self.format:
138 dot = "."
139 uriBase = "http%s://%s/%s%s%s" %(
140 secure_str, self.domain, uri, dot, self.format)
141
142 headers = {}
143 if self.auth:
144 headers.update(self.auth.generate_headers())
145 arg_data = self.auth.encode_params(uriBase, method, kwargs)
146 if method == 'GET':
147 uriBase += '?' + arg_data
148 body = None
149 else:
150 body = arg_data.encode('utf8')
151
152 req = urllib_request.Request(uriBase, body, headers)
153
154 try:
155 handle = urllib_request.urlopen(req)
156 if "json" == self.format:
157 res = json.loads(handle.read().decode('utf8'))
158 return wrap_response(res, handle.headers)
159 else:
160 return wrap_response(
161 handle.read().decode('utf8'), handle.headers)
162 except urllib_error.HTTPError as e:
163 if (e.code == 304):
164 return []
165 else:
166 raise TwitterHTTPError(e, uri, self.format, arg_data)
167
168 class Twitter(TwitterCall):
169 """
170 The minimalist yet fully featured Twitter API class.
171
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:
176
177 http://dev.twitter.com/doc
178
179
180 Examples::
181
182 twitter = Twitter(
183 auth=OAuth(token, token_key, con_secret, con_secret_key)))
184
185 # Get the public timeline
186 twitter.statuses.public_timeline()
187
188 # Get a particular friend's timeline
189 twitter.statuses.friends_timeline(id="billybob")
190
191 # Also supported (but totally weird)
192 twitter.statuses.friends_timeline.billybob()
193
194 # Send a direct message
195 twitter.direct_messages.new(
196 user="billybob",
197 text="I think yer swell!")
198
199 # Get the members of a particular list of a particular friend
200 twitter.user.listname.members(user="billybob", listname="billysbuds")
201
202
203 Searching Twitter::
204
205 twitter_search = Twitter(domain="search.twitter.com")
206
207 # Find the latest search trends
208 twitter_search.trends()
209
210 # Search for the latest News on #gaza
211 twitter_search.search(q="#gaza")
212
213
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::
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']
227
228
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::
234
235 twitter = Twitter(format="xml")
236
237 The output will not be parsed in any way. It will be a raw string
238 of XML.
239
240 """
241 def __init__(
242 self, format="json",
243 domain="twitter.com", secure=True, auth=None,
244 api_version=''):
245 """
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`
250 instance::
251
252 twitter = Twitter(auth=OAuth(
253 token, token_secret, consumer_key, consumer_secret))
254
255
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.
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/'.
271 """
272 if not auth:
273 auth = NoAuth()
274
275 if (format not in ("json", "xml", "")):
276 raise ValueError("Unknown data format '%s'" %(format))
277
278 uriparts = ()
279 if api_version:
280 uriparts += (str(api_version),)
281
282 TwitterCall.__init__(
283 self, auth=auth, format=format, domain=domain,
284 secure=secure, uriparts=uriparts)
285
286
287 __all__ = ["Twitter", "TwitterError", "TwitterHTTPError", "TwitterResponse"]