Example #1
0
    def call(self, action, params, options=None):
        if options is None:
            options = {}
        endpoint = self._get_endpoint()
        headers = {}
        if options.get("IsMultipart"):
            headers["Content-Type"] = _multipart_content

        req_inter = RequestInternal(endpoint,
                                    self.profile.httpProfile.reqMethod,
                                    self._requestPath,
                                    header=headers)
        self._build_req_inter(action, params, req_inter)

        apiRequest = ApiRequest(self._get_endpoint(),
                                self.profile.httpProfile.reqTimeout)

        resp_inter = apiRequest.send_request(req_inter)
        self._check_status(resp_inter)
        data = resp_inter.data
        if sys.version_info[0] > 2:
            data = data.decode()
        else:
            data = data.decode(encoding='UTF-8')
        return data
    def call(self, action, params, options=None):
        endpoint = self._get_endpoint()

        req_inter = RequestInternal(endpoint,
                                    self.profile.httpProfile.reqMethod,
                                    self._requestPath)
        self._build_req_inter(action, params, req_inter, options)

        apiRequest = ApiRequest(self._get_endpoint(),
                                self.profile.httpProfile.reqTimeout)

        resp_inter = apiRequest.send_request(req_inter)
        self._check_status(resp_inter)
        data = resp_inter.data
        if sys.version_info[0] > 2:
            data = data.decode()
        else:
            data = data.decode('UTF-8')
        return data
