123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- # -*- coding: utf-8 -*-
- from __future__ import absolute_import, unicode_literals
- import inspect
- import logging
- import threading
- import requests
- import six
- from library import to_binary, to_text
- from library.wechatpy.client.api.base import BaseWeChatAPI
- from library.wechatpy.constants import WeChatErrorCode
- from library.wechatbase.exceptions import WeChatException, APILimitedException, WechatNetworkException
- from library.wechatpy.utils import json
- from library.wechatpy import access_token_key, jsapi_ticket_key
- logger = logging.getLogger(__name__)
- def _is_api_endpoint(obj):
- return isinstance(obj, BaseWeChatAPI)
- class BaseWeChatClient(object):
- API_BASE_URL = ''
- def __new__(cls, *args, **kwargs):
- self = super(BaseWeChatClient, cls).__new__(cls)
- api_endpoints = inspect.getmembers(self, _is_api_endpoint)
- for name, api in api_endpoints:
- api_cls = type(api)
- api = api_cls(self)
- setattr(self, name, api)
- return self
- def __init__(self, appid, session, timeout=None, auto_retry=True):
- self.appid = appid
- self.session = session
- self.timeout = timeout
- self.auto_retry = auto_retry
- self.my_retry_count = threading.local()
- def __str__(self):
- _repr = '{kclass}(appid: {appid})'.format(
- kclass=self.__class__.__name__,
- appid=self.appid)
- if six.PY2:
- return to_binary(_repr)
- else:
- return to_text(_repr)
- def __repr__(self):
- return str(self)
- @property
- def retry_count(self):
- if not hasattr(self.my_retry_count, 'retry_count'):
- self.my_retry_count.retry_count = 0
- return self.my_retry_count.retry_count
- @retry_count.setter
- def retry_count(self, retry_count):
- self.my_retry_count.retry_count = retry_count
- @property
- def access_token_key(self):
- return access_token_key(self.appid)
- @property
- def jsapi_ticket_key(self):
- return jsapi_ticket_key(self.appid)
- def _request(self, method, url_or_endpoint, **kwargs):
- if not url_or_endpoint.startswith(('http://', 'https://')):
- api_base_url = kwargs.pop('api_base_url', self.API_BASE_URL)
- url = '{base}{endpoint}'.format(
- base=api_base_url,
- endpoint=url_or_endpoint
- )
- else:
- url = url_or_endpoint
- if 'params' not in kwargs:
- kwargs['params'] = {}
- if isinstance(kwargs['params'], dict) and \
- 'access_token' not in kwargs['params']:
- kwargs['params']['access_token'] = self.access_token
- if isinstance(kwargs.get('data', ''), dict):
- body = json.dumps(kwargs['data'], ensure_ascii=False)
- body = body.encode('utf-8')
- kwargs['data'] = body
- kwargs['timeout'] = kwargs.get('timeout', self.timeout)
- result_processor = kwargs.pop('result_processor', None)
- with requests.sessions.Session() as _session:
- res = _session.request(
- method=method,
- url=url,
- **kwargs
- )
- try:
- res.raise_for_status()
- except requests.RequestException as reqe:
- raise WechatNetworkException(
- errCode='HTTP{}'.format(res.status_code),
- errMsg=reqe.message,
- client=self,
- request=reqe.request,
- response=reqe.response
- )
- return self._handle_result(
- res, method, url, result_processor, **kwargs
- )
- def _decode_result(self, res):
- try:
- result = json.loads(res.content.decode('utf-8', 'ignore'), strict=False)
- except (TypeError, ValueError):
- # Return origin response object if we can not decode it as JSON
- logger.debug('Can not decode response as JSON', exc_info=True)
- return res
- return result
- def _handle_result(self, res, method=None, url=None,
- result_processor=None, **kwargs):
- if not isinstance(res, dict):
- # Dirty hack around asyncio based AsyncWeChatClient
- result = self._decode_result(res)
- else:
- result = res
- logger.debug('Response for Wechat API<url={}> result: {}'.format(url, result))
- if not isinstance(result, dict):
- return result
- if 'base_resp' in result:
- # Different response in device APIs. Fuck tencent!
- result.update(result.pop('base_resp'))
- if 'errcode' in result:
- result['errcode'] = int(result['errcode'])
- if 'errcode' in result and result['errcode'] != 0:
- errcode = result['errcode']
- errmsg = result.get('errmsg', errcode)
- if errcode == WeChatErrorCode.OUT_OF_API_FREQ_LIMIT.value:
- # 操作频繁就直接跳出
- raise APILimitedException(
- errCode=errcode,
- errMsg=errmsg,
- client=self,
- request=res.request,
- response=res)
- auto_retry = False
- if self.auto_retry:
- if errcode in (WeChatErrorCode.INVALID_ACCESS_TOKEN.value, WeChatErrorCode.EXPIRED_ACCESS_TOKEN.value):
- auto_retry = True
- elif errcode in [WeChatErrorCode.SYSTEM_ERROR.value,
- WeChatErrorCode.SYSTEM_BUSY.value,
- WeChatErrorCode.INVALID_CREDENTIAL.value]:
- auto_retry = True
- if not auto_retry:
- raise WeChatException(
- errCode=errcode,
- errMsg=errmsg,
- client=self,
- request=res.request,
- response=res)
- self.retry_count = self.retry_count + 1
- if self.retry_count >= 3:
- logger.debug('reached the maximum number of retries. url = {}'.format(url))
- self.retry_count = 0
- raise WeChatException(
- errCode=errcode,
- errMsg=errmsg,
- client=self,
- request=res.request,
- response=res)
- else:
- logger.debug('retry wechat url = {}'.format(url))
- if errcode in (
- WeChatErrorCode.INVALID_ACCESS_TOKEN.value, WeChatErrorCode.EXPIRED_ACCESS_TOKEN.value):
- logger.info(
- '=== WechatToken === Access token({}) expired, fetch a new one and retry request<{}>'.format(
- self.appid, self.retry_count))
- access_token = self.fetch_access_token(kwargs['params'].get('access_token', None))
- kwargs['params']['access_token'] = access_token
- return self._request(
- method=method,
- url_or_endpoint=url,
- result_processor=result_processor,
- **kwargs)
- return result if not result_processor else result_processor(result)
- def get(self, url, **kwargs):
- return self._request(
- method='get',
- url_or_endpoint=url,
- **kwargs
- )
- def post(self, url, **kwargs):
- return self._request(
- method='post',
- url_or_endpoint=url,
- **kwargs
- )
- def fetch_jsapi_ticket(self, old_ticket=None):
- raise NotImplementedError()
- def fetch_access_token(self, old_token=None):
- raise NotImplementedError()
- @property
- def access_token(self):
- """ WeChat access token """
- access_token = self.session.get(self.access_token_key)
- if access_token:
- return access_token
- else:
- return self.fetch_access_token()
- @property
- def jsapi_ticket(self):
- jsapi_ticket = self.session.get(self.jsapi_ticket_key)
- if jsapi_ticket:
- return jsapi_ticket
- else:
- return self.fetch_jsapi_ticket()
|