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