tencentcloud-sdk-python-common 3.0.1388__py2.py3-none-any.whl → 3.1.15__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tencentcloud-sdk-python-common might be problematic. Click here for more details.
- tencentcloud/__init__.py +1 -1
- tencentcloud/common/abstract_client.py +35 -25
- tencentcloud/common/abstract_client_async.py +654 -0
- tencentcloud/common/common_client_async.py +45 -0
- tencentcloud/common/credential.py +83 -31
- tencentcloud/common/http/request_async.py +62 -0
- tencentcloud/common/retry_async.py +87 -0
- {tencentcloud_sdk_python_common-3.0.1388.dist-info → tencentcloud_sdk_python_common-3.1.15.dist-info}/METADATA +4 -4
- {tencentcloud_sdk_python_common-3.0.1388.dist-info → tencentcloud_sdk_python_common-3.1.15.dist-info}/RECORD +11 -7
- {tencentcloud_sdk_python_common-3.0.1388.dist-info → tencentcloud_sdk_python_common-3.1.15.dist-info}/WHEEL +1 -1
- {tencentcloud_sdk_python_common-3.0.1388.dist-info → tencentcloud_sdk_python_common-3.1.15.dist-info}/top_level.txt +0 -0
tencentcloud/__init__.py
CHANGED
|
@@ -28,8 +28,10 @@ import logging.handlers
|
|
|
28
28
|
|
|
29
29
|
try:
|
|
30
30
|
from urllib.parse import urlencode
|
|
31
|
+
from urllib.parse import urlparse
|
|
31
32
|
except ImportError:
|
|
32
33
|
from urllib import urlencode
|
|
34
|
+
from urlparse import urlparse
|
|
33
35
|
|
|
34
36
|
import tencentcloud
|
|
35
37
|
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
|
|
@@ -133,11 +135,11 @@ class AbstractClient(object):
|
|
|
133
135
|
elif self.profile.signMethod == "TC3-HMAC-SHA256" or options.get("IsMultipart") is True:
|
|
134
136
|
self._build_req_with_tc3_signature(action, params, req_inter, options)
|
|
135
137
|
elif self.profile.signMethod in ("HmacSHA1", "HmacSHA256"):
|
|
136
|
-
self._build_req_with_old_signature(action, params, req_inter)
|
|
138
|
+
self._build_req_with_old_signature(action, params, req_inter, options)
|
|
137
139
|
else:
|
|
138
140
|
raise TencentCloudSDKException("ClientError", "Invalid signature method.")
|
|
139
141
|
|
|
140
|
-
def _build_req_with_old_signature(self, action, params, req):
|
|
142
|
+
def _build_req_with_old_signature(self, action, params, req, options):
|
|
141
143
|
params = copy.deepcopy(self._fix_params(params))
|
|
142
144
|
params['Action'] = action[0].upper() + action[1:]
|
|
143
145
|
params['RequestClient'] = self.request_client
|
|
@@ -145,14 +147,16 @@ class AbstractClient(object):
|
|
|
145
147
|
params['Timestamp'] = int(time.time())
|
|
146
148
|
params['Version'] = self._apiVersion
|
|
147
149
|
|
|
150
|
+
cred_secret_id, cred_secret_key, cred_token = self.credential.get_credential_info()
|
|
151
|
+
|
|
148
152
|
if self.region:
|
|
149
153
|
params['Region'] = self.region
|
|
150
154
|
|
|
151
|
-
if
|
|
152
|
-
params['Token'] =
|
|
155
|
+
if cred_token:
|
|
156
|
+
params['Token'] = cred_token
|
|
153
157
|
|
|
154
|
-
if
|
|
155
|
-
params['SecretId'] =
|
|
158
|
+
if cred_secret_id:
|
|
159
|
+
params['SecretId'] = cred_secret_id
|
|
156
160
|
|
|
157
161
|
if self.profile.signMethod:
|
|
158
162
|
params['SignatureMethod'] = self.profile.signMethod
|
|
@@ -160,8 +164,8 @@ class AbstractClient(object):
|
|
|
160
164
|
if self.profile.language:
|
|
161
165
|
params['Language'] = self.profile.language
|
|
162
166
|
|
|
163
|
-
signInParam = self._format_sign_string(params)
|
|
164
|
-
params['Signature'] = Sign.sign(str(
|
|
167
|
+
signInParam = self._format_sign_string(params, options)
|
|
168
|
+
params['Signature'] = Sign.sign(str(cred_secret_key),
|
|
165
169
|
str(signInParam),
|
|
166
170
|
str(self.profile.signMethod))
|
|
167
171
|
|
|
@@ -185,8 +189,9 @@ class AbstractClient(object):
|
|
|
185
189
|
raise SDKError("ClientError",
|
|
186
190
|
"Invalid request method GET for multipart.")
|
|
187
191
|
|
|
188
|
-
endpoint = self._get_endpoint()
|
|
192
|
+
endpoint = self._get_endpoint(options=options)
|
|
189
193
|
timestamp = int(time.time())
|
|
194
|
+
cred_secret_id, cred_secret_key, cred_token = self.credential.get_credential_info()
|
|
190
195
|
req.header["Host"] = endpoint
|
|
191
196
|
req.header["X-TC-Action"] = action[0].upper() + action[1:]
|
|
192
197
|
req.header["X-TC-RequestClient"] = self.request_client
|
|
@@ -196,8 +201,8 @@ class AbstractClient(object):
|
|
|
196
201
|
req.header["X-TC-Content-SHA256"] = "UNSIGNED-PAYLOAD"
|
|
197
202
|
if self.region:
|
|
198
203
|
req.header['X-TC-Region'] = self.region
|
|
199
|
-
if
|
|
200
|
-
req.header['X-TC-Token'] =
|
|
204
|
+
if cred_token:
|
|
205
|
+
req.header['X-TC-Token'] = cred_token
|
|
201
206
|
if self.profile.language:
|
|
202
207
|
req.header['X-TC-Language'] = self.profile.language
|
|
203
208
|
|
|
@@ -213,13 +218,13 @@ class AbstractClient(object):
|
|
|
213
218
|
|
|
214
219
|
service = self._service
|
|
215
220
|
date = datetime.utcfromtimestamp(timestamp).strftime('%Y-%m-%d')
|
|
216
|
-
signature = self._get_tc3_signature(params, req, date, service, options)
|
|
221
|
+
signature = self._get_tc3_signature(params, req, date, service, cred_secret_key, options)
|
|
217
222
|
|
|
218
223
|
auth = "TC3-HMAC-SHA256 Credential=%s/%s/%s/tc3_request, SignedHeaders=content-type;host, Signature=%s" % (
|
|
219
|
-
|
|
224
|
+
cred_secret_id, date, service, signature)
|
|
220
225
|
req.header["Authorization"] = auth
|
|
221
226
|
|
|
222
|
-
def _get_tc3_signature(self, params, req, date, service, options=None):
|
|
227
|
+
def _get_tc3_signature(self, params, req, date, service, secret_key, options=None):
|
|
223
228
|
options = options or {}
|
|
224
229
|
canonical_uri = req.uri
|
|
225
230
|
canonical_querystring = ""
|
|
@@ -256,8 +261,7 @@ class AbstractClient(object):
|
|
|
256
261
|
req.header["X-TC-Timestamp"],
|
|
257
262
|
credential_scope,
|
|
258
263
|
digest)
|
|
259
|
-
|
|
260
|
-
return Sign.sign_tc3(self.credential.secret_key, date, service, string2sign)
|
|
264
|
+
return Sign.sign_tc3(secret_key, date, service, string2sign)
|
|
261
265
|
|
|
262
266
|
def _build_req_without_signature(self, action, params, req, options=None):
|
|
263
267
|
content_type = self._default_content_type
|
|
@@ -276,7 +280,7 @@ class AbstractClient(object):
|
|
|
276
280
|
raise SDKError("ClientError",
|
|
277
281
|
"Invalid request method GET for multipart.")
|
|
278
282
|
|
|
279
|
-
endpoint = self._get_endpoint()
|
|
283
|
+
endpoint = self._get_endpoint(options=options)
|
|
280
284
|
timestamp = int(time.time())
|
|
281
285
|
req.header["Host"] = endpoint
|
|
282
286
|
req.header["X-TC-Action"] = action[0].upper() + action[1:]
|
|
@@ -333,20 +337,23 @@ class AbstractClient(object):
|
|
|
333
337
|
logger.debug("GetResponse: %s", ResponsePrettyFormatter(resp_inter))
|
|
334
338
|
raise TencentCloudSDKException("ServerNetworkError", resp_inter.content)
|
|
335
339
|
|
|
336
|
-
def _format_sign_string(self, params):
|
|
340
|
+
def _format_sign_string(self, params, options=None):
|
|
337
341
|
formatParam = {}
|
|
338
342
|
for k in params:
|
|
339
343
|
formatParam[k.replace('_', '.')] = params[k]
|
|
340
344
|
strParam = '&'.join('%s=%s' % (k, formatParam[k]) for k in sorted(formatParam))
|
|
341
|
-
msg = '%s%s%s?%s' % (
|
|
345
|
+
msg = '%s%s%s?%s' % (
|
|
346
|
+
self.profile.httpProfile.reqMethod, self._get_endpoint(options=options), self._requestPath, strParam)
|
|
342
347
|
return msg
|
|
343
348
|
|
|
344
349
|
def _get_service_domain(self):
|
|
345
350
|
rootDomain = self.profile.httpProfile.rootDomain
|
|
346
351
|
return self._service + "." + rootDomain
|
|
347
352
|
|
|
348
|
-
def _get_endpoint(self):
|
|
353
|
+
def _get_endpoint(self, options=None):
|
|
349
354
|
endpoint = self.profile.httpProfile.endpoint
|
|
355
|
+
if not endpoint and options:
|
|
356
|
+
endpoint = urlparse(options.get("Endpoint", "")).hostname
|
|
350
357
|
if endpoint is None:
|
|
351
358
|
endpoint = self._get_service_domain()
|
|
352
359
|
return endpoint
|
|
@@ -420,7 +427,7 @@ class AbstractClient(object):
|
|
|
420
427
|
headers["X-TC-TraceId"] = str(uuid.uuid4())
|
|
421
428
|
if not self.profile.disable_region_breaker:
|
|
422
429
|
return self._call_with_region_breaker(action, params, options, headers)
|
|
423
|
-
req = RequestInternal(self._get_endpoint(),
|
|
430
|
+
req = RequestInternal(self._get_endpoint(options=options),
|
|
424
431
|
self.profile.httpProfile.reqMethod,
|
|
425
432
|
self._requestPath,
|
|
426
433
|
header=headers)
|
|
@@ -444,7 +451,7 @@ class AbstractClient(object):
|
|
|
444
451
|
return retryer.send_request(_call_once).content
|
|
445
452
|
|
|
446
453
|
def _call_with_region_breaker(self, action, params, options=None, headers=None):
|
|
447
|
-
endpoint = self._get_endpoint()
|
|
454
|
+
endpoint = self._get_endpoint(options=options)
|
|
448
455
|
generation, need_break = self.circuit_breaker.before_requests()
|
|
449
456
|
if need_break:
|
|
450
457
|
endpoint = self._service + "." + self.profile.region_breaker_profile.backup_endpoint
|
|
@@ -470,7 +477,7 @@ class AbstractClient(object):
|
|
|
470
477
|
self._check_error(resp)
|
|
471
478
|
return resp.content
|
|
472
479
|
|
|
473
|
-
def call_octet_stream(self, action, headers, body):
|
|
480
|
+
def call_octet_stream(self, action, headers, body, options=None):
|
|
474
481
|
"""Invoke API with application/ocet-stream content-type.
|
|
475
482
|
|
|
476
483
|
Note:
|
|
@@ -490,12 +497,14 @@ class AbstractClient(object):
|
|
|
490
497
|
if self.profile.httpProfile.reqMethod != "POST":
|
|
491
498
|
raise SDKError("ClientError", "Invalid request method.")
|
|
492
499
|
|
|
493
|
-
|
|
500
|
+
if not options:
|
|
501
|
+
options = {}
|
|
502
|
+
req = RequestInternal(self._get_endpoint(options=options),
|
|
494
503
|
self.profile.httpProfile.reqMethod,
|
|
495
504
|
self._requestPath,
|
|
496
505
|
header=headers)
|
|
497
506
|
req.data = body
|
|
498
|
-
options
|
|
507
|
+
options["IsOctetStream"] = True
|
|
499
508
|
self._build_req_inter(action, None, req, options)
|
|
500
509
|
|
|
501
510
|
resp = self.request.send_request(req)
|
|
@@ -538,6 +547,7 @@ class AbstractClient(object):
|
|
|
538
547
|
:param options: Request options, like {"SkipSign": False, "IsMultipart": False, "IsOctetStream": False, "BinaryParams": []}
|
|
539
548
|
:type options: dict
|
|
540
549
|
"""
|
|
550
|
+
|
|
541
551
|
def _call_once():
|
|
542
552
|
resp = self._call(action, params, options, headers)
|
|
543
553
|
self._check_status(resp)
|
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2017-2021 Tencent Ltd.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
import copy
|
|
17
|
+
import hashlib
|
|
18
|
+
import json
|
|
19
|
+
import logging
|
|
20
|
+
import logging.handlers
|
|
21
|
+
import random
|
|
22
|
+
import sys
|
|
23
|
+
import time
|
|
24
|
+
import uuid
|
|
25
|
+
import warnings
|
|
26
|
+
from datetime import datetime
|
|
27
|
+
from typing import Dict, Type, Union, List, Callable, Awaitable, Optional
|
|
28
|
+
|
|
29
|
+
import httpx
|
|
30
|
+
|
|
31
|
+
import tencentcloud
|
|
32
|
+
from tencentcloud.common.abstract_client import logger, urlparse, urlencode
|
|
33
|
+
from tencentcloud.common.abstract_model import AbstractModel
|
|
34
|
+
from tencentcloud.common.circuit_breaker import CircuitBreaker
|
|
35
|
+
from tencentcloud.common.credential import Credential
|
|
36
|
+
from tencentcloud.common.exception import TencentCloudSDKException
|
|
37
|
+
from tencentcloud.common.http.request_async import ApiRequest, ApiResponse, ResponsePrettyFormatter, \
|
|
38
|
+
RequestPrettyFormatter
|
|
39
|
+
from tencentcloud.common.profile.client_profile import ClientProfile, RegionBreakerProfile
|
|
40
|
+
from tencentcloud.common.retry_async import NoopRetryer
|
|
41
|
+
from tencentcloud.common.sign import Sign
|
|
42
|
+
|
|
43
|
+
warnings.filterwarnings("ignore", module="tencentcloud", category=UserWarning)
|
|
44
|
+
|
|
45
|
+
_json_content = 'application/json'
|
|
46
|
+
_multipart_content = 'multipart/form-data'
|
|
47
|
+
_form_urlencoded_content = 'application/x-www-form-urlencoded'
|
|
48
|
+
_octet_stream = "application/octet-stream"
|
|
49
|
+
|
|
50
|
+
LOGGER_NAME = "tencentcloud_sdk_common"
|
|
51
|
+
|
|
52
|
+
InterceptorType = Callable[["RequestChain"], Awaitable]
|
|
53
|
+
ParamsType = Union[Dict, str, bytes]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class RequestChain(object):
|
|
57
|
+
def __init__(self):
|
|
58
|
+
self.request: Optional[ApiRequest] = None
|
|
59
|
+
|
|
60
|
+
self._inters: List[InterceptorType] = []
|
|
61
|
+
self._idx = 0
|
|
62
|
+
|
|
63
|
+
async def proceed(self):
|
|
64
|
+
interceptor = self._inters[self._idx]
|
|
65
|
+
snapshot = self._new_snapshot()
|
|
66
|
+
snapshot._idx += 1
|
|
67
|
+
return await interceptor(snapshot)
|
|
68
|
+
|
|
69
|
+
def add_interceptor(self, inter: InterceptorType, idx=sys.maxsize):
|
|
70
|
+
self._inters.insert(idx, inter)
|
|
71
|
+
return self
|
|
72
|
+
|
|
73
|
+
def _new_snapshot(self) -> 'RequestChain':
|
|
74
|
+
new_chain = RequestChain()
|
|
75
|
+
new_chain.request = self.request
|
|
76
|
+
new_chain._inters = self._inters
|
|
77
|
+
new_chain._idx = self._idx
|
|
78
|
+
return new_chain
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class AbstractClient(object):
|
|
82
|
+
_requestPath = '/'
|
|
83
|
+
_params = {}
|
|
84
|
+
_apiVersion = ''
|
|
85
|
+
_endpoint = ''
|
|
86
|
+
_service = ''
|
|
87
|
+
_sdkVersion = 'SDK_PYTHON_%s' % tencentcloud.__version__
|
|
88
|
+
_default_content_type = _form_urlencoded_content
|
|
89
|
+
FMT = '%(asctime)s %(process)d %(filename)s L%(lineno)s %(levelname)s %(message)s'
|
|
90
|
+
|
|
91
|
+
def __init__(self, credential: Credential, region: str, profile: ClientProfile = None):
|
|
92
|
+
self.credential = credential
|
|
93
|
+
self.region = region
|
|
94
|
+
self.profile = profile or ClientProfile()
|
|
95
|
+
self.circuit_breaker = None
|
|
96
|
+
|
|
97
|
+
kwargs: Dict = {"timeout": self.profile.httpProfile.reqTimeout}
|
|
98
|
+
|
|
99
|
+
if not self.profile.httpProfile.keepAlive:
|
|
100
|
+
kwargs["limits"] = httpx.Limits(max_keepalive_connections=0)
|
|
101
|
+
|
|
102
|
+
if self.profile.httpProfile.proxy:
|
|
103
|
+
kwargs["proxies"] = self.profile.httpProfile.proxy
|
|
104
|
+
|
|
105
|
+
if self.profile.httpProfile.certification is False:
|
|
106
|
+
kwargs["verify"] = False
|
|
107
|
+
elif isinstance(self.profile.httpProfile.certification, str) and self.profile.httpProfile.certification != "":
|
|
108
|
+
kwargs["cert"] = self.profile.httpProfile.certification
|
|
109
|
+
|
|
110
|
+
self.http_client = httpx.AsyncClient(**kwargs)
|
|
111
|
+
|
|
112
|
+
if not self.profile.disable_region_breaker:
|
|
113
|
+
if self.profile.region_breaker_profile is None:
|
|
114
|
+
self.profile.region_breaker_profile = RegionBreakerProfile()
|
|
115
|
+
self.circuit_breaker = CircuitBreaker(self.profile.region_breaker_profile)
|
|
116
|
+
if self.profile.request_client:
|
|
117
|
+
self.request_client = self._sdkVersion + "; " + self.profile.request_client
|
|
118
|
+
else:
|
|
119
|
+
self.request_client = self._sdkVersion
|
|
120
|
+
|
|
121
|
+
async def __aenter__(self):
|
|
122
|
+
return self
|
|
123
|
+
|
|
124
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
125
|
+
await self.close()
|
|
126
|
+
|
|
127
|
+
async def close(self):
|
|
128
|
+
await self.http_client.aclose()
|
|
129
|
+
|
|
130
|
+
async def call_and_deserialize(
|
|
131
|
+
self,
|
|
132
|
+
action: str,
|
|
133
|
+
params: ParamsType,
|
|
134
|
+
resp_cls: Type = dict,
|
|
135
|
+
headers: Dict[str, str] = None,
|
|
136
|
+
opts: Dict = None,
|
|
137
|
+
):
|
|
138
|
+
opts = opts or {}
|
|
139
|
+
headers = headers or {}
|
|
140
|
+
chain = self._create_default_chain(action, params, resp_cls, headers, opts)
|
|
141
|
+
return await chain.proceed()
|
|
142
|
+
|
|
143
|
+
def _create_default_chain(
|
|
144
|
+
self,
|
|
145
|
+
action: str,
|
|
146
|
+
params: ParamsType,
|
|
147
|
+
resp_cls: Type,
|
|
148
|
+
headers: Dict[str, str],
|
|
149
|
+
opts: Dict,
|
|
150
|
+
):
|
|
151
|
+
chain = RequestChain()
|
|
152
|
+
chain.add_interceptor(self._inter_retry)
|
|
153
|
+
chain.add_interceptor(self._inter_deserialize_resp(resp_cls))
|
|
154
|
+
if self.circuit_breaker:
|
|
155
|
+
chain.add_interceptor(self._inter_breaker(opts))
|
|
156
|
+
chain.add_interceptor(self._inter_build_request(action, params, headers, opts))
|
|
157
|
+
chain.add_interceptor(self._inter_send_request)
|
|
158
|
+
return chain
|
|
159
|
+
|
|
160
|
+
async def _inter_retry(self, chain: RequestChain):
|
|
161
|
+
retryer = self.profile.retryer or NoopRetryer()
|
|
162
|
+
return await retryer.send_request(chain.proceed)
|
|
163
|
+
|
|
164
|
+
def _inter_build_request(
|
|
165
|
+
self,
|
|
166
|
+
action: str,
|
|
167
|
+
params: ParamsType,
|
|
168
|
+
headers: Dict[str, str],
|
|
169
|
+
opts: Dict,
|
|
170
|
+
):
|
|
171
|
+
async def inter(chain: RequestChain):
|
|
172
|
+
nonlocal action, params, opts, headers
|
|
173
|
+
|
|
174
|
+
if headers is None:
|
|
175
|
+
headers = {}
|
|
176
|
+
|
|
177
|
+
if not isinstance(headers, dict):
|
|
178
|
+
raise TencentCloudSDKException("ClientError", "headers must be a dict.")
|
|
179
|
+
|
|
180
|
+
if "x-tc-traceid" not in (k.lower() for k in headers.keys()):
|
|
181
|
+
headers["X-TC-TraceId"] = str(uuid.uuid4())
|
|
182
|
+
|
|
183
|
+
req = self._build_req(action, params, headers, opts)
|
|
184
|
+
|
|
185
|
+
if self.profile.httpProfile.apigw_endpoint:
|
|
186
|
+
req.host = self.profile.httpProfile.apigw_endpoint
|
|
187
|
+
req.headers["Host"] = req.host
|
|
188
|
+
|
|
189
|
+
# 低版本的 httpx 不会使用 Client.timeout, 需要 per request setup
|
|
190
|
+
req.extensions["timeout"] = self.http_client.timeout.as_dict()
|
|
191
|
+
chain.request = req
|
|
192
|
+
|
|
193
|
+
logger.debug("SendRequest:\n%s", RequestPrettyFormatter(req))
|
|
194
|
+
|
|
195
|
+
return await chain.proceed()
|
|
196
|
+
|
|
197
|
+
return inter
|
|
198
|
+
|
|
199
|
+
def _inter_breaker(self, opts: Dict):
|
|
200
|
+
async def inter(chain: RequestChain):
|
|
201
|
+
generation, need_break = self.circuit_breaker.before_requests()
|
|
202
|
+
|
|
203
|
+
endpoint = self._get_endpoint(opts=opts)
|
|
204
|
+
if need_break:
|
|
205
|
+
endpoint = self._service + "." + self.profile.region_breaker_profile.backup_endpoint
|
|
206
|
+
|
|
207
|
+
url = self.profile.httpProfile.scheme + endpoint + self._requestPath
|
|
208
|
+
chain.request.url = url
|
|
209
|
+
|
|
210
|
+
resp = None
|
|
211
|
+
try:
|
|
212
|
+
resp = await chain.proceed()
|
|
213
|
+
self.circuit_breaker.after_requests(generation, True)
|
|
214
|
+
return resp
|
|
215
|
+
except httpx.TransportError:
|
|
216
|
+
self.circuit_breaker.after_requests(generation, False)
|
|
217
|
+
raise
|
|
218
|
+
except TencentCloudSDKException as e:
|
|
219
|
+
success = resp and "RequestId" in (await resp.aread()) and e.code != "InternalError"
|
|
220
|
+
self.circuit_breaker.after_requests(generation, success)
|
|
221
|
+
raise
|
|
222
|
+
|
|
223
|
+
return inter
|
|
224
|
+
|
|
225
|
+
@staticmethod
|
|
226
|
+
def _inter_deserialize_resp(resp_cls: Type):
|
|
227
|
+
async def inter(chain: RequestChain):
|
|
228
|
+
resp = await chain.proceed()
|
|
229
|
+
|
|
230
|
+
await check_err(resp)
|
|
231
|
+
|
|
232
|
+
content_type = resp.headers["Content-Type"]
|
|
233
|
+
if content_type == "text/event-stream":
|
|
234
|
+
return deserialize_sse(resp)
|
|
235
|
+
|
|
236
|
+
return await deserialize_json(resp)
|
|
237
|
+
|
|
238
|
+
async def check_err(resp: ApiResponse):
|
|
239
|
+
if resp.status_code != 200:
|
|
240
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
241
|
+
logger.debug("GetResponse: %s", await ResponsePrettyFormatter(resp, format_body=True).astr())
|
|
242
|
+
raise TencentCloudSDKException("ServerNetworkError", await resp.aread())
|
|
243
|
+
|
|
244
|
+
ct = resp.headers.get('Content-Type')
|
|
245
|
+
if ct not in ('text/plain', _json_content):
|
|
246
|
+
return
|
|
247
|
+
|
|
248
|
+
content = await resp.aread()
|
|
249
|
+
data = json.loads(content)
|
|
250
|
+
if "Error" in data["Response"]:
|
|
251
|
+
code = data["Response"]["Error"]["Code"]
|
|
252
|
+
message = data["Response"]["Error"]["Message"]
|
|
253
|
+
reqid = data["Response"]["RequestId"]
|
|
254
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
255
|
+
logger.debug("GetResponse: %s", await ResponsePrettyFormatter(resp, format_body=True).astr())
|
|
256
|
+
raise TencentCloudSDKException(code, message, reqid)
|
|
257
|
+
if "DeprecatedWarning" in data["Response"]:
|
|
258
|
+
import warnings
|
|
259
|
+
warnings.filterwarnings("default")
|
|
260
|
+
warnings.warn("This action is deprecated, detail: %s" % data["Response"]["DeprecatedWarning"],
|
|
261
|
+
DeprecationWarning)
|
|
262
|
+
|
|
263
|
+
async def deserialize_json(resp: ApiResponse):
|
|
264
|
+
try:
|
|
265
|
+
if logger.isEnabledFor(logging.DEBUG):
|
|
266
|
+
logger.debug("GetResponse: %s", await ResponsePrettyFormatter(resp, format_body=True).astr())
|
|
267
|
+
|
|
268
|
+
content = await resp.aread()
|
|
269
|
+
if resp_cls == dict:
|
|
270
|
+
return json.loads(content)
|
|
271
|
+
|
|
272
|
+
if issubclass(resp_cls, AbstractModel):
|
|
273
|
+
resp_model = resp_cls()
|
|
274
|
+
resp_model._deserialize(json.loads(content)["Response"])
|
|
275
|
+
return resp_model
|
|
276
|
+
|
|
277
|
+
raise TencentCloudSDKException("ClientParamsError", "invalid resp_cls %s" % resp_cls)
|
|
278
|
+
finally:
|
|
279
|
+
await resp.aclose()
|
|
280
|
+
|
|
281
|
+
async def deserialize_sse(resp: ApiResponse):
|
|
282
|
+
logger.debug("GetResponse:\n%s", ResponsePrettyFormatter(resp, format_body=False))
|
|
283
|
+
e = {}
|
|
284
|
+
|
|
285
|
+
try:
|
|
286
|
+
async for line in resp.aiter_lines():
|
|
287
|
+
line = line.strip()
|
|
288
|
+
if not line:
|
|
289
|
+
yield e
|
|
290
|
+
e = {}
|
|
291
|
+
continue
|
|
292
|
+
|
|
293
|
+
logger.debug("GetResponse.Readline: %s", line)
|
|
294
|
+
|
|
295
|
+
# comment
|
|
296
|
+
if line[0] == ':':
|
|
297
|
+
continue
|
|
298
|
+
|
|
299
|
+
colon_idx = line.find(':')
|
|
300
|
+
key = line[:colon_idx]
|
|
301
|
+
val = line[colon_idx + 1:]
|
|
302
|
+
# If value starts with a U+0020 SPACE character, remove it from value.
|
|
303
|
+
if val and val[0] == " ":
|
|
304
|
+
val = val[1:]
|
|
305
|
+
if key == 'data':
|
|
306
|
+
# The spec allows for multiple data fields per event, concatenated them with "\n".
|
|
307
|
+
if 'data' not in e:
|
|
308
|
+
e['data'] = val
|
|
309
|
+
else:
|
|
310
|
+
e['data'] += '\n' + val
|
|
311
|
+
elif key in ('event', 'id'):
|
|
312
|
+
e[key] = val
|
|
313
|
+
elif key == 'retry':
|
|
314
|
+
e[key] = int(val)
|
|
315
|
+
finally:
|
|
316
|
+
await resp.aclose()
|
|
317
|
+
|
|
318
|
+
return inter
|
|
319
|
+
|
|
320
|
+
async def _inter_send_request(self, chain: RequestChain):
|
|
321
|
+
return await self.http_client.send(chain.request, stream=True)
|
|
322
|
+
|
|
323
|
+
def _get_service_domain(self):
|
|
324
|
+
root_domain = self.profile.httpProfile.rootDomain
|
|
325
|
+
return self._service + "." + root_domain
|
|
326
|
+
|
|
327
|
+
def _get_endpoint(self, opts=None):
|
|
328
|
+
endpoint = self.profile.httpProfile.endpoint
|
|
329
|
+
if not endpoint and opts:
|
|
330
|
+
endpoint = urlparse(opts.get("Endpoint", "")).hostname
|
|
331
|
+
if endpoint is None:
|
|
332
|
+
endpoint = self._get_service_domain()
|
|
333
|
+
return endpoint
|
|
334
|
+
|
|
335
|
+
def _build_req(self, action: str, params: ParamsType, headers: Dict[str, str], opts: Dict) -> ApiRequest:
|
|
336
|
+
if opts.get('SkipSign'):
|
|
337
|
+
return self._build_req_without_signature(action, params, headers, opts)
|
|
338
|
+
elif self.profile.signMethod == "TC3-HMAC-SHA256" or opts.get("IsMultipart") is True:
|
|
339
|
+
return self._build_req_with_tc3_signature(action, params, headers, opts)
|
|
340
|
+
elif self.profile.signMethod in ("HmacSHA1", "HmacSHA256"):
|
|
341
|
+
return self._build_req_with_old_signature(action, params, headers, opts)
|
|
342
|
+
else:
|
|
343
|
+
raise TencentCloudSDKException("ClientError", "Invalid signature method.")
|
|
344
|
+
|
|
345
|
+
def _build_req_without_signature(
|
|
346
|
+
self, action: str, params: ParamsType, headers: Dict[str, str], opts: Dict) -> ApiRequest:
|
|
347
|
+
method = self.profile.httpProfile.reqMethod
|
|
348
|
+
endpoint = self._get_endpoint(opts=opts)
|
|
349
|
+
url = "%s://%s%s" % (self.profile.httpProfile.scheme, endpoint, self._requestPath)
|
|
350
|
+
query = {}
|
|
351
|
+
body = ""
|
|
352
|
+
|
|
353
|
+
content_type = self._default_content_type
|
|
354
|
+
if method == 'GET':
|
|
355
|
+
content_type = _form_urlencoded_content
|
|
356
|
+
elif method == 'POST':
|
|
357
|
+
content_type = _json_content
|
|
358
|
+
if opts.get("IsMultipart"):
|
|
359
|
+
content_type = _multipart_content
|
|
360
|
+
if opts.get("IsOctetStream"):
|
|
361
|
+
content_type = _octet_stream
|
|
362
|
+
headers["Content-Type"] = content_type
|
|
363
|
+
|
|
364
|
+
if method == "GET" and content_type == _multipart_content:
|
|
365
|
+
raise TencentCloudSDKException("ClientError", "Invalid request method GET for multipart.")
|
|
366
|
+
|
|
367
|
+
endpoint = self._get_endpoint(opts=opts)
|
|
368
|
+
timestamp = int(time.time())
|
|
369
|
+
headers["Host"] = endpoint
|
|
370
|
+
headers["X-TC-Action"] = action[0].upper() + action[1:]
|
|
371
|
+
headers["X-TC-RequestClient"] = self.request_client
|
|
372
|
+
headers["X-TC-Timestamp"] = str(timestamp)
|
|
373
|
+
headers["X-TC-Version"] = self._apiVersion
|
|
374
|
+
if self.profile.unsignedPayload is True:
|
|
375
|
+
headers["X-TC-Content-SHA256"] = "UNSIGNED-PAYLOAD"
|
|
376
|
+
if self.region:
|
|
377
|
+
headers['X-TC-Region'] = self.region
|
|
378
|
+
if self.profile.language:
|
|
379
|
+
headers['X-TC-Language'] = self.profile.language
|
|
380
|
+
|
|
381
|
+
if method == 'GET':
|
|
382
|
+
params = copy.deepcopy(self._fix_params(params))
|
|
383
|
+
url += "?" + urlencode(params)
|
|
384
|
+
elif content_type == _json_content:
|
|
385
|
+
body = json.dumps(params)
|
|
386
|
+
elif content_type == _multipart_content:
|
|
387
|
+
boundary = uuid.uuid4().hex
|
|
388
|
+
headers["Content-Type"] = content_type + "; boundary=" + boundary
|
|
389
|
+
body = self._get_multipart_body(params, boundary, opts)
|
|
390
|
+
|
|
391
|
+
headers["Authorization"] = "SKIP"
|
|
392
|
+
return ApiRequest(method, url, params=query, content=body, headers=headers)
|
|
393
|
+
|
|
394
|
+
def _build_req_with_tc3_signature(
|
|
395
|
+
self, action: str, params: ParamsType, headers: Dict[str, str], opts: Dict) -> ApiRequest:
|
|
396
|
+
method = self.profile.httpProfile.reqMethod
|
|
397
|
+
endpoint = self._get_endpoint(opts=opts)
|
|
398
|
+
host = headers.get("Host", endpoint)
|
|
399
|
+
url = "%s://%s%s" % (self.profile.httpProfile.scheme, endpoint, self._requestPath)
|
|
400
|
+
query = ""
|
|
401
|
+
body = ""
|
|
402
|
+
|
|
403
|
+
content_type = self._default_content_type
|
|
404
|
+
if method == 'GET':
|
|
405
|
+
content_type = _form_urlencoded_content
|
|
406
|
+
elif method == 'POST':
|
|
407
|
+
content_type = _json_content
|
|
408
|
+
if opts.get("IsMultipart"):
|
|
409
|
+
content_type = _multipart_content
|
|
410
|
+
elif opts.get("IsOctetStream"):
|
|
411
|
+
if method != "POST":
|
|
412
|
+
raise TencentCloudSDKException("ClientError", "Invalid request method.")
|
|
413
|
+
content_type = _octet_stream
|
|
414
|
+
headers["Content-Type"] = content_type
|
|
415
|
+
|
|
416
|
+
if method == "GET" and content_type == _multipart_content:
|
|
417
|
+
raise TencentCloudSDKException("ClientError", "Invalid request method GET for multipart.")
|
|
418
|
+
|
|
419
|
+
timestamp = int(time.time())
|
|
420
|
+
cred_secret_id, cred_secret_key, cred_token = self.credential.get_credential_info()
|
|
421
|
+
headers["Host"] = host
|
|
422
|
+
headers["X-TC-Action"] = action[0].upper() + action[1:]
|
|
423
|
+
headers["X-TC-RequestClient"] = self.request_client
|
|
424
|
+
headers["X-TC-Timestamp"] = str(timestamp)
|
|
425
|
+
headers["X-TC-Version"] = self._apiVersion
|
|
426
|
+
if self.profile.unsignedPayload is True:
|
|
427
|
+
headers["X-TC-Content-SHA256"] = "UNSIGNED-PAYLOAD"
|
|
428
|
+
if self.region:
|
|
429
|
+
headers['X-TC-Region'] = self.region
|
|
430
|
+
if cred_token:
|
|
431
|
+
headers['X-TC-Token'] = cred_token
|
|
432
|
+
if self.profile.language:
|
|
433
|
+
headers['X-TC-Language'] = self.profile.language
|
|
434
|
+
|
|
435
|
+
if method == 'GET':
|
|
436
|
+
query = urlencode(copy.deepcopy(self._fix_params(params)))
|
|
437
|
+
elif content_type == _json_content:
|
|
438
|
+
body = json.dumps(params)
|
|
439
|
+
elif content_type == _multipart_content:
|
|
440
|
+
boundary = uuid.uuid4().hex
|
|
441
|
+
headers["Content-Type"] = content_type + "; boundary=" + boundary
|
|
442
|
+
body = self._get_multipart_body(params, boundary, opts)
|
|
443
|
+
elif content_type == _octet_stream:
|
|
444
|
+
body = params
|
|
445
|
+
|
|
446
|
+
if isinstance(body, str):
|
|
447
|
+
body = body.encode("utf-8")
|
|
448
|
+
|
|
449
|
+
service = self._service
|
|
450
|
+
date = datetime.utcfromtimestamp(timestamp).strftime('%Y-%m-%d')
|
|
451
|
+
signature = self._get_tc3_signature(
|
|
452
|
+
method, self._requestPath, query, body, headers, date, service, cred_secret_key, opts)
|
|
453
|
+
|
|
454
|
+
auth = "TC3-HMAC-SHA256 Credential=%s/%s/%s/tc3_request, SignedHeaders=content-type;host, Signature=%s" % (
|
|
455
|
+
cred_secret_id, date, service, signature)
|
|
456
|
+
headers["Authorization"] = auth
|
|
457
|
+
# httpx 0.22.0 版本会过滤掉空 value 的 params 如 "a=&b=2"
|
|
458
|
+
# 不能使用 params 参数, 需要用 url 绕过, 后续版本已经修复, 但是 py36 最高只能安装 0.22.0
|
|
459
|
+
url += "?" + query
|
|
460
|
+
return ApiRequest(method, url, content=body, headers=headers)
|
|
461
|
+
|
|
462
|
+
def _build_req_with_old_signature(
|
|
463
|
+
self, action: str, params: ParamsType, headers: Dict[str, str], opts: Dict) -> ApiRequest:
|
|
464
|
+
|
|
465
|
+
if opts.get("IsOctetStream"):
|
|
466
|
+
raise TencentCloudSDKException("ClientError", "Invalid signature method.")
|
|
467
|
+
|
|
468
|
+
method = self.profile.httpProfile.reqMethod
|
|
469
|
+
endpoint = self._get_endpoint(opts=opts)
|
|
470
|
+
url = "%s://%s%s" % (self.profile.httpProfile.scheme, endpoint, self._requestPath)
|
|
471
|
+
query = {}
|
|
472
|
+
body = ""
|
|
473
|
+
|
|
474
|
+
params = copy.deepcopy(self._fix_params(params))
|
|
475
|
+
params['Action'] = action[0].upper() + action[1:]
|
|
476
|
+
params['RequestClient'] = self.request_client
|
|
477
|
+
params['Nonce'] = random.randint(1, sys.maxsize)
|
|
478
|
+
params['Timestamp'] = int(time.time())
|
|
479
|
+
params['Version'] = self._apiVersion
|
|
480
|
+
|
|
481
|
+
cred_secret_id, cred_secret_key, cred_token = self.credential.get_credential_info()
|
|
482
|
+
|
|
483
|
+
if self.region:
|
|
484
|
+
params['Region'] = self.region
|
|
485
|
+
|
|
486
|
+
if cred_token:
|
|
487
|
+
params['Token'] = cred_token
|
|
488
|
+
|
|
489
|
+
if cred_secret_id:
|
|
490
|
+
params['SecretId'] = cred_secret_id
|
|
491
|
+
|
|
492
|
+
if self.profile.signMethod:
|
|
493
|
+
params['SignatureMethod'] = self.profile.signMethod
|
|
494
|
+
|
|
495
|
+
if self.profile.language:
|
|
496
|
+
params['Language'] = self.profile.language
|
|
497
|
+
|
|
498
|
+
signInParam = self._format_sign_string(params, opts)
|
|
499
|
+
params['Signature'] = Sign.sign(str(cred_secret_key),
|
|
500
|
+
str(signInParam),
|
|
501
|
+
str(self.profile.signMethod))
|
|
502
|
+
if method == "GET":
|
|
503
|
+
query = params
|
|
504
|
+
else:
|
|
505
|
+
body = urlencode(params)
|
|
506
|
+
|
|
507
|
+
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
|
508
|
+
return ApiRequest(method, url, params=query, content=body, headers=headers)
|
|
509
|
+
|
|
510
|
+
def _format_sign_string(self, params, opts=None):
|
|
511
|
+
formatParam = {}
|
|
512
|
+
for k in params:
|
|
513
|
+
formatParam[k.replace('_', '.')] = params[k]
|
|
514
|
+
strParam = '&'.join('%s=%s' % (k, formatParam[k]) for k in sorted(formatParam))
|
|
515
|
+
msg = '%s%s%s?%s' % (
|
|
516
|
+
self.profile.httpProfile.reqMethod, self._get_endpoint(opts=opts), self._requestPath, strParam)
|
|
517
|
+
return msg
|
|
518
|
+
|
|
519
|
+
def _fix_params(self, params):
|
|
520
|
+
if not isinstance(params, (dict,)):
|
|
521
|
+
return params
|
|
522
|
+
return self._format_params(None, params)
|
|
523
|
+
|
|
524
|
+
def _format_params(self, prefix, params):
|
|
525
|
+
d = {}
|
|
526
|
+
if params is None:
|
|
527
|
+
return d
|
|
528
|
+
|
|
529
|
+
if not isinstance(params, (tuple, list, dict)):
|
|
530
|
+
d[prefix] = params
|
|
531
|
+
return d
|
|
532
|
+
|
|
533
|
+
if isinstance(params, (list, tuple)):
|
|
534
|
+
for idx, item in enumerate(params):
|
|
535
|
+
if prefix:
|
|
536
|
+
key = "{0}.{1}".format(prefix, idx)
|
|
537
|
+
else:
|
|
538
|
+
key = "{0}".format(idx)
|
|
539
|
+
d.update(self._format_params(key, item))
|
|
540
|
+
return d
|
|
541
|
+
|
|
542
|
+
if isinstance(params, dict):
|
|
543
|
+
for k, v in params.items():
|
|
544
|
+
if prefix:
|
|
545
|
+
key = '{0}.{1}'.format(prefix, k)
|
|
546
|
+
else:
|
|
547
|
+
key = '{0}'.format(k)
|
|
548
|
+
d.update(self._format_params(key, v))
|
|
549
|
+
return d
|
|
550
|
+
|
|
551
|
+
raise TencentCloudSDKException("ClientParamsError", "some params type error")
|
|
552
|
+
|
|
553
|
+
def _get_multipart_body(self, params, boundary, options=None):
|
|
554
|
+
if options is None:
|
|
555
|
+
options = {}
|
|
556
|
+
# boundary and params key will never contain unicode characters
|
|
557
|
+
boundary = boundary.encode()
|
|
558
|
+
binparas = options.get("BinaryParams", [])
|
|
559
|
+
body = b''
|
|
560
|
+
for k, v in params.items():
|
|
561
|
+
kbytes = k.encode()
|
|
562
|
+
body += b'--%s\r\n' % boundary
|
|
563
|
+
body += b'Content-Disposition: form-data; name="%s"' % kbytes
|
|
564
|
+
if k in binparas:
|
|
565
|
+
body += b'; filename="%s"\r\n' % kbytes
|
|
566
|
+
else:
|
|
567
|
+
body += b"\r\n"
|
|
568
|
+
if isinstance(v, list) or isinstance(v, dict):
|
|
569
|
+
v = json.dumps(v)
|
|
570
|
+
body += b'Content-Type: application/json\r\n'
|
|
571
|
+
if sys.version_info[0] == 3 and isinstance(v, type("")):
|
|
572
|
+
v = v.encode()
|
|
573
|
+
body += b'\r\n%s\r\n' % v
|
|
574
|
+
if body != b'':
|
|
575
|
+
body += b'--%s--\r\n' % boundary
|
|
576
|
+
return body
|
|
577
|
+
|
|
578
|
+
@staticmethod
|
|
579
|
+
def _get_tc3_signature(method: str, path: str, query: str, body: bytes, headers: Dict, date: str, service: str,
|
|
580
|
+
secret_key: str, opts: Dict):
|
|
581
|
+
canonical_uri = path
|
|
582
|
+
canonical_querystring = query
|
|
583
|
+
payload = body
|
|
584
|
+
|
|
585
|
+
if headers.get("X-TC-Content-SHA256") == "UNSIGNED-PAYLOAD":
|
|
586
|
+
payload = b"UNSIGNED-PAYLOAD"
|
|
587
|
+
|
|
588
|
+
payload_hash = hashlib.sha256(payload).hexdigest()
|
|
589
|
+
|
|
590
|
+
canonical_headers = 'content-type:%s\nhost:%s\n' % (
|
|
591
|
+
headers["Content-Type"], headers["Host"])
|
|
592
|
+
signed_headers = 'content-type;host'
|
|
593
|
+
canonical_request = '%s\n%s\n%s\n%s\n%s\n%s' % (method,
|
|
594
|
+
canonical_uri,
|
|
595
|
+
canonical_querystring,
|
|
596
|
+
canonical_headers,
|
|
597
|
+
signed_headers,
|
|
598
|
+
payload_hash)
|
|
599
|
+
|
|
600
|
+
algorithm = 'TC3-HMAC-SHA256'
|
|
601
|
+
credential_scope = date + '/' + service + '/tc3_request'
|
|
602
|
+
if sys.version_info[0] == 3:
|
|
603
|
+
canonical_request = canonical_request.encode("utf8")
|
|
604
|
+
digest = hashlib.sha256(canonical_request).hexdigest()
|
|
605
|
+
string2sign = '%s\n%s\n%s\n%s' % (algorithm,
|
|
606
|
+
headers["X-TC-Timestamp"],
|
|
607
|
+
credential_scope,
|
|
608
|
+
digest)
|
|
609
|
+
return Sign.sign_tc3(secret_key, date, service, string2sign)
|
|
610
|
+
|
|
611
|
+
def set_stream_logger(self, stream=None, level=logging.DEBUG, log_format=None):
|
|
612
|
+
"""Add a stream handler
|
|
613
|
+
|
|
614
|
+
:param stream: e.g. ``sys.stdout`` ``sys.stdin`` ``sys.stderr``
|
|
615
|
+
:type stream: IO[str]
|
|
616
|
+
:param level: Logging level, e.g. ``logging.INFO``
|
|
617
|
+
:type level: int
|
|
618
|
+
:param log_format: Log message format
|
|
619
|
+
:type log_format: str
|
|
620
|
+
"""
|
|
621
|
+
log = logging.getLogger(LOGGER_NAME)
|
|
622
|
+
log.setLevel(level)
|
|
623
|
+
sh = logging.StreamHandler(stream)
|
|
624
|
+
sh.setLevel(level)
|
|
625
|
+
if log_format is None:
|
|
626
|
+
log_format = self.FMT
|
|
627
|
+
formatter = logging.Formatter(log_format)
|
|
628
|
+
sh.setFormatter(formatter)
|
|
629
|
+
log.addHandler(sh)
|
|
630
|
+
|
|
631
|
+
def set_file_logger(self, file_path, level=logging.DEBUG, log_format=None):
|
|
632
|
+
"""Add a file handler
|
|
633
|
+
|
|
634
|
+
:param file_path: path of log file
|
|
635
|
+
:type file_path: str
|
|
636
|
+
:param level: Logging level, e.g. ``logging.INFO``
|
|
637
|
+
:type level: int
|
|
638
|
+
:param log_format: Log message format
|
|
639
|
+
:type log_format: str
|
|
640
|
+
"""
|
|
641
|
+
log = logging.getLogger(LOGGER_NAME)
|
|
642
|
+
log.setLevel(level)
|
|
643
|
+
mb = 1024 * 1024
|
|
644
|
+
fh = logging.handlers.RotatingFileHandler(file_path, maxBytes=512 * mb, backupCount=10)
|
|
645
|
+
fh.setLevel(level)
|
|
646
|
+
if log_format is None:
|
|
647
|
+
log_format = self.FMT
|
|
648
|
+
formatter = logging.Formatter(log_format)
|
|
649
|
+
fh.setFormatter(formatter)
|
|
650
|
+
log.addHandler(fh)
|
|
651
|
+
|
|
652
|
+
def set_default_logger(self):
|
|
653
|
+
"""Set default log handler"""
|
|
654
|
+
pass
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 1999-2017 Tencent Ltd.
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
from tencentcloud.common.abstract_client_async import AbstractClient
|
|
18
|
+
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CommonClient(AbstractClient):
|
|
22
|
+
"""General client for all products.
|
|
23
|
+
|
|
24
|
+
With CommonClient, you only need to install the tencentcloud-sdk-python-common package to access APIs of all products.
|
|
25
|
+
See GitHub examples for usage details: https://github.com/TencentCloud/tencentcloud-sdk-python/tree/master/examples/common_client
|
|
26
|
+
|
|
27
|
+
:param service: Product name
|
|
28
|
+
:type service: str
|
|
29
|
+
:param version: Version of API
|
|
30
|
+
:type version: str
|
|
31
|
+
:param credential: Request credential
|
|
32
|
+
:type credential: tencentcloud.common.credential.Credential or tencentcloud.common.credential.STSAssumeRoleCredential or None
|
|
33
|
+
:param region: Request region
|
|
34
|
+
:type region: str
|
|
35
|
+
:param profile: Request SDK profile
|
|
36
|
+
:type profile: tencentcloud.common.profile.client_profile.ClientProfile
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, service, version, credential, region, profile=None):
|
|
40
|
+
if region is None or version is None or service is None:
|
|
41
|
+
raise TencentCloudSDKException("CommonClient Parameter Error, "
|
|
42
|
+
"credential region version service all required.")
|
|
43
|
+
self._apiVersion = version
|
|
44
|
+
self._service = service
|
|
45
|
+
super(CommonClient, self).__init__(credential, region, profile)
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import json
|
|
17
17
|
import os
|
|
18
18
|
import time
|
|
19
|
+
import threading
|
|
19
20
|
|
|
20
21
|
try:
|
|
21
22
|
# py3
|
|
@@ -70,6 +71,9 @@ class Credential(object):
|
|
|
70
71
|
@property
|
|
71
72
|
def secretKey(self):
|
|
72
73
|
return self.secret_key
|
|
74
|
+
|
|
75
|
+
def get_credential_info(self):
|
|
76
|
+
return self.secret_id, self.secret_key, self.token
|
|
73
77
|
|
|
74
78
|
|
|
75
79
|
class CVMRoleCredential(object):
|
|
@@ -91,6 +95,7 @@ class CVMRoleCredential(object):
|
|
|
91
95
|
self._secret_key = None
|
|
92
96
|
self._token = None
|
|
93
97
|
self._expired_ts = 0
|
|
98
|
+
self._lock = threading.Lock()
|
|
94
99
|
|
|
95
100
|
@property
|
|
96
101
|
def secretId(self):
|
|
@@ -98,8 +103,9 @@ class CVMRoleCredential(object):
|
|
|
98
103
|
|
|
99
104
|
@property
|
|
100
105
|
def secret_id(self):
|
|
101
|
-
self.
|
|
102
|
-
|
|
106
|
+
with self._lock:
|
|
107
|
+
self.update_credential()
|
|
108
|
+
return self._secret_id
|
|
103
109
|
|
|
104
110
|
@property
|
|
105
111
|
def secretKey(self):
|
|
@@ -107,13 +113,15 @@ class CVMRoleCredential(object):
|
|
|
107
113
|
|
|
108
114
|
@property
|
|
109
115
|
def secret_key(self):
|
|
110
|
-
self.
|
|
111
|
-
|
|
116
|
+
with self._lock:
|
|
117
|
+
self.update_credential()
|
|
118
|
+
return self._secret_key
|
|
112
119
|
|
|
113
120
|
@property
|
|
114
121
|
def token(self):
|
|
115
|
-
self.
|
|
116
|
-
|
|
122
|
+
with self._lock:
|
|
123
|
+
self.update_credential()
|
|
124
|
+
return self._token
|
|
117
125
|
|
|
118
126
|
def get_role_name(self):
|
|
119
127
|
if self.role:
|
|
@@ -159,6 +167,11 @@ class CVMRoleCredential(object):
|
|
|
159
167
|
return None
|
|
160
168
|
return self
|
|
161
169
|
|
|
170
|
+
def get_credential_info(self):
|
|
171
|
+
with self._lock:
|
|
172
|
+
self.update_credential()
|
|
173
|
+
return self._secret_id, self._secret_key, self._token
|
|
174
|
+
|
|
162
175
|
|
|
163
176
|
class STSAssumeRoleCredential(object):
|
|
164
177
|
"""Tencent Cloud Credential via STS service
|
|
@@ -200,31 +213,42 @@ class STSAssumeRoleCredential(object):
|
|
|
200
213
|
self._tmp_credential = None
|
|
201
214
|
if endpoint:
|
|
202
215
|
self._endpoint = endpoint
|
|
216
|
+
self._lock = threading.Lock()
|
|
203
217
|
|
|
204
218
|
@property
|
|
205
219
|
def secretId(self):
|
|
206
|
-
self.
|
|
207
|
-
|
|
220
|
+
with self._lock:
|
|
221
|
+
self._need_refresh()
|
|
222
|
+
return self._tmp_secret_id
|
|
208
223
|
|
|
209
224
|
@property
|
|
210
225
|
def secretKey(self):
|
|
211
|
-
self.
|
|
212
|
-
|
|
226
|
+
with self._lock:
|
|
227
|
+
self._need_refresh()
|
|
228
|
+
return self._tmp_secret_key
|
|
213
229
|
|
|
214
230
|
@property
|
|
215
231
|
def secret_id(self):
|
|
216
|
-
self.
|
|
217
|
-
|
|
232
|
+
with self._lock:
|
|
233
|
+
self._need_refresh()
|
|
234
|
+
return self._tmp_secret_id
|
|
218
235
|
|
|
219
236
|
@property
|
|
220
237
|
def secret_key(self):
|
|
221
|
-
self.
|
|
222
|
-
|
|
238
|
+
with self._lock:
|
|
239
|
+
self._need_refresh()
|
|
240
|
+
return self._tmp_secret_key
|
|
223
241
|
|
|
224
242
|
@property
|
|
225
243
|
def token(self):
|
|
226
|
-
self.
|
|
227
|
-
|
|
244
|
+
with self._lock:
|
|
245
|
+
self._need_refresh()
|
|
246
|
+
return self._token
|
|
247
|
+
|
|
248
|
+
def get_credential_info(self):
|
|
249
|
+
with self._lock:
|
|
250
|
+
self._need_refresh()
|
|
251
|
+
return self._tmp_secret_id, self._tmp_secret_key, self._token
|
|
228
252
|
|
|
229
253
|
def _need_refresh(self):
|
|
230
254
|
if None in [self._token, self._tmp_secret_key, self._tmp_secret_id] or self._expired_time < int(time.time()):
|
|
@@ -283,9 +307,8 @@ class ProfileCredential(object):
|
|
|
283
307
|
"""
|
|
284
308
|
|
|
285
309
|
def get_credential(self):
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
file_path = home_path + "/.tencentcloud/credentials"
|
|
310
|
+
if os.path.exists(os.path.expanduser("~/.tencentcloud/credentials")):
|
|
311
|
+
file_path = os.path.expanduser("~/.tencentcloud/credentials")
|
|
289
312
|
elif os.path.exists("/etc/tencentcloud/credentials"):
|
|
290
313
|
file_path = "/etc/tencentcloud/credentials"
|
|
291
314
|
else:
|
|
@@ -389,45 +412,67 @@ class OIDCRoleArnCredential(object):
|
|
|
389
412
|
_service = "sts"
|
|
390
413
|
_action = 'AssumeRoleWithWebIdentity'
|
|
391
414
|
_default_session_name = 'tencentcloud-python-sdk-'
|
|
415
|
+
_endpoint = "sts.tencentcloudapi.com"
|
|
392
416
|
|
|
393
|
-
def __init__(self, region, provider_id, web_identity_token, role_arn, role_session_name, duration_seconds=7200):
|
|
417
|
+
def __init__(self, region, provider_id, web_identity_token, role_arn, role_session_name, duration_seconds=7200, endpoint=None):
|
|
394
418
|
self._region = region
|
|
395
419
|
self._provider_id = provider_id
|
|
396
420
|
self._web_identity_token = web_identity_token
|
|
397
421
|
self._role_arn = role_arn
|
|
398
422
|
self._role_session_name = role_session_name
|
|
399
423
|
self._duration_seconds = duration_seconds
|
|
424
|
+
if endpoint:
|
|
425
|
+
self._endpoint = endpoint
|
|
400
426
|
|
|
401
427
|
self._token = None
|
|
402
428
|
self._tmp_secret_id = None
|
|
403
429
|
self._tmp_secret_key = None
|
|
404
430
|
self._expired_time = 0
|
|
405
431
|
self._is_tke = False
|
|
432
|
+
self._lock = threading.Lock()
|
|
406
433
|
|
|
407
434
|
@property
|
|
408
435
|
def secretId(self):
|
|
409
|
-
self.
|
|
410
|
-
|
|
436
|
+
with self._lock:
|
|
437
|
+
self._keep_fresh()
|
|
438
|
+
return self._tmp_secret_id
|
|
411
439
|
|
|
412
440
|
@property
|
|
413
441
|
def secretKey(self):
|
|
414
|
-
self.
|
|
415
|
-
|
|
442
|
+
with self._lock:
|
|
443
|
+
self._keep_fresh()
|
|
444
|
+
return self._tmp_secret_key
|
|
416
445
|
|
|
417
446
|
@property
|
|
418
447
|
def secret_id(self):
|
|
419
|
-
self.
|
|
420
|
-
|
|
448
|
+
with self._lock:
|
|
449
|
+
self._keep_fresh()
|
|
450
|
+
return self._tmp_secret_id
|
|
421
451
|
|
|
422
452
|
@property
|
|
423
453
|
def secret_key(self):
|
|
424
|
-
self.
|
|
425
|
-
|
|
454
|
+
with self._lock:
|
|
455
|
+
self._keep_fresh()
|
|
456
|
+
return self._tmp_secret_key
|
|
426
457
|
|
|
427
458
|
@property
|
|
428
459
|
def token(self):
|
|
429
|
-
self.
|
|
430
|
-
|
|
460
|
+
with self._lock:
|
|
461
|
+
self._keep_fresh()
|
|
462
|
+
return self._token
|
|
463
|
+
|
|
464
|
+
def get_credential_info(self):
|
|
465
|
+
with self._lock:
|
|
466
|
+
self._keep_fresh()
|
|
467
|
+
return self._tmp_secret_id, self._tmp_secret_key, self._token
|
|
468
|
+
|
|
469
|
+
@property
|
|
470
|
+
def endpoint(self):
|
|
471
|
+
return self._endpoint
|
|
472
|
+
|
|
473
|
+
@endpoint.setter
|
|
474
|
+
def endpoint(self, endpoint):
|
|
475
|
+
self._endpoint = endpoint
|
|
431
476
|
|
|
432
477
|
def _keep_fresh(self):
|
|
433
478
|
if None in [self._token, self._tmp_secret_key, self._tmp_secret_id] or self._expired_time < int(time.time()):
|
|
@@ -436,7 +481,14 @@ class OIDCRoleArnCredential(object):
|
|
|
436
481
|
def refresh(self):
|
|
437
482
|
if self._is_tke:
|
|
438
483
|
self._init_from_tke()
|
|
439
|
-
|
|
484
|
+
|
|
485
|
+
http_profile = HttpProfile()
|
|
486
|
+
http_profile.endpoint = self._endpoint
|
|
487
|
+
client_profile = ClientProfile()
|
|
488
|
+
client_profile.httpProfile = http_profile
|
|
489
|
+
|
|
490
|
+
common_client = CommonClient(credential=None, region=self._region, version=self._version,
|
|
491
|
+
service=self._service, profile=client_profile)
|
|
440
492
|
params = {
|
|
441
493
|
"ProviderId": self._provider_id,
|
|
442
494
|
"WebIdentityToken": self._web_identity_token,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
__all__ = ["ApiRequest", "ApiResponse", "RequestPrettyFormatter", "ResponsePrettyFormatter"]
|
|
6
|
+
|
|
7
|
+
ApiRequest = httpx.Request
|
|
8
|
+
ApiResponse = httpx.Response
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RequestPrettyFormatter(object):
|
|
12
|
+
def __init__(self, req: ApiRequest, format_body=True, delimiter="\n"):
|
|
13
|
+
self._req = req
|
|
14
|
+
self._format_body = format_body
|
|
15
|
+
self._delimiter = delimiter
|
|
16
|
+
|
|
17
|
+
def __str__(self):
|
|
18
|
+
lines = ["%s %s" % (self._req.method, self._req.url)]
|
|
19
|
+
for k, v in self._req.headers.items():
|
|
20
|
+
lines.append("%s: %s" % (k, v))
|
|
21
|
+
lines.append("")
|
|
22
|
+
if self._format_body:
|
|
23
|
+
try:
|
|
24
|
+
lines.append(self._req.content.decode("utf-8"))
|
|
25
|
+
except UnicodeDecodeError:
|
|
26
|
+
# binary body
|
|
27
|
+
import base64
|
|
28
|
+
lines.append("base64_body:" + base64.standard_b64encode(self._req.content).decode())
|
|
29
|
+
return self._delimiter.join(lines)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ResponsePrettyFormatter(object):
|
|
33
|
+
def __init__(self, resp: ApiResponse, format_body=True, delimiter="\n"):
|
|
34
|
+
self._resp = resp
|
|
35
|
+
self._format_body = format_body
|
|
36
|
+
self._delimiter = delimiter
|
|
37
|
+
|
|
38
|
+
def __str__(self):
|
|
39
|
+
lines = ['%s %d %s' % (self.str_ver(self._resp.http_version), self._resp.status_code, self._resp.reason_phrase)]
|
|
40
|
+
for k, v in self._resp.headers.items():
|
|
41
|
+
lines.append('%s: %s' % (k, v))
|
|
42
|
+
return self._delimiter.join(lines)
|
|
43
|
+
|
|
44
|
+
async def astr(self):
|
|
45
|
+
lines = ['%s %d %s' % (self.str_ver(self._resp.http_version), self._resp.status_code, self._resp.reason_phrase)]
|
|
46
|
+
for k, v in self._resp.headers.items():
|
|
47
|
+
lines.append('%s: %s' % (k, v))
|
|
48
|
+
if self._format_body:
|
|
49
|
+
lines.append('')
|
|
50
|
+
lines.append((await self._resp.aread()).decode("utf-8"))
|
|
51
|
+
return self._delimiter.join(lines)
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def str_ver(ver):
|
|
55
|
+
if ver == 10:
|
|
56
|
+
return "HTTP/1.0"
|
|
57
|
+
elif ver == 11:
|
|
58
|
+
return "HTTP/1.1"
|
|
59
|
+
elif ver == 20:
|
|
60
|
+
return "HTTP/2.0"
|
|
61
|
+
else:
|
|
62
|
+
return str(ver)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
|
|
6
|
+
from tencentcloud.common.exception import TencentCloudSDKException
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class NoopRetryer(object):
|
|
10
|
+
"""configuration without retry
|
|
11
|
+
|
|
12
|
+
NoopRetryer is a retry policy that does nothing.
|
|
13
|
+
It is useful when you don't want to retry.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
async def send_request(self, fn):
|
|
17
|
+
return await fn()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class StandardRetryer(object):
|
|
21
|
+
"""Retry configuration
|
|
22
|
+
|
|
23
|
+
StandardRetryer is a retry policy that retries on network errors or frequency limitation.
|
|
24
|
+
:param max_attempts: Maximum number of attempts.
|
|
25
|
+
:type max_attempts: int
|
|
26
|
+
:param backoff_fn: A function that takes the number of attempts and returns the number of seconds to sleep before the next retry.
|
|
27
|
+
Default sleep time is 2^n seconds, n is the number of attempts.
|
|
28
|
+
:type backoff_fn: function
|
|
29
|
+
:param logger: A logger to log retry attempts. If not provided, no logging will be performed.
|
|
30
|
+
:type logger: logging.Logger
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, max_attempts=3, backoff_fn=None, logger=None):
|
|
34
|
+
self._max_attempts = max_attempts
|
|
35
|
+
self._backoff_fn = backoff_fn or self.backoff
|
|
36
|
+
self._logger = logger
|
|
37
|
+
|
|
38
|
+
async def send_request(self, fn):
|
|
39
|
+
resp = None
|
|
40
|
+
err = None
|
|
41
|
+
|
|
42
|
+
for n in range(self._max_attempts):
|
|
43
|
+
try:
|
|
44
|
+
resp = await fn()
|
|
45
|
+
except TencentCloudSDKException as e:
|
|
46
|
+
err = e
|
|
47
|
+
except httpx.TransportError as e:
|
|
48
|
+
err = e
|
|
49
|
+
|
|
50
|
+
if not await self.should_retry(resp, err):
|
|
51
|
+
if err:
|
|
52
|
+
raise err
|
|
53
|
+
return resp
|
|
54
|
+
|
|
55
|
+
sleep = await self._backoff_fn(n)
|
|
56
|
+
await self.on_retry(n, sleep, resp, err)
|
|
57
|
+
await asyncio.sleep(sleep)
|
|
58
|
+
|
|
59
|
+
raise err
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
async def should_retry(resp, err):
|
|
63
|
+
if not err:
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
if isinstance(err, httpx.TransportError):
|
|
67
|
+
return True
|
|
68
|
+
|
|
69
|
+
if not isinstance(err, TencentCloudSDKException):
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
ec = err.get_code()
|
|
73
|
+
if ec in (
|
|
74
|
+
"ClientNetworkError", "ServerNetworkError", "RequestLimitExceeded",
|
|
75
|
+
"RequestLimitExceeded.UinLimitExceeded", "RequestLimitExceeded.GlobalRegionUinLimitExceeded"
|
|
76
|
+
):
|
|
77
|
+
return True
|
|
78
|
+
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
async def backoff(n):
|
|
83
|
+
return 2 ** n
|
|
84
|
+
|
|
85
|
+
async def on_retry(self, n, sleep, resp, err):
|
|
86
|
+
if self._logger:
|
|
87
|
+
self._logger.debug("retry: n=%d sleep=%ss err=%s", n, sleep, err)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tencentcloud-sdk-python-common
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.1.15
|
|
4
4
|
Summary: Tencent Cloud Common SDK for Python
|
|
5
5
|
Home-page: https://github.com/TencentCloud/tencentcloud-sdk-python
|
|
6
6
|
Author: Tencent Cloud
|
|
@@ -15,7 +15,9 @@ Classifier: Programming Language :: Python :: 2.7
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.6
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.7
|
|
18
|
-
Requires-Dist: requests
|
|
18
|
+
Requires-Dist: requests >=2.16.0
|
|
19
|
+
Provides-Extra: async
|
|
20
|
+
Requires-Dist: httpx >=0.22.0 ; extra == 'async'
|
|
19
21
|
|
|
20
22
|
============================
|
|
21
23
|
Tencent Cloud SDK for Python
|
|
@@ -44,5 +46,3 @@ or download source code from github and install:
|
|
|
44
46
|
$ cd tencentcloud-sdk-python
|
|
45
47
|
$ python package.py --components common common
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
|
|
@@ -1,21 +1,25 @@
|
|
|
1
|
-
tencentcloud/__init__.py,sha256=
|
|
1
|
+
tencentcloud/__init__.py,sha256=JuMsQkc9Aytx9jig2NEn0vw6wd4IjT_uL9_Se91Cm4Y,629
|
|
2
2
|
tencentcloud/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
tencentcloud/common/abstract_client.py,sha256=
|
|
3
|
+
tencentcloud/common/abstract_client.py,sha256=hqnD1TruJPHxITCOgFrfZUovb27UWYbt71S-qJB3Bzw,25608
|
|
4
|
+
tencentcloud/common/abstract_client_async.py,sha256=mf9wiOwyB8lV1X1fqbJw_HjMfMUc92rRr5gePsThLkY,25945
|
|
4
5
|
tencentcloud/common/abstract_model.py,sha256=LJ9BkK1Djh_gdx3Cy3trEOtJRGtrM-ikeqRzO1PgwiY,2418
|
|
5
6
|
tencentcloud/common/circuit_breaker.py,sha256=p_6ssklMRtR-YKNGcFLd3ClUvZPs8ZLnVkhjcOZA0DY,4301
|
|
6
7
|
tencentcloud/common/common_client.py,sha256=1Dr96VCEoJgDPmnurI46ko_cSROtRQbcHef_qeOKZco,2043
|
|
7
|
-
tencentcloud/common/
|
|
8
|
+
tencentcloud/common/common_client_async.py,sha256=UHVB6ziHrbYceoCzFOTynP5jOC3TQa6xApOJ5w4F-C0,2027
|
|
9
|
+
tencentcloud/common/credential.py,sha256=hTZ0ROxdCkd4rCF0dplMg6lMrb3HK0euMNrhGD3WjiA,18254
|
|
8
10
|
tencentcloud/common/retry.py,sha256=4h6lG2KEVezhnfZt1BvSkftNupuu98g-n1g1EKu62IE,2323
|
|
11
|
+
tencentcloud/common/retry_async.py,sha256=9276QKafeQwnEv3PaBJ81U1YK2Kt9CPyNhkBlavVVQU,2554
|
|
9
12
|
tencentcloud/common/sign.py,sha256=Mm51G6huhhT91yayB-KKWCQFIuy59dFzdSwolNfSuwM,1568
|
|
10
13
|
tencentcloud/common/exception/__init__.py,sha256=xK4vqaAlCWY5pGTDaz21vzIO6izEBESCNDeBNvojqYo,735
|
|
11
14
|
tencentcloud/common/exception/tencent_cloud_sdk_exception.py,sha256=aFTVjvZC-mp74oEjELqI3BFrfgXi1yLKppNd5XzvqFY,760
|
|
12
15
|
tencentcloud/common/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
16
|
tencentcloud/common/http/pre_conn.py,sha256=aeEqmTc2JKO8Wd6r_SwLofCyrfPv-A0hQV00SZAgpU0,2488
|
|
14
17
|
tencentcloud/common/http/request.py,sha256=Qgo3BI4cbbEv1Y2EvlB8KQSh1brvPKRypSdfnY_Yay0,5642
|
|
18
|
+
tencentcloud/common/http/request_async.py,sha256=4AOLDdeVgYi10hrjlzOknx3ash-K_RZqToyRlorMvok,2131
|
|
15
19
|
tencentcloud/common/profile/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
20
|
tencentcloud/common/profile/client_profile.py,sha256=gvqlN4orPjQKA7kfZw0GMLuwXkKnSMgLo2S-6Mq3qh0,5221
|
|
17
21
|
tencentcloud/common/profile/http_profile.py,sha256=4or_nmNoy-Yd9MWpxxPhvbaeaAjx3ZYTNgTl6nt6ETU,2256
|
|
18
|
-
tencentcloud_sdk_python_common-3.
|
|
19
|
-
tencentcloud_sdk_python_common-3.
|
|
20
|
-
tencentcloud_sdk_python_common-3.
|
|
21
|
-
tencentcloud_sdk_python_common-3.
|
|
22
|
+
tencentcloud_sdk_python_common-3.1.15.dist-info/METADATA,sha256=Y6ZyZDlnrSx4j6hl90cCM8LKwwPR8jiVYrYNogmFNPM,1552
|
|
23
|
+
tencentcloud_sdk_python_common-3.1.15.dist-info/WHEEL,sha256=Ll72iyqtt6Rbxp-Q7FSafYA1LeRv98X15xcZWRsFEmY,109
|
|
24
|
+
tencentcloud_sdk_python_common-3.1.15.dist-info/top_level.txt,sha256=g-8OyzoqI6O6LiS85zkeNzhB-osEnRIPZMdyRd_0eL0,13
|
|
25
|
+
tencentcloud_sdk_python_common-3.1.15.dist-info/RECORD,,
|
|
File without changes
|