Example #3
0
class AbstractClient(object):
    _requestPath = '/'
    _params = {}
    _apiVersion = ''
    _endpoint = ''
    _sdkVersion = 'SDK_PYTHON_%s' % tencentcloud.__version__
    _default_content_type = _form_urlencoded_content

    def __init__(self, credential, region, profile=None):
        if credential is None:
            raise TencentCloudSDKException("InvalidCredential",
                                           "Credential is None or invalid")
        self.credential = credential
        self.region = region
        self.profile = ClientProfile() if profile is None else profile
        self.request = ApiRequest(self._get_endpoint(),
                                  self.profile.httpProfile.reqTimeout)
        if self.profile.httpProfile.keepAlive:
            self.request.set_keep_alive()

        # self.secretId = self.credential.secretId
        # self.secretKey = self.credential.secretKey
        # self.defaultRegion = self.region or ''
        # self.reqMethod = self.profile.httpProfile.reqMethod
        # self.signMethod = self.profile.signMethod
        # self.requestHost = '.'.join((self._service_name, self.profile.httpProfile.endpoint))
        # self.apiRequest = ApiRequest(self.requestHost, req_timeout=self.profile.httpProfile.reqTimeout)
        # self.token = self.credential.token or ''

    def _fix_params(self, params):
        if not isinstance(params, (dict, )):
            return params
        return self._format_params(None, params)

    def _format_params(self, prefix, params):
        d = {}
        if params is None:
            return d

        if not isinstance(params, (tuple, list, dict)):
            d[prefix] = params
            return d

        if isinstance(params, (list, tuple)):
            for idx, item in enumerate(params):
                if prefix:
                    key = "{0}.{1}".format(prefix, idx)
                else:
                    key = "{0}".format(idx)
                d.update(self._format_params(key, item))
            return d

        if isinstance(params, dict):
            for k, v in params.items():
                if prefix:
                    key = '{0}.{1}'.format(prefix, k)
                else:
                    key = '{0}'.format(k)
                d.update(self._format_params(key, v))
            return d

        raise TencentCloudSDKException("ClientParamsError",
                                       "some params type error")

    def _build_req_inter(self, action, params, req_inter, options=None):
        options = options or {}
        if self.profile.signMethod == "TC3-HMAC-SHA256" or options.get(
                "IsMultipart") is True:
            self._build_req_with_tc3_signature(action, params, req_inter,
                                               options)
        elif self.profile.signMethod in ("HmacSHA1", "HmacSHA256"):
            self._build_req_with_old_signature(action, params, req_inter)
        else:
            raise TencentCloudSDKException("ClientError",
                                           "Invalid signature method.")

    def _build_req_with_old_signature(self, action, params, req):
        params = copy.deepcopy(self._fix_params(params))
        params['Action'] = action[0].upper() + action[1:]
        params['RequestClient'] = self._sdkVersion
        params['Nonce'] = random.randint(1, sys.maxsize)
        params['Timestamp'] = int(time.time())
        params['Version'] = self._apiVersion

        if self.region:
            params['Region'] = self.region

        if self.credential.token:
            params['Token'] = self.credential.token

        if self.credential.secretId:
            params['SecretId'] = self.credential.secretId

        if self.profile.signMethod:
            params['SignatureMethod'] = self.profile.signMethod

        signInParam = self._format_sign_string(params)
        params['Signature'] = Sign.sign(str(self.credential.secretKey),
                                        str(signInParam),
                                        str(self.profile.signMethod))

        req.data = urlencode(params)
        req.header["Content-Type"] = "application/x-www-form-urlencoded"

    def _build_req_with_tc3_signature(self, action, params, req, options=None):
        content_type = self._default_content_type
        if req.method == 'GET':
            content_type = _form_urlencoded_content
        elif req.method == 'POST':
            content_type = _json_content
        options = options or {}
        if options.get("IsMultipart"):
            content_type = _multipart_content
        req.header["Content-Type"] = content_type

        endpoint = self._get_endpoint()
        service = endpoint.split('.')[0]
        timestamp = int(time.time())
        date = datetime.utcfromtimestamp(timestamp).strftime('%Y-%m-%d')

        req.header["Host"] = endpoint
        req.header["X-TC-Action"] = action[0].upper() + action[1:]
        req.header["X-TC-RequestClient"] = self._sdkVersion
        req.header["X-TC-Timestamp"] = timestamp
        req.header["X-TC-Version"] = self._apiVersion
        if self.profile.unsignedPayload is True:
            req.header["X-TC-Content-SHA256"] = "UNSIGNED-PAYLOAD"
        if self.region:
            req.header['X-TC-Region'] = self.region
        if self.credential.token:
            req.header['X-TC-Token'] = self.credential.token

        signature = self._get_tc3_signature(params, req, date, service,
                                            options)

        auth = "TC3-HMAC-SHA256"
        auth += " Credential=%s/%s/%s/tc3_request" % (self.credential.secretId,
                                                      date, service)
        auth += ", SignedHeaders=content-type;host, Signature=%s" % signature
        req.header["Authorization"] = auth

    def _get_tc3_signature(self, params, req, date, service, options=None):
        options = options or {}
        canonical_uri = req.uri
        canonical_querystring = ''

        if req.method == 'GET' and options.get("IsMultipart") is not True:
            params = copy.deepcopy(self._fix_params(params))
            req.data = urlencode(params)
            canonical_querystring = req.data
            payload = ""
        else:
            ct = req.header["Content-Type"]
            if ct == _json_content:
                req.data = json.dumps(params)
            elif ct == _multipart_content:
                boundary = uuid.uuid4().hex
                req.header["Content-Type"] = ct + "; boundary=" + boundary
                req.data = self._get_multipart_body(params, boundary, options)
            else:
                raise Exception("Unsupported content type: %s" % ct)

            payload = req.data

        if req.header.get("X-TC-Content-SHA256") == "UNSIGNED-PAYLOAD":
            payload = "UNSIGNED-PAYLOAD"

        if sys.version_info[0] == 3 and isinstance(payload, type("")):
            payload = payload.encode("utf8")
        payload_hash = hashlib.sha256(payload).hexdigest()

        canonical_headers = 'content-type:%s\nhost:%s\n' % (
            req.header["Content-Type"], req.header["Host"])
        signed_headers = 'content-type;host'
        canonical_request = '%s\n%s\n%s\n%s\n%s\n%s' % (
            req.method, canonical_uri, canonical_querystring,
            canonical_headers, signed_headers, payload_hash)

        algorithm = 'TC3-HMAC-SHA256'
        credential_scope = date + '/' + service + '/tc3_request'
        if sys.version_info[0] == 3:
            canonical_request = canonical_request.encode("utf8")
        digest = hashlib.sha256(canonical_request).hexdigest()
        string2sign = '%s\n%s\n%s\n%s' % (
            algorithm, req.header["X-TC-Timestamp"], credential_scope, digest)

        signature = Sign.sign_tc3(self.credential.secretKey, date, service,
                                  string2sign)
        return signature

    # it must return bytes instead of string
    def _get_multipart_body(self, params, boundary, options=None):
        if options is None:
            options = {}
        # boundary and params key will never contain unicode characters
        boundary = boundary.encode()
        binparas = options.get("BinaryParams", [])
        body = b''
        for k, v in params.items():
            kbytes = k.encode()
            body += b'--%s\r\n' % boundary
            body += b'Content-Disposition: form-data; name="%s"' % kbytes
            if k in binparas:
                body += b'; filename="%s"\r\n' % kbytes
            else:
                body += b"\r\n"
                if isinstance(v, list) or isinstance(v, dict):
                    v = json.dumps(v)
                    body += b'Content-Type: application/json\r\n'
            if sys.version_info[0] == 3 and isinstance(v, type("")):
                v = v.encode()
            body += b'\r\n%s\r\n' % v
        if body != b'':
            body += b'--%s--\r\n' % boundary
        return body

    def _check_status(self, resp_inter):
        if resp_inter.status != 200:
            raise TencentCloudSDKException("ServerNetworkError",
                                           resp_inter.data)

    def _format_sign_string(self, params):
        formatParam = {}
        for k in params:
            formatParam[k.replace('_', '.')] = params[k]
        strParam = '&'.join('%s=%s' % (k, formatParam[k])
                            for k in sorted(formatParam))
        msg = '%s%s%s?%s' % (self.profile.httpProfile.reqMethod,
                             self._get_endpoint(), self._requestPath, strParam)
        return msg

    def _get_endpoint(self):
        endpoint = self.profile.httpProfile.endpoint
        if endpoint is None:
            endpoint = self._endpoint
        return endpoint

    def call(self, action, params, options=None):
        endpoint = self._get_endpoint()

        req_inter = RequestInternal(endpoint,
                                    self.profile.httpProfile.reqMethod,
                                    self._requestPath)
        self._build_req_inter(action, params, req_inter, options)

        resp_inter = self.request.send_request(req_inter)
        self._check_status(resp_inter)
        data = resp_inter.data
        if sys.version_info[0] > 2:
            data = data.decode()
        else:
            data = data.decode('UTF-8')
        return data
