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