]> jfr.im git - z_archive/twitter.git/blame - twitter/api.py
Merge branch 'master' into jordan_patch
[z_archive/twitter.git] / twitter / api.py
CommitLineData
b0dedfc0
MV
1"""
2Attempting to patch to accommodate API like the list interface.
3Note: Make sure not to use keyword substitutions that have the same name
4as an argument that will get encoded.
5"""
7364ea65 6
de072195 7import urllib2
7364ea65 8
5251ea48 9from exceptions import Exception
10
612ececa 11from twitter.twitter_globals import POST_ACTIONS
d20da7f3 12from twitter.auth import UserPassAuth, NoAuth
4e9d6343 13
f1a8ed67 14def _py26OrGreater():
15 import sys
16 return sys.hexversion > 0x20600f0
17
18if _py26OrGreater():
19 import json
20else:
21 import simplejson as json
22
5251ea48 23class TwitterError(Exception):
21e3bd23 24 """
64a8d213
B
25 Base Exception thrown by the Twitter object when there is a
26 general error interacting with the API.
21e3bd23 27 """
5251ea48 28 pass
29
64a8d213
B
30class TwitterHTTPError(TwitterError):
31 """
32 Exception thrown by the Twitter object when there is an
33 HTTP error interacting with twitter.com.
34 """
35 def __init__(self, e, uri, format, encoded_args):
36 self.e = e
37 self.uri = uri
38 self.format = format
39 self.encoded_args = encoded_args
40
41 def __str__(self):
68b3e2ee
MV
42 return (
43 "Twitter sent status %i for URL: %s.%s using parameters: "
44 "(%s)\ndetails: %s" %(
45 self.e.code, self.uri, self.format, self.encoded_args,
46 self.e.fp.read()))
64a8d213 47
7364ea65 48class TwitterCall(object):
c8d451e8 49 def __init__(
568331a9 50 self, auth, format, domain, uri="", agent=None,
7e43e2ed 51 uriparts=None, secure=True):
568331a9 52 self.auth = auth
a55e6a11 53 self.format = format
153dee29 54 self.domain = domain
7364ea65 55 self.uri = uri
4a6070c8 56 self.agent = agent
b0dedfc0 57 self.uriparts = uriparts
9a148ed1 58 self.secure = secure
fd2bc885 59
7364ea65 60 def __getattr__(self, k):
61 try:
62 return object.__getattr__(self, k)
63 except AttributeError:
b0dedfc0
MV
64 """Instead of incrementally building the uri string, now we
65 just append to uriparts. We'll build the uri later."""
7364ea65 66 return TwitterCall(
568331a9 67 self.auth, self.format, self.domain,
b0dedfc0 68 self.uri, self.agent, self.uriparts + (k,))
fd2bc885 69
7364ea65 70 def __call__(self, **kwargs):
b0dedfc0 71 #build the uri
da45d039 72 uri = self.uri
b0dedfc0
MV
73 for uripart in self.uriparts:
74 #if this part matches a keyword argument, use the supplied value
75 #otherwise, just use the part
76 uri = uri + "/" + kwargs.pop(uripart,uripart)
7364ea65 77 method = "GET"
612ececa 78 for action in POST_ACTIONS:
b0dedfc0 79 if uri.endswith(action):
2dab41b1 80 method = "POST"
4ad03f81
MV
81 if (self.agent):
82 kwargs["source"] = self.agent
2dab41b1 83 break
612ececa 84
b0dedfc0
MV
85 """This handles a special case. It isn't really needed anymore because now
86 we can insert an id value (or any other value) at the end of the
87 uri (or anywhere else).
88 However we can leave it for backward compatibility."""
da45d039
MV
89 id = kwargs.pop('id', None)
90 if id:
91 uri += "/%s" %(id)
4e9d6343 92
568331a9
MH
93 secure_str = ''
94 if self.secure:
95 secure_str = 's'
6c527e72
MV
96 dot = ""
97 if self.format != '':
98 dot = "."
99 uriBase = "http%s://%s/%s%s%s" %(
100 secure_str, self.domain, uri, dot, self.format)
568331a9 101
7364ea65 102 argStr = ""
de072195 103 argData = None
102acdb1 104 if (method == "GET"):
fd2bc885
WD
105 if self.encoded_args:
106 argStr = "?%s" %(self.encoded_args)
102acdb1 107 else:
fd2bc885 108 argData = self.encoded_args
5b8b1ead 109
110 headers = {}
4a6070c8
HN
111 if (self.agent):
112 headers["X-Twitter-Client"] = self.agent
568331a9
MH
113 if self.auth is not None:
114 headers.update(self.auth.generate_headers())
102acdb1 115
568331a9 116 req = urllib2.Request(uriBase+argStr, argData, headers)
9a148ed1 117
7364ea65 118 try:
102acdb1 119 handle = urllib2.urlopen(req)
de072195
HN
120 if "json" == self.format:
121 return json.loads(handle.read())
122 else:
123 return handle.read()
124 except urllib2.HTTPError, e:
125 if (e.code == 304):
7364ea65 126 return []
de072195 127 else:
64a8d213 128 raise TwitterHTTPError(e, uri, self.format, self.encoded_args)
102acdb1 129
7364ea65 130class Twitter(TwitterCall):
131 """
132 The minimalist yet fully featured Twitter API class.
4e9d6343 133
7364ea65 134 Get RESTful data by accessing members of this class. The result
135 is decoded python objects (lists and dicts).
136
137 The Twitter API is documented here:
153dee29 138
0b486eda
HN
139 http://apiwiki.twitter.com/
140 http://groups.google.com/group/twitter-development-talk/web/api-documentation
4e9d6343 141
7364ea65 142 Examples::
4e9d6343 143
69e1f98e
MV
144 twitter = Twitter(
145 auth=OAuth(token, token_key, con_secret, con_secret_key)))
4e9d6343 146
7364ea65 147 # Get the public timeline
148 twitter.statuses.public_timeline()
4e9d6343 149
7364ea65 150 # Get a particular friend's timeline
151 twitter.statuses.friends_timeline(id="billybob")
4e9d6343 152
7364ea65 153 # Also supported (but totally weird)
154 twitter.statuses.friends_timeline.billybob()
4e9d6343 155
7364ea65 156 # Send a direct message
157 twitter.direct_messages.new(
158 user="billybob",
159 text="I think yer swell!")
160
b0dedfc0
MV
161 # Get the members of a particular list of a particular friend
162 twitter.user.listname.members(user="billybob", listname="billysbuds")
163
69e1f98e 164
153dee29 165 Searching Twitter::
4e9d6343 166
0b486eda 167 twitter_search = Twitter(domain="search.twitter.com")
153dee29 168
0b486eda
HN
169 # Find the latest search trends
170 twitter_search.trends()
153dee29 171
0b486eda
HN
172 # Search for the latest News on #gaza
173 twitter_search.search(q="#gaza")
153dee29 174
7364ea65 175
68b3e2ee
MV
176 Using the data returned
177 -----------------------
178
179 Twitter API calls return decoded JSON. This is converted into
180 a bunch of Python lists, dicts, ints, and strings. For example::
7364ea65 181
182 x = twitter.statuses.public_timeline()
183
184 # The first 'tweet' in the timeline
185 x[0]
186
187 # The screen name of the user who wrote the first 'tweet'
188 x[0]['user']['screen_name']
4e9d6343 189
4e9d6343 190
68b3e2ee
MV
191 Getting raw XML data
192 --------------------
193
194 If you prefer to get your Twitter data in XML format, pass
195 format="xml" to the Twitter object when you instantiate it::
4e9d6343 196
a55e6a11 197 twitter = Twitter(format="xml")
4e9d6343 198
a55e6a11 199 The output will not be parsed in any way. It will be a raw string
200 of XML.
68b3e2ee 201
7364ea65 202 """
45688301 203 def __init__(
68b3e2ee 204 self, email=None, password=None, format="json",
24baad0c 205 domain="twitter.com", agent=None, secure=True, auth=None,
1cc9ab0b 206 api_version=''):
7364ea65 207 """
68b3e2ee
MV
208 Create a new twitter API connector.
209
210 Pass an `auth` parameter to use the credentials of a specific
211 user. Generally you'll want to pass an `OAuth`
69e1f98e
MV
212 instance::
213
214 twitter = Twitter(auth=OAuth(
215 token, token_secret, consumer_key, consumer_secret))
216
217
218 Alternately you can pass `email` and `password` parameters but
219 this authentication mode will be deactive by Twitter very soon
220 and is not recommended::
221
222 twitter = Twitter(email="blah@blah.com", password="foobar")
223
68b3e2ee
MV
224
225 `domain` lets you change the domain you are connecting. By
226 default it's twitter.com but `search.twitter.com` may be
227 useful too.
228
229 If `secure` is False you will connect with HTTP instead of
230 HTTPS.
231
232 The value of `agent` is sent in the `X-Twitter-Client`
233 header. This is deprecated. Instead Twitter determines the
234 application using the OAuth Client Key and Client Key Secret
235 parameters.
1cc9ab0b
MV
236
237 `api_version` is used to set the base uri. By default it's
238 nothing, but if you set it to '1' your URI will start with
239 '1/'.
7364ea65 240 """
568331a9 241 if email is not None or password is not None:
68b3e2ee 242 if auth:
1cc9ab0b 243 raise ValueError(
68b3e2ee
MV
244 "Can't specify 'email'/'password' and 'auth' params"
245 " simultaneously.")
568331a9
MH
246 auth = UserPassAuth(email, password)
247
d20da7f3
MV
248 if not auth:
249 auth = NoAuth()
250
6c527e72 251 if (format not in ("json", "xml", "")):
68b3e2ee
MV
252 raise ValueError("Unknown data format '%s'" %(format))
253
254 uri = ""
255 if api_version:
256 uri = str(api_version)
257
9a148ed1 258 TwitterCall.__init__(
68b3e2ee 259 self, auth, format, domain, uri, agent,
7e43e2ed
MV
260 (), secure=secure)
261
7364ea65 262
64a8d213 263__all__ = ["Twitter", "TwitterError", "TwitterHTTPError"]