2
# Copyright 2009-2010 Joshua Roesslein
3
# See LICENSE for details.
10
from tweepy.error import TweepError
11
from tweepy.utils import convert_to_utf8_str
13
re_path_template = re.compile('{\w+}')
16
def bind_api(**config):
18
class APIMethod(object):
21
payload_type = config.get('payload_type', None)
22
payload_list = config.get('payload_list', False)
23
allowed_param = config.get('allowed_param', [])
24
method = config.get('method', 'GET')
25
require_auth = config.get('require_auth', False)
26
search_api = config.get('search_api', False)
28
def __init__(self, api, args, kargs):
29
# If authentication is required and no credentials
30
# are provided, throw an error.
31
if self.require_auth and not api.auth:
32
raise TweepError('Authentication required!')
35
self.post_data = kargs.pop('post_data', None)
36
self.retry_count = kargs.pop('retry_count', api.retry_count)
37
self.retry_delay = kargs.pop('retry_delay', api.retry_delay)
38
self.retry_errors = kargs.pop('retry_errors', api.retry_errors)
39
self.headers = kargs.pop('headers', {})
40
self.build_parameters(args, kargs)
42
# Pick correct URL root to use
44
self.api_root = api.search_root
46
self.api_root = api.api_root
48
# Perform any path variable substitution
52
self.scheme = 'https://'
54
self.scheme = 'http://'
57
self.host = api.search_host
61
# Manually set Host header to fix an issue in python 2.5
62
# or older where Host is set including the 443 port.
63
# This causes Twitter to issue 301 redirect.
64
# See Issue http://github.com/joshthecoder/tweepy/issues/#issue/12
65
self.headers['Host'] = self.host
67
def build_parameters(self, args, kargs):
69
for idx, arg in enumerate(args):
74
self.parameters[self.allowed_param[idx]] = convert_to_utf8_str(arg)
76
raise TweepError('Too many parameters supplied!')
78
for k, arg in kargs.items():
81
if k in self.parameters:
82
raise TweepError('Multiple values for parameter %s supplied!' % k)
84
self.parameters[k] = convert_to_utf8_str(arg)
87
for variable in re_path_template.findall(self.path):
88
name = variable.strip('{}')
90
if name == 'user' and 'user' not in self.parameters and self.api.auth:
91
# No 'user' parameter provided, fetch it from Auth instead.
92
value = self.api.auth.get_username()
95
value = urllib.quote(self.parameters[name])
97
raise TweepError('No parameter value found for path variable: %s' % name)
98
del self.parameters[name]
100
self.path = self.path.replace(variable, value)
103
# Build the request URL
104
url = self.api_root + self.path
105
if len(self.parameters):
106
url = '%s?%s' % (url, urllib.urlencode(self.parameters))
108
# Query the cache if one is available
109
# and this request uses a GET method.
110
if self.api.cache and self.method == 'GET':
111
cache_result = self.api.cache.get(url)
112
# if cache result found and not expired, return it
114
# must restore api reference
115
if isinstance(cache_result, list):
116
for result in cache_result:
117
result._api = self.api
119
cache_result._api = self.api
122
# Continue attempting request until successful
123
# or maximum number of retries is reached.
124
retries_performed = 0
125
while retries_performed < self.retry_count + 1:
129
conn = httplib.HTTPSConnection(self.host)
131
conn = httplib.HTTPConnection(self.host)
133
# Apply authentication
135
self.api.auth.apply_auth(
136
self.scheme + self.host + url,
137
self.method, self.headers, self.parameters
142
conn.request(self.method, url, headers=self.headers, body=self.post_data)
143
resp = conn.getresponse()
145
raise TweepError('Failed to send request: %s' % e)
147
# Exit request loop if non-retry error code
148
if self.retry_errors:
149
if resp.status not in self.retry_errors: break
151
if resp.status == 200: break
153
# Sleep before retrying request again
154
time.sleep(self.retry_delay)
155
retries_performed += 1
157
# If an error was returned, throw an exception
158
self.api.last_response = resp
159
if resp.status != 200:
161
error_msg = self.api.parser.parse_error(resp.read())
163
error_msg = "Twitter error response: status code = %s" % resp.status
164
raise TweepError(error_msg, resp)
166
# Parse the response payload
167
result = self.api.parser.parse(self, resp.read())
171
# Store result into cache if one is available.
172
if self.api.cache and self.method == 'GET' and result:
173
self.api.cache.store(url, result)
178
def _call(api, *args, **kargs):
180
method = APIMethod(api, args, kargs)
181
return method.execute()
184
# Set pagination mode
185
if 'cursor' in APIMethod.allowed_param:
186
_call.pagination_mode = 'cursor'
187
elif 'page' in APIMethod.allowed_param:
188
_call.pagination_mode = 'page'