lbs.client.base 源代码

# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals

import inspect
import logging
import requests
from six.moves.urllib.parse import urljoin, urlencode

from lbs.core.exceptions import LbsClientException
from lbs.core.utils import json_loads

logger = logging.getLogger(__name__)


class LbsBaseAPI(object):

    API_BASE_URL = None

    def __init__(self, client=None):
        self._client = client

    def _get(self, url, params=None, **kwargs):
        if self.API_BASE_URL and 'api_base_url' not in kwargs:
            kwargs['api_base_url'] = self.API_BASE_URL
        return self._client.get(url, params, **kwargs)

    def _post(self, url, data=None, params=None, **kwargs):
        if self.API_BASE_URL and 'api_base_url' not in kwargs:
            kwargs['api_base_url'] = self.API_BASE_URL
        return self._client.post(url, data, params, **kwargs)

    def _gen_get_url(self, url, **kwargs):
        if self.API_BASE_URL and 'api_base_url' not in kwargs:
            kwargs['api_base_url'] = self.API_BASE_URL
        return self._client.gen_get_url(url, **kwargs)


def _is_api_endpoint(obj):
    return isinstance(obj, LbsBaseAPI)


class BaseClient(object):

    _http = requests.Session()

    API_BASE_URL = None

    def __new__(cls, *args, **kwargs):
        self = super(BaseClient, 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, timeout=None):
        self.timeout = timeout

    def get_logger(self):
        return logger

    def gen_get_url(self, url, **kwargs):
        url = self._real_url(url, kwargs)
        _, url, kwargs = self._handle_pre_request('get', url, kwargs)
        if "params" not in kwargs:
            return url
        if "?" not in url:
            return url + "?" + urlencode(kwargs["params"])
        if not url.endswith("&"):
            url += "&"
        url += urlencode(kwargs["params"])
        return url

    def _real_url(self, 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 = urljoin(api_base_url, url_or_endpoint)
        else:
            url = url_or_endpoint
        return url

    def _request(self, method, url_or_endpoint, **kwargs):
        url = self._real_url(url_or_endpoint, kwargs)
        if 'params' not in kwargs:
            kwargs['params'] = {}
        kwargs['timeout'] = kwargs.get('timeout', self.timeout)
        result_processor = kwargs.pop('result_processor', None)
        res = self._http.request(
            method=method,
            url=url,
            **kwargs
        )
        try:
            res.raise_for_status()
        except requests.RequestException as reqe:
            self.get_logger().error("\n【请求地址】: %s\n【请求参数】:%s \n%s\n【异常信息】:%s",
                                    url, kwargs.get('params', ''), kwargs.get('data', ''), reqe)
            raise LbsClientException(
                errcode=None,
                errmsg=None,
                client=self,
                request=reqe.request,
                response=reqe.response
            )

        result = self._handle_result(res, method, url, **kwargs)
        if result_processor is not None:
            result = result_processor(result)

        self.get_logger().debug("\n【请求地址】: %s\n【请求参数】:%s \n%s\n【响应数据】:%s",
                                url, kwargs.get('params', ''), kwargs.get('data', ''), result)
        return result

    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
            self.get_logger().debug('Can not decode response as JSON', exc_info=True)
            return res
        return result

    def _parse_error_code(self, url, kwargs, res, result, message_filed, code_field, ok_code, code_type=None):
        if not isinstance(result, dict):
            return result
        if code_type is not None:
            if code_field in result:
                result[code_field] = code_type(result[code_field])

        if code_field in result and result[code_field] != ok_code:
            errcode = result[code_field]
            errmsg = result.get(message_filed, errcode)

            self.get_logger().error("\n【请求地址】: %s\n【请求参数】:%s \n%s\n【错误信息】:%s",
                                    url, kwargs.get('params', ''), kwargs.get('data', ''), result)
            raise LbsClientException(
                errcode,
                errmsg,
                client=self,
                request=res.request,
                response=res
            )
        return result

    def _handle_result(self, res, method=None, url=None, **kwargs):
        if not isinstance(res, dict):
            # Dirty hack around asyncio based AsyncWeChatClient
            result = self._decode_result(res)
        else:
            result = res
        return result

    def _handle_pre_request(self, method, uri, kwargs):
        return method, uri, kwargs

    def _handle_request_except(self, e, func, *args, **kwargs):
        raise e

    def request(self, method, uri, **kwargs):
        method, uri_with_key, kwargs = self._handle_pre_request(method, uri, kwargs)
        try:
            return self._request(method, uri_with_key, **kwargs)
        except LbsClientException as e:
            return self._handle_request_except(e, self.request, method, uri, **kwargs)

    def get(self, uri, params=None, **kwargs):
        """
        get 接口请求

        :param uri: 请求url
        :param params: get 参数(dict 格式)
        """
        if params is not None:
            kwargs['params'] = params
        return self.request('GET', uri, **kwargs)

    def post(self, uri, data=None, params=None, **kwargs):
        """
        post 接口请求

        :param uri: 请求url
        :param data: post 数据(dict 格式会自动转换为json)
        :param params: post接口中url问号后参数(dict 格式)
        """
        if data is not None:
            kwargs['data'] = data
        if params is not None:
            kwargs['params'] = params
        return self.request('POST', uri, **kwargs)