binder.py 5.31 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
import re
9

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

12
13
re_path_template = re.compile('{\w+}')

Josh Roesslein's avatar
Josh Roesslein committed
14

15
16
def bind_api(path, payload_type=None, payload_list=False, allowed_param=[], method='GET',
                require_auth=False, timeout=None, search_api = False):
17

Josh Roesslein's avatar
Josh Roesslein committed
18
19
    def _call(api, *args, **kargs):
        # If require auth, throw exception if credentials not provided
Josh Roesslein's avatar
Josh Roesslein committed
20
        if require_auth and not api.auth:
Josh Roesslein's avatar
Josh Roesslein committed
21
22
            raise TweepError('Authentication required!')

Josh Roesslein's avatar
Josh Roesslein committed
23
24
        # check for post data
        post_data = kargs.pop('post_data', None)
Josh Roesslein's avatar
Josh Roesslein committed
25

26
        # check for retry request parameters
27
28
        retry_count = kargs.pop('retry_count', api.retry_count)
        retry_delay = kargs.pop('retry_delay', api.retry_delay)
29
        retry_errors = kargs.pop('retry_errors', api.retry_errors)
30

Josh Roesslein's avatar
Josh Roesslein committed
31
        # check for headers
Josh Roesslein's avatar
Josh Roesslein committed
32
        headers = kargs.pop('headers', {})
Josh Roesslein's avatar
Josh Roesslein committed
33
34

        # build parameter dict
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
        parameters = {}
        for idx, arg in enumerate(args):
            if isinstance(arg, unicode):
                arg = arg.encode('utf-8')
            elif not isinstance(arg, str):
                arg = str(arg)

            try:
                parameters[allowed_param[idx]] = arg
            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 isinstance(arg, unicode):
                arg = arg.encode('utf-8')
            elif not isinstance(arg, str):
                arg = str(arg)
            parameters[k] = arg
Josh Roesslein's avatar
Josh Roesslein committed
57

58
        # Pick correct URL root to use
Josh Roesslein's avatar
Josh Roesslein committed
59
60
61
62
63
        if search_api is False:
            api_root = api.api_root
        else:
            api_root = api.search_root

64
        # Build the request URL
65
        if len(parameters):
66
67
            # Replace any template variables in path
            tpath = str(path)
Joshua Roesslein's avatar
Joshua Roesslein committed
68
            for template in re_path_template.findall(tpath):
69
70
71
                name = template.strip('{}')
                try:
                    value = urllib.quote(parameters[name])
Joshua Roesslein's avatar
Joshua Roesslein committed
72
                    tpath = tpath.replace(template, value)
73
74
75
76
77
                except KeyError:
                    raise TweepError('Invalid path key: %s' % name)
                del parameters[name]

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

        # 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

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

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

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

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

            # Get response
            resp = conn.getresponse()

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

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

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

150
151
        # Parse the response payload
        result = api.parser.parse(api, payload_type, payload_list, resp.read())
152

Josh Roesslein's avatar
Josh Roesslein committed
153
154
155
        conn.close()

        # store result in cache
156
157
        if api.cache and method == 'GET' and result:
            api.cache.store(url, result)
Josh Roesslein's avatar
Josh Roesslein committed
158

159
        return result
Josh Roesslein's avatar
Josh Roesslein committed
160

161
162
163
164
165
166

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

Josh Roesslein's avatar
Josh Roesslein committed
168
169
    return _call