binder.py 5.9 KB
Newer Older
1
2
3
4
# Tweepy
# Copyright 2009 Joshua Roesslein
# See LICENSE

5
import httplib
Josh Roesslein's avatar
Josh Roesslein committed
6
import urllib
7
import time
8

Josh Roesslein's avatar
Josh Roesslein committed
9
10
from tweepy.parsers import parse_error
from tweepy.error import TweepError
11

12
try:
13
    import simplejson as json
14
15
except ImportError:
    try:
16
        import json  # Python 2.6+
17
18
    except ImportError:
        try:
19
            from django.utils import simplejson as json  # Google App Engine
20
21
22
        except ImportError:
            raise ImportError, "Can't load a json library"

Josh Roesslein's avatar
Josh Roesslein committed
23

24
def bind_api(path, parser, allowed_param=[], method='GET', require_auth=False,
25
              timeout=None, search_api = False):
26

Josh Roesslein's avatar
Josh Roesslein committed
27
28
    def _call(api, *args, **kargs):
        # If require auth, throw exception if credentials not provided
Josh Roesslein's avatar
Josh Roesslein committed
29
        if require_auth and not api.auth:
Josh Roesslein's avatar
Josh Roesslein committed
30
31
            raise TweepError('Authentication required!')

Josh Roesslein's avatar
Josh Roesslein committed
32
33
        # check for post data
        post_data = kargs.pop('post_data', None)
Josh Roesslein's avatar
Josh Roesslein committed
34

35
        # check for retry request parameters
36
37
        retry_count = kargs.pop('retry_count', api.retry_count)
        retry_delay = kargs.pop('retry_delay', api.retry_delay)
38
        retry_errors = kargs.pop('retry_errors', api.retry_errors)
39

Josh Roesslein's avatar
Josh Roesslein committed
40
        # check for headers
Josh Roesslein's avatar
Josh Roesslein committed
41
        headers = kargs.pop('headers', {})
Josh Roesslein's avatar
Josh Roesslein committed
42
43
44
45
46

        # build parameter dict
        if allowed_param:
            parameters = {}
            for idx, arg in enumerate(args):
47
48
                if isinstance(arg, unicode):
                    arg = arg.encode('utf-8')
Josh Roesslein's avatar
Josh Roesslein committed
49
                try:
50
                    parameters[allowed_param[idx]] = arg
Josh Roesslein's avatar
Josh Roesslein committed
51
52
53
54
55
56
57
58
59
                except IndexError:
                    raise TweepError('Too many parameters supplied!')
            for k, arg in kargs.items():
                if arg is None:
                    continue
                if k in parameters:
                    raise TweepError('Multiple values for parameter %s supplied!' % k)
                if k not in allowed_param:
                    raise TweepError('Invalid parameter %s supplied!' % k)
60
61
62
                if isinstance(arg, unicode):
                    arg = arg.encode('utf-8')
                parameters[k] = arg
63
        else:
Josh Roesslein's avatar
Josh Roesslein committed
64
65
66
67
68
            if len(args) > 0 or len(kargs) > 0:
                raise TweepError('This method takes no parameters!')
            parameters = None

        # Build url with parameters
Josh Roesslein's avatar
Josh Roesslein committed
69
70
71
72
73
        if search_api is False:
            api_root = api.api_root
        else:
            api_root = api.search_root

Josh Roesslein's avatar
Josh Roesslein committed
74
        if parameters:
75
            url = '%s?%s' % (api_root + path, urllib.urlencode(parameters))
Josh Roesslein's avatar
Josh Roesslein committed
76
        else:
77
            url = api_root + path
Josh Roesslein's avatar
Josh Roesslein committed
78
79
80
81
82
83
84
85
86
87
88
89
90
91

        # Check cache if caching enabled and method is GET
        if api.cache and method == 'GET':
            cache_result = api.cache.get(url, timeout)
            # if cache result found and not expired, return it
            if cache_result:
                # must restore api reference
                if isinstance(cache_result, list):
                    for result in cache_result:
                        result._api = api
                else:
                    cache_result._api = api
                return cache_result

