Completed
Push — master ( 5dd1bc...461eab )
by Messense
04:04
created

BaseWeChatClient._handle_result()   D

Complexity

Conditions 11

Size

Total Lines 53

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 38.2894

Importance

Changes 0
Metric Value
cc 11
dl 0
loc 53
ccs 9
cts 23
cp 0.3913
crap 38.2894
rs 4.1538
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like BaseWeChatClient._handle_result() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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