Completed
Push — master ( 647612...30829d )
by Messense
30:15 queued 29:41
created

BaseWeChatClient   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 227
Duplicated Lines 0 %

Test Coverage

Coverage 77.69%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 227
ccs 94
cts 121
cp 0.7769
rs 8.3157
c 1
b 0
f 0
wmc 43

11 Methods

Rating   Name   Duplication   Size   Complexity  
B _fetch_access_token() 0 37 5
A fetch_access_token() 0 2 1
F _handle_result() 0 50 11
A __init__() 0 22 3
A access_token_key() 0 3 1
C _request() 0 40 7
A post() 0 5 1
B __new__() 0 19 7
A _decode_result() 0 8 2
A access_token() 0 15 4
A get() 0 5 1

How to fix   Complexity   

Complex Class

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