Completed
Push — master ( ee935e...647612 )
by Messense
08:26
created

BaseWeChatClient._fetch_access_token()   B

Complexity

Conditions 5

Size

Total Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 5.0061

Importance

Changes 0
Metric Value
cc 5
c 0
b 0
f 0
dl 0
loc 37
ccs 15
cts 16
cp 0.9375
crap 5.0061
rs 8.0894
1
# -*- coding: utf-8 -*-
2 10
from __future__ import absolute_import, unicode_literals
3 10
import sys
4 10
import time
5 10
import inspect
6 10
import logging
7
8 10
import six
9 10
import requests
10 10
from wechatpy.utils import json, get_querystring
11 10
from wechatpy.session.memorystorage import MemoryStorage
12 10
from wechatpy.exceptions import WeChatClientException, APILimitedException
13 10
from wechatpy.client.api.base import BaseWeChatAPI
14
15
16 10
logger = logging.getLogger(__name__)
17
18
19 10
def _is_api_endpoint(obj):
20 8
    return isinstance(obj, BaseWeChatAPI)
21
22
23 10
class BaseWeChatClient(object):
24
25 10
    API_BASE_URL = ''
26
27 10
    def __new__(cls, *args, **kwargs):
28 10
        self = super(BaseWeChatClient, cls).__new__(cls)
29 10
        if sys.version_info[:2] == (2, 6):
30
            # Python 2.6 inspect.gemembers bug workaround
31
            # http://bugs.python.org/issue1785
32 2
            for _class in cls.__mro__:
33 2
                if issubclass(_class, BaseWeChatClient):
34 2
                    for name, api in _class.__dict__.items():
35 2
                        if isinstance(api, BaseWeChatAPI):
36 2
                            api_cls = type(api)
37 2
                            api = api_cls(self)
38 2
                            setattr(self, name, api)
39
        else:
40 8
            api_endpoints = inspect.getmembers(self, _is_api_endpoint)
41 8
            for name, api in api_endpoints:
42 8
                api_cls = type(api)
43 8
                api = api_cls(self)
44 8
                setattr(self, name, api)
45 10
        return self
46
47 10
    def __init__(self, appid, access_token=None, session=None, timeout=None, auto_retry=True):
48 10
        self.appid = appid
49 10
        self.expires_at = None
50 10
        self.session = session or MemoryStorage()
51 10
        self.timeout = timeout
52 10
        self.auto_retry = auto_retry
53
54 10
        if isinstance(session, six.string_types):
55 8
            from shove import Shove
56 8
            from wechatpy.session.shovestorage import ShoveStorage
57
58 8
            querystring = get_querystring(session)
59 8
            prefix = querystring.get('prefix', ['wechatpy'])[0]
60
61 8
            shove = Shove(session)
62 8
            storage = ShoveStorage(shove, prefix)
63 8
            self.session = storage
64
65 10
        if access_token:
66 10
            self.session.set(self.access_token_key, access_token)
67
68 10
    @property
69
    def access_token_key(self):
70 10
        return '{0}_access_token'.format(self.appid)
71
72 10
    def _request(self, method, url_or_endpoint, **kwargs):
73 10
        if not url_or_endpoint.startswith(('http://', 'https://')):
74 10
            api_base_url = kwargs.pop('api_base_url', self.API_BASE_URL)
75 10
            url = '{base}{endpoint}'.format(
76
                base=api_base_url,
77
                endpoint=url_or_endpoint
78
            )
79
        else:
80 10
            url = url_or_endpoint
81
82 10
        if 'params' not in kwargs:
83 10
            kwargs['params'] = {}
84 10
        if isinstance(kwargs['params'], dict) and \
85
                'access_token' not in kwargs['params']:
86 10
            kwargs['params']['access_token'] = self.access_token
87 10
        if isinstance(kwargs.get('data', ''), dict):
88 10
            body = json.dumps(kwargs['data'], ensure_ascii=False)
89 10
            body = body.encode('utf-8')
90 10
            kwargs['data'] = body
91
92 10
        kwargs['timeout'] = kwargs.get('timeout', self.timeout)
93 10
        result_processor = kwargs.pop('result_processor', None)
94 10
        res = requests.request(
95
            method=method,
96
            url=url,
97
            **kwargs
98
        )
99 10
        try:
100 10
            res.raise_for_status()
101
        except requests.RequestException as reqe:
