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 CHANGED
@@ -14,4 +14,4 @@
14
14
  # limitations under the License.
15
15
 
16
16
 
17
- __version__ = '3.0.1388'
17
+ __version__ = '3.1.15'
@@ -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 self.credential.token:
152
- params['Token'] = self.credential.token
155
+ if cred_token:
156
+ params['Token'] = cred_token
153
157
 
154
- if self.credential.secret_id:
155
- params['SecretId'] = self.credential.secret_id
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(self.credential.secret_key),
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 self.credential.token:
200
- req.header['X-TC-Token'] = self.credential.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
- self.credential.secret_id, date, service, signature)
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' % (self.profile.httpProfile.reqMethod, self._get_endpoint(), self._requestPath, strParam)
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
- req = RequestInternal(self._get_endpoint(),
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 = {"IsOctetStream": True}
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.update_credential()
102
- return self._secret_id
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.update_credential()
111
- return self._secret_key
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.update_credential()
116
- return self._token
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._need_refresh()
207
- return self._tmp_secret_id
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._need_refresh()
212
- return self._tmp_secret_key
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._need_refresh()
217
- return self._tmp_secret_id
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._need_refresh()
222
- return self._tmp_secret_key
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._need_refresh()
227
- return self._token
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
- home_path = os.environ.get('HOME') or os.environ.get('HOMEPATH')
287
- if os.path.exists(home_path + "/.tencentcloud/credentials"):
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._keep_fresh()
410
- return self._tmp_secret_id
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._keep_fresh()
415
- return self._tmp_secret_key
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._keep_fresh()
420
- return self._tmp_secret_id
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._keep_fresh()
425
- return self._tmp_secret_key
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._keep_fresh()
430
- return self._token
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
- common_client = CommonClient(credential=None, region=self._region, version=self._version, service=self._service)
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.0.1388
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 (>=2.16.0)
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=P_6PiW8DYNHHHgqiC-uyC2CmatjXuaUnBhcwC2nkIBg,631
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=W-kuyBPX23VbUV3sNpqhVMEivSg_Un7IRI_yk9IFCpQ,25100
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/credential.py,sha256=nKOvRoHS5w2tNDyZN626iXU-H6CCEbXkGJtfcNFRqE0,16602
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.0.1388.dist-info/METADATA,sha256=UA9VfQRN5nmA_smnX-4BweUfxGIWjW-6YlqpXFCRRtw,1487
19
- tencentcloud_sdk_python_common-3.0.1388.dist-info/WHEEL,sha256=z9j0xAa_JmUKMpmz72K0ZGALSM_n-wQVmGbleXx2VHg,110
20
- tencentcloud_sdk_python_common-3.0.1388.dist-info/top_level.txt,sha256=g-8OyzoqI6O6LiS85zkeNzhB-osEnRIPZMdyRd_0eL0,13
21
- tencentcloud_sdk_python_common-3.0.1388.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.37.1)
2
+ Generator: setuptools (75.3.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any