# coding=utf-8 import hashlib import inspect import time from collections import OrderedDict from urlparse import urljoin import typing import requests import logging import simplejson as json from simplejson import JSONDecodeError from library.jdopen.client.api.base import BaseJdOpenAPI from library.jdopen.constants import JdOpenResultCode from library.jdopen.exceptions import JdOpenException if typing.TYPE_CHECKING: from requests import Response logger = logging.getLogger(__name__) class JdOpenBaseClient(object): API_BASE_URL = "" def __new__(cls, *args, **kwargs): self = super(JdOpenBaseClient, cls).__new__(cls) apis = inspect.getmembers(self, lambda x: inspect.isclass(x) and issubclass(x, BaseJdOpenAPI)) for apiName, apiClient in apis: setattr(self, apiName, apiClient(self)) return self def __init__(self, accessKey, secretKey, timeout=None, auto_retry=True): self._accessKey = accessKey self._secretKey = secretKey self._timeout = timeout self._auto_retry = auto_retry @staticmethod def _decode_result(res): # type:(Response) -> typing.Optional[dict, str] """ 解析request返回的结果 """ try: result = json.loads(res.content.decode('utf-8', 'ignore'), strict=False) except (TypeError, ValueError, JSONDecodeError): logger.debug(u'错误的解析结构', exc_info=True) return res return result def calc_token(self, timestamp, path=None, body=None): data = OrderedDict() data["secretKey"] = self._secretKey data["timestamp"] = timestamp if path: data["path"] = path if body is not None: if isinstance(body, dict): data["body"] = json.dumps(body, separators=(",", ":")) else: data["body"] = body s = '&'.join([u'{}={}'.format(k, v) for k, v in data.items()]) s = s.encode('utf-8') token = hashlib.sha1(s).hexdigest().upper() return token def _request(self, method, path, **kwargs): # 修补url logger.info("[{} send request], method = {}, path = {}, data = {}".format(self.__class__.__name__, method, path, kwargs)) urlBase = kwargs.pop("urlBase", self.API_BASE_URL) url = urljoin(urlBase, path) # 修补data数据 if isinstance(kwargs.get("data", ""), dict): data = kwargs.pop("data") kwargs['data'] = json.dumps(data, separators=(',', ':')) # 修补请求头 timestamp = kwargs.pop("timestamp", str(int(time.time()))) headers = { "Content-Type": "application/json", "accessKey": self._accessKey, "timestamp": timestamp, "token": self.calc_token(timestamp, path, body=kwargs.get("data", None)) } kwargs.setdefault("params", {}) kwargs.setdefault("timeout", self._timeout) callback = kwargs.pop("callback", None) with requests.sessions.Session() as _session: res = _session.request( method=method, url=url, headers=headers, **kwargs ) try: res.raise_for_status() except requests.RequestException as rre: logger.info("[{} send request] error! status code = {}, error = {}".format(self.__class__.__name__, res.status_code, rre)) raise JdOpenException( errCode='HTTP{}'.format(res.status_code), errMsg=rre.message, client=self, request=rre.request, response=rre.response ) return self._handle_result( res, method, url, callback, **kwargs ) def _handle_result(self, res, method, url, callback, **kwargs): """ 主要用户重试 """ result = self._decode_result(res) logger.info("[{} handle_result] result = {}".format(self.__class__.__name__, result)) if "result" not in result: return result # 正常请求 if result["result"] == JdOpenResultCode.SUCCESS: return callback(result) if callback else result # 异常请求 error = result["error"] raise JdOpenException( errCode=error.get("errorCode", ''), errMsg=error.get("errorMsg", ''), client=self, request=res.request, response=res ) def get(self, url, **kwargs): return self._request("get", url, **kwargs) def post(self, url, **kwargs): return self._request("post", url, **kwargs)