102
            raise WeChatClientException(
103
                errcode=None,
104
                errmsg=None,
105
                client=self,
106
                request=reqe.request,
107
                response=reqe.response
108
            )
109
110 10
        return self._handle_result(
111
            res, method, url, result_processor, **kwargs
112
        )
113
114 10
    def _decode_result(self, res):
115 10
        try:
116 10
            result = json.loads(res.content.decode('utf-8', 'ignore'), strict=False)
117
        except (TypeError, ValueError):
118
            # Return origin response object if we can not decode it as JSON
119
            logger.debug('Can not decode response as JSON', exc_info=True)
120
            return res
121 10
        return result
122
123 10
    def _handle_result(self, res, method=None, url=None,
124
                       result_processor=None, **kwargs):
125 10
        if not isinstance(res, dict):
126
            # Dirty hack around asyncio based AsyncWeChatClient
127 10
            result = self._decode_result(res)
128
        else:
129
            result = res
130
131 10
        if not isinstance(result, dict):
132
            return result
133
134 10
        if 'base_resp' in result:
135
            # Different response in device APIs. Fuck tencent!
136
            result.update(result.pop('base_resp'))
137 10
        if 'errcode' in result:
138 10
            result['errcode'] = int(result['errcode'])
139
140 10
        if 'errcode' in result and result['errcode'] != 0:
141
            errcode = result['errcode']
142
            errmsg = result.get('errmsg', errcode)
143
            if errcode in (40001, 40014, 42001) and self.auto_retry:
144
                logger.info('Access token expired, fetch a new one and retry request')
145
                self.fetch_access_token()
146
                access_token = self.session.get(self.access_token_key)
147
                kwargs['params']['access_token'] = access_token
148
                return self._request(
149
                    method=method,
150
                    url_or_endpoint=url,
151
                    result_processor=result_processor,
152
                    **kwargs
153
                )
154
            elif errcode == 45009:
155
                # api freq out of limit
156
                raise APILimitedException(
157
                    errcode,
158
                    errmsg,
159
                    client=self,
160
                    request=res.request,
161
                    response=res
162
                )
163
            else:
164
                raise WeChatClientException(
165
                    errcode,
166
                    errmsg,
167
                    client=self,
168
                    request=res.request,
169
                    response=res
170
                )
171
172 10
        return result if not result_processor else result_processor(result)
173
174 10
    def get(self, url, **kwargs):
175 10
        return self._request(
176
            method='get',
177
            url_or_endpoint=url,
178
            **kwargs
179
        )
180
181 10
    _get = get
182
183 10
    def post(self, url, **kwargs):
184 10
        return self._request(
185
            method='post',
186
            url_or_endpoint=url,
187
            **kwargs
188
        )
189
190 10
    _post = post
191
192 10
    def _fetch_access_token(self, url, params):
193
        """ The real fetch access token """
194 10
        logger.info('Fetching access token')
195 10
        res = requests.get(
196
            url=url,
197
            params=params
198
        )
199 10
        try:
200 10
            res.raise_for_status()
201 10
        except requests.RequestException as reqe:
202 10
            raise WeChatClientException(
203
                errcode=None,
204
                errmsg=None,
205
                client=self,
206
                request=reqe.request,
207
                response=reqe.response
208
            )
209 10
        result = res.json()
210 10
        if 'errcode' in result and result['errcode'] != 0:
211
            raise WeChatClientException(
212
                result['errcode'],
213
                result['errmsg'],
214
                client=self,
215
                request=res.request,
216
                response=res
217
            )
218
219 10
        expires_in = 7200
220 10
        if 'expires_in' in result:
221 10
            expires_in = result['expires_in']
222 10
        self.session.set(
223
            self.access_token_key,
224
            result['access_token'],
225
            expires_in
226
        )
227 10
        self.expires_at = int(time.time()) + expires_in
228 10
        return result
229
230 10
    def fetch_access_token(self):
231
        raise NotImplementedError()
232
233 10
    @property
234
    def access_token(self):
235
        """ WeChat access token """
236 10
        access_token = self.session.get(self.access_token_key)
237 10
        if access_token:
238 10
            if not self.expires_at:
239
                # user provided access_token, just return it
240 10
                return access_token
241
242 10
            timestamp = time.time()
243 10
            if self.expires_at - timestamp > 60:
244 10
                return access_token
245
246 10
        self.fetch_access_token()
247
        return self.session.get(self.access_token_key)
248