Completed
Push — master ( 8da5ea...73888a )
by Messense
04:25
created

BaseWeChatClient._get()   A

Complexity

Conditions 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1.2963

Importance

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