class AbstractClient(object):
    _requestPath = '/'
    _params = {}
    _apiVersion = ''
    _endpoint = ''
    _service = ''
    _sdkVersion = 'SDK_PYTHON_%s' % tencentcloud.__version__
    _default_content_type = _form_urlencoded_content
    FMT = '%(asctime)s %(process)d %(filename)s L%(lineno)s %(levelname)s %(message)s'

    def __init__(self, credential, region, profile=None):
        if credential is None:
            raise TencentCloudSDKException("InvalidCredential",
                                           "Credential is None or invalid")
        self.credential = credential
        self.region = region
        self.profile = ClientProfile() if profile is None else profile
        is_http = True if self.profile.httpProfile.scheme == "http" else False
        self.request = ApiRequest(
            self._get_endpoint(),
            req_timeout=self.profile.httpProfile.reqTimeout,
            proxy=self.profile.httpProfile.proxy,
            is_http=is_http,
            certification=self.profile.httpProfile.certification)
        if self.profile.httpProfile.keepAlive:
            self.request.set_keep_alive()

    def _fix_params(self, params):
        if not isinstance(params, (dict, )):
            return params
        return self._format_params(None, params)

    def _format_params(self, prefix, params):
        d = {}
        if params is None:
            return d

        if not isinstance(params, (tuple, list, dict)):
            d[prefix] = params
            return d

        if isinstance(params, (list, tuple)):
            for idx, item in enumerate(params):
                if prefix:
                    key = "{0}.{1}".format(prefix, idx)
                else:
                    key = "{0}".format(idx)
                d.update(self._format_params(key, item))
            return d

        if isinstance(params, dict):
            for k, v in params.items():
                if prefix:
                    key = '{0}.{1}'.format(prefix, k)
                else:
                    key = '{0}'.format(k)
                d.update(self._format_params(key, v))
            return d

        raise TencentCloudSDKException("ClientParamsError",
                                       "some params type error")

    def _build_req_inter(self, action, params, req_inter, options=None):
        options = options or {}
        if self.profile.signMethod == "TC3-HMAC-SHA256" or options.get(
                "IsMultipart") is True:
            self._build_req_with_tc3_signature(action, params, req_inter,
                                               options)
        elif self.profile.signMethod in ("HmacSHA1", "HmacSHA256"):
            self._build_req_with_old_signature(action, params, req_inter)
        else:
            raise TencentCloudSDKException("ClientError",
                                           "Invalid signature method.")

    def _build_req_with_old_signature(self, action, params, req):
        params = copy.deepcopy(self._fix_params(params))
        params['Action'] = action[0].upper() + action[1:]
        params['RequestClient'] = self._sdkVersion
        params['Nonce'] = random.randint(1, sys.maxsize)
        params['Timestamp'] = int(time.time())
        params['Version'] = self._apiVersion

        if self.region:
            params['Region'] = self.region

        if self.credential.token:
            params['Token'] = self.credential.token

        if self.credential.secret_id:
            params['SecretId'] = self.credential.secret_id

        if self.profile.signMethod:
            params['SignatureMethod'] = self.profile.signMethod

        if self.profile.language:
            params['Language'] = self.profile.language

        signInParam = self._format_sign_string(params)
        params['Signature'] = Sign.sign(str(self.credential.secret_key),
                                        str(signInParam),
                                        str(self.profile.signMethod))

        req.data = urlencode(params)
        req.header["Content-Type"] = "application/x-www-form-urlencoded"

    def _build_req_with_tc3_signature(self, action, params, req, options=None):
        content_type = self._default_content_type
        if req.method == 'GET':
            content_type = _form_urlencoded_content
        elif req.method == 'POST':
            content_type = _json_content
        options = options or {}
        if options.get("IsMultipart"):
            content_type = _multipart_content
        if options.get("IsOctetStream"):
            content_type = _octet_stream
        req.header["Content-Type"] = content_type

        if req.method == "GET" and content_type == _multipart_content:
            raise SDKError("ClientError",
                           "Invalid request method GET for multipart.")

        endpoint = self._get_endpoint()
        timestamp = int(time.time())
        req.header["Host"] = endpoint
        req.header["X-TC-Action"] = action[0].upper() + action[1:]
        req.header["X-TC-RequestClient"] = self._sdkVersion
        req.header["X-TC-Timestamp"] = str(timestamp)
        req.header["X-TC-Version"] = self._apiVersion
        if self.profile.unsignedPayload is True:
            req.header["X-TC-Content-SHA256"] = "UNSIGNED-PAYLOAD"
        if self.region:
            req.header['X-TC-Region'] = self.region
        if self.credential.token:
            req.header['X-TC-Token'] = self.credential.token
        if self.profile.language:
            req.header['X-TC-Language'] = self.profile.language

        if req.method == 'GET':
            params = copy.deepcopy(self._fix_params(params))
            req.data = urlencode(params)
        elif content_type == _json_content:
            req.data = json.dumps(params)
        elif content_type == _multipart_content:
            boundary = uuid.uuid4().hex
            req.header[
                "Content-Type"] = content_type + "; boundary=" + boundary
            req.data = self._get_multipart_body(params, boundary, options)

        service = endpoint.split('.')[0]
        date = datetime.utcfromtimestamp(timestamp).strftime('%Y-%m-%d')
        signature = self._get_tc3_signature(params, req, date, service,
                                            options)

        auth = "TC3-HMAC-SHA256 Credential=%s/%s/%s/tc3_request, SignedHeaders=content-type;host, Signature=%s" % (
            self.credential.secret_id, date, service, signature)
        req.header["Authorization"] = auth

    def _get_tc3_signature(self, params, req, date, service, options=None):
        options = options or {}
        canonical_uri = req.uri
        canonical_querystring = ""
        payload = req.data

        if req.method == 'GET':
            canonical_querystring = req.data
            payload = ""

        if req.header.get("X-TC-Content-SHA256") == "UNSIGNED-PAYLOAD":
            payload = "UNSIGNED-PAYLOAD"

        if sys.version_info[0] == 3 and isinstance(payload, type("")):
            payload = payload.encode("utf8")

        payload_hash = hashlib.sha256(payload).hexdigest()

        canonical_headers = 'content-type:%s\nhost:%s\n' % (
            req.header["Content-Type"], req.header["Host"])
        signed_headers = 'content-type;host'
        canonical_request = '%s\n%s\n%s\n%s\n%s\n%s' % (
            req.method, canonical_uri, canonical_querystring,
            canonical_headers, signed_headers, payload_hash)

        algorithm = 'TC3-HMAC-SHA256'
        credential_scope = date + '/' + service + '/tc3_request'
        if sys.version_info[0] == 3:
            canonical_request = canonical_request.encode("utf8")
        digest = hashlib.sha256(canonical_request).hexdigest()
        string2sign = '%s\n%s\n%s\n%s' % (
            algorithm, req.header["X-TC-Timestamp"], credential_scope, digest)

        return Sign.sign_tc3(self.credential.secret_key, date, service,
                             string2sign)

    # it must return bytes instead of string
    def _get_multipart_body(self, params, boundary, options=None):
        if options is None:
            options = {}
        # boundary and params key will never contain unicode characters
        boundary = boundary.encode()
        binparas = options.get("BinaryParams", [])
        body = b''
        for k, v in params.items():
            kbytes = k.encode()
            body += b'--%s\r\n' % boundary
            body += b'Content-Disposition: form-data; name="%s"' % kbytes
            if k in binparas:
                body += b'; filename="%s"\r\n' % kbytes
            else:
                body += b"\r\n"
                if isinstance(v, list) or isinstance(v, dict):
                    v = json.dumps(v)
                    body += b'Content-Type: application/json\r\n'
            if sys.version_info[0] == 3 and isinstance(v, type("")):
                v = v.encode()
            body += b'\r\n%s\r\n' % v
        if body != b'':
            body += b'--%s--\r\n' % boundary
        return body

    def _check_status(self, resp_inter):
        if resp_inter.status != 200:
            raise TencentCloudSDKException("ServerNetworkError",
                                           resp_inter.data)

    def _format_sign_string(self, params):
        formatParam = {}
        for k in params:
            formatParam[k.replace('_', '.')] = params[k]
        strParam = '&'.join('%s=%s' % (k, formatParam[k])
                            for k in sorted(formatParam))
        msg = '%s%s%s?%s' % (self.profile.httpProfile.reqMethod,
                             self._get_endpoint(), self._requestPath, strParam)
        return msg

    def _get_service_domain(self):
        rootDomain = self.profile.httpProfile.rootDomain
        return self._service + "." + rootDomain

    def _get_endpoint(self):
        endpoint = self.profile.httpProfile.endpoint
        if endpoint is None:
            endpoint = self._get_service_domain()
        return endpoint

    def call(self, action, params, options=None, headers=None):
        req = RequestInternal(self._get_endpoint(),
                              self.profile.httpProfile.reqMethod,
                              self._requestPath,
                              header=headers)
        self._build_req_inter(action, params, req, options)

        resp_inter = self.request.send_request(req)
        self._check_status(resp_inter)
        data = resp_inter.data
        return data

    def call_octet_stream(self, action, headers, body):
        """
        Invoke API with application/ocet-stream content-type.

        Note:
        1. only specific API can be invoked in such manner.
        2. only TC3-HMAC-SHA256 signature method can be specified.
        3. only POST request method can be specified

        :type action: str
        :param action: Specific API action name.
        :type headers: dict
        :param headers: Header parameters for this API.
        :type body: bytes
        :param body: Bytes of requested body
        """
        if self.profile.signMethod != "TC3-HMAC-SHA256":
            raise SDKError("ClientError", "Invalid signature method.")
        if self.profile.httpProfile.reqMethod != "POST":
            raise SDKError("ClientError", "Invalid request method.")

        req = RequestInternal(self._get_endpoint(),
                              self.profile.httpProfile.reqMethod,
                              self._requestPath)
        for key in headers:
            req.header[key] = headers[key]
        req.data = body
        options = {"IsOctetStream": True}
        self._build_req_inter(action, None, req, options)

        resp = self.request.send_request(req)
        self._check_status(resp)
        data = resp.data

        json_rsp = json.loads(data)
        if "Error" in json_rsp["Response"]:
            code = json_rsp["Response"]["Error"]["Code"]
            message = json_rsp["Response"]["Error"]["Message"]
            reqid = json_rsp["Response"]["RequestId"]
            raise TencentCloudSDKException(code, message, reqid)
        return json_rsp

    def call_json(self, action, params, headers=None):
        """
        Call api with json object and return with json object.

        :type action: str
        :param action: api name e.g. ``DescribeInstances``
        :type params: dict
        :param params: params with this action
        :type header: dict
        :param header: request header, like {"X-TC-TraceId": "ffe0c072-8a5d-4e17-8887-a8a60252abca"}
        """
        body = self.call(action, params, headers=headers)
        response = json.loads(body)
        if "Error" not in response["Response"]:
            return response
        else:
            code = response["Response"]["Error"]["Code"]
            message = response["Response"]["Error"]["Message"]
            reqid = response["Response"]["RequestId"]
            raise TencentCloudSDKException(code, message, reqid)

    def set_stream_logger(self,
                          stream=None,
                          level=logging.DEBUG,
                          log_format=None):
        """
        Add a stream handler

        :type stream: IO[str]
        :param stream: e.g. ``sys.stdout`` ``sys.stdin`` ``sys.stderr``
        :type level: int
        :param level: Logging level, e.g. ``logging.INFO``
        :type log_format: str
        :param log_format: Log message format
        """
        log = logging.getLogger(LOGGER_NAME)
        log.setLevel(level)
        sh = logging.StreamHandler(stream)
        sh.setLevel(level)
        if log_format is None:
            log_format = self.FMT
        formatter = logging.Formatter(log_format)
        sh.setFormatter(formatter)
        log.addHandler(sh)

    def set_file_logger(self, file_path, level=logging.DEBUG, log_format=None):
        """
        Add a file handler

        :type file_path: str
        :param file_path: path of log file
        :type level: int
        :param level: Logging level, e.g. ``logging.INFO``
        :type log_format: str
        :param log_format: Log message format
        """
        log = logging.getLogger(LOGGER_NAME)
        log.setLevel(level)
        mb = 1024 * 1024
        fh = logging.handlers.RotatingFileHandler(file_path,
                                                  maxBytes=512 * mb,
                                                  backupCount=10)
        fh.setLevel(level)
        if log_format is None:
            log_format = self.FMT
        formatter = logging.Formatter(log_format)
        fh.setFormatter(formatter)
        log.addHandler(fh)

    def set_default_logger(self):
        """
        Set default log handler
        """
        log = logging.getLogger(LOGGER_NAME)
        log.handlers = []
        logger.addHandler(EmptyHandler())