]> projects.mako.cc - twitter-api-cdsw/blob - tweepy/binder.py
1f8c61960a2c9a14f8b75395428e50bf8fe577b9
[twitter-api-cdsw] / tweepy / binder.py
1 # Tweepy
2 # Copyright 2009-2010 Joshua Roesslein
3 # See LICENSE for details.
4
5 import httplib
6 import urllib
7 import time
8 import re
9 from StringIO import StringIO
10 import gzip
11
12 from tweepy.error import TweepError
13 from tweepy.utils import convert_to_utf8_str
14 from tweepy.models import Model
15
16 re_path_template = re.compile('{\w+}')
17
18
19 def bind_api(**config):
20
21     class APIMethod(object):
22
23         path = config['path']
24         payload_type = config.get('payload_type', None)
25         payload_list = config.get('payload_list', False)
26         allowed_param = config.get('allowed_param', [])
27         method = config.get('method', 'GET')
28         require_auth = config.get('require_auth', False)
29         search_api = config.get('search_api', False)
30         use_cache = config.get('use_cache', True)
31
32         def __init__(self, api, args, kargs):
33             # If authentication is required and no credentials
34             # are provided, throw an error.
35             if self.require_auth and not api.auth:
36                 raise TweepError('Authentication required!')
37
38             self.api = api
39             self.post_data = kargs.pop('post_data', None)
40             self.retry_count = kargs.pop('retry_count', api.retry_count)
41             self.retry_delay = kargs.pop('retry_delay', api.retry_delay)
42             self.retry_errors = kargs.pop('retry_errors', api.retry_errors)
43             self.headers = kargs.pop('headers', {})
44             self.build_parameters(args, kargs)
45
46             # Pick correct URL root to use
47             if self.search_api:
48                 self.api_root = api.search_root
49             else:
50                 self.api_root = api.api_root
51
52             # Perform any path variable substitution
53             self.build_path()
54
55             if api.secure:
56                 self.scheme = 'https://'
57             else:
58                 self.scheme = 'http://'
59
60             if self.search_api:
61                 self.host = api.search_host
62             else:
63                 self.host = api.host
64
65             # Manually set Host header to fix an issue in python 2.5
66             # or older where Host is set including the 443 port.
67             # This causes Twitter to issue 301 redirect.
68             # See Issue https://github.com/tweepy/tweepy/issues/12
69             self.headers['Host'] = self.host
70
71         def build_parameters(self, args, kargs):
72             self.parameters = {}
73             for idx, arg in enumerate(args):
74                 if arg is None:
75                     continue
76
77                 try:
78                     self.parameters[self.allowed_param[idx]] = convert_to_utf8_str(arg)
79                 except IndexError:
80                     raise TweepError('Too many parameters supplied!')
81
82             for k, arg in kargs.items():
83                 if arg is None:
84                     continue
85                 if k in self.parameters:
86                     raise TweepError('Multiple values for parameter %s supplied!' % k)
87
88                 self.parameters[k] = convert_to_utf8_str(arg)
89
90         def build_path(self):
91             for variable in re_path_template.findall(self.path):
92                 name = variable.strip('{}')
93
94                 if name == 'user' and 'user' not in self.parameters and self.api.auth:
95                     # No 'user' parameter provided, fetch it from Auth instead.
96                     value = self.api.auth.get_username()
97                 else:
98                     try:
99                         value = urllib.quote(self.parameters[name])
100                     except KeyError:
101                         raise TweepError('No parameter value found for path variable: %s' % name)
102                     del self.parameters[name]
103
104                 self.path = self.path.replace(variable, value)
105
106         def execute(self):
107             self.api.cached_result = False
108
109             # Build the request URL
110             url = self.api_root + self.path
111             if len(self.parameters):
112                 url = '%s?%s' % (url, urllib.urlencode(self.parameters))
113
114             # Query the cache if one is available
115             # and this request uses a GET method.
116             if self.use_cache and self.api.cache and self.method == 'GET':
117                 cache_result = self.api.cache.get(url)
118                 # if cache result found and not expired, return it
119                 if cache_result:
120                     # must restore api reference
121                     if isinstance(cache_result, list):
122                         for result in cache_result:
123                             if isinstance(result, Model):
124                                 result._api = self.api
125                     else:
126                         if isinstance(cache_result, Model):
127                             cache_result._api = self.api
128                     self.api.cached_result = True
129                     return cache_result
130
131             # Continue attempting request until successful
132             # or maximum number of retries is reached.
133             retries_performed = 0
134             while retries_performed < self.retry_count + 1:
135                 # Open connection
136                 if self.api.secure:
137                     conn = httplib.HTTPSConnection(self.host, timeout=self.api.timeout)
138                 else:
139                     conn = httplib.HTTPConnection(self.host, timeout=self.api.timeout)
140
141                 # Apply authentication
142                 if self.api.auth:
143                     self.api.auth.apply_auth(
144                             self.scheme + self.host + url,
145                             self.method, self.headers, self.parameters
146                     )
147
148                 # Request compression if configured
149                 if self.api.compression:
150                     self.headers['Accept-encoding'] = 'gzip'
151
152                 # Execute request
153                 try:
154                     conn.request(self.method, url, headers=self.headers, body=self.post_data)
155                     resp = conn.getresponse()
156                 except Exception as e:
157                     raise TweepError('Failed to send request: %s' % e)
158
159                 # Exit request loop if non-retry error code
160                 if self.retry_errors:
161                     if resp.status not in self.retry_errors: break
162                 else:
163                     if resp.status == 200: break
164
165                 # Sleep before retrying request again
166                 time.sleep(self.retry_delay)
167                 retries_performed += 1
168
169             # If an error was returned, throw an exception
170             self.api.last_response = resp
171             if resp.status and not 200 <= resp.status < 300:
172                 try:
173                     error_msg = self.api.parser.parse_error(resp.read())
174                 except Exception:
175                     error_msg = "Twitter error response: status code = %s" % resp.status
176                 raise TweepError(error_msg, resp)
177
178             # Parse the response payload
179             body = resp.read()
180             if resp.getheader('Content-Encoding', '') == 'gzip':
181                 try:
182                     zipper = gzip.GzipFile(fileobj=StringIO(body))
183                     body = zipper.read()
184                 except Exception as e:
185                     raise TweepError('Failed to decompress data: %s' % e)
186             result = self.api.parser.parse(self, body)
187
188             conn.close()
189
190             # Store result into cache if one is available.
191             if self.use_cache and self.api.cache and self.method == 'GET' and result:
192                 self.api.cache.store(url, result)
193
194             return result
195
196
197     def _call(api, *args, **kargs):
198
199         method = APIMethod(api, args, kargs)
200         return method.execute()
201
202
203     # Set pagination mode
204     if 'cursor' in APIMethod.allowed_param:
205         _call.pagination_mode = 'cursor'
206     elif 'max_id' in APIMethod.allowed_param and \
207          'since_id' in APIMethod.allowed_param:
208         _call.pagination_mode = 'id'
209     elif 'page' in APIMethod.allowed_param:
210         _call.pagination_mode = 'page'
211
212     return _call
213

Benjamin Mako Hill || Want to submit a patch?