92
        # get scheme and host
Josh Roesslein's avatar
Josh Roesslein committed
93
        if api.secure:
94
            scheme = 'https://'
Josh Roesslein's avatar
Josh Roesslein committed
95
        else:
96
            scheme = 'http://'
Josh Roesslein's avatar
Josh Roesslein committed
97
98
99
100
        if search_api is False:
            host = api.host
        else:
            host = api.search_host
Josh Roesslein's avatar
Josh Roesslein committed
101

102
        # Continue attempting request until successful
Josh Roesslein's avatar
Josh Roesslein committed
103
        # or maximum number of retries is reached.
104
105
106
107
108
        retries_performed = 0
        while retries_performed < retry_count + 1:
            # Open connection
            # FIXME: add timeout
            if api.secure:
109
                conn = httplib.HTTPSConnection(host)
110
            else:
111
                conn = httplib.HTTPConnection(host)
112
113

            # Apply authentication
Josh Roesslein's avatar
Josh Roesslein committed
114
115
            if api.auth:
                api.auth.apply_auth(
116
                        scheme + host + url,
117
118
119
120
                        method, headers, parameters
                )

            # Build request
121
122
123
124
            try:
                conn.request(method, url, headers=headers, body=post_data)
            except Exception, e:
                raise TweepError('Failed to send request: %s' % e)
125
126
127
128

            # Get response
            resp = conn.getresponse()

129
            # Exit request loop if non-retry error code
130
131
132
133
            if retry_errors is None:
                if resp.status == 200: break
            else:
                if resp.status not in retry_errors: break
134
135
136
137

            # Sleep before retrying request again
            time.sleep(retry_delay)
            retries_performed += 1
Josh Roesslein's avatar
Josh Roesslein committed
138
139

        # If an error was returned, throw an exception
140
        api.last_response = resp
Josh Roesslein's avatar
Josh Roesslein committed
141
142
        if resp.status != 200:
            try:
Josh Roesslein's avatar
Josh Roesslein committed
143
                error_msg = parse_error(json.loads(resp.read()))
Josh Roesslein's avatar
Josh Roesslein committed
144
145
146
147
            except Exception:
                error_msg = "Twitter error response: status code = %s" % resp.status
            raise TweepError(error_msg)

148
149
150
        # Parse json respone body
        try:
            jobject = json.loads(resp.read())
Josh Roesslein's avatar
Josh Roesslein committed
151
152
        except Exception, e:
            raise TweepError("Failed to parse json: %s" % e)
153

154
155
156
157
158
159
160
161
        # Parse cursor infomation
        if isinstance(jobject, dict):
            next_cursor = jobject.get('next_cursor')
            prev_cursor = jobject.get('previous_cursor')
        else:
            next_cursor = None
            prev_cursor = None

162
        # Pass json object into parser
163
        try:
Josh Roesslein's avatar
Josh Roesslein committed
164
            if parameters and 'cursor' in parameters:
165
166
167
                out = parser(jobject, api), next_cursor, prev_cursor
            else:
                out = parser(jobject, api)
Josh Roesslein's avatar
Josh Roesslein committed
168
169
        except Exception, e:
            raise TweepError("Failed to parse response: %s" % e)
170

Josh Roesslein's avatar
Josh Roesslein committed
171
172
173
174
175
176
177
178
        conn.close()

        # store result in cache
        if api.cache and method == 'GET':
            api.cache.store(url, out)

        return out

179
180
181
182
183
184

    # Set pagination mode
    if 'cursor' in allowed_param:
        _call.pagination_mode = 'cursor'
    elif 'page' in allowed_param:
        _call.pagination_mode = 'page'
185

Josh Roesslein's avatar
Josh Roesslein committed
186
187
    return _call