]> jfr.im git - z_archive/twitter.git/blob - twitter/api.py
Merge branch 'master' into jordan_patch
[z_archive/twitter.git] / twitter / api.py
1 """
2 Attempting to patch to accommodate API like the list interface.
3 Note: Make sure not to use keyword substitutions that have the same name
4 as an argument that will get encoded.
5 """
6
7 import urllib2
8
9 from exceptions import Exception
10
11 from twitter.twitter_globals import POST_ACTIONS
12 from twitter.auth import UserPassAuth, NoAuth
13
14 def _py26OrGreater():
15 import sys
16 return sys.hexversion > 0x20600f0
17
18 if _py26OrGreater():
19 import json
20 else:
21 import simplejson as json
22
23 class TwitterError(Exception):
24 """
25 Base Exception thrown by the Twitter object when there is a
26 general error interacting with the API.
27 """
28 pass
29
30 class 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):
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()))
47
48 class TwitterCall(object):
49 def __init__(
50 self, auth, format, domain, uri="", agent=None,
51 uriparts=None, secure=True):
52 self.auth = auth
53 self.format = format
54 self.domain = domain
55 self.uri = uri
56 self.agent = agent
57 self.uriparts = uriparts
58 self.secure = secure
59
60 def __getattr__(self, k):
61 try:
62 return object.__getattr__(self, k)
63 except AttributeError:
64 """Instead of incrementally building the uri string, now we
65 just append to uriparts. We'll build the uri later."""
66 return TwitterCall(
67 self.auth, self.format, self.domain,
68 self.uri, self.agent, self.uriparts + (k,))
69
70 def __call__(self, **kwargs):
71 #build the uri
72 uri = self.uri
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)
77 method = "GET"
78 for action in POST_ACTIONS:
79 if uri.endswith(action):
80 method = "POST"
81 if (self.agent):
82 kwargs["source"] = self.agent
83 break
84
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."""
89 id = kwargs.pop('id', None)
90 if id:
91 uri += "/%s" %(id)
92
93 secure_str = ''
94 if self.secure:
95 secure_str = 's'
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)
101
102 argStr = ""
103 argData = None
104 if (method == "GET"):
105 if self.encoded_args:
106 argStr = "?%s" %(self.encoded_args)
107 else:
108 argData = self.encoded_args
109
110 headers = {}
111 if (self.agent):
112 headers["X-Twitter-Client"] = self.agent
113 if self.auth is not None:
114 headers.update(self.auth.generate_headers())
115
116 req = urllib2.Request(uriBase+argStr, argData, headers)
117
118 try:
119 handle = urllib2.urlopen(req)
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):
126 return []
127 else:
128 raise TwitterHTTPError(e, uri, self.format, self.encoded_args)
129
130 class Twitter(TwitterCall):
131 """
132 The minimalist yet fully featured Twitter API class.
133
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:
138
139 http://apiwiki.twitter.com/
140 http://groups.google.com/group/twitter-development-talk/web/api-documentation
141
142 Examples::
143
144 twitter = Twitter(
145 auth=OAuth(token, token_key, con_secret, con_secret_key)))
146
147 # Get the public timeline
148 twitter.statuses.public_timeline()
149
150 # Get a particular friend's timeline
151 twitter.statuses.friends_timeline(id="billybob")
152
153 # Also supported (but totally weird)
154 twitter.statuses.friends_timeline.billybob()
155
156 # Send a direct message
157 twitter.direct_messages.new(
158 user="billybob",
159 text="I think yer swell!")
160
161 # Get the members of a particular list of a particular friend
162 twitter.user.listname.members(user="billybob", listname="billysbuds")
163
164
165 Searching Twitter::
166
167 twitter_search = Twitter(domain="search.twitter.com")
168
169 # Find the latest search trends
170 twitter_search.trends()
171
172 # Search for the latest News on #gaza
173 twitter_search.search(q="#gaza")
174
175
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::
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']
189
190
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::
196
197 twitter = Twitter(format="xml")
198
199 The output will not be parsed in any way. It will be a raw string
200 of XML.
201
202 """
203 def __init__(
204 self, email=None, password=None, format="json",
205 domain="twitter.com", agent=None, secure=True, auth=None,
206 api_version=''):
207 """
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`
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
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.
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/'.
240 """
241 if email is not None or password is not None:
242 if auth:
243 raise ValueError(
244 "Can't specify 'email'/'password' and 'auth' params"
245 " simultaneously.")
246 auth = UserPassAuth(email, password)
247
248 if not auth:
249 auth = NoAuth()
250
251 if (format not in ("json", "xml", "")):
252 raise ValueError("Unknown data format '%s'" %(format))
253
254 uri = ""
255 if api_version:
256 uri = str(api_version)
257
258 TwitterCall.__init__(
259 self, auth, format, domain, uri, agent,
260 (), secure=secure)
261
262
263 __all__ = ["Twitter", "TwitterError", "TwitterHTTPError"]