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