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